summaryrefslogtreecommitdiff
path: root/src/op_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/op_mode')
-rwxr-xr-xsrc/op_mode/accelppp.py46
-rwxr-xr-xsrc/op_mode/bgp.py173
-rwxr-xr-xsrc/op_mode/bridge.py82
-rwxr-xr-xsrc/op_mode/clear_conntrack.py5
-rwxr-xr-xsrc/op_mode/clear_dhcp_lease.py6
-rwxr-xr-xsrc/op_mode/config_mgmt.py85
-rwxr-xr-xsrc/op_mode/connect_disconnect.py10
-rwxr-xr-xsrc/op_mode/conntrack.py12
-rwxr-xr-xsrc/op_mode/conntrack_sync.py227
-rwxr-xr-xsrc/op_mode/container.py30
-rwxr-xr-xsrc/op_mode/dhcp.py201
-rwxr-xr-xsrc/op_mode/dns.py6
-rwxr-xr-xsrc/op_mode/dns_dynamic.py113
-rwxr-xr-xsrc/op_mode/dns_forwarding_reset.py2
-rwxr-xr-xsrc/op_mode/dns_forwarding_statistics.py2
-rwxr-xr-xsrc/op_mode/dynamic_dns.py107
-rwxr-xr-xsrc/op_mode/firewall.py335
-rwxr-xr-xsrc/op_mode/flow_accounting_op.py10
-rwxr-xr-xsrc/op_mode/format_disk.py8
-rwxr-xr-xsrc/op_mode/generate_interfaces_debug_archive.py115
-rwxr-xr-xsrc/op_mode/generate_ipsec_debug_archive.py2
-rwxr-xr-xsrc/op_mode/generate_openconnect_otp_key.py4
-rwxr-xr-xsrc/op_mode/generate_ovpn_client_file.py2
-rwxr-xr-xsrc/op_mode/generate_public_key_command.py59
-rwxr-xr-xsrc/op_mode/generate_ssh_server_key.py6
-rwxr-xr-xsrc/op_mode/generate_system_login_user.py4
-rwxr-xr-xsrc/op_mode/igmp-proxy.py97
-rwxr-xr-xsrc/op_mode/ikev2_profile_generator.py2
-rwxr-xr-xsrc/op_mode/interfaces.py438
-rwxr-xr-xsrc/op_mode/ipoe-control.py5
-rwxr-xr-xsrc/op_mode/ipsec.py509
-rwxr-xr-xsrc/op_mode/lldp.py149
-rwxr-xr-xsrc/op_mode/lldp_op.py127
-rwxr-xr-xsrc/op_mode/log.py2
-rwxr-xr-xsrc/op_mode/memory.py2
-rwxr-xr-xsrc/op_mode/nat.py54
-rwxr-xr-xsrc/op_mode/neighbor.py18
-rwxr-xr-xsrc/op_mode/nhrp.py101
-rwxr-xr-xsrc/op_mode/openconnect-control.py11
-rwxr-xr-xsrc/op_mode/openconnect.py2
-rwxr-xr-xsrc/op_mode/openvpn.py72
-rwxr-xr-xsrc/op_mode/ping.py12
-rwxr-xr-xsrc/op_mode/pki.py64
-rwxr-xr-xsrc/op_mode/policy_route.py8
-rwxr-xr-xsrc/op_mode/powerctrl.py19
-rwxr-xr-xsrc/op_mode/ppp-server-ctrl.py5
-rwxr-xr-xsrc/op_mode/reset_openvpn.py4
-rwxr-xr-xsrc/op_mode/reset_vpn.py77
-rwxr-xr-xsrc/op_mode/restart_dhcp_relay.py4
-rwxr-xr-xsrc/op_mode/restart_frr.py12
-rwxr-xr-xsrc/op_mode/reverseproxy.py239
-rwxr-xr-xsrc/op_mode/route.py43
-rwxr-xr-xsrc/op_mode/sflow.py108
-rwxr-xr-xsrc/op_mode/show-bond.py2
-rwxr-xr-xsrc/op_mode/show_acceleration.py26
-rwxr-xr-xsrc/op_mode/show_dhcp.py260
-rwxr-xr-xsrc/op_mode/show_dhcpv6.py220
-rwxr-xr-xsrc/op_mode/show_igmpproxy.py241
-rwxr-xr-xsrc/op_mode/show_interfaces.py310
-rwxr-xr-xsrc/op_mode/show_ipsec_sa.py130
-rwxr-xr-xsrc/op_mode/show_nat66_statistics.py63
-rwxr-xr-xsrc/op_mode/show_nat66_translations.py204
-rwxr-xr-xsrc/op_mode/show_nat_statistics.py63
-rwxr-xr-xsrc/op_mode/show_nat_translations.py216
-rwxr-xr-xsrc/op_mode/show_ntp.sh29
-rwxr-xr-xsrc/op_mode/show_openconnect_otp.py42
-rwxr-xr-xsrc/op_mode/show_openvpn_mfa.py6
-rwxr-xr-xsrc/op_mode/show_raid.sh10
-rwxr-xr-xsrc/op_mode/show_sensors.py22
-rw-r--r--src/op_mode/show_techsupport_report.py304
-rwxr-xr-xsrc/op_mode/show_virtual_server.py2
-rwxr-xr-xsrc/op_mode/show_vpn_ra.py56
-rwxr-xr-xsrc/op_mode/show_wireless.py4
-rwxr-xr-xsrc/op_mode/show_wwan.py10
-rwxr-xr-xsrc/op_mode/show_xdp_stats.sh7
-rwxr-xr-xsrc/op_mode/snmp.py2
-rwxr-xr-xsrc/op_mode/snmp_ifmib.py4
-rwxr-xr-xsrc/op_mode/storage.py4
-rwxr-xr-xsrc/op_mode/traceroute.py2
-rwxr-xr-xsrc/op_mode/uptime.py8
-rwxr-xr-xsrc/op_mode/vpn_ike_sa.py6
-rwxr-xr-xsrc/op_mode/vpn_ipsec.py87
-rwxr-xr-xsrc/op_mode/vrf.py25
-rwxr-xr-xsrc/op_mode/vrrp.py11
-rwxr-xr-xsrc/op_mode/vyos-op-cmd-wrapper.sh6
-rwxr-xr-xsrc/op_mode/webproxy_update_blacklist.sh9
-rwxr-xr-xsrc/op_mode/wireguard_client.py6
-rwxr-xr-xsrc/op_mode/zone.py215
-rwxr-xr-xsrc/op_mode/zone_policy.py81
89 files changed, 3604 insertions, 2925 deletions
diff --git a/src/op_mode/accelppp.py b/src/op_mode/accelppp.py
index 2fd045dc3..67ce786d0 100755
--- a/src/op_mode/accelppp.py
+++ b/src/op_mode/accelppp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -21,40 +21,62 @@ import vyos.accel_ppp
import vyos.opmode
from vyos.configquery import ConfigTreeQuery
-from vyos.util import rc_cmd
+from vyos.utils.process import rc_cmd
accel_dict = {
'ipoe': {
'port': 2002,
- 'path': 'service ipoe-server'
+ 'path': 'service ipoe-server',
+ 'base_path': 'service ipoe-server'
},
'pppoe': {
'port': 2001,
- 'path': 'service pppoe-server'
+ 'path': 'service pppoe-server',
+ 'base_path': 'service pppoe-server'
},
'pptp': {
'port': 2003,
- 'path': 'vpn pptp'
+ 'path': 'vpn pptp',
+ 'base_path': 'vpn pptp'
},
'l2tp': {
'port': 2004,
- 'path': 'vpn l2tp'
+ 'path': 'vpn l2tp',
+ 'base_path': 'vpn l2tp remote-access'
},
'sstp': {
'port': 2005,
- 'path': 'vpn sstp'
+ 'path': 'vpn sstp',
+ 'base_path': 'vpn sstp'
}
}
-def _get_raw_statistics(accel_output, pattern):
- return vyos.accel_ppp.get_server_statistics(accel_output, pattern, sep=':')
+def _get_config_settings(protocol):
+ '''Get config dict from VyOS configuration'''
+ conf = ConfigTreeQuery()
+ base_path = accel_dict[protocol]['base_path']
+ data = conf.get_config_dict(base_path,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ if conf.exists(f'{base_path} authentication local-users'):
+ # Delete sensitive data
+ del data['authentication']['local_users']
+ return {'config_option': data}
+
+
+def _get_raw_statistics(accel_output, pattern, protocol):
+ return {
+ **vyos.accel_ppp.get_server_statistics(accel_output, pattern, sep=':'),
+ **_get_config_settings(protocol)
+ }
def _get_raw_sessions(port):
- cmd_options = 'show sessions ifname,username,ip,ip6,ip6-dp,type,state,' \
- 'uptime-raw,calling-sid,called-sid,sid,comp,rx-bytes-raw,' \
+ cmd_options = 'show sessions ifname,username,ip,ip6,ip6-dp,type,rate-limit,' \
+ 'state,uptime-raw,calling-sid,called-sid,sid,comp,rx-bytes-raw,' \
'tx-bytes-raw,rx-pkts,tx-pkts'
output = vyos.accel_ppp.accel_cmd(port, cmd_options)
parsed_data: list[dict[str, str]] = vyos.accel_ppp.accel_out_parse(
@@ -103,7 +125,7 @@ def show_statistics(raw: bool, protocol: str):
rc, output = rc_cmd(f'/usr/bin/accel-cmd -p {port} show stat')
if raw:
- return _get_raw_statistics(output, pattern)
+ return _get_raw_statistics(output, pattern, protocol)
return output
diff --git a/src/op_mode/bgp.py b/src/op_mode/bgp.py
index 23001a9d7..096113cb4 100755
--- a/src/op_mode/bgp.py
+++ b/src/op_mode/bgp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -15,100 +15,133 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Purpose:
-# Displays bgp neighbors information.
-# Used by the "show bgp (vrf <tag>) ipv4|ipv6 neighbors" commands.
+# Displays BGP neighbors and tables information.
import re
import sys
import typing
-import jmespath
from jinja2 import Template
-from humps import decamelize
-
-from vyos.configquery import ConfigTreeQuery
import vyos.opmode
-
frr_command_template = Template("""
-{% if family %}
- show bgp
- {{ 'vrf ' ~ vrf if vrf else '' }}
- {{ 'ipv6' if family == 'inet6' else 'ipv4'}}
- {{ 'neighbor ' ~ peer if peer else 'summary' }}
+show bgp
+
+{## VRF and family modifiers that may precede any options ##}
+
+{% if vrf %}
+ vrf {{vrf}}
+{% endif %}
+
+{% if family == "inet" %}
+ ipv4
+{% elif family == "inet6" %}
+ ipv6
+{% elif family == "l2vpn" %}
+ l2vpn evpn
+{% endif %}
+
+{% if family_modifier == "unicast" %}
+ unicast
+{% elif family_modifier == "multicast" %}
+ multicast
+{% elif family_modifier == "flowspec" %}
+ flowspec
+{% elif family_modifier == "vpn" %}
+ vpn
+{% endif %}
+
+{## Mutually exclusive query parameters ##}
+
+{# Network prefix #}
+{% if prefix %}
+ {{prefix}}
+
+ {% if longer_prefixes %}
+ longer-prefixes
+ {% elif best_path %}
+ bestpath
+ {% endif %}
{% endif %}
+{# Regex #}
+{% if regex %}
+ regex {{regex}}
+{% endif %}
+
+{## Raw modifier ##}
+
{% if raw %}
json
{% endif %}
""")
+ArgFamily = typing.Literal['inet', 'inet6', 'l2vpn']
+ArgFamilyModifier = typing.Literal['unicast', 'labeled_unicast', 'multicast', 'vpn', 'flowspec']
+
+def show_summary(raw: bool):
+ from vyos.utils.process import cmd
+
+ if raw:
+ from json import loads
+
+ output = cmd(f"vtysh -c 'show bgp summary json'").strip()
+
+ # FRR 8.5 correctly returns an empty object when BGP is not running,
+ # we don't need to do anything special here
+ return loads(output)
+ else:
+ output = cmd(f"vtysh -c 'show bgp summary'")
+ return output
-def _verify(func):
- """Decorator checks if BGP config exists
- BGP configuration can be present under vrf <tag>
- If we do npt get arg 'peer' then it can be 'bgp summary'
- """
- from functools import wraps
-
- @wraps(func)
- def _wrapper(*args, **kwargs):
- config = ConfigTreeQuery()
- afi = 'ipv6' if kwargs.get('family') == 'inet6' else 'ipv4'
- global_vrfs = ['all', 'default']
- peer = kwargs.get('peer')
- vrf = kwargs.get('vrf')
- unconf_message = f'BGP or neighbor is not configured'
- # Add option to check the specific neighbor if we have arg 'peer'
- peer_opt = f'neighbor {peer} address-family {afi}-unicast' if peer else ''
- vrf_opt = ''
- if vrf and vrf not in global_vrfs:
- vrf_opt = f'vrf name {vrf}'
- # Check if config does not exist
- if not config.exists(f'{vrf_opt} protocols bgp {peer_opt}'):
- raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
- return func(*args, **kwargs)
-
- return _wrapper
-
-
-@_verify
-def show_neighbors(raw: bool,
- family: str,
- peer: typing.Optional[str],
- vrf: typing.Optional[str]):
- kwargs = dict(locals())
- frr_command = frr_command_template.render(kwargs)
- frr_command = re.sub(r'\s+', ' ', frr_command)
-
- from vyos.util import cmd
- output = cmd(f"vtysh -c '{frr_command}'")
+def show_neighbors(raw: bool):
+ from vyos.utils.process import cmd
+ from vyos.utils.dict import dict_to_list
if raw:
from json import loads
- data = loads(output)
- # Get list of the peers
- peers = jmespath.search('*.peers | [0]', data)
- if peers:
- # Create new dict, delete old key 'peers'
- # add key 'peers' neighbors to the list
- list_peers = []
- new_dict = jmespath.search('* | [0]', data)
- if 'peers' in new_dict:
- new_dict.pop('peers')
-
- for neighbor, neighbor_options in peers.items():
- neighbor_options['neighbor'] = neighbor
- list_peers.append(neighbor_options)
- new_dict['peers'] = list_peers
- return decamelize(new_dict)
- data = jmespath.search('* | [0]', data)
- return decamelize(data)
+ output = cmd(f"vtysh -c 'show bgp neighbors json'").strip()
+ d = loads(output)
+ return dict_to_list(d, save_key_to="neighbor")
else:
+ output = cmd(f"vtysh -c 'show bgp neighbors'")
return output
+def show(raw: bool,
+ family: ArgFamily,
+ family_modifier: ArgFamilyModifier,
+ prefix: typing.Optional[str],
+ longer_prefixes: typing.Optional[bool],
+ best_path: typing.Optional[bool],
+ regex: typing.Optional[str],
+ vrf: typing.Optional[str]):
+ from vyos.utils.dict import dict_to_list
+
+ if (longer_prefixes or best_path) and (prefix is None):
+ raise ValueError("longer_prefixes and best_path can only be used when prefix is given")
+ elif (family == "l2vpn") and (family_modifier is not None):
+ raise ValueError("l2vpn family does not accept any modifiers")
+ else:
+ kwargs = dict(locals())
+
+ frr_command = frr_command_template.render(kwargs)
+ frr_command = re.sub(r'\s+', ' ', frr_command)
+
+ from vyos.utils.process import cmd
+ output = cmd(f"vtysh -c '{frr_command}'")
+
+ if raw:
+ from json import loads
+ d = loads(output)
+ if not ("routes" in d):
+ raise vyos.opmode.InternalError("FRR returned a BGP table with no routes field")
+ d = d["routes"]
+ routes = dict_to_list(d, save_key_to="route_key")
+ return routes
+ else:
+ return output
if __name__ == '__main__':
try:
diff --git a/src/op_mode/bridge.py b/src/op_mode/bridge.py
index d6098c158..185db4f20 100755
--- a/src/op_mode/bridge.py
+++ b/src/op_mode/bridge.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -22,12 +22,13 @@ import typing
from sys import exit
from tabulate import tabulate
-from vyos.util import cmd, rc_cmd
-from vyos.util import dict_search
+from vyos.utils.process import cmd
+from vyos.utils.process import rc_cmd
+from vyos.utils.process import call
+from vyos.utils.dict import dict_search
import vyos.opmode
-
def _get_json_data():
"""
Get bridge data format JSON
@@ -44,11 +45,14 @@ def _get_raw_data_summary():
return data_dict
-def _get_raw_data_vlan():
+def _get_raw_data_vlan(tunnel:bool=False):
"""
:returns dict
"""
- json_data = cmd('bridge --json --compressvlans vlan show')
+ show = 'show'
+ if tunnel:
+ show = 'tunnel'
+ json_data = cmd(f'bridge --json --compressvlans vlan {show}')
data_dict = json.loads(json_data)
return data_dict
@@ -128,13 +132,38 @@ def _get_formatted_output_vlan(data):
if vlan_entry.get('vlanEnd'):
vlan_end = vlan_entry.get('vlanEnd')
vlan = f'{vlan}-{vlan_end}'
- flags = ', '.join(vlan_entry.get('flags')).lower()
+ flags_raw = vlan_entry.get('flags')
+ flags = ', '.join(flags_raw if isinstance(flags_raw,list) else "").lower()
data_entries.append([interface, vlan, flags])
- headers = ["Interface", "Vlan", "Flags"]
+ headers = ["Interface", "VLAN", "Flags"]
output = tabulate(data_entries, headers)
return output
+def _get_formatted_output_vlan_tunnel(data):
+ data_entries = []
+ for entry in data:
+ interface = entry.get('ifname')
+ first = True
+ for tunnel_entry in entry.get('tunnels'):
+ vlan = tunnel_entry.get('vlan')
+ vni = tunnel_entry.get('tunid')
+ if first:
+ data_entries.append([interface, vlan, vni])
+ first = False
+ else:
+ # Group by VXLAN interface only - no need to repeat
+ # VXLAN interface name for every VLAN <-> VNI mapping
+ #
+ # Interface VLAN VNI
+ # ----------- ------ -----
+ # vxlan0 100 100
+ # 200 200
+ data_entries.append(['', vlan, vni])
+
+ headers = ["Interface", "VLAN", "VNI"]
+ output = tabulate(data_entries, headers)
+ return output
def _get_formatted_output_fdb(data):
data_entries = []
@@ -163,6 +192,23 @@ def _get_formatted_output_mdb(data):
output = tabulate(data_entries, headers)
return output
+def _get_bridge_detail(iface):
+ """Get interface detail statistics"""
+ return call(f'vtysh -c "show interface {iface}"')
+
+def _get_bridge_detail_nexthop_group(iface):
+ """Get interface detail nexthop_group statistics"""
+ return call(f'vtysh -c "show interface {iface} nexthop-group"')
+
+def _get_bridge_detail_nexthop_group_raw(iface):
+ out = cmd(f'vtysh -c "show interface {iface} nexthop-group"')
+ return out
+
+def _get_bridge_detail_raw(iface):
+ """Get interface detail json statistics"""
+ data = cmd(f'vtysh -c "show interface {iface} json"')
+ data_dict = json.loads(data)
+ return data_dict
def show(raw: bool):
bridge_data = _get_raw_data_summary()
@@ -172,12 +218,15 @@ def show(raw: bool):
return _get_formatted_output_summary(bridge_data)
-def show_vlan(raw: bool):
- bridge_vlan = _get_raw_data_vlan()
+def show_vlan(raw: bool, tunnel: typing.Optional[bool]):
+ bridge_vlan = _get_raw_data_vlan(tunnel)
if raw:
return bridge_vlan
else:
- return _get_formatted_output_vlan(bridge_vlan)
+ if tunnel:
+ return _get_formatted_output_vlan_tunnel(bridge_vlan)
+ else:
+ return _get_formatted_output_vlan(bridge_vlan)
def show_fdb(raw: bool, interface: str):
@@ -195,6 +244,17 @@ def show_mdb(raw: bool, interface: str):
else:
return _get_formatted_output_mdb(mdb_data)
+def show_detail(raw: bool, nexthop_group: typing.Optional[bool], interface: str):
+ if raw:
+ if nexthop_group:
+ return _get_bridge_detail_nexthop_group_raw(interface)
+ else:
+ return _get_bridge_detail_raw(interface)
+ else:
+ if nexthop_group:
+ return _get_bridge_detail_nexthop_group(interface)
+ else:
+ return _get_bridge_detail(interface)
if __name__ == '__main__':
try:
diff --git a/src/op_mode/clear_conntrack.py b/src/op_mode/clear_conntrack.py
index 423694187..fec7cf144 100755
--- a/src/op_mode/clear_conntrack.py
+++ b/src/op_mode/clear_conntrack.py
@@ -16,8 +16,9 @@
import sys
-from vyos.util import ask_yes_no
-from vyos.util import cmd, DEVNULL
+from vyos.utils.io import ask_yes_no
+from vyos.utils.process import cmd
+from vyos.utils.process import DEVNULL
if not ask_yes_no("This will clear all currently tracked and expected connections. Continue?"):
sys.exit(1)
diff --git a/src/op_mode/clear_dhcp_lease.py b/src/op_mode/clear_dhcp_lease.py
index 250dbcce1..f372d3af0 100755
--- a/src/op_mode/clear_dhcp_lease.py
+++ b/src/op_mode/clear_dhcp_lease.py
@@ -7,9 +7,9 @@ from isc_dhcp_leases import Lease
from isc_dhcp_leases import IscDhcpLeases
from vyos.configquery import ConfigTreeQuery
-from vyos.util import ask_yes_no
-from vyos.util import call
-from vyos.util import commit_in_progress
+from vyos.utils.io import ask_yes_no
+from vyos.utils.process import call
+from vyos.utils.commit import commit_in_progress
config = ConfigTreeQuery()
diff --git a/src/op_mode/config_mgmt.py b/src/op_mode/config_mgmt.py
new file mode 100755
index 000000000..66de26d1f
--- /dev/null
+++ b/src/op_mode/config_mgmt.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import typing
+
+import vyos.opmode
+from vyos.config_mgmt import ConfigMgmt
+
+def show_commit_diff(raw: bool, rev: int, rev2: typing.Optional[int],
+ commands: bool):
+ config_mgmt = ConfigMgmt()
+ config_diff = config_mgmt.show_commit_diff(rev, rev2, commands)
+
+ if raw:
+ rev2 = (rev+1) if rev2 is None else rev2
+ if commands:
+ d = {f'config_command_diff_{rev2}_{rev}': config_diff}
+ else:
+ d = {f'config_file_diff_{rev2}_{rev}': config_diff}
+ return d
+
+ return config_diff
+
+def show_commit_file(raw: bool, rev: int):
+ config_mgmt = ConfigMgmt()
+ config_file = config_mgmt.show_commit_file(rev)
+
+ if raw:
+ d = {f'config_revision_{rev}': config_file}
+ return d
+
+ return config_file
+
+def show_commit_log(raw: bool):
+ config_mgmt = ConfigMgmt()
+
+ msg = ''
+ if config_mgmt.max_revisions == 0:
+ msg = ('commit-revisions is not configured;\n'
+ 'commit log is empty or stale:\n\n')
+
+ data = config_mgmt.get_raw_log_data()
+ if raw:
+ return data
+
+ out = config_mgmt.format_log_data(data)
+ out = msg + out
+
+ return out
+
+def show_commit_log_brief(raw: bool):
+ # used internally for completion help for 'rollback'
+ # option 'raw' will return same as 'show_commit_log'
+ config_mgmt = ConfigMgmt()
+
+ data = config_mgmt.get_raw_log_data()
+ if raw:
+ return data
+
+ out = config_mgmt.format_log_data_brief(data)
+
+ return out
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/connect_disconnect.py b/src/op_mode/connect_disconnect.py
index d39e88bf3..89f929be7 100755
--- a/src/op_mode/connect_disconnect.py
+++ b/src/op_mode/connect_disconnect.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 VyOS maintainers and contributors
+# Copyright (C) 2020-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -19,10 +19,10 @@ import argparse
from psutil import process_iter
-from vyos.util import call
-from vyos.util import commit_in_progress
-from vyos.util import DEVNULL
-from vyos.util import is_wwan_connected
+from vyos.utils.process import call
+from vyos.utils.commit import commit_in_progress
+from vyos.utils.network import is_wwan_connected
+from vyos.utils.process import DEVNULL
def check_ppp_interface(interface):
if not os.path.isfile(f'/etc/ppp/peers/{interface}'):
diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py
index fff537936..cf8adf795 100755
--- a/src/op_mode/conntrack.py
+++ b/src/op_mode/conntrack.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -15,14 +15,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
+import typing
import xmltodict
from tabulate import tabulate
-from vyos.util import cmd
-from vyos.util import run
+from vyos.utils.process import cmd
+from vyos.utils.process import run
import vyos.opmode
+ArgFamily = typing.Literal['inet', 'inet6']
def _get_xml_data(family):
"""
@@ -116,7 +118,7 @@ def get_formatted_output(dict_data):
reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src
reply_dst = f'{reply_dst}:{reply_dport}' if reply_dport else reply_dst
state = meta['state'] if 'state' in meta else ''
- mark = meta['mark']
+ mark = meta['mark'] if 'mark' in meta else ''
zone = meta['zone'] if 'zone' in meta else ''
data_entries.append(
[conn_id, orig_src, orig_dst, reply_src, reply_dst, proto, state, timeout, mark, zone])
@@ -126,7 +128,7 @@ def get_formatted_output(dict_data):
return output
-def show(raw: bool, family: str):
+def show(raw: bool, family: ArgFamily):
family = 'ipv6' if family == 'inet6' else 'ipv4'
conntrack_data = _get_raw_data(family)
if raw:
diff --git a/src/op_mode/conntrack_sync.py b/src/op_mode/conntrack_sync.py
index 54ecd6d0e..a38688e45 100755
--- a/src/op_mode/conntrack_sync.py
+++ b/src/op_mode/conntrack_sync.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -15,52 +15,42 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import sys
import syslog
import xmltodict
+import vyos.opmode
+
from argparse import ArgumentParser
from vyos.configquery import CliShellApiConfigQuery
from vyos.configquery import ConfigTreeQuery
-from vyos.util import call
-from vyos.util import commit_in_progress
-from vyos.util import cmd
-from vyos.util import run
+from vyos.utils.commit import commit_in_progress
+from vyos.utils.process import call
+from vyos.utils.process import cmd
+from vyos.utils.process import run
from vyos.template import render_to_string
conntrackd_bin = '/usr/sbin/conntrackd'
conntrackd_config = '/run/conntrackd/conntrackd.conf'
failover_state_file = '/var/run/vyatta-conntrackd-failover-state'
-parser = ArgumentParser(description='Conntrack Sync')
-group = parser.add_mutually_exclusive_group()
-group.add_argument('--restart', help='Restart connection tracking synchronization service', action='store_true')
-group.add_argument('--reset-cache-internal', help='Reset internal cache', action='store_true')
-group.add_argument('--reset-cache-external', help='Reset external cache', action='store_true')
-group.add_argument('--show-internal', help='Show internal (main) tracking cache', action='store_true')
-group.add_argument('--show-external', help='Show external (main) tracking cache', action='store_true')
-group.add_argument('--show-internal-expect', help='Show internal (expect) tracking cache', action='store_true')
-group.add_argument('--show-external-expect', help='Show external (expect) tracking cache', action='store_true')
-group.add_argument('--show-statistics', help='Show connection syncing statistics', action='store_true')
-group.add_argument('--show-status', help='Show conntrack-sync status', action='store_true')
-
def is_configured():
""" Check if conntrack-sync service is configured """
config = CliShellApiConfigQuery()
if not config.exists(['service', 'conntrack-sync']):
- print('Service conntrackd-sync not configured!')
- exit(1)
+ raise vyos.opmode.UnconfiguredSubsystem("conntrack-sync is not configured!")
def send_bulk_update():
""" send bulk update of internal-cache to other systems """
tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -B')
if tmp > 0:
- print('ERROR: failed to send bulk update to other conntrack-sync systems')
+ raise vyos.opmode.Error('Failed to send bulk update to other conntrack-sync systems')
def request_sync():
""" request resynchronization with other systems """
tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -n')
if tmp > 0:
- print('ERROR: failed to request resynchronization of external cache')
+ raise vyos.opmode.Error('Failed to request resynchronization of external cache')
def flush_cache(direction):
""" flush conntrackd cache (internal or external) """
@@ -68,9 +58,9 @@ def flush_cache(direction):
raise ValueError()
tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -f {direction}')
if tmp > 0:
- print('ERROR: failed to clear {direction} cache')
+ raise vyos.opmode.Error('Failed to clear {direction} cache')
-def xml_to_stdout(xml):
+def from_xml(raw, xml):
out = []
for line in xml.splitlines():
if line == '\n':
@@ -78,108 +68,131 @@ def xml_to_stdout(xml):
parsed = xmltodict.parse(line)
out.append(parsed)
- print(render_to_string('conntrackd/conntrackd.op-mode.j2', {'data' : out}))
-
-if __name__ == '__main__':
- args = parser.parse_args()
- syslog.openlog(ident='conntrack-tools', logoption=syslog.LOG_PID,
- facility=syslog.LOG_INFO)
+ if raw:
+ return out
+ else:
+ return render_to_string('conntrackd/conntrackd.op-mode.j2', {'data' : out})
+
+def restart():
+ is_configured()
+ if commit_in_progress():
+ raise vyos.opmode.CommitInProgress('Cannot restart conntrackd while a commit is in progress')
+
+ syslog.syslog('Restarting conntrack sync service...')
+ cmd('systemctl restart conntrackd.service')
+ # request resynchronization with other systems
+ request_sync()
+ # send bulk update of internal-cache to other systems
+ send_bulk_update()
+
+def reset_external_cache():
+ is_configured()
+ syslog.syslog('Resetting external cache of conntrack sync service...')
+
+ # flush the external cache
+ flush_cache('external')
+ # request resynchronization with other systems
+ request_sync()
+
+def reset_internal_cache():
+ is_configured()
+ syslog.syslog('Resetting internal cache of conntrack sync service...')
+ # flush the internal cache
+ flush_cache('internal')
+
+ # request resynchronization of internal cache with kernel conntrack table
+ tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -R')
+ if tmp > 0:
+ print('ERROR: failed to resynchronize internal cache with kernel conntrack table')
- if args.restart:
- is_configured()
- if commit_in_progress():
- print('Cannot restart conntrackd while a commit is in progress')
- exit(1)
-
- syslog.syslog('Restarting conntrack sync service...')
- cmd('systemctl restart conntrackd.service')
- # request resynchronization with other systems
- request_sync()
- # send bulk update of internal-cache to other systems
- send_bulk_update()
-
- elif args.reset_cache_external:
- is_configured()
- syslog.syslog('Resetting external cache of conntrack sync service...')
+ # send bulk update of internal-cache to other systems
+ send_bulk_update()
- # flush the external cache
- flush_cache('external')
- # request resynchronization with other systems
- request_sync()
+def _show_cache(raw, opts):
+ is_configured()
+ out = cmd(f'{conntrackd_bin} -C {conntrackd_config} {opts} -x')
+ return from_xml(raw, out)
- elif args.reset_cache_internal:
- is_configured()
- syslog.syslog('Resetting internal cache of conntrack sync service...')
- # flush the internal cache
- flush_cache('internal')
+def show_external_cache(raw: bool):
+ opts = '-e ct'
+ return _show_cache(raw, opts)
- # request resynchronization of internal cache with kernel conntrack table
- tmp = run(f'{conntrackd_bin} -C {conntrackd_config} -R')
- if tmp > 0:
- print('ERROR: failed to resynchronize internal cache with kernel conntrack table')
+def show_external_expect(raw: bool):
+ opts = '-e expect'
+ return _show_cache(raw, opts)
- # send bulk update of internal-cache to other systems
- send_bulk_update()
+def show_internal_cache(raw: bool):
+ opts = '-i ct'
+ return _show_cache(raw, opts)
- elif args.show_external or args.show_internal or args.show_external_expect or args.show_internal_expect:
- is_configured()
- opt = ''
- if args.show_external:
- opt = '-e ct'
- elif args.show_external_expect:
- opt = '-e expect'
- elif args.show_internal:
- opt = '-i ct'
- elif args.show_internal_expect:
- opt = '-i expect'
-
- if args.show_external or args.show_internal:
- print('Main Table Entries:')
- else:
- print('Expect Table Entries:')
- out = cmd(f'sudo {conntrackd_bin} -C {conntrackd_config} {opt} -x')
- xml_to_stdout(out)
+def show_internal_expect(raw: bool):
+ opts = '-i expect'
+ return _show_cache(raw, opts)
- elif args.show_statistics:
+def show_statistics(raw: bool):
+ if raw:
+ raise vyos.opmode.UnsupportedOperation("Machine-readable conntrack-sync statistics are not available yet")
+ else:
is_configured()
config = ConfigTreeQuery()
print('\nMain Table Statistics:\n')
- call(f'sudo {conntrackd_bin} -C {conntrackd_config} -s')
+ call(f'{conntrackd_bin} -C {conntrackd_config} -s')
print()
if config.exists(['service', 'conntrack-sync', 'expect-sync']):
print('\nExpect Table Statistics:\n')
- call(f'sudo {conntrackd_bin} -C {conntrackd_config} -s exp')
+ call(f'{conntrackd_bin} -C {conntrackd_config} -s exp')
print()
- elif args.show_status:
- is_configured()
- config = ConfigTreeQuery()
- ct_sync_intf = config.list_nodes(['service', 'conntrack-sync', 'interface'])
- ct_sync_intf = ', '.join(ct_sync_intf)
- failover_state = "no transition yet!"
- expect_sync_protocols = "disabled"
-
- if config.exists(['service', 'conntrack-sync', 'failover-mechanism', 'vrrp']):
- failover_mechanism = "vrrp"
- vrrp_sync_grp = config.value(['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group'])
-
- if os.path.isfile(failover_state_file):
- with open(failover_state_file, "r") as f:
- failover_state = f.readline()
-
- if config.exists(['service', 'conntrack-sync', 'expect-sync']):
- expect_sync_protocols = config.values(['service', 'conntrack-sync', 'expect-sync'])
- if 'all' in expect_sync_protocols:
- expect_sync_protocols = ["ftp", "sip", "h323", "nfs", "sqlnet"]
+def show_status(raw: bool):
+ is_configured()
+ config = ConfigTreeQuery()
+ ct_sync_intf = config.list_nodes(['service', 'conntrack-sync', 'interface'])
+ ct_sync_intf = ', '.join(ct_sync_intf)
+ failover_state = "no transition yet!"
+ expect_sync_protocols = []
+
+ if config.exists(['service', 'conntrack-sync', 'failover-mechanism', 'vrrp']):
+ failover_mechanism = "vrrp"
+ vrrp_sync_grp = config.value(['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group'])
+
+ if os.path.isfile(failover_state_file):
+ with open(failover_state_file, "r") as f:
+ failover_state = f.readline()
+
+ if config.exists(['service', 'conntrack-sync', 'expect-sync']):
+ expect_sync_protocols = config.values(['service', 'conntrack-sync', 'expect-sync'])
+ if 'all' in expect_sync_protocols:
+ expect_sync_protocols = ["ftp", "sip", "h323", "nfs", "sqlnet"]
+
+ if raw:
+ status_data = {
+ "sync_interface": ct_sync_intf,
+ "failover_mechanism": failover_mechanism,
+ "sync_group": vrrp_sync_grp,
+ "last_transition": failover_state,
+ "sync_protocols": expect_sync_protocols
+ }
+
+ return status_data
+ else:
+ if expect_sync_protocols:
expect_sync_protocols = ', '.join(expect_sync_protocols)
-
+ else:
+ expect_sync_protocols = "disabled"
show_status = (f'\nsync-interface : {ct_sync_intf}\n'
f'failover-mechanism : {failover_mechanism} [sync-group {vrrp_sync_grp}]\n'
- f'last state transition : {failover_state}'
+ f'last state transition : {failover_state}\n'
f'ExpectationSync : {expect_sync_protocols}')
- print(show_status)
+ return show_status
- else:
- parser.print_help()
- exit(1)
+if __name__ == '__main__':
+ syslog.openlog(ident='conntrack-tools', logoption=syslog.LOG_PID, facility=syslog.LOG_INFO)
+
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/container.py b/src/op_mode/container.py
index ce466ffc1..5a022d0c0 100755
--- a/src/op_mode/container.py
+++ b/src/op_mode/container.py
@@ -19,11 +19,10 @@ import sys
from sys import exit
-from vyos.util import cmd
+from vyos.utils.process import cmd
import vyos.opmode
-
def _get_json_data(command: str) -> list:
"""
Get container command format JSON
@@ -36,9 +35,22 @@ def _get_raw_data(command: str) -> list:
data = json.loads(json_data)
return data
+def add_image(name: str):
+ from vyos.utils.process import rc_cmd
+
+ rc, output = rc_cmd(f'podman image pull {name}')
+ if rc != 0:
+ raise vyos.opmode.InternalError(output)
+
+def delete_image(name: str):
+ from vyos.utils.process import rc_cmd
+
+ rc, output = rc_cmd(f'podman image rm --force {name}')
+ if rc != 0:
+ raise vyos.opmode.InternalError(output)
def show_container(raw: bool):
- command = 'sudo podman ps --all'
+ command = 'podman ps --all'
container_data = _get_raw_data(command)
if raw:
return container_data
@@ -47,8 +59,8 @@ def show_container(raw: bool):
def show_image(raw: bool):
- command = 'sudo podman image ls'
- container_data = _get_raw_data('sudo podman image ls')
+ command = 'podman image ls'
+ container_data = _get_raw_data('podman image ls')
if raw:
return container_data
else:
@@ -56,7 +68,7 @@ def show_image(raw: bool):
def show_network(raw: bool):
- command = 'sudo podman network ls'
+ command = 'podman network ls'
container_data = _get_raw_data(command)
if raw:
return container_data
@@ -65,13 +77,13 @@ def show_network(raw: bool):
def restart(name: str):
- from vyos.util import rc_cmd
+ from vyos.utils.process import rc_cmd
- rc, output = rc_cmd(f'sudo podman restart {name}')
+ rc, output = rc_cmd(f'systemctl restart vyos-container-{name}.service')
if rc != 0:
print(output)
return None
- print(f'Container name "{name}" restarted!')
+ print(f'Container "{name}" restarted!')
return output
diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py
index 07e9b7d6c..77f38992b 100755
--- a/src/op_mode/dhcp.py
+++ b/src/op_mode/dhcp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -14,35 +14,35 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
import sys
-from ipaddress import ip_address
import typing
from datetime import datetime
-from sys import exit
-from tabulate import tabulate
+from glob import glob
+from ipaddress import ip_address
from isc_dhcp_leases import IscDhcpLeases
+from tabulate import tabulate
+
+import vyos.opmode
from vyos.base import Warning
from vyos.configquery import ConfigTreeQuery
-from vyos.util import cmd
-from vyos.util import dict_search
-from vyos.util import is_systemd_service_running
-
-import vyos.opmode
+from vyos.utils.dict import dict_search
+from vyos.utils.file import read_file
+from vyos.utils.process import cmd
+from vyos.utils.process import is_systemd_service_running
+time_string = "%a %b %d %H:%M:%S %Z %Y"
config = ConfigTreeQuery()
-pool_key = "shared-networkname"
-
-
-def _in_pool(lease, pool):
- if pool_key in lease.sets:
- if lease.sets[pool_key] == pool:
- return True
- return False
+lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup']
+sort_valid_inet = ['end', 'mac', 'hostname', 'ip', 'pool', 'remaining', 'start', 'state']
+sort_valid_inet6 = ['end', 'iaid_duid', 'ip', 'last_communication', 'pool', 'remaining', 'state', 'type']
+ArgFamily = typing.Literal['inet', 'inet6']
+ArgState = typing.Literal['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup']
def _utc_to_local(utc_dt):
return datetime.fromtimestamp((datetime.fromtimestamp(utc_dt) - datetime(1970, 1, 1)).total_seconds())
@@ -71,7 +71,7 @@ def _find_list_of_dict_index(lst, key='ip', value='') -> int:
return idx
-def _get_raw_server_leases(family, pool=None) -> list:
+def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[]) -> list:
"""
Get DHCP server leases
:return list
@@ -79,18 +79,21 @@ def _get_raw_server_leases(family, pool=None) -> list:
lease_file = '/config/dhcpdv6.leases' if family == 'inet6' else '/config/dhcpd.leases'
data = []
leases = IscDhcpLeases(lease_file).get()
- if pool is not None:
- if config.exists(f'service dhcp-server shared-network-name {pool}'):
- leases = list(filter(lambda x: _in_pool(x, pool), leases))
+
+ if pool is None:
+ pool = _get_dhcp_pools(family=family)
+ else:
+ pool = [pool]
+
for lease in leases:
data_lease = {}
data_lease['ip'] = lease.ip
data_lease['state'] = lease.binding_state
data_lease['pool'] = lease.sets.get('shared-networkname', '')
- data_lease['end'] = lease.end.timestamp()
+ data_lease['end'] = lease.end.timestamp() if lease.end else None
if family == 'inet':
- data_lease['hardware'] = lease.ethernet
+ data_lease['mac'] = lease.ethernet
data_lease['start'] = lease.start.timestamp()
data_lease['hostname'] = lease.hostname
@@ -100,18 +103,20 @@ def _get_raw_server_leases(family, pool=None) -> list:
lease_types_long = {'na': 'non-temporary', 'ta': 'temporary', 'pd': 'prefix delegation'}
data_lease['type'] = lease_types_long[lease.type]
- data_lease['remaining'] = lease.end - datetime.utcnow()
+ data_lease['remaining'] = '-'
- if data_lease['remaining'].days >= 0:
- # substraction gives us a timedelta object which can't be formatted with strftime
- # so we use str(), split gets rid of the microseconds
- data_lease['remaining'] = str(data_lease["remaining"]).split('.')[0]
- else:
- data_lease['remaining'] = ''
+ if lease.end:
+ data_lease['remaining'] = lease.end - datetime.utcnow()
+
+ if data_lease['remaining'].days >= 0:
+ # substraction gives us a timedelta object which can't be formatted with strftime
+ # so we use str(), split gets rid of the microseconds
+ data_lease['remaining'] = str(data_lease["remaining"]).split('.')[0]
# Do not add old leases
- if data_lease['remaining'] != '':
- data.append(data_lease)
+ if data_lease['remaining'] != '' and data_lease['pool'] in pool and data_lease['state'] != 'free':
+ if not state or data_lease['state'] in state:
+ data.append(data_lease)
# deduplicate
checked = []
@@ -123,26 +128,31 @@ def _get_raw_server_leases(family, pool=None) -> list:
idx = _find_list_of_dict_index(data, key='ip', value=addr)
data.pop(idx)
+ if sorted:
+ if sorted == 'ip':
+ data.sort(key = lambda x:ip_address(x['ip']))
+ else:
+ data.sort(key = lambda x:x[sorted])
return data
-def _get_formatted_server_leases(raw_data, family):
+def _get_formatted_server_leases(raw_data, family='inet'):
data_entries = []
if family == 'inet':
for lease in raw_data:
ipaddr = lease.get('ip')
- hw_addr = lease.get('hardware')
+ hw_addr = lease.get('mac')
state = lease.get('state')
start = lease.get('start')
start = _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S')
end = lease.get('end')
- end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S')
+ end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S') if end else '-'
remain = lease.get('remaining')
pool = lease.get('pool')
hostname = lease.get('hostname')
data_entries.append([ipaddr, hw_addr, state, start, end, remain, pool, hostname])
- headers = ['IP Address', 'Hardware address', 'State', 'Lease start', 'Lease expiration', 'Remaining', 'Pool',
+ headers = ['IP Address', 'MAC address', 'State', 'Lease start', 'Lease expiration', 'Remaining', 'Pool',
'Hostname']
if family == 'inet6':
@@ -247,7 +257,7 @@ def _verify(func):
@_verify
-def show_pool_statistics(raw: bool, family: str, pool: typing.Optional[str]):
+def show_pool_statistics(raw: bool, family: ArgFamily, pool: typing.Optional[str]):
pool_data = _get_raw_pool_statistics(family=family, pool=pool)
if raw:
return pool_data
@@ -256,17 +266,122 @@ def show_pool_statistics(raw: bool, family: str, pool: typing.Optional[str]):
@_verify
-def show_server_leases(raw: bool, family: str):
+def show_server_leases(raw: bool, family: ArgFamily, pool: typing.Optional[str],
+ sorted: typing.Optional[str], state: typing.Optional[ArgState]):
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
- if not is_systemd_service_running('isc-dhcp-server.service'):
- Warning('DHCP server is configured but not started. Data may be stale.')
+ v = '6' if family == 'inet6' else ''
+ service_name = 'DHCPv6' if family == 'inet6' else 'DHCP'
+ if not is_systemd_service_running(f'isc-dhcp-server{v}.service'):
+ Warning(f'{service_name} server is configured but not started. Data may be stale.')
+
+ v = 'v6' if family == 'inet6' else ''
+ if pool and pool not in _get_dhcp_pools(family=family):
+ raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!')
+
+ if state and state not in lease_valid_states:
+ raise vyos.opmode.IncorrectValue(f'DHCP{v} state "{state}" is invalid!')
+
+ sort_valid = sort_valid_inet6 if family == 'inet6' else sort_valid_inet
+ if sorted and sorted not in sort_valid:
+ raise vyos.opmode.IncorrectValue(f'DHCP{v} sort "{sorted}" is invalid!')
- leases = _get_raw_server_leases(family)
+ lease_data = _get_raw_server_leases(family=family, pool=pool, sorted=sorted, state=state)
if raw:
- return leases
+ return lease_data
else:
- return _get_formatted_server_leases(leases, family)
+ return _get_formatted_server_leases(lease_data, family=family)
+
+
+def _get_raw_client_leases(family='inet', interface=None):
+ from time import mktime
+ from datetime import datetime
+ from vyos.defaults import directories
+ from vyos.utils.network import get_interface_vrf
+
+ lease_dir = directories['isc_dhclient_dir']
+ lease_files = []
+ lease_data = []
+
+ if interface:
+ tmp = f'{lease_dir}/dhclient_{interface}.lease'
+ if os.path.exists(tmp):
+ lease_files.append(tmp)
+ else:
+ # All DHCP leases
+ lease_files = glob(f'{lease_dir}/dhclient_*.lease')
+
+ for lease in lease_files:
+ tmp = {}
+ with open(lease, 'r') as f:
+ for line in f.readlines():
+ line = line.rstrip()
+ if 'last_update' not in tmp:
+ # ISC dhcp client contains least_update timestamp in human readable
+ # format this makes less sense for an API and also the expiry
+ # timestamp is provided in UNIX time. Convert string (e.g. Sun Jul
+ # 30 18:13:44 CEST 2023) to UNIX time (1690733624)
+ tmp.update({'last_update' : int(mktime(datetime.strptime(line, time_string).timetuple()))})
+ continue
+
+ k, v = line.split('=')
+ tmp.update({k : v.replace("'", "")})
+
+ if 'interface' in tmp:
+ vrf = get_interface_vrf(tmp['interface'])
+ if vrf: tmp.update({'vrf' : vrf})
+ lease_data.append(tmp)
+
+ return lease_data
+
+def _get_formatted_client_leases(lease_data, family):
+ from time import localtime
+ from time import strftime
+
+ from vyos.utils.network import is_intf_addr_assigned
+
+ data_entries = []
+ for lease in lease_data:
+ if not lease.get('new_ip_address'):
+ continue
+ data_entries.append(["Interface", lease['interface']])
+ if 'new_ip_address' in lease:
+ tmp = '[Active]' if is_intf_addr_assigned(lease['interface'], lease['new_ip_address']) else '[Inactive]'
+ data_entries.append(["IP address", lease['new_ip_address'], tmp])
+ if 'new_subnet_mask' in lease:
+ data_entries.append(["Subnet Mask", lease['new_subnet_mask']])
+ if 'new_domain_name' in lease:
+ data_entries.append(["Domain Name", lease['new_domain_name']])
+ if 'new_routers' in lease:
+ data_entries.append(["Router", lease['new_routers']])
+ if 'new_domain_name_servers' in lease:
+ data_entries.append(["Name Server", lease['new_domain_name_servers']])
+ if 'new_dhcp_server_identifier' in lease:
+ data_entries.append(["DHCP Server", lease['new_dhcp_server_identifier']])
+ if 'new_dhcp_lease_time' in lease:
+ data_entries.append(["DHCP Server", lease['new_dhcp_lease_time']])
+ if 'vrf' in lease:
+ data_entries.append(["VRF", lease['vrf']])
+ if 'last_update' in lease:
+ tmp = strftime(time_string, localtime(int(lease['last_update'])))
+ data_entries.append(["Last Update", tmp])
+ if 'new_expiry' in lease:
+ tmp = strftime(time_string, localtime(int(lease['new_expiry'])))
+ data_entries.append(["Expiry", tmp])
+
+ # Add empty marker
+ data_entries.append([''])
+
+ output = tabulate(data_entries, tablefmt='plain')
+
+ return output
+
+def show_client_leases(raw: bool, family: ArgFamily, interface: typing.Optional[str]):
+ lease_data = _get_raw_client_leases(family=family, interface=interface)
+ if raw:
+ return lease_data
+ else:
+ return _get_formatted_client_leases(lease_data, family=family)
if __name__ == '__main__':
try:
diff --git a/src/op_mode/dns.py b/src/op_mode/dns.py
index a0e47d7ad..2168aef89 100755
--- a/src/op_mode/dns.py
+++ b/src/op_mode/dns.py
@@ -17,11 +17,10 @@
import sys
-from sys import exit
from tabulate import tabulate
from vyos.configquery import ConfigTreeQuery
-from vyos.util import cmd
+from vyos.utils.process import cmd
import vyos.opmode
@@ -75,8 +74,7 @@ def show_forwarding_statistics(raw: bool):
config = ConfigTreeQuery()
if not config.exists('service dns forwarding'):
- print("DNS forwarding is not configured")
- exit(0)
+ raise vyos.opmode.UnconfiguredSubsystem('DNS forwarding is not configured')
dns_data = _get_raw_forwarding_statistics()
if raw:
diff --git a/src/op_mode/dns_dynamic.py b/src/op_mode/dns_dynamic.py
new file mode 100755
index 000000000..12aa5494a
--- /dev/null
+++ b/src/op_mode/dns_dynamic.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018-2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import argparse
+import sys
+import time
+from tabulate import tabulate
+
+from vyos.config import Config
+from vyos.template import is_ipv4, is_ipv6
+from vyos.utils.process import call
+
+cache_file = r'/run/ddclient/ddclient.cache'
+
+columns = {
+ 'host': 'Hostname',
+ 'ipv4': 'IPv4 address',
+ 'status-ipv4': 'IPv4 status',
+ 'ipv6': 'IPv6 address',
+ 'status-ipv6': 'IPv6 status',
+ 'mtime': 'Last update',
+}
+
+
+def _get_formatted_host_records(host_data):
+ data_entries = []
+ for entry in host_data:
+ data_entries.append([entry.get(key) for key in columns.keys()])
+
+ header = columns.values()
+ output = tabulate(data_entries, header, numalign='left')
+ return output
+
+
+def show_status():
+ # A ddclient status file might not always exist
+ if not os.path.exists(cache_file):
+ sys.exit(0)
+
+ data = []
+
+ with open(cache_file, 'r') as f:
+ for line in f:
+ if line.startswith('#'):
+ continue
+
+ props = {}
+ # ddclient cache rows have properties in 'key=value' format separated by comma
+ # we pick up the ones we are interested in
+ for kvraw in line.split(' ')[0].split(','):
+ k, v = kvraw.split('=')
+ if k in list(columns.keys()) + ['ip', 'status']: # ip and status are legacy keys
+ props[k] = v
+
+ # Extract IPv4 and IPv6 address and status from legacy keys
+ # Dual-stack isn't supported in legacy format, 'ip' and 'status' are for one of IPv4 or IPv6
+ if 'ip' in props:
+ if is_ipv4(props['ip']):
+ props['ipv4'] = props['ip']
+ props['status-ipv4'] = props['status']
+ elif is_ipv6(props['ip']):
+ props['ipv6'] = props['ip']
+ props['status-ipv6'] = props['status']
+ del props['ip']
+
+ # Convert mtime to human readable format
+ if 'mtime' in props:
+ props['mtime'] = time.strftime(
+ "%Y-%m-%d %H:%M:%S", time.localtime(int(props['mtime'], base=10)))
+
+ data.append(props)
+
+ print(_get_formatted_host_records(data))
+
+
+def update_ddns():
+ call('systemctl stop ddclient.service')
+ if os.path.exists(cache_file):
+ os.remove(cache_file)
+ call('systemctl start ddclient.service')
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument("--status", help="Show DDNS status", action="store_true")
+ group.add_argument("--update", help="Update DDNS on a given interface", action="store_true")
+ args = parser.parse_args()
+
+ # Do nothing if service is not configured
+ c = Config()
+ if not c.exists_effective('service dns dynamic'):
+ print("Dynamic DNS not configured")
+ sys.exit(1)
+
+ if args.status:
+ show_status()
+ elif args.update:
+ update_ddns()
diff --git a/src/op_mode/dns_forwarding_reset.py b/src/op_mode/dns_forwarding_reset.py
index bfc640a26..55e20918f 100755
--- a/src/op_mode/dns_forwarding_reset.py
+++ b/src/op_mode/dns_forwarding_reset.py
@@ -25,7 +25,7 @@ import argparse
from sys import exit
from vyos.config import Config
-from vyos.util import call
+from vyos.utils.process import call
PDNS_CMD='/usr/bin/rec_control --socket-dir=/run/powerdns'
diff --git a/src/op_mode/dns_forwarding_statistics.py b/src/op_mode/dns_forwarding_statistics.py
index d79b6c024..32b5c76a7 100755
--- a/src/op_mode/dns_forwarding_statistics.py
+++ b/src/op_mode/dns_forwarding_statistics.py
@@ -4,7 +4,7 @@ import jinja2
from sys import exit
from vyos.config import Config
-from vyos.util import cmd
+from vyos.utils.process import cmd
PDNS_CMD='/usr/bin/rec_control --socket-dir=/run/powerdns'
diff --git a/src/op_mode/dynamic_dns.py b/src/op_mode/dynamic_dns.py
deleted file mode 100755
index 263a3b6a5..000000000
--- a/src/op_mode/dynamic_dns.py
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import argparse
-import jinja2
-import sys
-import time
-
-from vyos.config import Config
-from vyos.util import call
-
-cache_file = r'/run/ddclient/ddclient.cache'
-
-OUT_TMPL_SRC = """
-{% for entry in hosts %}
-ip address : {{ entry.ip }}
-host-name : {{ entry.host }}
-last update : {{ entry.time }}
-update-status: {{ entry.status }}
-
-{% endfor %}
-"""
-
-def show_status():
- # A ddclient status file must not always exist
- if not os.path.exists(cache_file):
- sys.exit(0)
-
- data = {
- 'hosts': []
- }
-
- with open(cache_file, 'r') as f:
- for line in f:
- if line.startswith('#'):
- continue
-
- outp = {
- 'host': '',
- 'ip': '',
- 'time': ''
- }
-
- if 'host=' in line:
- host = line.split('host=')[1]
- if host:
- outp['host'] = host.split(',')[0]
-
- if 'ip=' in line:
- ip = line.split('ip=')[1]
- if ip:
- outp['ip'] = ip.split(',')[0]
-
- if 'mtime=' in line:
- mtime = line.split('mtime=')[1]
- if mtime:
- outp['time'] = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(int(mtime.split(',')[0], base=10)))
-
- if 'status=' in line:
- status = line.split('status=')[1]
- if status:
- outp['status'] = status.split(',')[0]
-
- data['hosts'].append(outp)
-
- tmpl = jinja2.Template(OUT_TMPL_SRC)
- print(tmpl.render(data))
-
-
-def update_ddns():
- call('systemctl stop ddclient.service')
- if os.path.exists(cache_file):
- os.remove(cache_file)
- call('systemctl start ddclient.service')
-
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser()
- group = parser.add_mutually_exclusive_group()
- group.add_argument("--status", help="Show DDNS status", action="store_true")
- group.add_argument("--update", help="Update DDNS on a given interface", action="store_true")
- args = parser.parse_args()
-
- # Do nothing if service is not configured
- c = Config()
- if not c.exists_effective('service dns dynamic'):
- print("Dynamic DNS not configured")
- sys.exit(1)
-
- if args.status:
- show_status()
- elif args.update:
- update_ddns()
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
index 46bda5f7e..23b4b8459 100755
--- a/src/op_mode/firewall.py
+++ b/src/op_mode/firewall.py
@@ -21,65 +21,31 @@ import re
import tabulate
from vyos.config import Config
-from vyos.util import cmd
-from vyos.util import dict_search_args
+from vyos.utils.process import cmd
+from vyos.utils.dict import dict_search_args
-def get_firewall_interfaces(firewall, name=None, ipv6=False):
- directions = ['in', 'out', 'local']
-
- if 'interface' in firewall:
- for ifname, if_conf in firewall['interface'].items():
- for direction in directions:
- if direction not in if_conf:
- continue
-
- fw_conf = if_conf[direction]
- name_str = f'({ifname},{direction})'
-
- if 'name' in fw_conf:
- fw_name = fw_conf['name']
-
- if not name:
- firewall['name'][fw_name]['interface'].append(name_str)
- elif not ipv6 and name == fw_name:
- firewall['interface'].append(name_str)
-
- if 'ipv6_name' in fw_conf:
- fw_name = fw_conf['ipv6_name']
-
- if not name:
- firewall['ipv6_name'][fw_name]['interface'].append(name_str)
- elif ipv6 and name == fw_name:
- firewall['interface'].append(name_str)
-
- return firewall
-
-def get_config_firewall(conf, name=None, ipv6=False, interfaces=True):
+def get_config_firewall(conf, hook=None, priority=None, ipv6=False):
config_path = ['firewall']
- if name:
- config_path += ['ipv6-name' if ipv6 else 'name', name]
+ if hook:
+ config_path += ['ipv6' if ipv6 else 'ipv4', hook]
+ if priority:
+ config_path += [priority]
firewall = conf.get_config_dict(config_path, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
- if firewall and interfaces:
- if name:
- firewall['interface'] = {}
- else:
- if 'name' in firewall:
- for fw_name, name_conf in firewall['name'].items():
- name_conf['interface'] = []
- if 'ipv6_name' in firewall:
- for fw_name, name_conf in firewall['ipv6_name'].items():
- name_conf['interface'] = []
-
- get_firewall_interfaces(firewall, name, ipv6)
return firewall
-def get_nftables_details(name, ipv6=False):
+def get_nftables_details(hook, priority, ipv6=False):
suffix = '6' if ipv6 else ''
+ aux = 'IPV6_' if ipv6 else ''
name_prefix = 'NAME6_' if ipv6 else 'NAME_'
- command = f'sudo nft list chain ip{suffix} vyos_filter {name_prefix}{name}'
+ if hook == 'name' or hook == 'ipv6-name':
+ command = f'sudo nft list chain ip{suffix} vyos_filter {name_prefix}{priority}'
+ else:
+ up_hook = hook.upper()
+ command = f'sudo nft list chain ip{suffix} vyos_filter VYOS_{aux}{up_hook}_{priority}'
+
try:
results = cmd(command)
except:
@@ -87,7 +53,7 @@ def get_nftables_details(name, ipv6=False):
out = {}
for line in results.split('\n'):
- comment_search = re.search(rf'{name}[\- ](\d+|default-action)', line)
+ comment_search = re.search(rf'{priority}[\- ](\d+|default-action)', line)
if not comment_search:
continue
@@ -102,18 +68,15 @@ def get_nftables_details(name, ipv6=False):
out[rule_id] = rule
return out
-def output_firewall_name(name, name_conf, ipv6=False, single_rule_id=None):
+def output_firewall_name(hook, priority, firewall_conf, ipv6=False, single_rule_id=None):
ip_str = 'IPv6' if ipv6 else 'IPv4'
- print(f'\n---------------------------------\n{ip_str} Firewall "{name}"\n')
+ print(f'\n---------------------------------\n{ip_str} Firewall "{hook} {priority}"\n')
- if name_conf['interface']:
- print('Active on: {0}\n'.format(" ".join(name_conf['interface'])))
-
- details = get_nftables_details(name, ipv6)
+ details = get_nftables_details(hook, priority, ipv6)
rows = []
- if 'rule' in name_conf:
- for rule_id, rule_conf in name_conf['rule'].items():
+ if 'rule' in firewall_conf:
+ for rule_id, rule_conf in firewall_conf['rule'].items():
if single_rule_id and rule_id != single_rule_id:
continue
@@ -128,8 +91,8 @@ def output_firewall_name(name, name_conf, ipv6=False, single_rule_id=None):
row.append(rule_details['conditions'])
rows.append(row)
- if 'default_action' in name_conf and not single_rule_id:
- row = ['default', name_conf['default_action'], 'all']
+ if 'default_action' in firewall_conf and not single_rule_id:
+ row = ['default', firewall_conf['default_action'], 'all']
if 'default-action' in details:
rule_details = details['default-action']
row.append(rule_details.get('packets', 0))
@@ -140,26 +103,72 @@ def output_firewall_name(name, name_conf, ipv6=False, single_rule_id=None):
header = ['Rule', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions']
print(tabulate.tabulate(rows, header) + '\n')
-def output_firewall_name_statistics(name, name_conf, ipv6=False, single_rule_id=None):
+def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_rule_id=None):
ip_str = 'IPv6' if ipv6 else 'IPv4'
- print(f'\n---------------------------------\n{ip_str} Firewall "{name}"\n')
-
- if name_conf['interface']:
- print('Active on: {0}\n'.format(" ".join(name_conf['interface'])))
+ print(f'\n---------------------------------\n{ip_str} Firewall "{hook} {prior}"\n')
- details = get_nftables_details(name, ipv6)
+ details = get_nftables_details(hook, prior, ipv6)
rows = []
- if 'rule' in name_conf:
- for rule_id, rule_conf in name_conf['rule'].items():
+ if 'rule' in prior_conf:
+ for rule_id, rule_conf in prior_conf['rule'].items():
if single_rule_id and rule_id != single_rule_id:
continue
if 'disable' in rule_conf:
continue
- source_addr = dict_search_args(rule_conf, 'source', 'address') or '0.0.0.0/0'
- dest_addr = dict_search_args(rule_conf, 'destination', 'address') or '0.0.0.0/0'
+ # Get source
+ source_addr = dict_search_args(rule_conf, 'source', 'address')
+ if not source_addr:
+ source_addr = dict_search_args(rule_conf, 'source', 'group', 'address_group')
+ if not source_addr:
+ source_addr = dict_search_args(rule_conf, 'source', 'group', 'network_group')
+ if not source_addr:
+ source_addr = dict_search_args(rule_conf, 'source', 'group', 'domain_group')
+ if not source_addr:
+ source_addr = dict_search_args(rule_conf, 'source', 'fqdn')
+ if not source_addr:
+ source_addr = dict_search_args(rule_conf, 'source', 'geoip', 'country_code')
+ if source_addr:
+ source_addr = str(source_addr)[1:-1].replace('\'','')
+ if 'inverse_match' in dict_search_args(rule_conf, 'source', 'geoip'):
+ source_addr = 'NOT ' + str(source_addr)
+ if not source_addr:
+ source_addr = 'any'
+
+ # Get destination
+ dest_addr = dict_search_args(rule_conf, 'destination', 'address')
+ if not dest_addr:
+ dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'address_group')
+ if not dest_addr:
+ dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'network_group')
+ if not dest_addr:
+ dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'domain_group')
+ if not dest_addr:
+ dest_addr = dict_search_args(rule_conf, 'destination', 'fqdn')
+ if not dest_addr:
+ dest_addr = dict_search_args(rule_conf, 'destination', 'geoip', 'country_code')
+ if dest_addr:
+ dest_addr = str(dest_addr)[1:-1].replace('\'','')
+ if 'inverse_match' in dict_search_args(rule_conf, 'destination', 'geoip'):
+ dest_addr = 'NOT ' + str(dest_addr)
+ if not dest_addr:
+ dest_addr = 'any'
+
+ # Get inbound interface
+ iiface = dict_search_args(rule_conf, 'inbound_interface', 'interface_name')
+ if not iiface:
+ iiface = dict_search_args(rule_conf, 'inbound_interface', 'interface_group')
+ if not iiface:
+ iiface = 'any'
+
+ # Get outbound interface
+ oiface = dict_search_args(rule_conf, 'outbound_interface', 'interface_name')
+ if not oiface:
+ oiface = dict_search_args(rule_conf, 'outbound_interface', 'interface_group')
+ if not oiface:
+ oiface = 'any'
row = [rule_id]
if rule_id in details:
@@ -172,9 +181,26 @@ def output_firewall_name_statistics(name, name_conf, ipv6=False, single_rule_id=
row.append(rule_conf['action'])
row.append(source_addr)
row.append(dest_addr)
+ row.append(iiface)
+ row.append(oiface)
rows.append(row)
- if 'default_action' in name_conf and not single_rule_id:
+
+ if hook in ['input', 'forward', 'output']:
+ row = ['default']
+ row.append('N/A')
+ row.append('N/A')
+ if 'default_action' in prior_conf:
+ row.append(prior_conf['default_action'])
+ else:
+ row.append('accept')
+ row.append('any')
+ row.append('any')
+ row.append('any')
+ row.append('any')
+ rows.append(row)
+
+ elif 'default_action' in prior_conf and not single_rule_id:
row = ['default']
if 'default-action' in details:
rule_details = details['default-action']
@@ -183,13 +209,15 @@ def output_firewall_name_statistics(name, name_conf, ipv6=False, single_rule_id=
else:
row.append('0')
row.append('0')
- row.append(name_conf['default_action'])
- row.append('0.0.0.0/0') # Source
- row.append('0.0.0.0/0') # Dest
+ row.append(prior_conf['default_action'])
+ row.append('any') # Source
+ row.append('any') # Dest
+ row.append('any') # inbound-interface
+ row.append('any') # outbound-interface
rows.append(row)
if rows:
- header = ['Rule', 'Packets', 'Bytes', 'Action', 'Source', 'Destination']
+ header = ['Rule', 'Packets', 'Bytes', 'Action', 'Source', 'Destination', 'Inbound-Interface', 'Outbound-interface']
print(tabulate.tabulate(rows, header) + '\n')
def show_firewall():
@@ -201,52 +229,102 @@ def show_firewall():
if not firewall:
return
- if 'name' in firewall:
- for name, name_conf in firewall['name'].items():
- output_firewall_name(name, name_conf, ipv6=False)
+ if 'ipv4' in firewall:
+ for hook, hook_conf in firewall['ipv4'].items():
+ for prior, prior_conf in firewall['ipv4'][hook].items():
+ output_firewall_name(hook, prior, prior_conf, ipv6=False)
+
+ if 'ipv6' in firewall:
+ for hook, hook_conf in firewall['ipv6'].items():
+ for prior, prior_conf in firewall['ipv6'][hook].items():
+ output_firewall_name(hook, prior, prior_conf, ipv6=True)
- if 'ipv6_name' in firewall:
- for name, name_conf in firewall['ipv6_name'].items():
- output_firewall_name(name, name_conf, ipv6=True)
+def show_firewall_family(family):
+ print(f'Rulesets {family} Information')
-def show_firewall_name(name, ipv6=False):
+ conf = Config()
+ firewall = get_config_firewall(conf)
+
+ if not firewall:
+ return
+
+ for hook, hook_conf in firewall[family].items():
+ for prior, prior_conf in firewall[family][hook].items():
+ if family == 'ipv6':
+ output_firewall_name(hook, prior, prior_conf, ipv6=True)
+ else:
+ output_firewall_name(hook, prior, prior_conf, ipv6=False)
+
+def show_firewall_name(hook, priority, ipv6=False):
print('Ruleset Information')
conf = Config()
- firewall = get_config_firewall(conf, name, ipv6)
+ firewall = get_config_firewall(conf, hook, priority, ipv6)
if firewall:
- output_firewall_name(name, firewall, ipv6)
+ output_firewall_name(hook, priority, firewall, ipv6)
-def show_firewall_rule(name, rule_id, ipv6=False):
+def show_firewall_rule(hook, priority, rule_id, ipv6=False):
print('Rule Information')
conf = Config()
- firewall = get_config_firewall(conf, name, ipv6)
+ firewall = get_config_firewall(conf, hook, priority, ipv6)
if firewall:
- output_firewall_name(name, firewall, ipv6, rule_id)
+ output_firewall_name(hook, priority, firewall, ipv6, rule_id)
def show_firewall_group(name=None):
conf = Config()
- firewall = get_config_firewall(conf, interfaces=False)
+ firewall = get_config_firewall(conf)
if 'group' not in firewall:
return
def find_references(group_type, group_name):
out = []
- for name_type in ['name', 'ipv6_name']:
- if name_type not in firewall:
- continue
- for name, name_conf in firewall[name_type].items():
- if 'rule' not in name_conf:
- continue
- for rule_id, rule_conf in name_conf['rule'].items():
- source_group = dict_search_args(rule_conf, 'source', 'group', group_type)
- dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type)
- if source_group and group_name == source_group:
- out.append(f'{name}-{rule_id}')
- elif dest_group and group_name == dest_group:
- out.append(f'{name}-{rule_id}')
+ family = []
+ if group_type in ['address_group', 'network_group']:
+ family = ['ipv4']
+ elif group_type == 'ipv6_address_group':
+ family = ['ipv6']
+ group_type = 'address_group'
+ elif group_type == 'ipv6_network_group':
+ family = ['ipv6']
+ group_type = 'network_group'
+ else:
+ family = ['ipv4', 'ipv6']
+
+ for item in family:
+ for name_type in ['name', 'ipv6_name', 'forward', 'input', 'output']:
+ if item in firewall:
+ if name_type not in firewall[item]:
+ continue
+ for priority, priority_conf in firewall[item][name_type].items():
+ if priority not in firewall[item][name_type]:
+ continue
+ for rule_id, rule_conf in priority_conf['rule'].items():
+ source_group = dict_search_args(rule_conf, 'source', 'group', group_type)
+ dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type)
+ in_interface = dict_search_args(rule_conf, 'inbound_interface', 'interface_group')
+ out_interface = dict_search_args(rule_conf, 'outbound_interface', 'interface_group')
+ if source_group:
+ if source_group[0] == "!":
+ source_group = source_group[1:]
+ if group_name == source_group:
+ out.append(f'{item}-{name_type}-{priority}-{rule_id}')
+ if dest_group:
+ if dest_group[0] == "!":
+ dest_group = dest_group[1:]
+ if group_name == dest_group:
+ out.append(f'{item}-{name_type}-{priority}-{rule_id}')
+ if in_interface:
+ if in_interface[0] == "!":
+ in_interface = in_interface[1:]
+ if group_name == in_interface:
+ out.append(f'{item}-{name_type}-{priority}-{rule_id}')
+ if out_interface:
+ if out_interface[0] == "!":
+ out_interface = out_interface[1:]
+ if group_name == out_interface:
+ out.append(f'{item}-{name_type}-{priority}-{rule_id}')
return out
header = ['Name', 'Type', 'References', 'Members']
@@ -258,7 +336,7 @@ def show_firewall_group(name=None):
continue
references = find_references(group_type, group_name)
- row = [group_name, group_type, '\n'.join(references) or 'N/A']
+ row = [group_name, group_type, '\n'.join(references) or 'N/D']
if 'address' in group_conf:
row.append("\n".join(sorted(group_conf['address'])))
elif 'network' in group_conf:
@@ -267,8 +345,10 @@ def show_firewall_group(name=None):
row.append("\n".join(sorted(group_conf['mac_address'])))
elif 'port' in group_conf:
row.append("\n".join(sorted(group_conf['port'])))
+ elif 'interface' in group_conf:
+ row.append("\n".join(sorted(group_conf['interface'])))
else:
- row.append('N/A')
+ row.append('N/D')
rows.append(row)
if rows:
@@ -284,28 +364,28 @@ def show_summary():
if not firewall:
return
- header = ['Ruleset Name', 'Description', 'References']
+ header = ['Ruleset Hook', 'Ruleset Priority', 'Description', 'References']
v4_out = []
v6_out = []
- if 'name' in firewall:
- for name, name_conf in firewall['name'].items():
- description = name_conf.get('description', '')
- interfaces = ", ".join(name_conf['interface'])
- v4_out.append([name, description, interfaces])
+ if 'ipv4' in firewall:
+ for hook, hook_conf in firewall['ipv4'].items():
+ for prior, prior_conf in firewall['ipv4'][hook].items():
+ description = prior_conf.get('description', '')
+ v4_out.append([hook, prior, description])
- if 'ipv6_name' in firewall:
- for name, name_conf in firewall['ipv6_name'].items():
- description = name_conf.get('description', '')
- interfaces = ", ".join(name_conf['interface'])
- v6_out.append([name, description, interfaces or 'N/A'])
+ if 'ipv6' in firewall:
+ for hook, hook_conf in firewall['ipv6'].items():
+ for prior, prior_conf in firewall['ipv6'][hook].items():
+ description = prior_conf.get('description', '')
+ v6_out.append([hook, prior, description])
if v6_out:
- print('\nIPv6 name:\n')
+ print('\nIPv6 Ruleset:\n')
print(tabulate.tabulate(v6_out, header) + '\n')
if v4_out:
- print('\nIPv4 name:\n')
+ print('\nIPv4 Ruleset:\n')
print(tabulate.tabulate(v4_out, header) + '\n')
show_firewall_group()
@@ -319,18 +399,23 @@ def show_statistics():
if not firewall:
return
- if 'name' in firewall:
- for name, name_conf in firewall['name'].items():
- output_firewall_name_statistics(name, name_conf, ipv6=False)
+ if 'ipv4' in firewall:
+ for hook, hook_conf in firewall['ipv4'].items():
+ for prior, prior_conf in firewall['ipv4'][hook].items():
+ output_firewall_name_statistics(hook,prior, prior_conf, ipv6=False)
- if 'ipv6_name' in firewall:
- for name, name_conf in firewall['ipv6_name'].items():
- output_firewall_name_statistics(name, name_conf, ipv6=True)
+ if 'ipv6' in firewall:
+ for hook, hook_conf in firewall['ipv6'].items():
+ for prior, prior_conf in firewall['ipv6'][hook].items():
+ output_firewall_name_statistics(hook,prior, prior_conf, ipv6=True)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--action', help='Action', required=False)
parser.add_argument('--name', help='Firewall name', required=False, action='store', nargs='?', default='')
+ parser.add_argument('--family', help='IP family', required=False, action='store', nargs='?', default='')
+ parser.add_argument('--hook', help='Firewall hook', required=False, action='store', nargs='?', default='')
+ parser.add_argument('--priority', help='Firewall priority', required=False, action='store', nargs='?', default='')
parser.add_argument('--rule', help='Firewall Rule ID', required=False)
parser.add_argument('--ipv6', help='IPv6 toggle', action='store_true')
@@ -338,11 +423,13 @@ if __name__ == '__main__':
if args.action == 'show':
if not args.rule:
- show_firewall_name(args.name, args.ipv6)
+ show_firewall_name(args.hook, args.priority, args.ipv6)
else:
- show_firewall_rule(args.name, args.rule, args.ipv6)
+ show_firewall_rule(args.hook, args.priority, args.rule, args.ipv6)
elif args.action == 'show_all':
show_firewall()
+ elif args.action == 'show_family':
+ show_firewall_family(args.family)
elif args.action == 'show_group':
show_firewall_group(args.name)
elif args.action == 'show_statistics':
diff --git a/src/op_mode/flow_accounting_op.py b/src/op_mode/flow_accounting_op.py
index 514143cd7..497ccafdf 100755
--- a/src/op_mode/flow_accounting_op.py
+++ b/src/op_mode/flow_accounting_op.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -13,18 +13,18 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
import sys
import argparse
import re
import ipaddress
import os.path
+
from tabulate import tabulate
from json import loads
-from vyos.util import cmd
-from vyos.util import commit_in_progress
-from vyos.util import run
+from vyos.utils.commit import commit_in_progress
+from vyos.utils.process import cmd
+from vyos.utils.process import run
from vyos.logger import syslog
# some default values
diff --git a/src/op_mode/format_disk.py b/src/op_mode/format_disk.py
index b3ba44e87..31ceb196a 100755
--- a/src/op_mode/format_disk.py
+++ b/src/op_mode/format_disk.py
@@ -20,10 +20,10 @@ import re
from datetime import datetime
-from vyos.util import ask_yes_no
-from vyos.util import call
-from vyos.util import cmd
-from vyos.util import DEVNULL
+from vyos.utils.io import ask_yes_no
+from vyos.utils.process import call
+from vyos.utils.process import cmd
+from vyos.utils.process import DEVNULL
def list_disks():
disks = set()
diff --git a/src/op_mode/generate_interfaces_debug_archive.py b/src/op_mode/generate_interfaces_debug_archive.py
new file mode 100755
index 000000000..3059aad23
--- /dev/null
+++ b/src/op_mode/generate_interfaces_debug_archive.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from datetime import datetime
+from pathlib import Path
+from shutil import rmtree
+from socket import gethostname
+from sys import exit
+from tarfile import open as tar_open
+from vyos.utils.process import rc_cmd
+import os
+
+# define a list of commands that needs to be executed
+
+CMD_LIST: list[str] = [
+ "journalctl -b -n 500",
+ "journalctl -b -k -n 500",
+ "ip -s l",
+ "cat /proc/interrupts",
+ "cat /proc/softirqs",
+ "top -b -d 1 -n 2 -1",
+ "netstat -l",
+ "cat /proc/net/dev",
+ "cat /proc/net/softnet_stat",
+ "cat /proc/net/icmp",
+ "cat /proc/net/udp",
+ "cat /proc/net/tcp",
+ "cat /proc/net/netstat",
+ "sysctl net",
+ "timeout 10 tcpdump -c 500 -eni any port not 22"
+]
+
+CMD_INTERFACES_LIST: list[str] = [
+ "ethtool -i ",
+ "ethtool -S ",
+ "ethtool -g ",
+ "ethtool -c ",
+ "ethtool -a ",
+ "ethtool -k ",
+ "ethtool -i ",
+ "ethtool --phy-statistics "
+]
+
+# get intefaces info
+interfaces_list = os.popen('ls /sys/class/net/').read().split()
+
+# modify CMD_INTERFACES_LIST for all interfaces
+CMD_INTERFACES_LIST_MOD=[]
+for command_interface in interfaces_list:
+ for command_interfacev2 in CMD_INTERFACES_LIST:
+ CMD_INTERFACES_LIST_MOD.append (f'{command_interfacev2}{command_interface}')
+
+# execute a command and save the output to a file
+
+def save_stdout(command: str, file: Path) -> None:
+ rc, stdout = rc_cmd(command)
+ body: str = f'''### {command} ###
+Command: {command}
+Exit code: {rc}
+Stdout:
+{stdout}
+
+'''
+ with file.open(mode='a') as f:
+ f.write(body)
+
+# get local host name
+hostname: str = gethostname()
+# get current time
+time_now: str = datetime.now().isoformat(timespec='seconds')
+
+# define a temporary directory for logs and collected data
+tmp_dir: Path = Path(f'/tmp/drops-debug_{time_now}')
+# set file paths
+drops_file: Path = Path(f'{tmp_dir}/drops.txt')
+interfaces_file: Path = Path(f'{tmp_dir}/interfaces.txt')
+archive_file: str = f'/tmp/packet-drops-debug_{time_now}.tar.bz2'
+
+# create files
+tmp_dir.mkdir()
+drops_file.touch()
+interfaces_file.touch()
+
+try:
+ # execute all commands
+ for command in CMD_LIST:
+ save_stdout(command, drops_file)
+ for command_interface in CMD_INTERFACES_LIST_MOD:
+ save_stdout(command_interface, interfaces_file)
+
+ # create an archive
+ with tar_open(name=archive_file, mode='x:bz2') as tar_file:
+ tar_file.add(tmp_dir)
+
+ # inform user about success
+ print(f'Debug file is generated and located in {archive_file}')
+except Exception as err:
+ print(f'Error during generating a debug file: {err}')
+finally:
+ # cleanup
+ rmtree(tmp_dir)
+ exit()
diff --git a/src/op_mode/generate_ipsec_debug_archive.py b/src/op_mode/generate_ipsec_debug_archive.py
index 1422559a8..60195d48b 100755
--- a/src/op_mode/generate_ipsec_debug_archive.py
+++ b/src/op_mode/generate_ipsec_debug_archive.py
@@ -20,7 +20,7 @@ from shutil import rmtree
from socket import gethostname
from sys import exit
from tarfile import open as tar_open
-from vyos.util import rc_cmd
+from vyos.utils.process import rc_cmd
# define a list of commands that needs to be executed
CMD_LIST: list[str] = [
diff --git a/src/op_mode/generate_openconnect_otp_key.py b/src/op_mode/generate_openconnect_otp_key.py
index 363bcf3ea..99b67d261 100755
--- a/src/op_mode/generate_openconnect_otp_key.py
+++ b/src/op_mode/generate_openconnect_otp_key.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -17,7 +17,7 @@
import argparse
import os
-from vyos.util import popen
+from vyos.utils.process import popen
from secrets import token_hex
from base64 import b32encode
diff --git a/src/op_mode/generate_ovpn_client_file.py b/src/op_mode/generate_ovpn_client_file.py
index 0628e6135..cec370a07 100755
--- a/src/op_mode/generate_ovpn_client_file.py
+++ b/src/op_mode/generate_ovpn_client_file.py
@@ -22,7 +22,7 @@ from textwrap import fill
from vyos.configquery import ConfigTreeQuery
from vyos.ifconfig import Section
-from vyos.util import cmd
+from vyos.utils.process import cmd
client_config = """
diff --git a/src/op_mode/generate_public_key_command.py b/src/op_mode/generate_public_key_command.py
index f071ae350..8ba55c901 100755
--- a/src/op_mode/generate_public_key_command.py
+++ b/src/op_mode/generate_public_key_command.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -19,28 +19,51 @@ import sys
import urllib.parse
import vyos.remote
+from vyos.template import generate_uuid4
-def get_key(path):
+
+def get_key(path) -> list:
+ """Get public keys from a local file or remote URL
+
+ Args:
+ path: Path to the public keys file
+
+ Returns: list of public keys split by new line
+
+ """
url = urllib.parse.urlparse(path)
if url.scheme == 'file' or url.scheme == '':
with open(os.path.expanduser(path), 'r') as f:
key_string = f.read()
else:
key_string = vyos.remote.get_remote_config(path)
- return key_string.split()
-
-try:
- username = sys.argv[1]
- algorithm, key, identifier = get_key(sys.argv[2])
-except Exception as e:
- print("Failed to retrieve the public key: {}".format(e))
- sys.exit(1)
-
-print('# To add this key as an embedded key, run the following commands:')
-print('configure')
-print(f'set system login user {username} authentication public-keys {identifier} key {key}')
-print(f'set system login user {username} authentication public-keys {identifier} type {algorithm}')
-print('commit')
-print('save')
-print('exit')
+ return key_string.split('\n')
+
+
+if __name__ == "__main__":
+ first_loop = True
+
+ for k in get_key(sys.argv[2]):
+ k = k.split()
+ # Skip empty list entry
+ if k == []:
+ continue
+
+ try:
+ username = sys.argv[1]
+ # Github keys don't have identifier for example 'vyos@localhost'
+ # 'ssh-rsa AAAA... vyos@localhost'
+ # Generate uuid4 identifier
+ identifier = f'github@{generate_uuid4("")}' if sys.argv[2].startswith('https://github.com') else k[2]
+ algorithm, key = k[0], k[1]
+ except Exception as e:
+ print("Failed to retrieve the public key: {}".format(e))
+ sys.exit(1)
+
+ if first_loop:
+ print('# To add this key as an embedded key, run the following commands:')
+ print('configure')
+ print(f'set system login user {username} authentication public-keys {identifier} key {key}')
+ print(f'set system login user {username} authentication public-keys {identifier} type {algorithm}')
+ first_loop = False
diff --git a/src/op_mode/generate_ssh_server_key.py b/src/op_mode/generate_ssh_server_key.py
index 43e94048d..d6063c43c 100755
--- a/src/op_mode/generate_ssh_server_key.py
+++ b/src/op_mode/generate_ssh_server_key.py
@@ -15,9 +15,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sys import exit
-from vyos.util import ask_yes_no
-from vyos.util import cmd
-from vyos.util import commit_in_progress
+from vyos.utils.io import ask_yes_no
+from vyos.utils.process import cmd
+from vyos.utils.commit import commit_in_progress
if not ask_yes_no('Do you really want to remove the existing SSH host keys?'):
exit(0)
diff --git a/src/op_mode/generate_system_login_user.py b/src/op_mode/generate_system_login_user.py
index 8f8827b1b..1b328eae0 100755
--- a/src/op_mode/generate_system_login_user.py
+++ b/src/op_mode/generate_system_login_user.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -17,7 +17,7 @@
import argparse
import os
-from vyos.util import popen
+from vyos.utils.process import popen
from secrets import token_hex
from base64 import b32encode
diff --git a/src/op_mode/igmp-proxy.py b/src/op_mode/igmp-proxy.py
new file mode 100755
index 000000000..709e25915
--- /dev/null
+++ b/src/op_mode/igmp-proxy.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# File: show_igmpproxy.py
+# Purpose:
+# Display istatistics from IPv4 IGMP proxy.
+# Used by the "run show ip multicast" command tree.
+
+import ipaddress
+import json
+import socket
+import sys
+import tabulate
+
+import vyos.config
+import vyos.opmode
+
+from vyos.utils.convert import bytes_to_human
+from vyos.utils.io import print_error
+from vyos.utils.process import process_named_running
+
+def _is_configured():
+ """Check if IGMP proxy is configured"""
+ return vyos.config.Config().exists_effective('protocols igmp-proxy')
+
+def _kernel_to_ip(addr):
+ """
+ Convert any given address from Linux kernel to a proper, IPv4 address
+ using the correct host byte order.
+ """
+ # Convert from hex 'FE000A0A' to decimal '4261415434'
+ addr = int(addr, 16)
+ # Kernel ABI _always_ uses network byte order.
+ addr = socket.ntohl(addr)
+ return str(ipaddress.IPv4Address(addr))
+
+def _process_mr_vif():
+ """Read rows from /proc/net/ip_mr_vif into dicts."""
+ result = []
+ with open('/proc/net/ip_mr_vif', 'r') as f:
+ next(f)
+ for line in f:
+ result.append({
+ 'Interface': line.split()[1],
+ 'PktsIn' : int(line.split()[3]),
+ 'PktsOut' : int(line.split()[5]),
+ 'BytesIn' : int(line.split()[2]),
+ 'BytesOut' : int(line.split()[4]),
+ 'Local' : _kernel_to_ip(line.split()[7]),
+ })
+ return result
+
+def show_interface(raw: bool):
+ if data := _process_mr_vif():
+ if raw:
+ # Make the interface name the key for each row.
+ table = {}
+ for v in data:
+ table[v.pop('Interface')] = v
+ return json.loads(json.dumps(table))
+ # Make byte values human-readable for the table.
+ arr = []
+ for x in data:
+ arr.append({k: bytes_to_human(v) if k.startswith('Bytes') \
+ else v for k, v in x.items()})
+ return tabulate.tabulate(arr, headers='keys')
+
+
+if not _is_configured():
+ print_error('IGMP proxy is not configured.')
+ sys.exit(0)
+if not process_named_running('igmpproxy'):
+ print_error('IGMP proxy is not running.')
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print_error(e)
+ sys.exit(1)
diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py
index a22f04c45..5454cc0ce 100755
--- a/src/op_mode/ikev2_profile_generator.py
+++ b/src/op_mode/ikev2_profile_generator.py
@@ -24,7 +24,7 @@ from cryptography.x509.oid import NameOID
from vyos.configquery import ConfigTreeQuery
from vyos.pki import load_certificate
from vyos.template import render_to_string
-from vyos.util import ask_input
+from vyos.utils.io import ask_input
# Apple profiles only support one IKE/ESP encryption cipher and hash, whereas
# VyOS comes with a multitude of different proposals for a connection.
diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py
new file mode 100755
index 000000000..782e178c6
--- /dev/null
+++ b/src/op_mode/interfaces.py
@@ -0,0 +1,438 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import re
+import sys
+import glob
+import json
+import typing
+from datetime import datetime
+from tabulate import tabulate
+
+import vyos.opmode
+from vyos.ifconfig import Section
+from vyos.ifconfig import Interface
+from vyos.ifconfig import VRRP
+from vyos.utils.process import cmd
+from vyos.utils.process import rc_cmd
+from vyos.utils.process import call
+
+def catch_broken_pipe(func):
+ def wrapped(*args, **kwargs):
+ try:
+ func(*args, **kwargs)
+ except (BrokenPipeError, KeyboardInterrupt):
+ # Flush output to /dev/null and bail out.
+ os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
+ return wrapped
+
+# The original implementation of filtered_interfaces has signature:
+# (ifnames: list, iftypes: typing.Union[str, list], vif: bool, vrrp: bool) -> intf: Interface:
+# Arg types allowed in CLI (ifnames: str, iftypes: str) were manually
+# re-typed from argparse args.
+# We include the function in a general form, however op-mode standard
+# functions will restrict to the CLI-allowed arg types, wrapped in Optional.
+def filtered_interfaces(ifnames: typing.Union[str, list],
+ iftypes: typing.Union[str, list],
+ vif: bool, vrrp: bool) -> Interface:
+ """
+ get all interfaces from the OS and return them; ifnames can be used to
+ filter which interfaces should be considered
+
+ ifnames: a list of interface names to consider, empty do not filter
+
+ return an instance of the Interface class
+ """
+ if isinstance(ifnames, str):
+ ifnames = [ifnames] if ifnames else []
+ if isinstance(iftypes, list):
+ for iftype in iftypes:
+ yield from filtered_interfaces(ifnames, iftype, vif, vrrp)
+
+ for ifname in Section.interfaces(iftypes):
+ # Bail out early if interface name not part of our search list
+ if ifnames and ifname not in ifnames:
+ continue
+
+ # As we are only "reading" from the interface - we must use the
+ # generic base class which exposes all the data via a common API
+ interface = Interface(ifname, create=False, debug=False)
+
+ # VLAN interfaces have a '.' in their name by convention
+ if vif and not '.' in ifname:
+ continue
+
+ if vrrp:
+ vrrp_interfaces = VRRP.active_interfaces()
+ if ifname not in vrrp_interfaces:
+ continue
+
+ yield interface
+
+def _split_text(text, used=0):
+ """
+ take a string and attempt to split it to fit with the width of the screen
+
+ text: the string to split
+ used: number of characted already used in the screen
+ """
+ no_tty = call('tty -s')
+
+ returned = cmd('stty size') if not no_tty else ''
+ returned = returned.split()
+ if len(returned) == 2:
+ _, columns = tuple(int(_) for _ in returned)
+ else:
+ _, columns = (40, 80)
+
+ desc_len = columns - used
+
+ line = ''
+ for word in text.split():
+ if len(line) + len(word) < desc_len:
+ line = f'{line} {word}'
+ continue
+ if line:
+ yield line[1:]
+ else:
+ line = f'{line} {word}'
+
+ yield line[1:]
+
+def _get_counter_val(prev, now):
+ """
+ attempt to correct a counter if it wrapped, copied from perl
+
+ prev: previous counter
+ now: the current counter
+ """
+ # This function has to deal with both 32 and 64 bit counters
+ if prev == 0:
+ return now
+
+ # device is using 64 bit values assume they never wrap
+ value = now - prev
+ if (now >> 32) != 0:
+ return value
+
+ # The counter has rolled. If the counter has rolled
+ # multiple times since the prev value, then this math
+ # is meaningless.
+ if value < 0:
+ value = (4294967296 - prev) + now
+
+ return value
+
+def _pppoe(ifname):
+ out = cmd('ps -C pppd -f')
+ if ifname in out:
+ return 'C'
+ if ifname in [_.split('/')[-1] for _ in glob.glob('/etc/ppp/peers/pppoe*')]:
+ return 'D'
+ return ''
+
+def _find_intf_by_ifname(intf_l: list, name: str):
+ for d in intf_l:
+ if d['ifname'] == name:
+ return d
+ return {}
+
+# lifted out of operational.py to separate formatting from data
+def _format_stats(stats, indent=4):
+ stat_names = {
+ 'rx': ['bytes', 'packets', 'errors', 'dropped', 'overrun', 'mcast'],
+ 'tx': ['bytes', 'packets', 'errors', 'dropped', 'carrier', 'collisions'],
+ }
+
+ stats_dir = {
+ 'rx': ['rx_bytes', 'rx_packets', 'rx_errors', 'rx_dropped', 'rx_over_errors', 'multicast'],
+ 'tx': ['tx_bytes', 'tx_packets', 'tx_errors', 'tx_dropped', 'tx_carrier_errors', 'collisions'],
+ }
+ tabs = []
+ for rtx in list(stats_dir):
+ tabs.append([f'{rtx.upper()}:', ] + stat_names[rtx])
+ tabs.append(['', ] + [stats[_] for _ in stats_dir[rtx]])
+
+ s = tabulate(
+ tabs,
+ stralign="right",
+ numalign="right",
+ tablefmt="plain"
+ )
+
+ p = ' '*indent
+ return f'{p}' + s.replace('\n', f'\n{p}')
+
+def _get_raw_data(ifname: typing.Optional[str],
+ iftype: typing.Optional[str],
+ vif: bool, vrrp: bool) -> list:
+ if ifname is None:
+ ifname = ''
+ if iftype is None:
+ iftype = ''
+ ret =[]
+ for interface in filtered_interfaces(ifname, iftype, vif, vrrp):
+ res_intf = {}
+ cache = interface.operational.load_counters()
+
+ out = cmd(f'ip -json addr show {interface.ifname}')
+ res_intf_l = json.loads(out)
+ res_intf = res_intf_l[0]
+
+ if res_intf['link_type'] == 'tunnel6':
+ # Note that 'ip -6 tun show {interface.ifname}' is not json
+ # aware, so find in list
+ out = cmd('ip -json -6 tun show')
+ tunnel = json.loads(out)
+ res_intf['tunnel6'] = _find_intf_by_ifname(tunnel,
+ interface.ifname)
+ if 'ip6_tnl_f_use_orig_tclass' in res_intf['tunnel6']:
+ res_intf['tunnel6']['tclass'] = 'inherit'
+ del res_intf['tunnel6']['ip6_tnl_f_use_orig_tclass']
+
+ res_intf['counters_last_clear'] = int(cache.get('timestamp', 0))
+
+ res_intf['description'] = interface.get_alias()
+
+ stats = interface.operational.get_stats()
+ for k in list(stats):
+ stats[k] = _get_counter_val(cache[k], stats[k])
+
+ res_intf['stats'] = stats
+
+ ret.append(res_intf)
+
+ # find pppoe interfaces that are in a transitional/dead state
+ if ifname.startswith('pppoe') and not _find_intf_by_ifname(ret, ifname):
+ pppoe_intf = {}
+ pppoe_intf['unhandled'] = None
+ pppoe_intf['ifname'] = ifname
+ pppoe_intf['state'] = _pppoe(ifname)
+ ret.append(pppoe_intf)
+
+ return ret
+
+def _get_summary_data(ifname: typing.Optional[str],
+ iftype: typing.Optional[str],
+ vif: bool, vrrp: bool) -> list:
+ if ifname is None:
+ ifname = ''
+ if iftype is None:
+ iftype = ''
+ ret = []
+ for interface in filtered_interfaces(ifname, iftype, vif, vrrp):
+ res_intf = {}
+
+ res_intf['ifname'] = interface.ifname
+ res_intf['oper_state'] = interface.operational.get_state()
+ res_intf['admin_state'] = interface.get_admin_state()
+ res_intf['addr'] = [_ for _ in interface.get_addr() if not _.startswith('fe80::')]
+ res_intf['description'] = interface.get_alias()
+
+ ret.append(res_intf)
+
+ # find pppoe interfaces that are in a transitional/dead state
+ if ifname.startswith('pppoe') and not _find_intf_by_ifname(ret, ifname):
+ pppoe_intf = {}
+ pppoe_intf['unhandled'] = None
+ pppoe_intf['ifname'] = ifname
+ pppoe_intf['state'] = _pppoe(ifname)
+ ret.append(pppoe_intf)
+
+ return ret
+
+def _get_counter_data(ifname: typing.Optional[str],
+ iftype: typing.Optional[str],
+ vif: bool, vrrp: bool) -> list:
+ if ifname is None:
+ ifname = ''
+ if iftype is None:
+ iftype = ''
+ ret = []
+ for interface in filtered_interfaces(ifname, iftype, vif, vrrp):
+ res_intf = {}
+
+ oper = interface.operational.get_state()
+
+ if oper not in ('up','unknown'):
+ continue
+
+ stats = interface.operational.get_stats()
+ cache = interface.operational.load_counters()
+ res_intf['ifname'] = interface.ifname
+ res_intf['rx_packets'] = _get_counter_val(cache['rx_packets'], stats['rx_packets'])
+ res_intf['rx_bytes'] = _get_counter_val(cache['rx_bytes'], stats['rx_bytes'])
+ res_intf['tx_packets'] = _get_counter_val(cache['tx_packets'], stats['tx_packets'])
+ res_intf['tx_bytes'] = _get_counter_val(cache['tx_bytes'], stats['tx_bytes'])
+ res_intf['rx_dropped'] = _get_counter_val(cache['rx_dropped'], stats['rx_dropped'])
+ res_intf['tx_dropped'] = _get_counter_val(cache['tx_dropped'], stats['tx_dropped'])
+ res_intf['rx_over_errors'] = _get_counter_val(cache['rx_over_errors'], stats['rx_over_errors'])
+ res_intf['tx_carrier_errors'] = _get_counter_val(cache['tx_carrier_errors'], stats['tx_carrier_errors'])
+
+ ret.append(res_intf)
+
+ return ret
+
+@catch_broken_pipe
+def _format_show_data(data: list):
+ unhandled = []
+ for intf in data:
+ if 'unhandled' in intf:
+ unhandled.append(intf)
+ continue
+ # instead of reformatting data, call non-json output:
+ rc, out = rc_cmd(f"ip addr show {intf['ifname']}")
+ if rc != 0:
+ continue
+ out = re.sub('^\d+:\s+','',out)
+ # add additional data already collected
+ if 'tunnel6' in intf:
+ t6_d = intf['tunnel6']
+ t6_str = 'encaplimit %s hoplimit %s tclass %s flowlabel %s (flowinfo %s)' % (
+ t6_d.get('encap_limit', ''), t6_d.get('hoplimit', ''),
+ t6_d.get('tclass', ''), t6_d.get('flowlabel', ''),
+ t6_d.get('flowinfo', ''))
+ out = re.sub('(\n\s+)(link/tunnel6)', f'\g<1>{t6_str}\g<1>\g<2>', out)
+ print(out)
+ ts = intf.get('counters_last_clear', 0)
+ if ts:
+ when = datetime.fromtimestamp(ts).strftime("%a %b %d %R:%S %Z %Y")
+ print(f' Last clear: {when}')
+ description = intf.get('description', '')
+ if description:
+ print(f' Description: {description}')
+
+ stats = intf.get('stats', {})
+ if stats:
+ print()
+ print(_format_stats(stats))
+
+ for intf in unhandled:
+ string = {
+ 'C': 'Coming up',
+ 'D': 'Link down'
+ }[intf['state']]
+ print(f"{intf['ifname']}: {string}")
+
+ return 0
+
+@catch_broken_pipe
+def _format_show_summary(data):
+ format1 = '%-16s %-33s %-4s %s'
+ format2 = '%-16s %s'
+
+ print('Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down')
+ print(format1 % ("Interface", "IP Address", "S/L", "Description"))
+ print(format1 % ("---------", "----------", "---", "-----------"))
+
+ unhandled = []
+ for intf in data:
+ if 'unhandled' in intf:
+ unhandled.append(intf)
+ continue
+ ifname = [intf['ifname'],]
+ oper = ['u',] if intf['oper_state'] in ('up', 'unknown') else ['D',]
+ admin = ['u',] if intf['admin_state'] in ('up', 'unknown') else ['A',]
+ addrs = intf['addr'] or ['-',]
+ descs = list(_split_text(intf['description'], 0))
+
+ while ifname or oper or admin or addrs or descs:
+ i = ifname.pop(0) if ifname else ''
+ a = addrs.pop(0) if addrs else ''
+ d = descs.pop(0) if descs else ''
+ s = [admin.pop(0)] if admin else []
+ l = [oper.pop(0)] if oper else []
+ if len(a) < 33:
+ print(format1 % (i, a, '/'.join(s+l), d))
+ else:
+ print(format2 % (i, a))
+ print(format1 % ('', '', '/'.join(s+l), d))
+
+ for intf in unhandled:
+ string = {
+ 'C': 'u/D',
+ 'D': 'A/D'
+ }[intf['state']]
+ print(format1 % (ifname, '', string, ''))
+
+ return 0
+
+@catch_broken_pipe
+def _format_show_counters(data: list):
+ data_entries = []
+ for entry in data:
+ interface = entry.get('ifname')
+ rx_packets = entry.get('rx_packets')
+ rx_bytes = entry.get('rx_bytes')
+ tx_packets = entry.get('tx_packets')
+ tx_bytes = entry.get('tx_bytes')
+ rx_dropped = entry.get('rx_dropped')
+ tx_dropped = entry.get('tx_dropped')
+ rx_errors = entry.get('rx_over_errors')
+ tx_errors = entry.get('tx_carrier_errors')
+ data_entries.append([interface, rx_packets, rx_bytes, tx_packets, tx_bytes, rx_dropped, tx_dropped, rx_errors, tx_errors])
+
+ headers = ['Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes', 'Rx Dropped', 'Tx Dropped', 'Rx Errors', 'Tx Errors']
+ output = tabulate(data_entries, headers, numalign="left")
+ print (output)
+ return output
+
+def show(raw: bool, intf_name: typing.Optional[str],
+ intf_type: typing.Optional[str],
+ vif: bool, vrrp: bool):
+ data = _get_raw_data(intf_name, intf_type, vif, vrrp)
+ if raw:
+ return data
+ return _format_show_data(data)
+
+def show_summary(raw: bool, intf_name: typing.Optional[str],
+ intf_type: typing.Optional[str],
+ vif: bool, vrrp: bool):
+ data = _get_summary_data(intf_name, intf_type, vif, vrrp)
+ if raw:
+ return data
+ return _format_show_summary(data)
+
+def show_counters(raw: bool, intf_name: typing.Optional[str],
+ intf_type: typing.Optional[str],
+ vif: bool, vrrp: bool):
+ data = _get_counter_data(intf_name, intf_type, vif, vrrp)
+ if raw:
+ return data
+ return _format_show_counters(data)
+
+def clear_counters(intf_name: typing.Optional[str],
+ intf_type: typing.Optional[str],
+ vif: bool, vrrp: bool):
+ for interface in filtered_interfaces(intf_name, intf_type, vif, vrrp):
+ interface.operational.clear_counters()
+
+def reset_counters(intf_name: typing.Optional[str],
+ intf_type: typing.Optional[str],
+ vif: bool, vrrp: bool):
+ for interface in filtered_interfaces(intf_name, intf_type, vif, vrrp):
+ interface.operational.reset_counters()
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/ipoe-control.py b/src/op_mode/ipoe-control.py
index 7111498b2..0f33beca7 100755
--- a/src/op_mode/ipoe-control.py
+++ b/src/op_mode/ipoe-control.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -18,7 +18,8 @@ import sys
import argparse
from vyos.config import Config
-from vyos.util import popen, run
+from vyos.utils.process import popen
+from vyos.utils.process import run
cmd_dict = {
'cmd_base' : '/usr/bin/accel-cmd -p 2002 ',
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
index e0d204a0a..44d41219e 100755
--- a/src/op_mode/ipsec.py
+++ b/src/op_mode/ipsec.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -13,26 +13,21 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
import re
import sys
import typing
-from collections import OrderedDict
from hurry import filesize
from re import split as re_split
from tabulate import tabulate
-from subprocess import TimeoutExpired
-from vyos.util import call
-from vyos.util import convert_data
-from vyos.util import seconds_to_human
+from vyos.utils.convert import convert_data
+from vyos.utils.convert import seconds_to_human
+from vyos.utils.process import cmd
+from vyos.configquery import ConfigTreeQuery
import vyos.opmode
-
-
-SWANCTL_CONF = '/etc/swanctl/swanctl.conf'
+import vyos.ipsec
def _convert(text):
@@ -43,21 +38,31 @@ def _alphanum_key(key):
return [_convert(c) for c in re_split('([0-9]+)', str(key))]
-def _get_vici_sas():
- from vici import Session as vici_session
-
+def _get_raw_data_sas():
try:
- session = vici_session()
- except Exception:
- raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
- sas = list(session.list_sas())
- return sas
+ get_sas = vyos.ipsec.get_vici_sas()
+ sas = convert_data(get_sas)
+ return sas
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
-def _get_raw_data_sas():
- get_sas = _get_vici_sas()
- sas = convert_data(get_sas)
- return sas
+def _get_output_swanctl_sas_from_list(ra_output_list: list) -> str:
+ """
+ Template for output for VICI
+ Inserts \n after each IKE SA
+ :param ra_output_list: IKE SAs list
+ :type ra_output_list: list
+ :return: formatted string
+ :rtype: str
+ """
+ output = '';
+ for sa_val in ra_output_list:
+ for sa in sa_val.values():
+ swanctl_output: str = cmd(
+ f'sudo swanctl -l --ike-id {sa["uniqueid"]}')
+ output = f'{output}{swanctl_output}\n\n'
+ return output
def _get_formatted_output_sas(sas):
@@ -139,22 +144,14 @@ def _get_formatted_output_sas(sas):
# Connections block
-def _get_vici_connections():
- from vici import Session as vici_session
-
- try:
- session = vici_session()
- except Exception:
- raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
- connections = list(session.list_conns())
- return connections
-
def _get_convert_data_connections():
- get_connections = _get_vici_connections()
- connections = convert_data(get_connections)
- return connections
-
+ try:
+ get_connections = vyos.ipsec.get_vici_connections()
+ connections = convert_data(get_connections)
+ return connections
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
def _get_parent_sa_proposal(connection_name: str, data: list) -> dict:
"""Get parent SA proposals by connection name
@@ -173,7 +170,7 @@ def _get_parent_sa_proposal(connection_name: str, data: list) -> dict:
for sa in data:
# check if parent SA exist
if connection_name not in sa.keys():
- return {}
+ continue
if 'encr-alg' in sa[connection_name]:
encr_alg = sa.get(connection_name, '').get('encr-alg')
cipher = encr_alg.split('_')[0]
@@ -203,16 +200,17 @@ def _get_parent_sa_state(connection_name: str, data: list) -> str:
Returns:
Parent SA connection state
"""
+ ike_state = 'down'
if not data:
- return 'down'
+ return ike_state
for sa in data:
# check if parent SA exist
- if connection_name not in sa.keys():
- return 'down'
- if sa[connection_name]['state'].lower() == 'established':
- return 'up'
- else:
- return 'down'
+ for connection, connection_conf in sa.items():
+ if connection_name != connection:
+ continue
+ if connection_conf['state'].lower() == 'established':
+ ike_state = 'up'
+ return ike_state
def _get_child_sa_state(connection_name: str, tunnel_name: str,
@@ -227,19 +225,21 @@ def _get_child_sa_state(connection_name: str, tunnel_name: str,
Returns:
str: `up` if child SA state is 'installed' otherwise `down`
"""
+ child_sa = 'down'
if not data:
- return 'down'
+ return child_sa
for sa in data:
# check if parent SA exist
if connection_name not in sa.keys():
- return 'down'
+ continue
child_sas = sa[connection_name]['child-sas']
# Get all child SA states
# there can be multiple SAs per tunnel
child_sa_states = [
- v['state'] for k, v in child_sas.items() if v['name'] == tunnel_name
+ v['state'] for k, v in child_sas.items() if
+ v['name'] == tunnel_name
]
- return 'up' if 'INSTALLED' in child_sa_states else 'down'
+ return 'up' if 'INSTALLED' in child_sa_states else child_sa
def _get_child_sa_info(connection_name: str, tunnel_name: str,
@@ -257,7 +257,7 @@ def _get_child_sa_info(connection_name: str, tunnel_name: str,
for sa in data:
# check if parent SA exist
if connection_name not in sa.keys():
- return {}
+ continue
child_sas = sa[connection_name]['child-sas']
# Get all child SA data
# Skip temp SA name (first key), get only SA values as dict
@@ -404,39 +404,170 @@ def _get_formatted_output_conections(data):
# Connections block end
-def get_peer_connections(peer, tunnel):
- search = rf'^[\s]*({peer}-(tunnel-[\d]+|vti)).*'
- matches = []
- if not os.path.exists(SWANCTL_CONF):
- raise vyos.opmode.UnconfiguredSubsystem("IPsec not initialized")
- suffix = None if tunnel is None else (f'tunnel-{tunnel}' if
- tunnel.isnumeric() else tunnel)
- with open(SWANCTL_CONF, 'r') as f:
- for line in f.readlines():
- result = re.match(search, line)
- if result:
- if tunnel is None:
- matches.append(result[1])
+def _get_childsa_id_list(ike_sas: list) -> list:
+ """
+ Generate list of CHILD SA ids based on list of OrderingDict
+ wich is returned by vici
+ :param ike_sas: list of IKE SAs generated by vici
+ :type ike_sas: list
+ :return: list of IKE SAs ids
+ :rtype: list
+ """
+ list_childsa_id: list = []
+ for ike in ike_sas:
+ for ike_sa in ike.values():
+ for child_sa in ike_sa['child-sas'].values():
+ list_childsa_id.append(child_sa['uniqueid'].decode('ascii'))
+ return list_childsa_id
+
+
+def _get_all_sitetosite_peers_name_list() -> list:
+ """
+ Return site-to-site peers configuration
+ :return: site-to-site peers configuration
+ :rtype: list
+ """
+ conf: ConfigTreeQuery = ConfigTreeQuery()
+ config_path = ['vpn', 'ipsec', 'site-to-site', 'peer']
+ peers_config = conf.get_config_dict(config_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ peers_list: list = []
+ for name in peers_config:
+ peers_list.append(name)
+ return peers_list
+
+
+def reset_peer(peer: str, tunnel: typing.Optional[str] = None):
+ # Convert tunnel to Strongwan format of CHILD_SA
+ tunnel_sw = None
+ if tunnel:
+ if tunnel.isnumeric():
+ tunnel_sw = f'{peer}-tunnel-{tunnel}'
+ elif tunnel == 'vti':
+ tunnel_sw = f'{peer}-vti'
+ try:
+ sa_list: list = vyos.ipsec.get_vici_sas_by_name(peer, tunnel_sw)
+ if not sa_list:
+ raise vyos.opmode.IncorrectValue(
+ f'Peer\'s {peer} SA(s) not found, aborting')
+ if tunnel and sa_list:
+ childsa_id_list: list = _get_childsa_id_list(sa_list)
+ if not childsa_id_list:
+ raise vyos.opmode.IncorrectValue(
+ f'Peer {peer} tunnel {tunnel} SA(s) not found, aborting')
+ vyos.ipsec.terminate_vici_by_name(peer, tunnel_sw)
+ print(f'Peer {peer} reset result: success')
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
+ except (vyos.ipsec.ViciCommandError) as err:
+ raise vyos.opmode.IncorrectValue(err)
+
+
+def reset_all_peers():
+ sitetosite_list = _get_all_sitetosite_peers_name_list()
+ if sitetosite_list:
+ for peer_name in sitetosite_list:
+ try:
+ reset_peer(peer_name)
+ except (vyos.opmode.IncorrectValue) as err:
+ print(err)
+ print('Peers reset result: success')
+ else:
+ raise vyos.opmode.UnconfiguredSubsystem(
+ 'VPN IPSec site-to-site is not configured, aborting')
+
+
+def _get_ra_session_list_by_username(username: typing.Optional[str] = None):
+ """
+ Return list of remote-access IKE_SAs uniqueids
+ :param username:
+ :type username:
+ :return:
+ :rtype:
+ """
+ list_sa_id = []
+ sa_list = _get_raw_data_sas()
+ for sa_val in sa_list:
+ for sa in sa_val.values():
+ if 'remote-eap-id' in sa:
+ if username:
+ if username == sa['remote-eap-id']:
+ list_sa_id.append(sa['uniqueid'])
else:
- if result[2] == suffix:
- matches.append(result[1])
- return matches
+ list_sa_id.append(sa['uniqueid'])
+ return list_sa_id
-def reset_peer(peer: str, tunnel:typing.Optional[str]):
- conns = get_peer_connections(peer, tunnel)
+def reset_ra(username: typing.Optional[str] = None):
+ #Reset remote-access ipsec sessions
+ if username:
+ list_sa_id = _get_ra_session_list_by_username(username)
+ else:
+ list_sa_id = _get_ra_session_list_by_username()
+ if list_sa_id:
+ vyos.ipsec.terminate_vici_ikeid_list(list_sa_id)
- if not conns:
- raise vyos.opmode.IncorrectValue('Peer or tunnel(s) not found, aborting')
- for conn in conns:
+def reset_profile_dst(profile: str, tunnel: str, nbma_dst: str):
+ if profile and tunnel and nbma_dst:
+ ike_sa_name = f'dmvpn-{profile}-{tunnel}'
try:
- call(f'sudo /usr/sbin/ipsec down {conn}{{*}}', timeout = 10)
- call(f'sudo /usr/sbin/ipsec up {conn}', timeout = 10)
- except TimeoutExpired as e:
- raise vyos.opmode.InternalError(f'Timed out while resetting {conn}')
-
- print('Peer reset result: success')
+ # Get IKE SAs
+ sa_list = convert_data(
+ vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None))
+ if not sa_list:
+ raise vyos.opmode.IncorrectValue(
+ f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting')
+ sa_nbma_list = list([x for x in sa_list if
+ ike_sa_name in x and x[ike_sa_name][
+ 'remote-host'] == nbma_dst])
+ if not sa_nbma_list:
+ raise vyos.opmode.IncorrectValue(
+ f'SA(s) for profile {profile} tunnel {tunnel} remote-host {nbma_dst} not found, aborting')
+ # terminate IKE SAs
+ vyos.ipsec.terminate_vici_ikeid_list(list(
+ [x[ike_sa_name]['uniqueid'] for x in sa_nbma_list if
+ ike_sa_name in x]))
+ # initiate IKE SAs
+ for ike in sa_nbma_list:
+ if ike_sa_name in ike:
+ vyos.ipsec.vici_initiate(ike_sa_name, 'dmvpn',
+ ike[ike_sa_name]['local-host'],
+ ike[ike_sa_name]['remote-host'])
+ print(
+ f'Profile {profile} tunnel {tunnel} remote-host {nbma_dst} reset result: success')
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
+ except (vyos.ipsec.ViciCommandError) as err:
+ raise vyos.opmode.IncorrectValue(err)
+
+
+def reset_profile_all(profile: str, tunnel: str):
+ if profile and tunnel:
+ ike_sa_name = f'dmvpn-{profile}-{tunnel}'
+ try:
+ # Get IKE SAs
+ sa_list: list = convert_data(
+ vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None))
+ if not sa_list:
+ raise vyos.opmode.IncorrectValue(
+ f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting')
+ # terminate IKE SAs
+ vyos.ipsec.terminate_vici_by_name(ike_sa_name, None)
+ # initiate IKE SAs
+ for ike in sa_list:
+ if ike_sa_name in ike:
+ vyos.ipsec.vici_initiate(ike_sa_name, 'dmvpn',
+ ike[ike_sa_name]['local-host'],
+ ike[ike_sa_name]['remote-host'])
+ print(
+ f'Profile {profile} tunnel {tunnel} remote-host {ike[ike_sa_name]["remote-host"]} reset result: success')
+ print(f'Profile {profile} tunnel {tunnel} reset result: success')
+ except (vyos.ipsec.ViciInitiateError) as err:
+ raise vyos.opmode.UnconfiguredSubsystem(err)
+ except (vyos.ipsec.ViciCommandError) as err:
+ raise vyos.opmode.IncorrectValue(err)
def show_sa(raw: bool):
@@ -446,6 +577,24 @@ def show_sa(raw: bool):
return _get_formatted_output_sas(sa_data)
+def _get_output_sas_detail(ra_output_list: list) -> str:
+ """
+ Formate all IKE SAs detail output
+ :param ra_output_list: IKE SAs list
+ :type ra_output_list: list
+ :return: formatted RA IKE SAs detail output
+ :rtype: str
+ """
+ return _get_output_swanctl_sas_from_list(ra_output_list)
+
+
+def show_sa_detail(raw: bool):
+ sa_data = _get_raw_data_sas()
+ if raw:
+ return sa_data
+ return _get_output_sas_detail(sa_data)
+
+
def show_connections(raw: bool):
list_conns = _get_convert_data_connections()
list_sas = _get_raw_data_sas()
@@ -463,6 +612,212 @@ def show_connections_summary(raw: bool):
return _get_raw_connections_summary(list_conns, list_sas)
+def _get_ra_sessions(username: typing.Optional[str] = None) -> list:
+ """
+ Return list of remote-access IKE_SAs from VICI by username.
+ If username unspecified, return all remote-access IKE_SAs
+ :param username: Username of RA connection
+ :type username: str
+ :return: list of ra remote-access IKE_SAs
+ :rtype: list
+ """
+ list_sa = []
+ sa_list = _get_raw_data_sas()
+ for conn in sa_list:
+ for sa in conn.values():
+ if 'remote-eap-id' in sa:
+ if username:
+ if username == sa['remote-eap-id']:
+ list_sa.append(conn)
+ else:
+ list_sa.append(conn)
+ return list_sa
+
+
+def _filter_ikesas(list_sa: list, filter_key: str, filter_value: str) -> list:
+ """
+ Filter IKE SAs by specifice key
+ :param list_sa: list of IKE SAs
+ :type list_sa: list
+ :param filter_key: Filter Key
+ :type filter_key: str
+ :param filter_value: Filter Value
+ :type filter_value: str
+ :return: Filtered list of IKE SAs
+ :rtype: list
+ """
+ filtered_sa_list = []
+ for conn in list_sa:
+ for sa in conn.values():
+ if sa[filter_key] and sa[filter_key] == filter_value:
+ filtered_sa_list.append(conn)
+ return filtered_sa_list
+
+
+def _get_last_installed_childsa(sa: dict) -> str:
+ """
+ Return name of last installed active Child SA
+ :param sa: Dictionary with Child SAs
+ :type sa: dict
+ :return: Name of the Last installed active Child SA
+ :rtype: str
+ """
+ child_sa_name = None
+ child_sa_id = 0
+ for sa_name, child_sa in sa['child-sas'].items():
+ if child_sa['state'] == 'INSTALLED':
+ if child_sa_id == 0 or int(child_sa['uniqueid']) > child_sa_id:
+ child_sa_id = int(child_sa['uniqueid'])
+ child_sa_name = sa_name
+ return child_sa_name
+
+
+def _get_formatted_ike_proposal(sa: dict) -> str:
+ """
+ Return IKE proposal string in format
+ EncrALG-EncrKeySize/PFR/HASH/DH-GROUP
+ :param sa: IKE SA
+ :type sa: dict
+ :return: IKE proposal string
+ :rtype: str
+ """
+ proposal = ''
+ proposal = f'{proposal}{sa["encr-alg"]}' if 'encr-alg' in sa else proposal
+ proposal = f'{proposal}-{sa["encr-keysize"]}' if 'encr-keysize' in sa else proposal
+ proposal = f'{proposal}/{sa["prf-alg"]}' if 'prf-alg' in sa else proposal
+ proposal = f'{proposal}/{sa["integ-alg"]}' if 'integ-alg' in sa else proposal
+ proposal = f'{proposal}/{sa["dh-group"]}' if 'dh-group' in sa else proposal
+ return proposal
+
+
+def _get_formatted_ipsec_proposal(sa: dict) -> str:
+ """
+ Return IPSec proposal string in format
+ Protocol: EncrALG-EncrKeySize/HASH/PFS
+ :param sa: Child SA
+ :type sa: dict
+ :return: IPSec proposal string
+ :rtype: str
+ """
+ proposal = ''
+ proposal = f'{proposal}{sa["protocol"]}' if 'protocol' in sa else proposal
+ proposal = f'{proposal}:{sa["encr-alg"]}' if 'encr-alg' in sa else proposal
+ proposal = f'{proposal}-{sa["encr-keysize"]}' if 'encr-keysize' in sa else proposal
+ proposal = f'{proposal}/{sa["integ-alg"]}' if 'integ-alg' in sa else proposal
+ proposal = f'{proposal}/{sa["dh-group"]}' if 'dh-group' in sa else proposal
+ return proposal
+
+
+def _get_output_ra_sas_detail(ra_output_list: list) -> str:
+ """
+ Formate RA IKE SAs detail output
+ :param ra_output_list: IKE SAs list
+ :type ra_output_list: list
+ :return: formatted RA IKE SAs detail output
+ :rtype: str
+ """
+ return _get_output_swanctl_sas_from_list(ra_output_list)
+
+
+def _get_formatted_output_ra_summary(ra_output_list: list):
+ sa_data = []
+ for conn in ra_output_list:
+ for sa in conn.values():
+ sa_id = sa['uniqueid'] if 'uniqueid' in sa else ''
+ sa_username = sa['remote-eap-id'] if 'remote-eap-id' in sa else ''
+ sa_protocol = f'IKEv{sa["version"]}' if 'version' in sa else ''
+ sa_remotehost = sa['remote-host'] if 'remote-host' in sa else ''
+ sa_remoteid = sa['remote-id'] if 'remote-id' in sa else ''
+ sa_ike_proposal = _get_formatted_ike_proposal(sa)
+ sa_tunnel_ip = sa['remote-vips'][0]
+ child_sa_key = _get_last_installed_childsa(sa)
+ if child_sa_key:
+ child_sa = sa['child-sas'][child_sa_key]
+ sa_ipsec_proposal = _get_formatted_ipsec_proposal(child_sa)
+ sa_state = "UP"
+ sa_uptime = seconds_to_human(sa['established'])
+ else:
+ sa_ipsec_proposal = ''
+ sa_state = "DOWN"
+ sa_uptime = ''
+ sa_data.append(
+ [sa_id, sa_username, sa_protocol, sa_state, sa_uptime,
+ sa_tunnel_ip,
+ sa_remotehost, sa_remoteid, sa_ike_proposal,
+ sa_ipsec_proposal])
+
+ headers = ["Connection ID", "Username", "Protocol", "State", "Uptime",
+ "Tunnel IP", "Remote Host", "Remote ID", "IKE Proposal",
+ "IPSec Proposal"]
+ sa_data = sorted(sa_data, key=_alphanum_key)
+ output = tabulate(sa_data, headers)
+ return output
+
+
+def show_ra_detail(raw: bool, username: typing.Optional[str] = None,
+ conn_id: typing.Optional[str] = None):
+ list_sa: list = _get_ra_sessions()
+ if username:
+ list_sa = _filter_ikesas(list_sa, 'remote-eap-id', username)
+ elif conn_id:
+ list_sa = _filter_ikesas(list_sa, 'uniqueid', conn_id)
+ if not list_sa:
+ raise vyos.opmode.IncorrectValue(
+ f'No active connections found, aborting')
+ if raw:
+ return list_sa
+ return _get_output_ra_sas_detail(list_sa)
+
+
+def show_ra_summary(raw: bool):
+ list_sa: list = _get_ra_sessions()
+ if not list_sa:
+ raise vyos.opmode.IncorrectValue(
+ f'No active connections found, aborting')
+ if raw:
+ return list_sa
+ return _get_formatted_output_ra_summary(list_sa)
+
+
+# PSK block
+def _get_raw_psk():
+ conf: ConfigTreeQuery = ConfigTreeQuery()
+ config_path = ['vpn', 'ipsec', 'authentication', 'psk']
+ psk_config = conf.get_config_dict(config_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ psk_list = []
+ for psk, psk_data in psk_config.items():
+ psk_data['psk'] = psk
+ psk_list.append(psk_data)
+
+ return psk_list
+
+
+def _get_formatted_psk(psk_list):
+ headers = ["PSK", "Id", "Secret"]
+ formatted_data = []
+
+ for psk_data in psk_list:
+ formatted_data.append([psk_data["psk"], "\n".join(psk_data["id"]), psk_data["secret"]])
+
+ return tabulate(formatted_data, headers=headers)
+
+
+def show_psk(raw: bool):
+ config = ConfigTreeQuery()
+ if not config.exists('vpn ipsec authentication psk'):
+ raise vyos.opmode.UnconfiguredSubsystem('VPN ipsec psk authentication is not configured')
+
+ psk = _get_raw_psk()
+ if raw:
+ return psk
+ return _get_formatted_psk(psk)
+
+# PSK block end
+
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/op_mode/lldp.py b/src/op_mode/lldp.py
new file mode 100755
index 000000000..c287b8fa6
--- /dev/null
+++ b/src/op_mode/lldp.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import jmespath
+import json
+import sys
+import typing
+
+from tabulate import tabulate
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.utils.process import cmd
+from vyos.utils.dict import dict_search
+
+import vyos.opmode
+unconf_message = 'LLDP is not configured'
+capability_codes = """Capability Codes: R - Router, B - Bridge, W - Wlan r - Repeater, S - Station
+ D - Docsis, T - Telephone, O - Other
+
+"""
+
+def _verify(func):
+ """Decorator checks if LLDP config exists"""
+ from functools import wraps
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = ConfigTreeQuery()
+ if not config.exists(['service', 'lldp']):
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+ return _wrapper
+
+def _get_raw_data(interface=None, detail=False):
+ """
+ If interface name is not set - get all interfaces
+ """
+ tmp = 'lldpcli -f json show neighbors'
+ if detail:
+ tmp += f' details'
+ if interface:
+ tmp += f' ports {interface}'
+ output = cmd(tmp)
+ data = json.loads(output)
+ if not data:
+ return []
+ return data
+
+def _get_formatted_output(raw_data):
+ data_entries = []
+ tmp = dict_search('lldp.interface', raw_data)
+ if not tmp:
+ return None
+ # One can not always ensure that "interface" is of type list, add safeguard.
+ # E.G. Juniper Networks, Inc. ex2300-c-12t only has a dict, not a list of dicts
+ if isinstance(tmp, dict):
+ tmp = [tmp]
+ for neighbor in tmp:
+ for local_if, values in neighbor.items():
+ tmp = []
+
+ # Device field
+ if 'chassis' in values:
+ tmp.append(next(iter(values['chassis'])))
+ else:
+ tmp.append('')
+
+ # Local Port field
+ tmp.append(local_if)
+
+ # Protocol field
+ tmp.append(values['via'])
+
+ # Capabilities
+ cap = ''
+ capabilities = jmespath.search('chassis.[*][0][0].capability', values)
+ # One can not always ensure that "capability" is of type list, add
+ # safeguard. E.G. Unify US-24-250W only has a dict, not a list of dicts
+ if isinstance(capabilities, dict):
+ capabilities = [capabilities]
+ if capabilities:
+ for capability in capabilities:
+ if capability['enabled']:
+ if capability['type'] == 'Router':
+ cap += 'R'
+ if capability['type'] == 'Bridge':
+ cap += 'B'
+ if capability['type'] == 'Wlan':
+ cap += 'W'
+ if capability['type'] == 'Station':
+ cap += 'S'
+ if capability['type'] == 'Repeater':
+ cap += 'r'
+ if capability['type'] == 'Telephone':
+ cap += 'T'
+ if capability['type'] == 'Docsis':
+ cap += 'D'
+ if capability['type'] == 'Other':
+ cap += 'O'
+ tmp.append(cap)
+
+ # Remote software platform
+ platform = jmespath.search('chassis.[*][0][0].descr', values)
+ tmp.append(platform[:37])
+
+ # Remote interface
+ interface = jmespath.search('port.descr', values)
+ if not interface:
+ interface = jmespath.search('port.id.value', values)
+ if not interface:
+ interface = 'Unknown'
+ tmp.append(interface)
+
+ # Add individual neighbor to output list
+ data_entries.append(tmp)
+
+ headers = ["Device", "Local Port", "Protocol", "Capability", "Platform", "Remote Port"]
+ output = tabulate(data_entries, headers, numalign="left")
+ return capability_codes + output
+
+@_verify
+def show_neighbors(raw: bool, interface: typing.Optional[str], detail: typing.Optional[bool]):
+ lldp_data = _get_raw_data(interface=interface, detail=detail)
+ if raw:
+ return lldp_data
+ else:
+ return _get_formatted_output(lldp_data)
+
+if __name__ == "__main__":
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py
deleted file mode 100755
index 17f6bf552..000000000
--- a/src/op_mode/lldp_op.py
+++ /dev/null
@@ -1,127 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import argparse
-import jinja2
-import json
-
-from sys import exit
-from tabulate import tabulate
-
-from vyos.util import cmd
-from vyos.config import Config
-
-parser = argparse.ArgumentParser()
-parser.add_argument("-a", "--all", action="store_true", help="Show LLDP neighbors on all interfaces")
-parser.add_argument("-d", "--detail", action="store_true", help="Show detailes LLDP neighbor information on all interfaces")
-parser.add_argument("-i", "--interface", action="store", help="Show LLDP neighbors on specific interface")
-
-# Please be careful if you edit the template.
-lldp_out = """Capability Codes: R - Router, B - Bridge, W - Wlan r - Repeater, S - Station
- D - Docsis, T - Telephone, O - Other
-
-Device ID Local Proto Cap Platform Port ID
---------- ----- ----- --- -------- -------
-{% for neighbor in neighbors %}
-{% for local_if, info in neighbor.items() %}
-{{ "%-25s" | format(info.chassis) }} {{ "%-9s" | format(local_if) }} {{ "%-6s" | format(info.proto) }} {{ "%-5s" | format(info.capabilities) }} {{ "%-20s" | format(info.platform[:18]) }} {{ info.remote_if }}
-{% endfor %}
-{% endfor %}
-"""
-
-def get_neighbors():
- return cmd('/usr/sbin/lldpcli -f json show neighbors')
-
-def parse_data(data, interface):
- output = []
- if not isinstance(data, list):
- data = [data]
-
- for neighbor in data:
- for local_if, values in neighbor.items():
- if interface is not None and local_if != interface:
- continue
- cap = ''
- for chassis, c_value in values.get('chassis', {}).items():
- # bail out early if no capabilities found
- if 'capability' not in c_value:
- continue
- capabilities = c_value['capability']
- if isinstance(capabilities, dict):
- capabilities = [capabilities]
-
- for capability in capabilities:
- if capability['enabled']:
- if capability['type'] == 'Router':
- cap += 'R'
- if capability['type'] == 'Bridge':
- cap += 'B'
- if capability['type'] == 'Wlan':
- cap += 'W'
- if capability['type'] == 'Station':
- cap += 'S'
- if capability['type'] == 'Repeater':
- cap += 'r'
- if capability['type'] == 'Telephone':
- cap += 'T'
- if capability['type'] == 'Docsis':
- cap += 'D'
- if capability['type'] == 'Other':
- cap += 'O'
-
- remote_if = 'Unknown'
- if 'descr' in values.get('port', {}):
- remote_if = values.get('port', {}).get('descr')
- elif 'id' in values.get('port', {}):
- remote_if = values.get('port', {}).get('id').get('value', 'Unknown')
-
- output.append({local_if: {'chassis': chassis,
- 'remote_if': remote_if,
- 'proto': values.get('via','Unknown'),
- 'platform': c_value.get('descr', 'Unknown'),
- 'capabilities': cap}})
-
- output = {'neighbors': output}
- return output
-
-if __name__ == '__main__':
- args = parser.parse_args()
- tmp = { 'neighbors' : [] }
-
- c = Config()
- if not c.exists_effective(['service', 'lldp']):
- print('Service LLDP is not configured')
- exit(0)
-
- if args.detail:
- print(cmd('/usr/sbin/lldpctl -f plain'))
- exit(0)
- elif args.all or args.interface:
- tmp = json.loads(get_neighbors())
- neighbors = dict()
-
- if 'interface' in tmp.get('lldp'):
- neighbors = tmp['lldp']['interface']
-
- else:
- parser.print_help()
- exit(1)
-
- tmpl = jinja2.Template(lldp_out, trim_blocks=True)
- config_text = tmpl.render(parse_data(neighbors, interface=args.interface))
- print(config_text)
-
- exit(0)
diff --git a/src/op_mode/log.py b/src/op_mode/log.py
index b0abd6191..797ba5a88 100755
--- a/src/op_mode/log.py
+++ b/src/op_mode/log.py
@@ -21,7 +21,7 @@ import typing
from jinja2 import Template
-from vyos.util import rc_cmd
+from vyos.utils.process import rc_cmd
import vyos.opmode
diff --git a/src/op_mode/memory.py b/src/op_mode/memory.py
index 7666de646..eb530035b 100755
--- a/src/op_mode/memory.py
+++ b/src/op_mode/memory.py
@@ -54,7 +54,7 @@ def _get_raw_data():
return mem_data
def _get_formatted_output(mem):
- from vyos.util import bytes_to_human
+ from vyos.utils.convert import bytes_to_human
# For human-readable outputs, we convert bytes to more convenient units
# (100M, 1.3G...)
diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py
index f899eb3dc..71a40c0e1 100755
--- a/src/op_mode/nat.py
+++ b/src/op_mode/nat.py
@@ -18,23 +18,23 @@ import jmespath
import json
import sys
import xmltodict
+import typing
-from sys import exit
from tabulate import tabulate
-from vyos.configquery import ConfigTreeQuery
-
-from vyos.util import cmd
-from vyos.util import dict_search
-
import vyos.opmode
+from vyos.configquery import ConfigTreeQuery
+from vyos.utils.process import cmd
+from vyos.utils.dict import dict_search
base = 'nat'
unconf_message = 'NAT is not configured'
+ArgDirection = typing.Literal['source', 'destination']
+ArgFamily = typing.Literal['inet', 'inet6']
-def _get_xml_translation(direction, family):
+def _get_xml_translation(direction, family, address=None):
"""
Get conntrack XML output --src-nat|--dst-nat
"""
@@ -42,7 +42,10 @@ def _get_xml_translation(direction, family):
opt = '--src-nat'
if direction == 'destination':
opt = '--dst-nat'
- return cmd(f'sudo conntrack --dump --family {family} {opt} --output xml')
+ tmp = f'conntrack --dump --family {family} {opt} --output xml'
+ if address:
+ tmp += f' --src {address}'
+ return cmd(tmp)
def _xml_to_dict(xml):
@@ -66,7 +69,7 @@ def _get_json_data(direction, family):
if direction == 'destination':
chain = 'PREROUTING'
family = 'ip6' if family == 'inet6' else 'ip'
- return cmd(f'sudo nft --json list chain {family} vyos_nat {chain}')
+ return cmd(f'nft --json list chain {family} vyos_nat {chain}')
def _get_raw_data_rules(direction, family):
@@ -82,11 +85,11 @@ def _get_raw_data_rules(direction, family):
return rules
-def _get_raw_translation(direction, family):
+def _get_raw_translation(direction, family, address=None):
"""
Return: dictionary
"""
- xml = _get_xml_translation(direction, family)
+ xml = _get_xml_translation(direction, family, address)
if len(xml) == 0:
output = {'conntrack':
{
@@ -231,7 +234,7 @@ def _get_formatted_output_statistics(data, direction):
return output
-def _get_formatted_translation(dict_data, nat_direction, family):
+def _get_formatted_translation(dict_data, nat_direction, family, verbose):
data_entries = []
if 'error' in dict_data['conntrack']:
return 'Entries not found'
@@ -269,14 +272,14 @@ def _get_formatted_translation(dict_data, nat_direction, family):
reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src
reply_dst = f'{reply_dst}:{reply_dport}' if reply_dport else reply_dst
state = meta['state'] if 'state' in meta else ''
- mark = meta['mark']
+ mark = meta.get('mark', '')
zone = meta['zone'] if 'zone' in meta else ''
if nat_direction == 'source':
- data_entries.append(
- [orig_src, reply_dst, proto, timeout, mark, zone])
+ tmp = [orig_src, reply_dst, proto, timeout, mark, zone]
+ data_entries.append(tmp)
elif nat_direction == 'destination':
- data_entries.append(
- [orig_dst, reply_src, proto, timeout, mark, zone])
+ tmp = [orig_dst, reply_src, proto, timeout, mark, zone]
+ data_entries.append(tmp)
headers = ["Pre-NAT", "Post-NAT", "Proto", "Timeout", "Mark", "Zone"]
output = tabulate(data_entries, headers, numalign="left")
@@ -297,7 +300,7 @@ def _verify(func):
@_verify
-def show_rules(raw: bool, direction: str, family: str):
+def show_rules(raw: bool, direction: ArgDirection, family: ArgFamily):
nat_rules = _get_raw_data_rules(direction, family)
if raw:
return nat_rules
@@ -306,7 +309,7 @@ def show_rules(raw: bool, direction: str, family: str):
@_verify
-def show_statistics(raw: bool, direction: str, family: str):
+def show_statistics(raw: bool, direction: ArgDirection, family: ArgFamily):
nat_statistics = _get_raw_data_rules(direction, family)
if raw:
return nat_statistics
@@ -315,13 +318,20 @@ def show_statistics(raw: bool, direction: str, family: str):
@_verify
-def show_translations(raw: bool, direction: str, family: str):
+def show_translations(raw: bool, direction: ArgDirection,
+ family: ArgFamily,
+ address: typing.Optional[str],
+ verbose: typing.Optional[bool]):
family = 'ipv6' if family == 'inet6' else 'ipv4'
- nat_translation = _get_raw_translation(direction, family)
+ nat_translation = _get_raw_translation(direction,
+ family=family,
+ address=address)
+
if raw:
return nat_translation
else:
- return _get_formatted_translation(nat_translation, direction, family)
+ return _get_formatted_translation(nat_translation, direction, family,
+ verbose)
if __name__ == '__main__':
diff --git a/src/op_mode/neighbor.py b/src/op_mode/neighbor.py
index 264dbdc72..8b3c45c7c 100755
--- a/src/op_mode/neighbor.py
+++ b/src/op_mode/neighbor.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -31,14 +31,14 @@ import sys
import typing
import vyos.opmode
+from vyos.utils.network import interface_exists
-def interface_exists(interface):
- import os
- return os.path.exists(f'/sys/class/net/{interface}')
+ArgFamily = typing.Literal['inet', 'inet6']
+ArgState = typing.Literal['reachable', 'stale', 'failed', 'permanent']
def get_raw_data(family, interface=None, state=None):
from json import loads
- from vyos.util import cmd
+ from vyos.utils.process import cmd
if interface:
if not interface_exists(interface):
@@ -88,7 +88,8 @@ def format_neighbors(neighs, interface=None):
headers = ["Address", "Interface", "Link layer address", "State"]
return tabulate(neighs, headers)
-def show(raw: bool, family: str, interface: typing.Optional[str], state: typing.Optional[str]):
+def show(raw: bool, family: ArgFamily, interface: typing.Optional[str],
+ state: typing.Optional[ArgState]):
""" Display neighbor table contents """
data = get_raw_data(family, interface, state=state)
@@ -97,8 +98,8 @@ def show(raw: bool, family: str, interface: typing.Optional[str], state: typing.
else:
return format_neighbors(data, interface)
-def reset(family: str, interface: typing.Optional[str], address: typing.Optional[str]):
- from vyos.util import run
+def reset(family: ArgFamily, interface: typing.Optional[str], address: typing.Optional[str]):
+ from vyos.utils.process import run
if address and interface:
raise ValueError("interface and address parameters are mutually exclusive")
@@ -110,7 +111,6 @@ def reset(family: str, interface: typing.Optional[str], address: typing.Optional
# Flush an entire neighbor table
run(f"""ip --family {family} neighbor flush""")
-
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/op_mode/nhrp.py b/src/op_mode/nhrp.py
new file mode 100755
index 000000000..e66f33079
--- /dev/null
+++ b/src/op_mode/nhrp.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import tabulate
+import vyos.opmode
+
+from vyos.utils.process import cmd
+from vyos.utils.process import process_named_running
+from vyos.utils.dict import colon_separated_to_dict
+
+
+def _get_formatted_output(output_dict: dict) -> str:
+ """
+ Create formatted table for CLI output
+ :param output_dict: dictionary for API
+ :type output_dict: dict
+ :return: tabulate string
+ :rtype: str
+ """
+ print(f"Status: {output_dict['Status']}")
+ output: str = tabulate.tabulate(output_dict['routes'], headers='keys',
+ numalign="left")
+ return output
+
+
+def _get_formatted_dict(output_string: str) -> dict:
+ """
+ Format string returned from CMD to API list
+ :param output_string: String received by CMD
+ :type output_string: str
+ :return: dictionary for API
+ :rtype: dict
+ """
+ formatted_dict: dict = {
+ 'Status': '',
+ 'routes': []
+ }
+ output_list: list = output_string.split('\n\n')
+ for list_a in output_list:
+ output_dict = colon_separated_to_dict(list_a, True)
+ if 'Status' in output_dict:
+ formatted_dict['Status'] = output_dict['Status']
+ else:
+ formatted_dict['routes'].append(output_dict)
+ return formatted_dict
+
+
+def show_interface(raw: bool):
+ """
+ Command 'show nhrp interface'
+ :param raw: if API
+ :type raw: bool
+ """
+ if not process_named_running('opennhrp'):
+ raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.')
+ interface_string: str = cmd('sudo opennhrpctl interface show')
+ interface_dict: dict = _get_formatted_dict(interface_string)
+ if raw:
+ return interface_dict
+ else:
+ return _get_formatted_output(interface_dict)
+
+
+def show_tunnel(raw: bool):
+ """
+ Command 'show nhrp tunnel'
+ :param raw: if API
+ :type raw: bool
+ """
+ if not process_named_running('opennhrp'):
+ raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.')
+ tunnel_string: str = cmd('sudo opennhrpctl show')
+ tunnel_dict: list = _get_formatted_dict(tunnel_string)
+ if raw:
+ return tunnel_dict
+ else:
+ return _get_formatted_output(tunnel_dict)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/openconnect-control.py b/src/op_mode/openconnect-control.py
index 20c50e779..b70d4fa16 100755
--- a/src/op_mode/openconnect-control.py
+++ b/src/op_mode/openconnect-control.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -18,12 +18,13 @@ import sys
import argparse
import json
-from vyos.config import Config
-from vyos.util import popen
-from vyos.util import run
-from vyos.util import DEVNULL
from tabulate import tabulate
+from vyos.config import Config
+from vyos.utils.process import popen
+from vyos.utils.process import run
+from vyos.utils.process import DEVNULL
+
occtl = '/usr/bin/occtl'
occtl_socket = '/run/ocserv/occtl.socket'
diff --git a/src/op_mode/openconnect.py b/src/op_mode/openconnect.py
index b21890728..cfa0678a7 100755
--- a/src/op_mode/openconnect.py
+++ b/src/op_mode/openconnect.py
@@ -19,7 +19,7 @@ import json
from tabulate import tabulate
from vyos.configquery import ConfigTreeQuery
-from vyos.util import rc_cmd
+from vyos.utils.process import rc_cmd
import vyos.opmode
diff --git a/src/op_mode/openvpn.py b/src/op_mode/openvpn.py
index 3797a7153..fd9d2db92 100755
--- a/src/op_mode/openvpn.py
+++ b/src/op_mode/openvpn.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -16,16 +16,21 @@
#
#
+import json
import os
import sys
+import typing
from tabulate import tabulate
import vyos.opmode
-from vyos.util import bytes_to_human
-from vyos.util import commit_in_progress
-from vyos.util import call
+from vyos.utils.convert import bytes_to_human
+from vyos.utils.commit import commit_in_progress
+from vyos.utils.process import call
+from vyos.utils.process import rc_cmd
from vyos.config import Config
+ArgMode = typing.Literal['client', 'server', 'site_to_site']
+
def _get_tunnel_address(peer_host, peer_port, status_file):
peer = peer_host + ':' + peer_port
lst = []
@@ -50,7 +55,7 @@ def _get_tunnel_address(peer_host, peer_port, status_file):
def _get_interface_status(mode: str, interface: str) -> dict:
status_file = f'/run/openvpn/{interface}.status'
- data = {
+ data: dict = {
'mode': mode,
'intf': interface,
'local_host': '',
@@ -60,7 +65,7 @@ def _get_interface_status(mode: str, interface: str) -> dict:
}
if not os.path.exists(status_file):
- raise vyos.opmode.DataUnavailable('No information for interface {interface}')
+ return data
with open(status_file, 'r') as f:
lines = f.readlines()
@@ -139,30 +144,54 @@ def _get_interface_status(mode: str, interface: str) -> dict:
return data
-def _get_raw_data(mode: str) -> dict:
- data = {}
+
+def _get_interface_state(iface):
+ rc, out = rc_cmd(f'ip --json link show dev {iface}')
+ try:
+ data = json.loads(out)
+ except:
+ return 'DOWN'
+ return data[0].get('operstate', 'DOWN')
+
+
+def _get_interface_description(iface):
+ rc, out = rc_cmd(f'ip --json link show dev {iface}')
+ try:
+ data = json.loads(out)
+ except:
+ return ''
+ return data[0].get('ifalias', '')
+
+
+def _get_raw_data(mode: str) -> list:
+ data: list = []
conf = Config()
conf_dict = conf.get_config_dict(['interfaces', 'openvpn'],
get_first_key=True)
if not conf_dict:
return data
- interfaces = [x for x in list(conf_dict) if conf_dict[x]['mode'] == mode]
+ interfaces = [x for x in list(conf_dict) if
+ conf_dict[x]['mode'].replace('-', '_') == mode]
for intf in interfaces:
- data[intf] = _get_interface_status(mode, intf)
- d = data[intf]
+ d = _get_interface_status(mode, intf)
+ d['state'] = _get_interface_state(intf)
+ d['description'] = _get_interface_description(intf)
d['local_host'] = conf_dict[intf].get('local-host', '')
d['local_port'] = conf_dict[intf].get('local-port', '')
- if mode in ['client', 'site-to-site']:
+ if conf.exists(f'interfaces openvpn {intf} server client'):
+ d['configured_clients'] = conf.list_nodes(f'interfaces openvpn {intf} server client')
+ if mode in ['client', 'site_to_site']:
for client in d['clients']:
if 'shared-secret-key-file' in list(conf_dict[intf]):
client['name'] = 'None (PSK)'
client['remote_host'] = conf_dict[intf].get('remote-host', [''])[0]
client['remote_port'] = conf_dict[intf].get('remote-port', '1194')
+ data.append(d)
return data
-def _format_openvpn(data: dict) -> str:
+def _format_openvpn(data: list) -> str:
if not data:
out = 'No OpenVPN interfaces configured'
return out
@@ -171,11 +200,12 @@ def _format_openvpn(data: dict) -> str:
'TX bytes', 'RX bytes', 'Connected Since']
out = ''
- data_out = []
- for intf in list(data):
- l_host = data[intf]['local_host']
- l_port = data[intf]['local_port']
- for client in list(data[intf]['clients']):
+ for d in data:
+ data_out = []
+ intf = d['intf']
+ l_host = d['local_host']
+ l_port = d['local_port']
+ for client in d['clients']:
r_host = client['remote_host']
r_port = client['remote_port']
@@ -190,11 +220,13 @@ def _format_openvpn(data: dict) -> str:
data_out.append([name, remote, tunnel, local, tx_bytes,
rx_bytes, online_since])
- out += tabulate(data_out, headers)
+ if data_out:
+ out += tabulate(data_out, headers)
+ out += "\n"
return out
-def show(raw: bool, mode: str) -> str:
+def show(raw: bool, mode: ArgMode) -> typing.Union[list,str]:
openvpn_data = _get_raw_data(mode)
if raw:
diff --git a/src/op_mode/ping.py b/src/op_mode/ping.py
index 610e63cb3..f1d87a118 100755
--- a/src/op_mode/ping.py
+++ b/src/op_mode/ping.py
@@ -18,7 +18,7 @@ import os
import sys
import socket
import ipaddress
-from vyos.util import get_all_vrfs
+from vyos.utils.network import get_all_vrfs
from vyos.ifconfig import Section
@@ -90,6 +90,16 @@ options = {
'type': '<seconds>',
'help': 'Number of seconds to wait between requests'
},
+ 'ipv4': {
+ 'ping': '{command} -4',
+ 'type': 'noarg',
+ 'help': 'Use IPv4 only'
+ },
+ 'ipv6': {
+ 'ping': '{command} -6',
+ 'type': 'noarg',
+ 'help': 'Use IPv6 only'
+ },
'mark': {
'ping': '{command} -m {value}',
'type': '<fwmark>',
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index 1e78c3a03..35c7ce0e2 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -25,33 +25,31 @@ from cryptography import x509
from cryptography.x509.oid import ExtendedKeyUsageOID
from vyos.config import Config
-from vyos.configquery import ConfigTreeQuery
-from vyos.configdict import dict_merge
from vyos.pki import encode_certificate, encode_public_key, encode_private_key, encode_dh_parameters
+from vyos.pki import get_certificate_fingerprint
from vyos.pki import create_certificate, create_certificate_request, create_certificate_revocation_list
from vyos.pki import create_private_key
from vyos.pki import create_dh_parameters
from vyos.pki import load_certificate, load_certificate_request, load_private_key
from vyos.pki import load_crl, load_dh_parameters, load_public_key
from vyos.pki import verify_certificate
-from vyos.xml import defaults
-from vyos.util import ask_input, ask_yes_no
-from vyos.util import cmd
-from vyos.util import install_into_config
+from vyos.utils.io import ask_input
+from vyos.utils.io import ask_yes_no
+from vyos.utils.misc import install_into_config
+from vyos.utils.process import cmd
CERT_REQ_END = '-----END CERTIFICATE REQUEST-----'
auth_dir = '/config/auth'
# Helper Functions
-conf = ConfigTreeQuery()
+conf = Config()
def get_default_values():
# Fetch default x509 values
base = ['pki', 'x509', 'default']
x509_defaults = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
get_first_key=True,
- no_tag_node_value_mangle=True)
- default_values = defaults(base)
- x509_defaults = dict_merge(default_values, x509_defaults)
+ with_recursive_defaults=True)
return x509_defaults
@@ -87,6 +85,9 @@ def get_config_certificate(name=None):
def get_certificate_ca(cert, ca_certs):
# Find CA certificate for given certificate
+ if not ca_certs:
+ return None
+
for ca_name, ca_dict in ca_certs.items():
if 'certificate' not in ca_dict:
continue
@@ -187,7 +188,7 @@ def install_ssh_key(name, public_key, private_key, passphrase=None):
def install_keypair(name, key_type, private_key=None, public_key=None, passphrase=None, prompt=True):
# Show/install conf commands for key-pair
-
+
config_paths = []
if public_key:
@@ -837,7 +838,7 @@ def import_openvpn_secret(name, path):
install_openvpn_key(name, key_data, key_version)
# Show functions
-def show_certificate_authority(name=None):
+def show_certificate_authority(name=None, pem=False):
headers = ['Name', 'Subject', 'Issuer CN', 'Issued', 'Expiry', 'Private Key', 'Parent']
data = []
certs = get_config_ca_certificate()
@@ -849,6 +850,11 @@ def show_certificate_authority(name=None):
continue
cert = load_certificate(cert_dict['certificate'])
+
+ if name and pem:
+ print(encode_certificate(cert))
+ return
+
parent_ca_name = get_certificate_ca(cert, certs)
cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0]
@@ -864,7 +870,7 @@ def show_certificate_authority(name=None):
print("Certificate Authorities:")
print(tabulate.tabulate(data, headers))
-def show_certificate(name=None):
+def show_certificate(name=None, pem=False):
headers = ['Name', 'Type', 'Subject CN', 'Issuer CN', 'Issued', 'Expiry', 'Revoked', 'Private Key', 'CA Present']
data = []
certs = get_config_certificate()
@@ -882,6 +888,10 @@ def show_certificate(name=None):
if not cert:
continue
+ if name and pem:
+ print(encode_certificate(cert))
+ return
+
ca_name = get_certificate_ca(cert, ca_certs)
cert_subject_cn = cert.subject.rfc4514_string().split(",")[0]
cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0]
@@ -903,7 +913,13 @@ def show_certificate(name=None):
print("Certificates:")
print(tabulate.tabulate(data, headers))
-def show_crl(name=None):
+def show_certificate_fingerprint(name, hash):
+ cert = get_config_certificate(name=name)
+ cert = load_certificate(cert['certificate'])
+
+ print(get_certificate_fingerprint(cert, hash))
+
+def show_crl(name=None, pem=False):
headers = ['CA Name', 'Updated', 'Revokes']
data = []
certs = get_config_ca_certificate()
@@ -924,9 +940,16 @@ def show_crl(name=None):
if not crl:
continue
+ if name and pem:
+ print(encode_certificate(crl))
+ continue
+
certs = get_revoked_by_serial_numbers([revoked.serial_number for revoked in crl])
data.append([cert_name, crl.last_update, ", ".join(certs)])
+ if name and pem:
+ return
+
print("Certificate Revocation Lists:")
print(tabulate.tabulate(data, headers))
@@ -940,6 +963,8 @@ if __name__ == '__main__':
parser.add_argument('--crl', help='Certificate Revocation List', required=False)
parser.add_argument('--sign', help='Sign certificate with specified CA', required=False)
parser.add_argument('--self-sign', help='Self-sign the certificate', action='store_true')
+ parser.add_argument('--pem', help='Output using PEM encoding', action='store_true')
+ parser.add_argument('--fingerprint', help='Show fingerprint and exit', action='store')
# SSH
parser.add_argument('--ssh', help='SSH Key', required=False)
@@ -1029,16 +1054,19 @@ if __name__ == '__main__':
if not conf.exists(['pki', 'ca', ca_name]):
print(f'CA "{ca_name}" does not exist!')
exit(1)
- show_certificate_authority(ca_name)
+ show_certificate_authority(ca_name, args.pem)
elif args.certificate:
cert_name = None if args.certificate == 'all' else args.certificate
if cert_name:
if not conf.exists(['pki', 'certificate', cert_name]):
print(f'Certificate "{cert_name}" does not exist!')
exit(1)
- show_certificate(None if args.certificate == 'all' else args.certificate)
+ if args.fingerprint is None:
+ show_certificate(None if args.certificate == 'all' else args.certificate, args.pem)
+ else:
+ show_certificate_fingerprint(args.certificate, args.fingerprint)
elif args.crl:
- show_crl(None if args.crl == 'all' else args.crl)
+ show_crl(None if args.crl == 'all' else args.crl, args.pem)
else:
show_certificate_authority()
show_certificate()
diff --git a/src/op_mode/policy_route.py b/src/op_mode/policy_route.py
index 5953786f3..eff99de7f 100755
--- a/src/op_mode/policy_route.py
+++ b/src/op_mode/policy_route.py
@@ -19,8 +19,8 @@ import re
import tabulate
from vyos.config import Config
-from vyos.util import cmd
-from vyos.util import dict_search_args
+from vyos.utils.process import cmd
+from vyos.utils.dict import dict_search_args
def get_config_policy(conf, name=None, ipv6=False):
config_path = ['policy']
@@ -61,8 +61,10 @@ def output_policy_route(name, route_conf, ipv6=False, single_rule_id=None):
ip_str = 'IPv6' if ipv6 else 'IPv4'
print(f'\n---------------------------------\n{ip_str} Policy Route "{name}"\n')
- if route_conf['interface']:
+ if route_conf.get('interface'):
print('Active on: {0}\n'.format(" ".join(route_conf['interface'])))
+ else:
+ print('Inactive - Not applied to any interfaces\n')
details = get_nftables_details(name, ipv6)
rows = []
diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py
index fd4f86d88..3ac5991b4 100755
--- a/src/op_mode/powerctrl.py
+++ b/src/op_mode/powerctrl.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -22,7 +22,11 @@ from datetime import datetime, timedelta, time as type_time, date as type_date
from sys import exit
from time import time
-from vyos.util import ask_yes_no, cmd, call, run, STDOUT
+from vyos.utils.io import ask_yes_no
+from vyos.utils.process import call
+from vyos.utils.process import cmd
+from vyos.utils.process import run
+from vyos.utils.process import STDOUT
systemd_sched_file = "/run/systemd/shutdown/scheduled"
@@ -102,8 +106,19 @@ def cancel_shutdown():
else:
print("Reboot or poweroff is not scheduled")
+def check_unsaved_config():
+ from vyos.config_mgmt import unsaved_commits
+ from vyos.utils.boot import boot_configuration_success
+
+ if unsaved_commits() and boot_configuration_success():
+ print("Warning: there are unsaved configuration changes!")
+ print("Run 'save' command if you do not want to lose those changes after reboot/shutdown.")
+ else:
+ pass
def execute_shutdown(time, reboot=True, ask=True):
+ check_unsaved_config()
+
action = "reboot" if reboot else "poweroff"
if not ask:
if not ask_yes_no(f"Are you sure you want to {action} this system?"):
diff --git a/src/op_mode/ppp-server-ctrl.py b/src/op_mode/ppp-server-ctrl.py
index e93963fdd..2bae5b32a 100755
--- a/src/op_mode/ppp-server-ctrl.py
+++ b/src/op_mode/ppp-server-ctrl.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 VyOS maintainers and contributors
+# Copyright (C) 2019-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -18,7 +18,8 @@ import sys
import argparse
from vyos.config import Config
-from vyos.util import popen, DEVNULL
+from vyos.utils.process import popen
+from vyos.utils.process import DEVNULL
cmd_dict = {
'cmd_base' : '/usr/bin/accel-cmd -p {} ',
diff --git a/src/op_mode/reset_openvpn.py b/src/op_mode/reset_openvpn.py
index efbf65083..cef5299da 100755
--- a/src/op_mode/reset_openvpn.py
+++ b/src/op_mode/reset_openvpn.py
@@ -16,8 +16,8 @@
import os
from sys import argv, exit
-from vyos.util import call
-from vyos.util import commit_in_progress
+from vyos.utils.process import call
+from vyos.utils.commit import commit_in_progress
if __name__ == '__main__':
if (len(argv) < 1):
diff --git a/src/op_mode/reset_vpn.py b/src/op_mode/reset_vpn.py
index 3a0ad941c..61d7c8c81 100755
--- a/src/op_mode/reset_vpn.py
+++ b/src/op_mode/reset_vpn.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -15,58 +15,47 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
-import argparse
+import typing
-from vyos.util import run
+from vyos.utils.process import run
+
+import vyos.opmode
cmd_dict = {
- 'cmd_base' : '/usr/bin/accel-cmd -p {} terminate {} {}',
- 'vpn_types' : {
- 'pptp' : 2003,
- 'l2tp' : 2004,
- 'sstp' : 2005
+ 'cmd_base': '/usr/bin/accel-cmd -p {} terminate {} {}',
+ 'vpn_types': {
+ 'pptp': 2003,
+ 'l2tp': 2004,
+ 'sstp': 2005
}
}
-def terminate_sessions(username='', interface='', protocol=''):
-
- # Reset vpn connections by username
+def reset_conn(protocol: str, username: typing.Optional[str] = None,
+ interface: typing.Optional[str] = None):
if protocol in cmd_dict['vpn_types']:
- if username == "all_users":
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol], 'all', ''))
- else:
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol], 'username', username))
-
- # Reset vpn connections by ifname
- elif interface:
- for proto in cmd_dict['vpn_types']:
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][proto], 'if', interface))
-
- elif username:
- # Reset all vpn connections
- if username == "all_users":
- for proto in cmd_dict['vpn_types']:
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][proto], 'all', ''))
+ # Reset by Interface
+ if interface:
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol],
+ 'if', interface))
+ return
+ # Reset by username
+ if username:
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol],
+ 'username', username))
+ # Reset all
else:
- for proto in cmd_dict['vpn_types']:
- run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][proto], 'username', username))
-
-def main():
- #parese args
- parser = argparse.ArgumentParser()
- parser.add_argument('--username', help='Terminate by username (all_users used for disconnect all users)', required=False)
- parser.add_argument('--interface', help='Terminate by interface', required=False)
- parser.add_argument('--protocol', help='Set protocol (pptp|l2tp|sstp)', required=False)
- args = parser.parse_args()
-
- if args.username or args.interface:
- terminate_sessions(username=args.username, interface=args.interface, protocol=args.protocol)
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol],
+ 'all',
+ ''))
else:
- print("Param --username or --interface required")
- sys.exit(1)
-
- terminate_sessions()
+ vyos.opmode.IncorrectValue('Unknown VPN Protocol, aborting')
if __name__ == '__main__':
- main()
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/restart_dhcp_relay.py b/src/op_mode/restart_dhcp_relay.py
index 9203c009f..3ead97f4c 100755
--- a/src/op_mode/restart_dhcp_relay.py
+++ b/src/op_mode/restart_dhcp_relay.py
@@ -23,8 +23,8 @@ import argparse
import os
import vyos.config
-from vyos.util import call
-from vyos.util import commit_in_progress
+from vyos.utils.process import call
+from vyos.utils.commit import commit_in_progress
parser = argparse.ArgumentParser()
diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py
index 91b25567a..5cce377eb 100755
--- a/src/op_mode/restart_frr.py
+++ b/src/op_mode/restart_frr.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2021 VyOS maintainers and contributors
+# Copyright (C) 2019-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -23,10 +23,10 @@ from logging.handlers import SysLogHandler
from shutil import rmtree
from vyos.base import Warning
-from vyos.util import call
-from vyos.util import ask_yes_no
-from vyos.util import process_named_running
-from vyos.util import makedir
+from vyos.utils.io import ask_yes_no
+from vyos.utils.file import makedir
+from vyos.utils.process import call
+from vyos.utils.process import process_named_running
# some default values
watchfrr = '/usr/lib/frr/watchfrr.sh'
@@ -139,7 +139,7 @@ def _reload_config(daemon):
# define program arguments
cmd_args_parser = argparse.ArgumentParser(description='restart frr daemons')
cmd_args_parser.add_argument('--action', choices=['restart'], required=True, help='action to frr daemons')
-cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ldpd', 'ospfd', 'ospf6d', 'isisd', 'ripd', 'ripngd', 'staticd', 'zebra'], required=False, nargs='*', help='select single or multiple daemons')
+cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ldpd', 'ospfd', 'ospf6d', 'isisd', 'ripd', 'ripngd', 'staticd', 'zebra', 'babeld'], required=False, nargs='*', help='select single or multiple daemons')
# parse arguments
cmd_args = cmd_args_parser.parse_args()
diff --git a/src/op_mode/reverseproxy.py b/src/op_mode/reverseproxy.py
new file mode 100755
index 000000000..44ffd7a37
--- /dev/null
+++ b/src/op_mode/reverseproxy.py
@@ -0,0 +1,239 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+import socket
+import sys
+import typing
+
+from sys import exit
+from tabulate import tabulate
+from vyos.configquery import ConfigTreeQuery
+
+import vyos.opmode
+
+socket_path = '/run/haproxy/admin.sock'
+timeout = 5
+
+
+def _execute_haproxy_command(command):
+ """Execute a command on the HAProxy UNIX socket and retrieve the response.
+
+ Args:
+ command (str): The command to be executed.
+
+ Returns:
+ str: The response received from the HAProxy UNIX socket.
+
+ Raises:
+ socket.error: If there is an error while connecting or communicating with the socket.
+
+ Finally:
+ Closes the socket connection.
+
+ Notes:
+ - HAProxy expects a newline character at the end of the command.
+ - The socket connection is established using the HAProxy UNIX socket.
+ - The response from the socket is received and decoded.
+
+ Example:
+ response = _execute_haproxy_command('show stat')
+ print(response)
+ """
+ try:
+ # HAProxy expects new line for command
+ command = f'{command}\n'
+
+ # Connect to the HAProxy UNIX socket
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ sock.connect(socket_path)
+
+ # Set the socket timeout
+ sock.settimeout(timeout)
+
+ # Send the command
+ sock.sendall(command.encode())
+
+ # Receive and decode the response
+ response = b''
+ while True:
+ data = sock.recv(4096)
+ if not data:
+ break
+ response += data
+ response = response.decode()
+
+ return (response)
+
+ except socket.error as e:
+ print(f"Error: {e}")
+
+ finally:
+ # Close the socket
+ sock.close()
+
+
+def _convert_seconds(seconds):
+ """Convert seconds to days, hours, minutes, and seconds.
+
+ Args:
+ seconds (int): The number of seconds to convert.
+
+ Returns:
+ tuple: A tuple containing the number of days, hours, minutes, and seconds.
+ """
+ minutes = seconds // 60
+ hours = minutes // 60
+ days = hours // 24
+
+ return days, hours % 24, minutes % 60, seconds % 60
+
+
+def _last_change_format(seconds):
+ """Format the time components into a string representation.
+
+ Args:
+ seconds (int): The total number of seconds.
+
+ Returns:
+ str: The formatted time string with days, hours, minutes, and seconds.
+
+ Examples:
+ >>> _last_change_format(1434)
+ '23m54s'
+ >>> _last_change_format(93734)
+ '1d0h23m54s'
+ >>> _last_change_format(85434)
+ '23h23m54s'
+ """
+ days, hours, minutes, seconds = _convert_seconds(seconds)
+ time_format = ""
+
+ if days:
+ time_format += f"{days}d"
+ if hours:
+ time_format += f"{hours}h"
+ if minutes:
+ time_format += f"{minutes}m"
+ if seconds:
+ time_format += f"{seconds}s"
+
+ return time_format
+
+
+def _get_json_data():
+ """Get haproxy data format JSON"""
+ return _execute_haproxy_command('show stat json')
+
+
+def _get_raw_data():
+ """Retrieve raw data from JSON and organize it into a dictionary.
+
+ Returns:
+ dict: A dictionary containing the organized data categorized
+ into frontend, backend, and server.
+ """
+
+ data = json.loads(_get_json_data())
+ lb_dict = {'frontend': [], 'backend': [], 'server': []}
+
+ for key in data:
+ frontend = []
+ backend = []
+ server = []
+ for entry in key:
+ obj_type = entry['objType'].lower()
+ position = entry['field']['pos']
+ name = entry['field']['name']
+ value = entry['value']['value']
+
+ dict_entry = {'pos': position, 'name': {name: value}}
+
+ if obj_type == 'frontend':
+ frontend.append(dict_entry)
+ elif obj_type == 'backend':
+ backend.append(dict_entry)
+ elif obj_type == 'server':
+ server.append(dict_entry)
+
+ if len(frontend) > 0:
+ lb_dict['frontend'].append(frontend)
+ if len(backend) > 0:
+ lb_dict['backend'].append(backend)
+ if len(server) > 0:
+ lb_dict['server'].append(server)
+
+ return lb_dict
+
+
+def _get_formatted_output(data):
+ """
+ Format the data into a tabulated output.
+
+ Args:
+ data (dict): The data to be formatted.
+
+ Returns:
+ str: The tabulated output representing the formatted data.
+ """
+ table = []
+ headers = [
+ "Proxy name", "Role", "Status", "Req rate", "Resp time", "Last change"
+ ]
+
+ for key in data:
+ for item in data[key]:
+ row = [None] * len(headers)
+
+ for element in item:
+ if 'pxname' in element['name']:
+ row[0] = element['name']['pxname']
+ elif 'svname' in element['name']:
+ row[1] = element['name']['svname']
+ elif 'status' in element['name']:
+ row[2] = element['name']['status']
+ elif 'req_rate' in element['name']:
+ row[3] = element['name']['req_rate']
+ elif 'rtime' in element['name']:
+ row[4] = f"{element['name']['rtime']} ms"
+ elif 'lastchg' in element['name']:
+ row[5] = _last_change_format(element['name']['lastchg'])
+ table.append(row)
+
+ out = tabulate(table, headers, numalign="left")
+ return out
+
+
+def show(raw: bool):
+ config = ConfigTreeQuery()
+ if not config.exists('load-balancing reverse-proxy'):
+ raise vyos.opmode.UnconfiguredSubsystem('Reverse-proxy is not configured')
+
+ data = _get_raw_data()
+ if raw:
+ return data
+ else:
+ return _get_formatted_output(data)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/route.py b/src/op_mode/route.py
index d07a34180..4aa57dbf4 100755
--- a/src/op_mode/route.py
+++ b/src/op_mode/route.py
@@ -54,20 +54,49 @@ frr_command_template = Template("""
{% endif %}
""")
-def show_summary(raw: bool):
- from vyos.util import cmd
+ArgFamily = typing.Literal['inet', 'inet6']
+
+def show_summary(raw: bool, family: ArgFamily, table: typing.Optional[int], vrf: typing.Optional[str]):
+ from vyos.utils.process import cmd
+
+ if family == 'inet':
+ family_cmd = 'ip'
+ elif family == 'inet6':
+ family_cmd = 'ipv6'
+ else:
+ raise ValueError(f"Unsupported address family {family}")
+
+ if (table is not None) and (vrf is not None):
+ raise ValueError("table and vrf options are mutually exclusive")
+
+ # Replace with Jinja if it ever starts growing
+ if table:
+ table_cmd = f"table {table}"
+ else:
+ table_cmd = ""
+
+ if vrf:
+ vrf_cmd = f"vrf {vrf}"
+ else:
+ vrf_cmd = ""
if raw:
from json import loads
- output = cmd(f"vtysh -c 'show ip route summary json'")
- return loads(output)
+ output = cmd(f"vtysh -c 'show {family_cmd} route {vrf_cmd} summary {table_cmd} json'").strip()
+
+ # If there are no routes in a table, its "JSON" output is an empty string,
+ # as of FRR 8.4.1
+ if output:
+ return loads(output)
+ else:
+ return {}
else:
- output = cmd(f"vtysh -c 'show ip route summary'")
+ output = cmd(f"vtysh -c 'show {family_cmd} route {vrf_cmd} summary {table_cmd}'")
return output
def show(raw: bool,
- family: str,
+ family: ArgFamily,
net: typing.Optional[str],
table: typing.Optional[int],
protocol: typing.Optional[str],
@@ -90,7 +119,7 @@ def show(raw: bool,
frr_command = frr_command_template.render(kwargs)
frr_command = re.sub(r'\s+', ' ', frr_command)
- from vyos.util import cmd
+ from vyos.utils.process import cmd
output = cmd(f"vtysh -c '{frr_command}'")
if raw:
diff --git a/src/op_mode/sflow.py b/src/op_mode/sflow.py
new file mode 100755
index 000000000..dca7f44cb
--- /dev/null
+++ b/src/op_mode/sflow.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import dbus
+import sys
+
+from tabulate import tabulate
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.utils.process import cmd
+
+import vyos.opmode
+
+
+def _get_raw_sflow():
+ bus = dbus.SystemBus()
+ config = ConfigTreeQuery()
+
+ interfaces = config.values('system sflow interface')
+ servers = config.list_nodes('system sflow server')
+
+ sflow = bus.get_object('net.sflow.hsflowd', '/net/sflow/hsflowd')
+ sflow_telemetry = dbus.Interface(
+ sflow, dbus_interface='net.sflow.hsflowd.telemetry')
+ agent_address = sflow_telemetry.GetAgent()
+ samples_dropped = int(sflow_telemetry.Get('dropped_samples'))
+ packet_drop_sent = int(sflow_telemetry.Get('event_samples'))
+ samples_packet_sent = int(sflow_telemetry.Get('flow_samples'))
+ samples_counter_sent = int(sflow_telemetry.Get('counter_samples'))
+ datagrams_sent = int(sflow_telemetry.Get('datagrams'))
+ rtmetric_samples = int(sflow_telemetry.Get('rtmetric_samples'))
+ event_samples_suppressed = int(sflow_telemetry.Get('event_samples_suppressed'))
+ samples_suppressed = int(sflow_telemetry.Get('flow_samples_suppressed'))
+ counter_samples_suppressed = int(
+ sflow_telemetry.Get("counter_samples_suppressed"))
+ version = sflow_telemetry.GetVersion()
+
+ sflow_dict = {
+ 'agent_address': agent_address,
+ 'sflow_interfaces': interfaces,
+ 'sflow_servers': servers,
+ 'counter_samples_sent': samples_counter_sent,
+ 'datagrams_sent': datagrams_sent,
+ 'packet_drop_sent': packet_drop_sent,
+ 'packet_samples_dropped': samples_dropped,
+ 'packet_samples_sent': samples_packet_sent,
+ 'rtmetric_samples': rtmetric_samples,
+ 'event_samples_suppressed': event_samples_suppressed,
+ 'flow_samples_suppressed': samples_suppressed,
+ 'counter_samples_suppressed': counter_samples_suppressed,
+ 'hsflowd_version': version
+ }
+ return sflow_dict
+
+
+def _get_formatted_sflow(data):
+ table = [
+ ['Agent address', f'{data.get("agent_address")}'],
+ ['sFlow interfaces', f'{data.get("sflow_interfaces", "n/a")}'],
+ ['sFlow servers', f'{data.get("sflow_servers", "n/a")}'],
+ ['Counter samples sent', f'{data.get("counter_samples_sent")}'],
+ ['Datagrams sent', f'{data.get("datagrams_sent")}'],
+ ['Packet samples sent', f'{data.get("packet_samples_sent")}'],
+ ['Packet samples dropped', f'{data.get("packet_samples_dropped")}'],
+ ['Packet drops sent', f'{data.get("packet_drop_sent")}'],
+ ['Packet drops suppressed', f'{data.get("event_samples_suppressed")}'],
+ ['Flow samples suppressed', f'{data.get("flow_samples_suppressed")}'],
+ ['Counter samples suppressed', f'{data.get("counter_samples_suppressed")}']
+ ]
+
+ return tabulate(table)
+
+
+def show(raw: bool):
+
+ config = ConfigTreeQuery()
+ if not config.exists('system sflow'):
+ raise vyos.opmode.UnconfiguredSubsystem(
+ '"system sflow" is not configured!')
+
+ sflow_data = _get_raw_sflow()
+ if raw:
+ return sflow_data
+ else:
+ return _get_formatted_sflow(sflow_data)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/show-bond.py b/src/op_mode/show-bond.py
index edf7847fc..f676e0841 100755
--- a/src/op_mode/show-bond.py
+++ b/src/op_mode/show-bond.py
@@ -19,7 +19,7 @@ import jinja2
from argparse import ArgumentParser
from vyos.ifconfig import Section
from vyos.ifconfig import BondIf
-from vyos.util import read_file
+from vyos.utils.file import read_file
from sys import exit
diff --git a/src/op_mode/show_acceleration.py b/src/op_mode/show_acceleration.py
index 752db3deb..1c4831f1d 100755
--- a/src/op_mode/show_acceleration.py
+++ b/src/op_mode/show_acceleration.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 VyOS maintainers and contributors
+# Copyright (C) 2019-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -13,7 +13,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
import sys
import os
@@ -21,15 +20,14 @@ import re
import argparse
from vyos.config import Config
-from vyos.util import popen
-from vyos.util import call
-
+from vyos.utils.process import call
+from vyos.utils.process import popen
def detect_qat_dev():
- output, err = popen('sudo lspci -nn', decode='utf-8')
+ output, err = popen('lspci -nn', decode='utf-8')
if not err:
data = re.findall('(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)', output)
- #If QAT devices found
+ # QAT devices found
if data:
return
print("\t No QAT device found")
@@ -44,11 +42,11 @@ def show_qat_status():
sys.exit(1)
# Show QAT service
- call('sudo /etc/init.d/qat_service status')
+ call('/etc/init.d/qat_service status')
# Return QAT devices
def get_qat_devices():
- data_st, err = popen('sudo /etc/init.d/qat_service status', decode='utf-8')
+ data_st, err = popen('/etc/init.d/qat_service status', decode='utf-8')
if not err:
elm_lst = re.findall('qat_dev\d', data_st)
print('\n'.join(elm_lst))
@@ -57,7 +55,7 @@ def get_qat_devices():
def get_qat_proc_path(qat_dev):
q_type = ""
q_bsf = ""
- output, err = popen('sudo /etc/init.d/qat_service status', decode='utf-8')
+ output, err = popen('/etc/init.d/qat_service status', decode='utf-8')
if not err:
# Parse QAT service output
data_st = output.split("\n")
@@ -95,20 +93,20 @@ args = parser.parse_args()
if args.hw:
detect_qat_dev()
# Show availible Intel QAT devices
- call('sudo lspci -nn | egrep -e \'8086:37c8|8086:19e2|8086:0435|8086:6f54\'')
+ call('lspci -nn | egrep -e \'8086:37c8|8086:19e2|8086:0435|8086:6f54\'')
elif args.flow and args.dev:
check_qat_if_conf()
- call('sudo cat '+get_qat_proc_path(args.dev)+"fw_counters")
+ call('cat '+get_qat_proc_path(args.dev)+"fw_counters")
elif args.interrupts:
check_qat_if_conf()
# Delete _dev from args.dev
- call('sudo cat /proc/interrupts | grep qat')
+ call('cat /proc/interrupts | grep qat')
elif args.status:
check_qat_if_conf()
show_qat_status()
elif args.conf and args.dev:
check_qat_if_conf()
- call('sudo cat '+get_qat_proc_path(args.dev)+"dev_cfg")
+ call('cat '+get_qat_proc_path(args.dev)+"dev_cfg")
elif args.dev_list:
get_qat_devices()
else:
diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py
deleted file mode 100755
index 4b1758eea..000000000
--- a/src/op_mode/show_dhcp.py
+++ /dev/null
@@ -1,260 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018-2021 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# TODO: merge with show_dhcpv6.py
-
-from json import dumps
-from argparse import ArgumentParser
-from ipaddress import ip_address
-from tabulate import tabulate
-from sys import exit
-from collections import OrderedDict
-from datetime import datetime
-
-from isc_dhcp_leases import Lease, IscDhcpLeases
-
-from vyos.base import Warning
-from vyos.config import Config
-from vyos.util import is_systemd_service_running
-
-lease_file = "/config/dhcpd.leases"
-pool_key = "shared-networkname"
-
-lease_display_fields = OrderedDict()
-lease_display_fields['ip'] = 'IP address'
-lease_display_fields['hardware_address'] = 'Hardware address'
-lease_display_fields['state'] = 'State'
-lease_display_fields['start'] = 'Lease start'
-lease_display_fields['end'] = 'Lease expiration'
-lease_display_fields['remaining'] = 'Remaining'
-lease_display_fields['pool'] = 'Pool'
-lease_display_fields['hostname'] = 'Hostname'
-
-lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup']
-
-def in_pool(lease, pool):
- if pool_key in lease.sets:
- if lease.sets[pool_key] == pool:
- return True
-
- return False
-
-def utc_to_local(utc_dt):
- return datetime.fromtimestamp((utc_dt - datetime(1970,1,1)).total_seconds())
-
-def get_lease_data(lease):
- data = {}
-
- # isc-dhcp lease times are in UTC so we need to convert them to local time to display
- try:
- data["start"] = utc_to_local(lease.start).strftime("%Y/%m/%d %H:%M:%S")
- except:
- data["start"] = ""
-
- try:
- data["end"] = utc_to_local(lease.end).strftime("%Y/%m/%d %H:%M:%S")
- except:
- data["end"] = ""
-
- try:
- data["remaining"] = lease.end - datetime.utcnow()
- # negative timedelta prints wrong so bypass it
- if (data["remaining"].days >= 0):
- # substraction gives us a timedelta object which can't be formatted with strftime
- # so we use str(), split gets rid of the microseconds
- data["remaining"] = str(data["remaining"]).split('.')[0]
- else:
- data["remaining"] = ""
- except:
- data["remaining"] = ""
-
- # currently not used but might come in handy
- # todo: parse into datetime string
- for prop in ['tstp', 'tsfp', 'atsfp', 'cltt']:
- if prop in lease.data:
- data[prop] = lease.data[prop]
- else:
- data[prop] = ''
-
- data["hardware_address"] = lease.ethernet
- data["hostname"] = lease.hostname
-
- data["state"] = lease.binding_state
- data["ip"] = lease.ip
-
- try:
- data["pool"] = lease.sets[pool_key]
- except:
- data["pool"] = ""
-
- return data
-
-def get_leases(config, leases, state, pool=None, sort='ip'):
- # get leases from file
- leases = IscDhcpLeases(lease_file).get()
-
- # filter leases by state
- if 'all' not in state:
- leases = list(filter(lambda x: x.binding_state in state, leases))
-
- # filter leases by pool name
- if pool is not None:
- if config.exists_effective("service dhcp-server shared-network-name {0}".format(pool)):
- leases = list(filter(lambda x: in_pool(x, pool), leases))
- else:
- print("Pool {0} does not exist.".format(pool))
- exit(0)
-
- # should maybe filter all state=active by lease.valid here?
-
- # sort by start time to dedupe (newest lease overrides older)
- leases = sorted(leases, key = lambda lease: lease.start)
-
- # dedupe by converting to dict
- leases_dict = {}
- for lease in leases:
- # dedupe by IP
- leases_dict[lease.ip] = lease
-
- # convert the lease data
- leases = list(map(get_lease_data, leases_dict.values()))
-
- # apply output/display sort
- if sort == 'ip':
- leases = sorted(leases, key = lambda lease: int(ip_address(lease['ip'])))
- else:
- leases = sorted(leases, key = lambda lease: lease[sort])
-
- return leases
-
-def show_leases(leases):
- lease_list = []
- for l in leases:
- lease_list_params = []
- for k in lease_display_fields.keys():
- lease_list_params.append(l[k])
- lease_list.append(lease_list_params)
-
- output = tabulate(lease_list, lease_display_fields.values())
-
- print(output)
-
-def get_pool_size(config, pool):
- size = 0
- subnets = config.list_effective_nodes("service dhcp-server shared-network-name {0} subnet".format(pool))
- for s in subnets:
- ranges = config.list_effective_nodes("service dhcp-server shared-network-name {0} subnet {1} range".format(pool, s))
- for r in ranges:
- start = config.return_effective_value("service dhcp-server shared-network-name {0} subnet {1} range {2} start".format(pool, s, r))
- stop = config.return_effective_value("service dhcp-server shared-network-name {0} subnet {1} range {2} stop".format(pool, s, r))
-
- # Add +1 because both range boundaries are inclusive
- size += int(ip_address(stop)) - int(ip_address(start)) + 1
-
- return size
-
-def show_pool_stats(stats):
- headers = ["Pool", "Size", "Leases", "Available", "Usage"]
- output = tabulate(stats, headers)
-
- print(output)
-
-if __name__ == '__main__':
- parser = ArgumentParser()
-
- group = parser.add_mutually_exclusive_group()
- group.add_argument("-l", "--leases", action="store_true", help="Show DHCP leases")
- group.add_argument("-s", "--statistics", action="store_true", help="Show DHCP statistics")
- group.add_argument("--allowed", type=str, choices=["sort", "state"], help="Show allowed values for argument")
-
- parser.add_argument("-p", "--pool", type=str, help="Show lease for specific pool")
- parser.add_argument("-S", "--sort", type=str, default='ip', help="Sort by")
- parser.add_argument("-t", "--state", type=str, nargs="+", default=["active"], help="Lease state to show (can specify multiple with spaces)")
- parser.add_argument("-j", "--json", action="store_true", default=False, help="Produce JSON output")
-
- args = parser.parse_args()
-
- conf = Config()
-
- if args.allowed == 'sort':
- print(' '.join(lease_display_fields.keys()))
- exit(0)
- elif args.allowed == 'state':
- print(' '.join(lease_valid_states))
- exit(0)
- elif args.allowed:
- parser.print_help()
- exit(1)
-
- if args.sort not in lease_display_fields.keys():
- print(f'Invalid sort key, choose from: {list(lease_display_fields.keys())}')
- exit(0)
-
- if not set(args.state) < set(lease_valid_states):
- print(f'Invalid lease state, choose from: {lease_valid_states}')
- exit(0)
-
- # Do nothing if service is not configured
- if not conf.exists_effective('service dhcp-server'):
- print("DHCP service is not configured.")
- exit(0)
-
- # if dhcp server is down, inactive leases may still be shown as active, so warn the user.
- if not is_systemd_service_running('isc-dhcp-server.service'):
- Warning('DHCP server is configured but not started. Data may be stale.')
-
- if args.leases:
- leases = get_leases(conf, lease_file, args.state, args.pool, args.sort)
-
- if args.json:
- print(dumps(leases, indent=4))
- else:
- show_leases(leases)
-
- elif args.statistics:
- pools = []
-
- # Get relevant pools
- if args.pool:
- pools = [args.pool]
- else:
- pools = conf.list_effective_nodes("service dhcp-server shared-network-name")
-
- # Get pool usage stats
- stats = []
- for p in pools:
- size = get_pool_size(conf, p)
- leases = len(get_leases(conf, lease_file, state='active', pool=p))
-
- use_percentage = round(leases / size * 100) if size != 0 else 0
-
- if args.json:
- pool_stats = {"pool": p, "size": size, "leases": leases,
- "available": (size - leases), "percentage": use_percentage}
- else:
- # For tabulate
- pool_stats = [p, size, leases, size - leases, "{0}%".format(use_percentage)]
- stats.append(pool_stats)
-
- # Print stats
- if args.json:
- print(dumps(stats, indent=4))
- else:
- show_pool_stats(stats)
-
- else:
- parser.print_help()
- exit(1)
diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py
deleted file mode 100755
index b34b730e6..000000000
--- a/src/op_mode/show_dhcpv6.py
+++ /dev/null
@@ -1,220 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018-2021 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# TODO: merge with show_dhcp.py
-
-from json import dumps
-from argparse import ArgumentParser
-from ipaddress import ip_address
-from tabulate import tabulate
-from sys import exit
-from collections import OrderedDict
-from datetime import datetime
-
-from isc_dhcp_leases import Lease, IscDhcpLeases
-
-from vyos.base import Warning
-from vyos.config import Config
-from vyos.util import is_systemd_service_running
-
-lease_file = "/config/dhcpdv6.leases"
-pool_key = "shared-networkname"
-
-lease_display_fields = OrderedDict()
-lease_display_fields['ip'] = 'IPv6 address'
-lease_display_fields['state'] = 'State'
-lease_display_fields['last_comm'] = 'Last communication'
-lease_display_fields['expires'] = 'Lease expiration'
-lease_display_fields['remaining'] = 'Remaining'
-lease_display_fields['type'] = 'Type'
-lease_display_fields['pool'] = 'Pool'
-lease_display_fields['iaid_duid'] = 'IAID_DUID'
-
-lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup']
-
-def in_pool(lease, pool):
- if pool_key in lease.sets:
- if lease.sets[pool_key] == pool:
- return True
-
- return False
-
-def format_hex_string(in_str):
- out_str = ""
-
- # if input is divisible by 2, add : every 2 chars
- if len(in_str) > 0 and len(in_str) % 2 == 0:
- out_str = ':'.join(a+b for a,b in zip(in_str[::2], in_str[1::2]))
- else:
- out_str = in_str
-
- return out_str
-
-def utc_to_local(utc_dt):
- return datetime.fromtimestamp((utc_dt - datetime(1970,1,1)).total_seconds())
-
-def get_lease_data(lease):
- data = {}
-
- # isc-dhcp lease times are in UTC so we need to convert them to local time to display
- try:
- data["expires"] = utc_to_local(lease.end).strftime("%Y/%m/%d %H:%M:%S")
- except:
- data["expires"] = ""
-
- try:
- data["last_comm"] = utc_to_local(lease.last_communication).strftime("%Y/%m/%d %H:%M:%S")
- except:
- data["last_comm"] = ""
-
- try:
- data["remaining"] = lease.end - datetime.utcnow()
- # negative timedelta prints wrong so bypass it
- if (data["remaining"].days >= 0):
- # substraction gives us a timedelta object which can't be formatted with strftime
- # so we use str(), split gets rid of the microseconds
- data["remaining"] = str(data["remaining"]).split('.')[0]
- else:
- data["remaining"] = ""
- except:
- data["remaining"] = ""
-
- # isc-dhcp records lease declarations as ia_{na|ta|pd} IAID_DUID {...}
- # where IAID_DUID is the combined IAID and DUID
- data["iaid_duid"] = format_hex_string(lease.host_identifier_string)
-
- lease_types_long = {"na": "non-temporary", "ta": "temporary", "pd": "prefix delegation"}
- data["type"] = lease_types_long[lease.type]
-
- data["state"] = lease.binding_state
- data["ip"] = lease.ip
-
- try:
- data["pool"] = lease.sets[pool_key]
- except:
- data["pool"] = ""
-
- return data
-
-def get_leases(config, leases, state, pool=None, sort='ip'):
- leases = IscDhcpLeases(lease_file).get()
-
- # filter leases by state
- if 'all' not in state:
- leases = list(filter(lambda x: x.binding_state in state, leases))
-
- # filter leases by pool name
- if pool is not None:
- if config.exists_effective("service dhcp-server shared-network-name {0}".format(pool)):
- leases = list(filter(lambda x: in_pool(x, pool), leases))
- else:
- print("Pool {0} does not exist.".format(pool))
- exit(0)
-
- # should maybe filter all state=active by lease.valid here?
-
- # sort by last_comm time to dedupe (newest lease overrides older)
- leases = sorted(leases, key = lambda lease: lease.last_communication)
-
- # dedupe by converting to dict
- leases_dict = {}
- for lease in leases:
- # dedupe by IP
- leases_dict[lease.ip] = lease
-
- # convert the lease data
- leases = list(map(get_lease_data, leases_dict.values()))
-
- # apply output/display sort
- if sort == 'ip':
- leases = sorted(leases, key = lambda k: int(ip_address(k['ip'].split('/')[0])))
- else:
- leases = sorted(leases, key = lambda k: k[sort])
-
- return leases
-
-def show_leases(leases):
- lease_list = []
- for l in leases:
- lease_list_params = []
- for k in lease_display_fields.keys():
- lease_list_params.append(l[k])
- lease_list.append(lease_list_params)
-
- output = tabulate(lease_list, lease_display_fields.values())
-
- print(output)
-
-if __name__ == '__main__':
- parser = ArgumentParser()
-
- group = parser.add_mutually_exclusive_group()
- group.add_argument("-l", "--leases", action="store_true", help="Show DHCPv6 leases")
- group.add_argument("-s", "--statistics", action="store_true", help="Show DHCPv6 statistics")
- group.add_argument("--allowed", type=str, choices=["pool", "sort", "state"], help="Show allowed values for argument")
-
- parser.add_argument("-p", "--pool", type=str, help="Show lease for specific pool")
- parser.add_argument("-S", "--sort", type=str, default='ip', help="Sort by")
- parser.add_argument("-t", "--state", type=str, nargs="+", default=["active"], help="Lease state to show (can specify multiple with spaces)")
- parser.add_argument("-j", "--json", action="store_true", default=False, help="Produce JSON output")
-
- args = parser.parse_args()
-
- conf = Config()
-
- if args.allowed == 'pool':
- if conf.exists_effective('service dhcpv6-server'):
- print(' '.join(conf.list_effective_nodes("service dhcpv6-server shared-network-name")))
- exit(0)
- elif args.allowed == 'sort':
- print(' '.join(lease_display_fields.keys()))
- exit(0)
- elif args.allowed == 'state':
- print(' '.join(lease_valid_states))
- exit(0)
- elif args.allowed:
- parser.print_help()
- exit(1)
-
- if args.sort not in lease_display_fields.keys():
- print(f'Invalid sort key, choose from: {list(lease_display_fields.keys())}')
- exit(0)
-
- if not set(args.state) < set(lease_valid_states):
- print(f'Invalid lease state, choose from: {lease_valid_states}')
- exit(0)
-
- # Do nothing if service is not configured
- if not conf.exists_effective('service dhcpv6-server'):
- print("DHCPv6 service is not configured")
- exit(0)
-
- # if dhcp server is down, inactive leases may still be shown as active, so warn the user.
- if not is_systemd_service_running('isc-dhcp-server6.service'):
- Warning('DHCPv6 server is configured but not started. Data may be stale.')
-
- if args.leases:
- leases = get_leases(conf, lease_file, args.state, args.pool, args.sort)
-
- if args.json:
- print(dumps(leases, indent=4))
- else:
- show_leases(leases)
- elif args.statistics:
- print("DHCPv6 statistics option is not available")
- else:
- parser.print_help()
- exit(1)
diff --git a/src/op_mode/show_igmpproxy.py b/src/op_mode/show_igmpproxy.py
deleted file mode 100755
index 4714e494b..000000000
--- a/src/op_mode/show_igmpproxy.py
+++ /dev/null
@@ -1,241 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-# File: show_igmpproxy.py
-# Purpose:
-# Display istatistics from IPv4 IGMP proxy.
-# Used by the "run show ip multicast" command tree.
-
-import sys
-import jinja2
-import argparse
-import ipaddress
-import socket
-
-import vyos.config
-
-# Output Template for "show ip multicast interface" command
-#
-# Example:
-# Interface BytesIn PktsIn BytesOut PktsOut Local
-# eth0 0.0b 0 0.0b 0 xxx.xxx.xxx.65
-# eth1 0.0b 0 0.0b 0 xxx.xxx.xx.201
-# eth0.3 0.0b 0 0.0b 0 xxx.xxx.x.7
-# tun1 0.0b 0 0.0b 0 xxx.xxx.xxx.2
-vif_out_tmpl = """
-{% for r in data %}
-{{ "%-10s"|format(r.interface) }} {{ "%-12s"|format(r.bytes_in) }} {{ "%-12s"|format(r.pkts_in) }} {{ "%-12s"|format(r.bytes_out) }} {{ "%-12s"|format(r.pkts_out) }} {{ "%-15s"|format(r.loc) }}
-{% endfor %}
-"""
-
-# Output Template for "show ip multicast mfc" command
-#
-# Example:
-# Group Origin In Out Pkts Bytes Wrong
-# xxx.xxx.xxx.250 xxx.xx.xxx.75 --
-# xxx.xxx.xx.124 xx.xxx.xxx.26 --
-mfc_out_tmpl = """
-{% for r in data %}
-{{ "%-15s"|format(r.group) }} {{ "%-15s"|format(r.origin) }} {{ "%-12s"|format(r.pkts) }} {{ "%-12s"|format(r.bytes) }} {{ "%-12s"|format(r.wrong) }} {{ "%-10s"|format(r.iif) }} {{ "%-20s"|format(r.oifs|join(', ')) }}
-{% endfor %}
-"""
-
-parser = argparse.ArgumentParser()
-parser.add_argument("--interface", action="store_true", help="Interface Statistics")
-parser.add_argument("--mfc", action="store_true", help="Multicast Forwarding Cache")
-
-def byte_string(size):
- # convert size to integer
- size = int(size)
-
- # One Terrabyte
- s_TB = 1024 * 1024 * 1024 * 1024
- # One Gigabyte
- s_GB = 1024 * 1024 * 1024
- # One Megabyte
- s_MB = 1024 * 1024
- # One Kilobyte
- s_KB = 1024
- # One Byte
- s_B = 1
-
- if size > s_TB:
- return str(round((size/s_TB), 2)) + 'TB'
- elif size > s_GB:
- return str(round((size/s_GB), 2)) + 'GB'
- elif size > s_MB:
- return str(round((size/s_MB), 2)) + 'MB'
- elif size > s_KB:
- return str(round((size/s_KB), 2)) + 'KB'
- else:
- return str(round((size/s_B), 2)) + 'b'
-
- return None
-
-def kernel2ip(addr):
- """
- Convert any given addr from Linux Kernel to a proper, IPv4 address
- using the correct host byte order.
- """
-
- # Convert from hex 'FE000A0A' to decimal '4261415434'
- addr = int(addr, 16)
- # Kernel ABI _always_ uses network byteorder
- addr = socket.ntohl(addr)
-
- return ipaddress.IPv4Address( addr )
-
-def do_mr_vif():
- """
- Read contents of file /proc/net/ip_mr_vif and print a more human
- friendly version to the command line. IPv4 addresses present as
- 32bit integers in hex format are converted to IPv4 notation, too.
- """
-
- with open('/proc/net/ip_mr_vif', 'r') as f:
- lines = len(f.readlines())
- if lines < 2:
- return None
-
- result = {
- 'data': []
- }
-
- # Build up table format string
- table_format = {
- 'interface': 'Interface',
- 'pkts_in' : 'PktsIn',
- 'pkts_out' : 'PktsOut',
- 'bytes_in' : 'BytesIn',
- 'bytes_out': 'BytesOut',
- 'loc' : 'Local'
- }
- result['data'].append(table_format)
-
- # read and parse information from /proc filesystema
- with open('/proc/net/ip_mr_vif', 'r') as f:
- header_line = next(f)
- for line in f:
- data = {
- 'interface': line.split()[1],
- 'pkts_in' : line.split()[3],
- 'pkts_out' : line.split()[5],
-
- # convert raw byte number to something more human readable
- # Note: could be replaced by Python3 hurry.filesize module
- 'bytes_in' : byte_string( line.split()[2] ),
- 'bytes_out': byte_string( line.split()[4] ),
-
- # convert IP address from hex 'FE000A0A' to decimal '4261415434'
- 'loc' : kernel2ip( line.split()[7] ),
- }
- result['data'].append(data)
-
- return result
-
-def do_mr_mfc():
- """
- Read contents of file /proc/net/ip_mr_cache and print a more human
- friendly version to the command line. IPv4 addresses present as
- 32bit integers in hex format are converted to IPv4 notation, too.
- """
-
- with open('/proc/net/ip_mr_cache', 'r') as f:
- lines = len(f.readlines())
- if lines < 2:
- return None
-
- # We need this to convert from interface index to a real interface name
- # Thus we also skip the format identifier on list index 0
- vif = do_mr_vif()['data'][1:]
-
- result = {
- 'data': []
- }
-
- # Build up table format string
- table_format = {
- 'group' : 'Group',
- 'origin': 'Origin',
- 'iif' : 'In',
- 'oifs' : ['Out'],
- 'pkts' : 'Pkts',
- 'bytes' : 'Bytes',
- 'wrong' : 'Wrong'
- }
- result['data'].append(table_format)
-
- # read and parse information from /proc filesystem
- with open('/proc/net/ip_mr_cache', 'r') as f:
- header_line = next(f)
- for line in f:
- data = {
- # convert IP address from hex 'FE000A0A' to decimal '4261415434'
- 'group' : kernel2ip( line.split()[0] ),
- 'origin': kernel2ip( line.split()[1] ),
-
- 'iif' : '--',
- 'pkts' : '',
- 'bytes' : '',
- 'wrong' : '',
- 'oifs' : []
- }
-
- iif = int( line.split()[2] )
- if not ((iif == -1) or (iif == 65535)):
- data['pkts'] = line.split()[3]
- data['bytes'] = byte_string( line.split()[4] )
- data['wrong'] = line.split()[5]
-
- # convert index to real interface name
- data['iif'] = vif[iif]['interface']
-
- # convert each output interface index to a real interface name
- for oif in line.split()[6:]:
- idx = int( oif.split(':')[0] )
- data['oifs'].append( vif[idx]['interface'] )
-
- result['data'].append(data)
-
- return result
-
-if __name__ == '__main__':
- args = parser.parse_args()
-
- # Do nothing if service is not configured
- c = vyos.config.Config()
- if not c.exists_effective('protocols igmp-proxy'):
- print("IGMP proxy is not configured")
- sys.exit(0)
-
- if args.interface:
- data = do_mr_vif()
- if data:
- tmpl = jinja2.Template(vif_out_tmpl)
- print(tmpl.render(data))
-
- sys.exit(0)
- elif args.mfc:
- data = do_mr_mfc()
- if data:
- tmpl = jinja2.Template(mfc_out_tmpl)
- print(tmpl.render(data))
-
- sys.exit(0)
- else:
- parser.print_help()
- sys.exit(1)
-
diff --git a/src/op_mode/show_interfaces.py b/src/op_mode/show_interfaces.py
deleted file mode 100755
index eac068274..000000000
--- a/src/op_mode/show_interfaces.py
+++ /dev/null
@@ -1,310 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright 2017-2021 VyOS maintainers and contributors <maintainers@vyos.io>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import re
-import sys
-import glob
-import argparse
-
-from vyos.ifconfig import Section
-from vyos.ifconfig import Interface
-from vyos.ifconfig import VRRP
-from vyos.util import cmd, call
-
-
-# interfaces = Sections.reserved()
-interfaces = ['eno', 'ens', 'enp', 'enx', 'eth', 'vmnet', 'lo', 'tun', 'wan', 'pppoe']
-glob_ifnames = '/sys/class/net/({})*'.format('|'.join(interfaces))
-
-
-actions = {}
-def register(name):
- """
- Decorator to register a function into actions with a name.
- `actions[name]' can be used to call the registered functions.
- We wrap each function in a SIGPIPE handler as all registered functions
- can be subject to a broken pipe if there are a lot of interfaces.
- """
- def _register(function):
- def handled_function(*args, **kwargs):
- try:
- function(*args, **kwargs)
- except BrokenPipeError:
- # Flush output to /dev/null and bail out.
- os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
- sys.exit(1)
- actions[name] = handled_function
- return handled_function
- return _register
-
-
-def filtered_interfaces(ifnames, iftypes, vif, vrrp):
- """
- get all the interfaces from the OS and returns them
- ifnames can be used to filter which interfaces should be considered
-
- ifnames: a list of interfaces names to consider, empty do not filter
- return an instance of the interface class
- """
- if isinstance(iftypes, list):
- for iftype in iftypes:
- yield from filtered_interfaces(ifnames, iftype, vif, vrrp)
-
- for ifname in Section.interfaces(iftypes):
- # Bail out early if interface name not part of our search list
- if ifnames and ifname not in ifnames:
- continue
-
- # As we are only "reading" from the interface - we must use the
- # generic base class which exposes all the data via a common API
- interface = Interface(ifname, create=False, debug=False)
-
- # VLAN interfaces have a '.' in their name by convention
- if vif and not '.' in ifname:
- continue
-
- if vrrp:
- vrrp_interfaces = VRRP.active_interfaces()
- if ifname not in vrrp_interfaces:
- continue
-
- yield interface
-
-
-def split_text(text, used=0):
- """
- take a string and attempt to split it to fit with the width of the screen
-
- text: the string to split
- used: number of characted already used in the screen
- """
- no_tty = call('tty -s')
-
- returned = cmd('stty size') if not no_tty else ''
- if len(returned) == 2:
- rows, columns = [int(_) for _ in returned]
- else:
- rows, columns = (40, 80)
-
- desc_len = columns - used
-
- line = ''
- for word in text.split():
- if len(line) + len(word) < desc_len:
- line = f'{line} {word}'
- continue
- if line:
- yield line[1:]
- else:
- line = f'{line} {word}'
-
- yield line[1:]
-
-
-def get_counter_val(clear, now):
- """
- attempt to correct a counter if it wrapped, copied from perl
-
- clear: previous counter
- now: the current counter
- """
- # This function has to deal with both 32 and 64 bit counters
- if clear == 0:
- return now
-
- # device is using 64 bit values assume they never wrap
- value = now - clear
- if (now >> 32) != 0:
- return value
-
- # The counter has rolled. If the counter has rolled
- # multiple times since the clear value, then this math
- # is meaningless.
- if (value < 0):
- value = (4294967296 - clear) + now
-
- return value
-
-
-@register('help')
-def usage(*args):
- print(f"Usage: {sys.argv[0]} [intf=NAME|intf-type=TYPE|vif|vrrp] action=ACTION")
- print(f" NAME = " + ' | '.join(Section.interfaces()))
- print(f" TYPE = " + ' | '.join(Section.sections()))
- print(f" ACTION = " + ' | '.join(actions))
- sys.exit(1)
-
-
-@register('allowed')
-def run_allowed(**kwarg):
- sys.stdout.write(' '.join(Section.interfaces()))
-
-
-def pppoe(ifname):
- out = cmd(f'ps -C pppd -f')
- if ifname in out:
- return 'C'
- elif ifname in [_.split('/')[-1] for _ in glob.glob('/etc/ppp/peers/pppoe*')]:
- return 'D'
- return ''
-
-
-@register('show')
-def run_show_intf(ifnames, iftypes, vif, vrrp):
- handled = []
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- handled.append(interface.ifname)
- cache = interface.operational.load_counters()
-
- out = cmd(f'ip addr show {interface.ifname}')
- out = re.sub(f'^\d+:\s+','',out)
- if re.search('link/tunnel6', out):
- tunnel = cmd(f'ip -6 tun show {interface.ifname}')
- # tun0: ip/ipv6 remote ::2 local ::1 encaplimit 4 hoplimit 64 tclass inherit flowlabel inherit (flowinfo 0x00000000)
- tunnel = re.sub('.*encap', 'encap', tunnel)
- out = re.sub('(\n\s+)(link/tunnel6)', f'\g<1>{tunnel}\g<1>\g<2>', out)
-
- print(out)
-
- timestamp = int(cache.get('timestamp', 0))
- if timestamp:
- when = interface.operational.strtime(timestamp)
- print(f' Last clear: {when}')
-
- description = interface.get_alias()
- if description:
- print(f' Description: {description}')
-
- print()
- print(interface.operational.formated_stats())
-
- for ifname in ifnames:
- if ifname not in handled and ifname.startswith('pppoe'):
- state = pppoe(ifname)
- if not state:
- continue
- string = {
- 'C': 'Coming up',
- 'D': 'Link down',
- }[state]
- print('{}: {}'.format(ifname, string))
-
-
-@register('show-brief')
-def run_show_intf_brief(ifnames, iftypes, vif, vrrp):
- format1 = '%-16s %-33s %-4s %s'
- format2 = '%-16s %s'
-
- print('Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down')
- print(format1 % ("Interface", "IP Address", "S/L", "Description"))
- print(format1 % ("---------", "----------", "---", "-----------"))
-
- handled = []
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- handled.append(interface.ifname)
-
- oper_state = interface.operational.get_state()
- admin_state = interface.get_admin_state()
-
- intf = [interface.ifname,]
-
- oper = ['u', ] if oper_state in ('up', 'unknown') else ['D', ]
- admin = ['u', ] if admin_state in ('up', 'unknown') else ['A', ]
- addrs = [_ for _ in interface.get_addr() if not _.startswith('fe80::')] or ['-', ]
- descs = list(split_text(interface.get_alias(),0))
-
- while intf or oper or admin or addrs or descs:
- i = intf.pop(0) if intf else ''
- a = addrs.pop(0) if addrs else ''
- d = descs.pop(0) if descs else ''
- s = [admin.pop(0)] if admin else []
- l = [oper.pop(0)] if oper else []
- if len(a) < 33:
- print(format1 % (i, a, '/'.join(s+l), d))
- else:
- print(format2 % (i, a))
- print(format1 % ('', '', '/'.join(s+l), d))
-
- for ifname in ifnames:
- if ifname not in handled and ifname.startswith('pppoe'):
- state = pppoe(ifname)
- if not state:
- continue
- string = {
- 'C': 'u/D',
- 'D': 'A/D',
- }[state]
- print(format1 % (ifname, '', string, ''))
-
-
-@register('show-count')
-def run_show_counters(ifnames, iftypes, vif, vrrp):
- formating = '%-12s %10s %10s %10s %10s'
- print(formating % ('Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes'))
-
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- oper = interface.operational.get_state()
-
- if oper not in ('up','unknown'):
- continue
-
- stats = interface.operational.get_stats()
- cache = interface.operational.load_counters()
- print(formating % (
- interface.ifname,
- get_counter_val(cache['rx_packets'], stats['rx_packets']),
- get_counter_val(cache['rx_bytes'], stats['rx_bytes']),
- get_counter_val(cache['tx_packets'], stats['tx_packets']),
- get_counter_val(cache['tx_bytes'], stats['tx_bytes']),
- ))
-
-
-@register('clear')
-def run_clear_intf(ifnames, iftypes, vif, vrrp):
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- print(f'Clearing {interface.ifname}')
- interface.operational.clear_counters()
-
-
-@register('reset')
-def run_reset_intf(ifnames, iftypes, vif, vrrp):
- for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
- interface.operational.reset_counters()
-
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser(add_help=False, description='Show interface information')
- parser.add_argument('--intf', action="store", type=str, default='', help='only show the specified interface(s)')
- parser.add_argument('--intf-type', action="store", type=str, default='', help='only show the specified interface type')
- parser.add_argument('--action', action="store", type=str, default='show', help='action to perform')
- parser.add_argument('--vif', action='store_true', default=False, help="only show vif interfaces")
- parser.add_argument('--vrrp', action='store_true', default=False, help="only show vrrp interfaces")
- parser.add_argument('--help', action='store_true', default=False, help="show help")
-
- args = parser.parse_args()
-
- def missing(*args):
- print('Invalid action [{args.action}]')
- usage()
-
- actions.get(args.action, missing)(
- [_ for _ in args.intf.split(' ') if _],
- [_ for _ in args.intf_type.split(' ') if _],
- args.vif,
- args.vrrp
- )
diff --git a/src/op_mode/show_ipsec_sa.py b/src/op_mode/show_ipsec_sa.py
deleted file mode 100755
index 5b8f00dba..000000000
--- a/src/op_mode/show_ipsec_sa.py
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2022 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-from re import split as re_split
-from sys import exit
-
-from hurry import filesize
-from tabulate import tabulate
-from vici import Session as vici_session
-
-from vyos.util import seconds_to_human
-
-
-def convert(text):
- return int(text) if text.isdigit() else text.lower()
-
-
-def alphanum_key(key):
- return [convert(c) for c in re_split('([0-9]+)', str(key))]
-
-
-def format_output(sas):
- sa_data = []
-
- for sa in sas:
- for parent_sa in sa.values():
- # create an item for each child-sa
- for child_sa in parent_sa.get('child-sas', {}).values():
- # prepare a list for output data
- sa_out_name = sa_out_state = sa_out_uptime = sa_out_bytes = sa_out_packets = sa_out_remote_addr = sa_out_remote_id = sa_out_proposal = 'N/A'
-
- # collect raw data
- sa_name = child_sa.get('name')
- sa_state = child_sa.get('state')
- sa_uptime = child_sa.get('install-time')
- sa_bytes_in = child_sa.get('bytes-in')
- sa_bytes_out = child_sa.get('bytes-out')
- sa_packets_in = child_sa.get('packets-in')
- sa_packets_out = child_sa.get('packets-out')
- sa_remote_addr = parent_sa.get('remote-host')
- sa_remote_id = parent_sa.get('remote-id')
- sa_proposal_encr_alg = child_sa.get('encr-alg')
- sa_proposal_integ_alg = child_sa.get('integ-alg')
- sa_proposal_encr_keysize = child_sa.get('encr-keysize')
- sa_proposal_dh_group = child_sa.get('dh-group')
-
- # format data to display
- if sa_name:
- sa_out_name = sa_name.decode()
- if sa_state:
- if sa_state == b'INSTALLED':
- sa_out_state = 'up'
- else:
- sa_out_state = 'down'
- if sa_uptime:
- sa_out_uptime = seconds_to_human(sa_uptime.decode())
- if sa_bytes_in and sa_bytes_out:
- bytes_in = filesize.size(int(sa_bytes_in.decode()))
- bytes_out = filesize.size(int(sa_bytes_out.decode()))
- sa_out_bytes = f'{bytes_in}/{bytes_out}'
- if sa_packets_in and sa_packets_out:
- packets_in = filesize.size(int(sa_packets_in.decode()),
- system=filesize.si)
- packets_out = filesize.size(int(sa_packets_out.decode()),
- system=filesize.si)
- sa_out_packets = f'{packets_in}/{packets_out}'
- if sa_remote_addr:
- sa_out_remote_addr = sa_remote_addr.decode()
- if sa_remote_id:
- sa_out_remote_id = sa_remote_id.decode()
- # format proposal
- if sa_proposal_encr_alg:
- sa_out_proposal = sa_proposal_encr_alg.decode()
- if sa_proposal_encr_keysize:
- sa_proposal_encr_keysize_str = sa_proposal_encr_keysize.decode()
- sa_out_proposal = f'{sa_out_proposal}_{sa_proposal_encr_keysize_str}'
- if sa_proposal_integ_alg:
- sa_proposal_integ_alg_str = sa_proposal_integ_alg.decode()
- sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_integ_alg_str}'
- if sa_proposal_dh_group:
- sa_proposal_dh_group_str = sa_proposal_dh_group.decode()
- sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_dh_group_str}'
-
- # add a new item to output data
- sa_data.append([
- sa_out_name, sa_out_state, sa_out_uptime, sa_out_bytes,
- sa_out_packets, sa_out_remote_addr, sa_out_remote_id,
- sa_out_proposal
- ])
-
- # return output data
- return sa_data
-
-
-if __name__ == '__main__':
- try:
- session = vici_session()
- sas = list(session.list_sas())
-
- sa_data = format_output(sas)
- sa_data = sorted(sa_data, key=alphanum_key)
-
- headers = [
- "Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out",
- "Remote address", "Remote ID", "Proposal"
- ]
- output = tabulate(sa_data, headers)
- print(output)
- except PermissionError:
- print("You do not have a permission to connect to the IPsec daemon")
- exit(1)
- except ConnectionRefusedError:
- print("IPsec is not runing")
- exit(1)
- except Exception as e:
- print("An error occured: {0}".format(e))
- exit(1)
diff --git a/src/op_mode/show_nat66_statistics.py b/src/op_mode/show_nat66_statistics.py
deleted file mode 100755
index cb10aed9f..000000000
--- a/src/op_mode/show_nat66_statistics.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import jmespath
-import json
-
-from argparse import ArgumentParser
-from jinja2 import Template
-from sys import exit
-from vyos.util import cmd
-
-OUT_TMPL_SRC="""
-rule pkts bytes interface
----- ---- ----- ---------
-{% for r in output %}
-{% if r.comment %}
-{% set packets = r.counter.packets %}
-{% set bytes = r.counter.bytes %}
-{% set interface = r.interface %}
-{# remove rule comment prefix #}
-{% set comment = r.comment | replace('SRC-NAT66-', '') | replace('DST-NAT66-', '') %}
-{{ "%-4s" | format(comment) }} {{ "%9s" | format(packets) }} {{ "%12s" | format(bytes) }} {{ interface }}
-{% endif %}
-{% endfor %}
-"""
-
-parser = ArgumentParser()
-group = parser.add_mutually_exclusive_group()
-group.add_argument("--source", help="Show statistics for configured source NAT rules", action="store_true")
-group.add_argument("--destination", help="Show statistics for configured destination NAT rules", action="store_true")
-args = parser.parse_args()
-
-if args.source or args.destination:
- tmp = cmd('sudo nft -j list table ip6 vyos_nat')
- tmp = json.loads(tmp)
-
- source = r"nftables[?rule.chain=='POSTROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }"
- destination = r"nftables[?rule.chain=='PREROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }"
- data = {
- 'output' : jmespath.search(source if args.source else destination, tmp),
- 'direction' : 'source' if args.source else 'destination'
- }
-
- tmpl = Template(OUT_TMPL_SRC, lstrip_blocks=True)
- print(tmpl.render(data))
- exit(0)
-else:
- parser.print_help()
- exit(1)
-
diff --git a/src/op_mode/show_nat66_translations.py b/src/op_mode/show_nat66_translations.py
deleted file mode 100755
index 045d64065..000000000
--- a/src/op_mode/show_nat66_translations.py
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2020 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-'''
-show nat translations
-'''
-
-import os
-import sys
-import ipaddress
-import argparse
-import xmltodict
-
-from vyos.util import popen
-from vyos.util import DEVNULL
-
-conntrack = '/usr/sbin/conntrack'
-
-verbose_format = "%-20s %-18s %-20s %-18s"
-normal_format = "%-20s %-20s %-4s %-8s %s"
-
-
-def headers(verbose, pipe):
- if verbose:
- return verbose_format % ('Pre-NAT src', 'Pre-NAT dst', 'Post-NAT src', 'Post-NAT dst')
- return normal_format % ('Pre-NAT', 'Post-NAT', 'Prot', 'Timeout', 'Type' if pipe else '')
-
-
-def command(srcdest, proto, ipaddr):
- command = f'{conntrack} -o xml -L -f ipv6'
-
- if proto:
- command += f' -p {proto}'
-
- if srcdest == 'source':
- command += ' -n'
- if ipaddr:
- command += f' --orig-src {ipaddr}'
- if srcdest == 'destination':
- command += ' -g'
- if ipaddr:
- command += f' --orig-dst {ipaddr}'
-
- return command
-
-
-def run(command):
- xml, code = popen(command,stderr=DEVNULL)
- if code:
- sys.exit('conntrack failed')
- return xml
-
-
-def content(xmlfile):
- xml = ''
- with open(xmlfile,'r') as r:
- xml += r.read()
- return xml
-
-
-def pipe():
- xml = ''
- while True:
- line = sys.stdin.readline()
- xml += line
- if '</conntrack>' in line:
- break
-
- sys.stdin = open('/dev/tty')
- return xml
-
-
-def process(data, stats, protocol, pipe, verbose, flowtype=''):
- if not data:
- return
-
- parsed = xmltodict.parse(data)
-
- print(headers(verbose, pipe))
-
- # to help the linter to detect typos
- ORIGINAL = 'original'
- REPLY = 'reply'
- INDEPENDANT = 'independent'
- SPORT = 'sport'
- DPORT = 'dport'
- SRC = 'src'
- DST = 'dst'
-
- for rule in parsed['conntrack']['flow']:
- src, dst, sport, dport, proto = {}, {}, {}, {}, {}
- packet_count, byte_count = {}, {}
- timeout, use = 0, 0
-
- rule_type = rule.get('type', '')
-
- for meta in rule['meta']:
- # print(meta)
- direction = meta['@direction']
-
- if direction in (ORIGINAL, REPLY):
- if 'layer3' in meta:
- l3 = meta['layer3']
- src[direction] = l3[SRC]
- dst[direction] = l3[DST]
-
- if 'layer4' in meta:
- l4 = meta['layer4']
- sp = l4.get(SPORT, '')
- dp = l4.get(DPORT, '')
- if sp:
- sport[direction] = sp
- if dp:
- dport[direction] = dp
- proto[direction] = l4.get('@protoname','')
-
- if stats and 'counters' in meta:
- packet_count[direction] = meta['packets']
- byte_count[direction] = meta['bytes']
- continue
-
- if direction == INDEPENDANT:
- timeout = meta['timeout']
- use = meta['use']
- continue
-
- in_src = '%s:%s' % (src[ORIGINAL], sport[ORIGINAL]) if ORIGINAL in sport else src[ORIGINAL]
- in_dst = '%s:%s' % (dst[ORIGINAL], dport[ORIGINAL]) if ORIGINAL in dport else dst[ORIGINAL]
-
- # inverted the the perl code !!?
- out_dst = '%s:%s' % (dst[REPLY], dport[REPLY]) if REPLY in dport else dst[REPLY]
- out_src = '%s:%s' % (src[REPLY], sport[REPLY]) if REPLY in sport else src[REPLY]
-
- if flowtype == 'source':
- v = ORIGINAL in sport and REPLY in dport
- f = '%s:%s' % (src[ORIGINAL], sport[ORIGINAL]) if v else src[ORIGINAL]
- t = '%s:%s' % (dst[REPLY], dport[REPLY]) if v else dst[REPLY]
- else:
- v = ORIGINAL in dport and REPLY in sport
- f = '%s:%s' % (dst[ORIGINAL], dport[ORIGINAL]) if v else dst[ORIGINAL]
- t = '%s:%s' % (src[REPLY], sport[REPLY]) if v else src[REPLY]
-
- # Thomas: I do not believe proto should be an option
- p = proto.get('original', '')
- if protocol and p != protocol:
- continue
-
- if verbose:
- msg = verbose_format % (in_src, in_dst, out_dst, out_src)
- p = f'{p}: ' if p else ''
- msg += f'\n {p}{f} ==> {t}'
- msg += f' timeout: {timeout}' if timeout else ''
- msg += f' use: {use} ' if use else ''
- msg += f' type: {rule_type}' if rule_type else ''
- print(msg)
- else:
- print(normal_format % (f, t, p, timeout, rule_type if rule_type else ''))
-
- if stats:
- for direction in ('original', 'reply'):
- if direction in packet_count:
- print(' %-8s: packets %s, bytes %s' % direction, packet_count[direction], byte_count[direction])
-
-
-def main():
- parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__)
- parser.add_argument('--verbose', help='provide more details about the flows', action='store_true')
- parser.add_argument('--proto', help='filter by protocol', default='', type=str)
- parser.add_argument('--file', help='read the conntrack xml from a file', type=str)
- parser.add_argument('--stats', help='add usage statistics', action='store_true')
- parser.add_argument('--type', help='NAT type (source, destination)', required=True, type=str)
- parser.add_argument('--ipaddr', help='source ip address to filter on', type=ipaddress.ip_address)
- parser.add_argument('--pipe', help='read conntrack xml data from stdin', action='store_true')
-
- arg = parser.parse_args()
-
- if arg.type not in ('source', 'destination'):
- sys.exit('Unknown NAT type!')
-
- if arg.pipe:
- process(pipe(), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type)
- elif arg.file:
- process(content(arg.file), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type)
- else:
- try:
- process(run(command(arg.type, arg.proto, arg.ipaddr)), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type)
- except:
- pass
-
-if __name__ == '__main__':
- main()
diff --git a/src/op_mode/show_nat_statistics.py b/src/op_mode/show_nat_statistics.py
deleted file mode 100755
index be41e083b..000000000
--- a/src/op_mode/show_nat_statistics.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import jmespath
-import json
-
-from argparse import ArgumentParser
-from jinja2 import Template
-from sys import exit
-from vyos.util import cmd
-
-OUT_TMPL_SRC="""
-rule pkts bytes interface
----- ---- ----- ---------
-{% for r in output %}
-{% if r.comment %}
-{% set packets = r.counter.packets %}
-{% set bytes = r.counter.bytes %}
-{% set interface = r.interface %}
-{# remove rule comment prefix #}
-{% set comment = r.comment | replace('SRC-NAT-', '') | replace('DST-NAT-', '') | replace(' tcp_udp', '') %}
-{{ "%-4s" | format(comment) }} {{ "%9s" | format(packets) }} {{ "%12s" | format(bytes) }} {{ interface }}
-{% endif %}
-{% endfor %}
-"""
-
-parser = ArgumentParser()
-group = parser.add_mutually_exclusive_group()
-group.add_argument("--source", help="Show statistics for configured source NAT rules", action="store_true")
-group.add_argument("--destination", help="Show statistics for configured destination NAT rules", action="store_true")
-args = parser.parse_args()
-
-if args.source or args.destination:
- tmp = cmd('sudo nft -j list table ip vyos_nat')
- tmp = json.loads(tmp)
-
- source = r"nftables[?rule.chain=='POSTROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }"
- destination = r"nftables[?rule.chain=='PREROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }"
- data = {
- 'output' : jmespath.search(source if args.source else destination, tmp),
- 'direction' : 'source' if args.source else 'destination'
- }
-
- tmpl = Template(OUT_TMPL_SRC, lstrip_blocks=True)
- print(tmpl.render(data))
- exit(0)
-else:
- parser.print_help()
- exit(1)
-
diff --git a/src/op_mode/show_nat_translations.py b/src/op_mode/show_nat_translations.py
deleted file mode 100755
index 508845e23..000000000
--- a/src/op_mode/show_nat_translations.py
+++ /dev/null
@@ -1,216 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2020-2022 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-'''
-show nat translations
-'''
-
-import os
-import sys
-import ipaddress
-import argparse
-import xmltodict
-
-from vyos.util import popen
-from vyos.util import DEVNULL
-
-conntrack = '/usr/sbin/conntrack'
-
-verbose_format = "%-20s %-18s %-20s %-18s"
-normal_format = "%-20s %-20s %-4s %-8s %s"
-
-
-def headers(verbose, pipe):
- if verbose:
- return verbose_format % ('Pre-NAT src', 'Pre-NAT dst', 'Post-NAT src', 'Post-NAT dst')
- return normal_format % ('Pre-NAT', 'Post-NAT', 'Prot', 'Timeout', 'Type' if pipe else '')
-
-
-def command(srcdest, proto, ipaddr):
- command = f'{conntrack} -o xml -L'
-
- if proto:
- command += f' -p {proto}'
-
- if srcdest == 'source':
- command += ' -n'
- if ipaddr:
- command += f' --orig-src {ipaddr}'
- if srcdest == 'destination':
- command += ' -g'
- if ipaddr:
- command += f' --orig-dst {ipaddr}'
-
- return command
-
-
-def run(command):
- xml, code = popen(command,stderr=DEVNULL)
- if code:
- sys.exit('conntrack failed')
- return xml
-
-
-def content(xmlfile):
- xml = ''
- with open(xmlfile,'r') as r:
- xml += r.read()
- return xml
-
-
-def pipe():
- xml = ''
- while True:
- line = sys.stdin.readline()
- xml += line
- if '</conntrack>' in line:
- break
-
- sys.stdin = open('/dev/tty')
- return xml
-
-
-def xml_to_dict(xml):
- """
- Convert XML to dictionary
- Return: dictionary
- """
- parse = xmltodict.parse(xml)
- # If only one NAT entry we must change dict T4499
- if 'meta' in parse['conntrack']['flow']:
- return dict(conntrack={'flow': [parse['conntrack']['flow']]})
- return parse
-
-
-def process(data, stats, protocol, pipe, verbose, flowtype=''):
- if not data:
- return
-
- parsed = xml_to_dict(data)
-
- print(headers(verbose, pipe))
-
- # to help the linter to detect typos
- ORIGINAL = 'original'
- REPLY = 'reply'
- INDEPENDANT = 'independent'
- SPORT = 'sport'
- DPORT = 'dport'
- SRC = 'src'
- DST = 'dst'
-
- for rule in parsed['conntrack']['flow']:
- src, dst, sport, dport, proto = {}, {}, {}, {}, {}
- packet_count, byte_count = {}, {}
- timeout, use = 0, 0
-
- rule_type = rule.get('type', '')
-
- for meta in rule['meta']:
- # print(meta)
- direction = meta['@direction']
-
- if direction in (ORIGINAL, REPLY):
- if 'layer3' in meta:
- l3 = meta['layer3']
- src[direction] = l3[SRC]
- dst[direction] = l3[DST]
-
- if 'layer4' in meta:
- l4 = meta['layer4']
- sp = l4.get(SPORT, '')
- dp = l4.get(DPORT, '')
- if sp:
- sport[direction] = sp
- if dp:
- dport[direction] = dp
- proto[direction] = l4.get('@protoname','')
-
- if stats and 'counters' in meta:
- packet_count[direction] = meta['packets']
- byte_count[direction] = meta['bytes']
- continue
-
- if direction == INDEPENDANT:
- timeout = meta['timeout']
- use = meta['use']
- continue
-
- in_src = '%s:%s' % (src[ORIGINAL], sport[ORIGINAL]) if ORIGINAL in sport else src[ORIGINAL]
- in_dst = '%s:%s' % (dst[ORIGINAL], dport[ORIGINAL]) if ORIGINAL in dport else dst[ORIGINAL]
-
- # inverted the the perl code !!?
- out_dst = '%s:%s' % (dst[REPLY], dport[REPLY]) if REPLY in dport else dst[REPLY]
- out_src = '%s:%s' % (src[REPLY], sport[REPLY]) if REPLY in sport else src[REPLY]
-
- if flowtype == 'source':
- v = ORIGINAL in sport and REPLY in dport
- f = '%s:%s' % (src[ORIGINAL], sport[ORIGINAL]) if v else src[ORIGINAL]
- t = '%s:%s' % (dst[REPLY], dport[REPLY]) if v else dst[REPLY]
- else:
- v = ORIGINAL in dport and REPLY in sport
- f = '%s:%s' % (dst[ORIGINAL], dport[ORIGINAL]) if v else dst[ORIGINAL]
- t = '%s:%s' % (src[REPLY], sport[REPLY]) if v else src[REPLY]
-
- # Thomas: I do not believe proto should be an option
- p = proto.get('original', '')
- if protocol and p != protocol:
- continue
-
- if verbose:
- msg = verbose_format % (in_src, in_dst, out_dst, out_src)
- p = f'{p}: ' if p else ''
- msg += f'\n {p}{f} ==> {t}'
- msg += f' timeout: {timeout}' if timeout else ''
- msg += f' use: {use} ' if use else ''
- msg += f' type: {rule_type}' if rule_type else ''
- print(msg)
- else:
- print(normal_format % (f, t, p, timeout, rule_type if rule_type else ''))
-
- if stats:
- for direction in ('original', 'reply'):
- if direction in packet_count:
- print(' %-8s: packets %s, bytes %s' % direction, packet_count[direction], byte_count[direction])
-
-
-def main():
- parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__)
- parser.add_argument('--verbose', help='provide more details about the flows', action='store_true')
- parser.add_argument('--proto', help='filter by protocol', default='', type=str)
- parser.add_argument('--file', help='read the conntrack xml from a file', type=str)
- parser.add_argument('--stats', help='add usage statistics', action='store_true')
- parser.add_argument('--type', help='NAT type (source, destination)', required=True, type=str)
- parser.add_argument('--ipaddr', help='source ip address to filter on', type=ipaddress.ip_address)
- parser.add_argument('--pipe', help='read conntrack xml data from stdin', action='store_true')
-
- arg = parser.parse_args()
-
- if arg.type not in ('source', 'destination'):
- sys.exit('Unknown NAT type!')
-
- if arg.pipe:
- process(pipe(), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type)
- elif arg.file:
- process(content(arg.file), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type)
- else:
- try:
- process(run(command(arg.type, arg.proto, arg.ipaddr)), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type)
- except:
- pass
-
-if __name__ == '__main__':
- main()
diff --git a/src/op_mode/show_ntp.sh b/src/op_mode/show_ntp.sh
index e9dd6c5c9..4b59b801e 100755
--- a/src/op_mode/show_ntp.sh
+++ b/src/op_mode/show_ntp.sh
@@ -1,39 +1,34 @@
#!/bin/sh
-basic=0
-info=0
+sourcestats=0
+tracking=0
while [[ "$#" -gt 0 ]]; do
case $1 in
- --info) info=1 ;;
- --basic) basic=1 ;;
- --server) server=$2; shift ;;
+ --sourcestats) sourcestats=1 ;;
+ --tracking) tracking=1 ;;
*) echo "Unknown parameter passed: $1" ;;
esac
shift
done
-if ! ps -C ntpd &>/dev/null; then
+if ! ps -C chronyd &>/dev/null; then
echo NTP daemon disabled
exit 1
fi
-PID=$(pgrep ntpd)
+PID=$(pgrep chronyd | head -n1)
VRF_NAME=$(ip vrf identify ${PID})
if [ ! -z ${VRF_NAME} ]; then
VRF_CMD="sudo ip vrf exec ${VRF_NAME}"
fi
-if [ $basic -eq 1 ]; then
- $VRF_CMD ntpq -n -c peers
-elif [ $info -eq 1 ]; then
- echo "=== sysingo ==="
- $VRF_CMD ntpq -n -c sysinfo
- echo
- echo "=== kerninfo ==="
- $VRF_CMD ntpq -n -c kerninfo
-elif [ ! -z $server ]; then
- $VRF_CMD /usr/sbin/ntpdate -q $server
+if [ $sourcestats -eq 1 ]; then
+ $VRF_CMD chronyc sourcestats -v
+elif [ $tracking -eq 1 ]; then
+ $VRF_CMD chronyc tracking -v
+else
+ echo "Unknown option"
fi
diff --git a/src/op_mode/show_openconnect_otp.py b/src/op_mode/show_openconnect_otp.py
index ae532ccc9..3771fb385 100755
--- a/src/op_mode/show_openconnect_otp.py
+++ b/src/op_mode/show_openconnect_otp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
-# Copyright 2017, 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2017-2023 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -17,12 +17,11 @@
import argparse
import os
+from base64 import b32encode
from vyos.config import Config
-from vyos.xml import defaults
-from vyos.configdict import dict_merge
-from vyos.util import popen
-from base64 import b32encode
+from vyos.utils.dict import dict_search_args
+from vyos.utils.process import popen
otp_file = '/run/ocserv/users.oath'
@@ -33,7 +32,7 @@ def check_uname_otp(username):
config = Config()
base_key = ['vpn', 'openconnect', 'authentication', 'local-users', 'username', username, 'otp', 'key']
if not config.exists(base_key):
- return None
+ return False
return True
def get_otp_ocserv(username):
@@ -41,21 +40,21 @@ def get_otp_ocserv(username):
base = ['vpn', 'openconnect']
if not config.exists(base):
return None
- ocserv = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- default_values = defaults(base)
- ocserv = dict_merge(default_values, ocserv)
- # workaround a "know limitation" - https://phabricator.vyos.net/T2665
- del ocserv['authentication']['local_users']['username']['otp']
- if not ocserv["authentication"]["local_users"]["username"]:
+
+ ocserv = config.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
+
+ user_path = ['authentication', 'local_users', 'username']
+ users = dict_search_args(ocserv, *user_path)
+
+ if users is None:
return None
- default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp']
- for user, params in ocserv['authentication']['local_users']['username'].items():
- # Not every configuration requires OTP settings
- if ocserv['authentication']['local_users']['username'][user].get('otp'):
- ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp'])
- result = ocserv['authentication']['local_users']['username'][username]
+
+ # function is called conditionally, if check_uname_otp true, so username
+ # exists
+ result = users[username]
+
return result
def display_otp_ocserv(username, params, info):
@@ -101,8 +100,7 @@ if __name__ == '__main__':
parser.add_argument('--info', action="store", type=str, default='full', help='Wich information to display')
args = parser.parse_args()
- check_otp = check_uname_otp(args.user)
- if check_otp:
+ if check_uname_otp(args.user):
user_otp_params = get_otp_ocserv(args.user)
display_otp_ocserv(args.user, user_otp_params, args.info)
else:
diff --git a/src/op_mode/show_openvpn_mfa.py b/src/op_mode/show_openvpn_mfa.py
index 1ab54600c..100c42154 100755
--- a/src/op_mode/show_openvpn_mfa.py
+++ b/src/op_mode/show_openvpn_mfa.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
-
-# Copyright 2017, 2021 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# Copyright 2017-2023 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -20,7 +20,7 @@ import socket
import urllib.parse
import argparse
-from vyos.util import popen
+from vyos.utils.process import popen
otp_file = '/config/auth/openvpn/{interface}-otp-secrets'
diff --git a/src/op_mode/show_raid.sh b/src/op_mode/show_raid.sh
index ba4174692..ab5d4d50f 100755
--- a/src/op_mode/show_raid.sh
+++ b/src/op_mode/show_raid.sh
@@ -1,5 +1,13 @@
#!/bin/bash
+if [ "$EUID" -ne 0 ]; then
+ # This should work without sudo because we have read
+ # access to the dev, but for some reason mdadm must be
+ # run as root in order to succeed.
+ echo "Please run as root"
+ exit 1
+fi
+
raid_set_name=$1
raid_sets=`cat /proc/partitions | grep md | awk '{ print $4 }'`
valid_set=`echo $raid_sets | grep $raid_set_name`
@@ -10,7 +18,7 @@ else
# This should work without sudo because we have read
# access to the dev, but for some reason mdadm must be
# run as root in order to succeed.
- sudo /sbin/mdadm --detail /dev/${raid_set_name}
+ mdadm --detail /dev/${raid_set_name}
else
echo "Must be administrator or root to display RAID status"
fi
diff --git a/src/op_mode/show_sensors.py b/src/op_mode/show_sensors.py
index 6ae477647..5e3084fe9 100755
--- a/src/op_mode/show_sensors.py
+++ b/src/op_mode/show_sensors.py
@@ -1,9 +1,25 @@
#!/usr/bin/env python3
+#
+# Copyright 2017-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
import re
import sys
-from vyos.util import popen
-from vyos.util import DEVNULL
+from vyos.utils.process import popen
+from vyos.utils.process import DEVNULL
+
output,retcode = popen("sensors --no-adapter", stderr=DEVNULL)
if retcode == 0:
print (output)
@@ -23,5 +39,3 @@ else:
print ("No sensors found")
sys.exit(1)
-
-
diff --git a/src/op_mode/show_techsupport_report.py b/src/op_mode/show_techsupport_report.py
new file mode 100644
index 000000000..53144fd52
--- /dev/null
+++ b/src/op_mode/show_techsupport_report.py
@@ -0,0 +1,304 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from typing import List
+from vyos.utils.process import rc_cmd
+from vyos.ifconfig import Section
+from vyos.ifconfig import Interface
+
+
+def print_header(command: str) -> None:
+ """Prints a command with headers '-'.
+
+ Example:
+
+ % print_header('Example command')
+
+ ---------------
+ Example command
+ ---------------
+ """
+ header_length = len(command) * '-'
+ print(f"\n{header_length}\n{command}\n{header_length}")
+
+
+def execute_command(command: str, header_text: str) -> None:
+ """Executes a command and prints the output with a header.
+
+ Example:
+ % execute_command('uptime', "Uptime of the system")
+
+ --------------------
+ Uptime of the system
+ --------------------
+ 20:21:57 up 9:04, 5 users, load average: 0.00, 0.00, 0.0
+
+ """
+ print_header(header_text)
+ try:
+ rc, output = rc_cmd(command)
+ print(output)
+ except Exception as e:
+ print(f"Error executing command: {command}")
+ print(f"Error message: {e}")
+
+
+def op(cmd: str) -> str:
+ """Returns a command with the VyOS operational mode wrapper."""
+ return f'/opt/vyatta/bin/vyatta-op-cmd-wrapper {cmd}'
+
+
+def get_ethernet_interfaces() -> List[Interface]:
+ """Returns a list of Ethernet interfaces."""
+ return Section.interfaces('ethernet')
+
+
+def show_version() -> None:
+ """Prints the VyOS version and package changes."""
+ execute_command(op('show version'), 'VyOS Version and Package Changes')
+
+
+def show_config_file() -> None:
+ """Prints the contents of a configuration file with a header."""
+ execute_command('cat /opt/vyatta/etc/config/config.boot', 'Configuration file')
+
+
+def show_running_config() -> None:
+ """Prints the running configuration."""
+ execute_command(op('show configuration'), 'Running configuration')
+
+
+def show_package_repository_config() -> None:
+ """Prints the package repository configuration file."""
+ execute_command('cat /etc/apt/sources.list', 'Package Repository Configuration File')
+ execute_command('ls -l /etc/apt/sources.list.d/', 'Repositories')
+
+
+def show_user_startup_scripts() -> None:
+ """Prints the user startup scripts."""
+ execute_command('cat /config/scripts/vyos-preconfig-bootup.script', 'User Startup Scripts (Preconfig)')
+ execute_command('cat /config/scripts/vyos-postconfig-bootup.script', 'User Startup Scripts (Postconfig)')
+
+
+def show_frr_config() -> None:
+ """Prints the FRR configuration."""
+ execute_command('vtysh -c "show run"', 'FRR configuration')
+
+
+def show_interfaces() -> None:
+ """Prints the interfaces."""
+ execute_command(op('show interfaces'), 'Interfaces')
+
+
+def show_interface_statistics() -> None:
+ """Prints the interface statistics."""
+ execute_command('ip -s link show', 'Interface statistics')
+
+
+def show_physical_interface_statistics() -> None:
+ """Prints the physical interface statistics."""
+ execute_command('/usr/bin/true', 'Physical Interface statistics')
+ for iface in get_ethernet_interfaces():
+ # Exclude vlans
+ if '.' in iface:
+ continue
+ execute_command(f'ethtool --driver {iface}', f'ethtool --driver {iface}')
+ execute_command(f'ethtool --statistics {iface}', f'ethtool --statistics {iface}')
+ execute_command(f'ethtool --show-ring {iface}', f'ethtool --show-ring {iface}')
+ execute_command(f'ethtool --show-coalesce {iface}', f'ethtool --show-coalesce {iface}')
+ execute_command(f'ethtool --pause {iface}', f'ethtool --pause {iface}')
+ execute_command(f'ethtool --show-features {iface}', f'ethtool --show-features {iface}')
+ execute_command(f'ethtool --phy-statistics {iface}', f'ethtool --phy-statistics {iface}')
+ execute_command('netstat --interfaces', 'netstat --interfaces')
+ execute_command('netstat --listening', 'netstat --listening')
+ execute_command('cat /proc/net/dev', 'cat /proc/net/dev')
+
+
+def show_bridge() -> None:
+ """Show bridge interfaces."""
+ execute_command(op('show bridge'), 'Show bridge')
+
+
+def show_arp() -> None:
+ """Prints ARP entries."""
+ execute_command(op('show arp'), 'ARP Table (Total entries)')
+ execute_command(op('show ipv6 neighbors'), 'show ipv6 neighbors')
+
+
+def show_route() -> None:
+ """Prints routing information."""
+
+ cmd_list_route = [
+ "show ip route bgp | head -108",
+ "show ip route cache",
+ "show ip route connected",
+ "show ip route forward",
+ "show ip route isis | head -108",
+ "show ip route kernel",
+ "show ip route ospf | head -108",
+ "show ip route rip",
+ "show ip route static",
+ "show ip route summary",
+ "show ip route supernets-only",
+ "show ip route table all",
+ "show ip route vrf all",
+ "show ipv6 route bgp | head 108",
+ "show ipv6 route cache",
+ "show ipv6 route connected",
+ "show ipv6 route forward",
+ "show ipv6 route isis",
+ "show ipv6 route kernel",
+ "show ipv6 route ospf",
+ "show ipv6 route rip",
+ "show ipv6 route static",
+ "show ipv6 route summary",
+ "show ipv6 route table all",
+ "show ipv6 route vrf all",
+ ]
+ for command in cmd_list_route:
+ execute_command(op(command), command)
+
+
+def show_firewall() -> None:
+ """Prints firweall information."""
+ execute_command('sudo nft list ruleset', 'nft list ruleset')
+
+
+def show_system() -> None:
+ """Prints system parameters."""
+ execute_command(op('show system image version'), 'Show System Image Version')
+ execute_command(op('show system image storage'), 'Show System Image Storage')
+
+
+def show_date() -> None:
+ """Print the current date."""
+ execute_command('date', 'Current Time')
+
+
+def show_installed_packages() -> None:
+ """Prints installed packages."""
+ execute_command('dpkg --list', 'Installed Packages')
+
+
+def show_loaded_modules() -> None:
+ """Prints loaded modules /proc/modules"""
+ execute_command('cat /proc/modules', 'Loaded Modules')
+
+
+def show_cpu_statistics() -> None:
+ """Prints CPU statistics."""
+ execute_command('/usr/bin/true', 'CPU')
+ execute_command('lscpu', 'Installed CPU\'s')
+ execute_command('top --iterations 1 --batch-mode --accum-time-toggle', 'Cumulative CPU Time Used by Running Processes')
+ execute_command('cat /proc/loadavg', 'Load Average')
+
+
+def show_system_interrupts() -> None:
+ """Prints system interrupts."""
+ execute_command('cat /proc/interrupts', 'Hardware Interrupt Counters')
+
+
+def show_soft_irqs() -> None:
+ """Prints soft IRQ's."""
+ execute_command('cat /proc/softirqs', 'Soft IRQ\'s')
+
+
+def show_softnet_statistics() -> None:
+ """Prints softnet statistics."""
+ execute_command('cat /proc/net/softnet_stat', 'cat /proc/net/softnet_stat')
+
+
+def show_running_processes() -> None:
+ """Prints current running processes"""
+ execute_command('ps -ef', 'Running Processes')
+
+
+def show_memory_usage() -> None:
+ """Prints memory usage"""
+ execute_command('/usr/bin/true', 'Memory')
+ execute_command('cat /proc/meminfo', 'Installed Memory')
+ execute_command('free', 'Memory Usage')
+
+
+def list_disks():
+ disks = set()
+ with open('/proc/partitions') as partitions_file:
+ for line in partitions_file:
+ fields = line.strip().split()
+ if len(fields) == 4 and fields[3].isalpha() and fields[3] != 'name':
+ disks.add(fields[3])
+ return disks
+
+
+def show_storage() -> None:
+ """Prints storage information."""
+ execute_command('cat /proc/devices', 'Devices')
+ execute_command('cat /proc/partitions', 'Partitions')
+
+ for disk in list_disks():
+ execute_command(f'fdisk --list /dev/{disk}', f'Partitioning for disk {disk}')
+
+
+def main():
+ # Configuration data
+ show_version()
+ show_config_file()
+ show_running_config()
+ show_package_repository_config()
+ show_user_startup_scripts()
+ show_frr_config()
+
+ # Interfaces
+ show_interfaces()
+ show_interface_statistics()
+ show_physical_interface_statistics()
+ show_bridge()
+ show_arp()
+
+ # Routing
+ show_route()
+
+ # Firewall
+ show_firewall()
+
+ # System
+ show_system()
+ show_date()
+ show_installed_packages()
+ show_loaded_modules()
+
+ # CPU
+ show_cpu_statistics()
+ show_system_interrupts()
+ show_soft_irqs()
+ show_softnet_statistics()
+
+ # Memory
+ show_memory_usage()
+
+ # Storage
+ show_storage()
+
+ # Processes
+ show_running_processes()
+
+ # TODO: Get information from clouds
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/op_mode/show_virtual_server.py b/src/op_mode/show_virtual_server.py
index 377180dec..7880edc97 100755
--- a/src/op_mode/show_virtual_server.py
+++ b/src/op_mode/show_virtual_server.py
@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from vyos.configquery import ConfigTreeQuery
-from vyos.util import call
+from vyos.utils.process import call
def is_configured():
""" Check if high-availability virtual-server is configured """
diff --git a/src/op_mode/show_vpn_ra.py b/src/op_mode/show_vpn_ra.py
deleted file mode 100755
index 73688c4ea..000000000
--- a/src/op_mode/show_vpn_ra.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2019 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import sys
-import re
-
-from vyos.util import popen
-
-# chech connection to pptp and l2tp daemon
-def get_sessions():
- absent_pptp = False
- absent_l2tp = False
- pptp_cmd = "accel-cmd -p 2003 show sessions"
- l2tp_cmd = "accel-cmd -p 2004 show sessions"
- err_pattern = "^Connection.+failed$"
- # This value for chack only output header without sessions.
- len_def_header = 170
-
- # Check pptp
- output, err = popen(pptp_cmd, decode='utf-8')
- if not err and len(output) > len_def_header and not re.search(err_pattern, output):
- print(output)
- else:
- absent_pptp = True
-
- # Check l2tp
- output, err = popen(l2tp_cmd, decode='utf-8')
- if not err and len(output) > len_def_header and not re.search(err_pattern, output):
- print(output)
- else:
- absent_l2tp = True
-
- if absent_l2tp and absent_pptp:
- print("No active remote access VPN sessions")
-
-
-def main():
- get_sessions()
-
-
-if __name__ == '__main__':
- main()
diff --git a/src/op_mode/show_wireless.py b/src/op_mode/show_wireless.py
index 19ab6771c..340163057 100755
--- a/src/op_mode/show_wireless.py
+++ b/src/op_mode/show_wireless.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 VyOS maintainers and contributors
+# Copyright (C) 2019-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -21,7 +21,7 @@ from sys import exit
from copy import deepcopy
from vyos.config import Config
-from vyos.util import popen
+from vyos.utils.process import popen
parser = argparse.ArgumentParser()
parser.add_argument("-s", "--scan", help="Scan for Wireless APs on given interface, e.g. 'wlan0'")
diff --git a/src/op_mode/show_wwan.py b/src/op_mode/show_wwan.py
index 529b5bd0f..bd97bb0e5 100755
--- a/src/op_mode/show_wwan.py
+++ b/src/op_mode/show_wwan.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -17,7 +17,8 @@
import argparse
from sys import exit
-from vyos.util import cmd
+from vyos.configquery import ConfigTreeQuery
+from vyos.utils.process import cmd
parser = argparse.ArgumentParser()
parser.add_argument("--model", help="Get module model", action="store_true")
@@ -49,6 +50,11 @@ def qmi_cmd(device, command, silent=False):
if __name__ == '__main__':
args = parser.parse_args()
+ tmp = ConfigTreeQuery()
+ if not tmp.exists(['interfaces', 'wwan', args.interface]):
+ print(f'Interface "{args.interface}" unconfigured!')
+ exit(1)
+
# remove the WWAN prefix from the interface, required for the CDC interface
if_num = args.interface.replace('wwan','')
cdc = f'/dev/cdc-wdm{if_num}'
diff --git a/src/op_mode/show_xdp_stats.sh b/src/op_mode/show_xdp_stats.sh
deleted file mode 100755
index a4ef33107..000000000
--- a/src/op_mode/show_xdp_stats.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-
-if cli-shell-api existsEffective interfaces $1 $2 xdp; then
- /usr/sbin/xdp_stats --dev "$2"
-else
- echo "XDP not enabled on $2"
-fi
diff --git a/src/op_mode/snmp.py b/src/op_mode/snmp.py
index 5fae67881..43f5d9e0a 100755
--- a/src/op_mode/snmp.py
+++ b/src/op_mode/snmp.py
@@ -24,7 +24,7 @@ import sys
import argparse
from vyos.config import Config
-from vyos.util import call
+from vyos.utils.process import call
config_file_daemon = r'/etc/snmp/snmpd.conf'
diff --git a/src/op_mode/snmp_ifmib.py b/src/op_mode/snmp_ifmib.py
index 2479936bd..c71febac9 100755
--- a/src/op_mode/snmp_ifmib.py
+++ b/src/op_mode/snmp_ifmib.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -24,7 +24,7 @@ import argparse
import netifaces
from vyos.config import Config
-from vyos.util import popen
+from vyos.utils.process import popen
parser = argparse.ArgumentParser(description='Retrieve SNMP interfaces information')
parser.add_argument('--ifindex', action='store', nargs='?', const='all', help='Show interface index')
diff --git a/src/op_mode/storage.py b/src/op_mode/storage.py
index d16e271bd..6bc3d3a2d 100755
--- a/src/op_mode/storage.py
+++ b/src/op_mode/storage.py
@@ -18,7 +18,7 @@
import sys
import vyos.opmode
-from vyos.util import cmd
+from vyos.utils.process import cmd
# FIY: As of coreutils from Debian Buster and Bullseye,
# the outpt looks like this:
@@ -43,7 +43,7 @@ def _get_system_storage(only_persistent=False):
def _get_raw_data():
from re import sub as re_sub
- from vyos.util import human_to_bytes
+ from vyos.utils.convert import human_to_bytes
out = _get_system_storage(only_persistent=True)
lines = out.splitlines()
diff --git a/src/op_mode/traceroute.py b/src/op_mode/traceroute.py
index 6c7030ea0..2f0edf53a 100755
--- a/src/op_mode/traceroute.py
+++ b/src/op_mode/traceroute.py
@@ -18,7 +18,7 @@ import os
import sys
import socket
import ipaddress
-from vyos.util import get_all_vrfs
+from vyos.utils.network import get_all_vrfs
from vyos.ifconfig import Section
diff --git a/src/op_mode/uptime.py b/src/op_mode/uptime.py
index 2ebe6783b..d6adf6f4d 100755
--- a/src/op_mode/uptime.py
+++ b/src/op_mode/uptime.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2022 VyOS maintainers and contributors
+# Copyright (C) 2021-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
@@ -20,7 +20,7 @@ import vyos.opmode
def _get_uptime_seconds():
from re import search
- from vyos.util import read_file
+ from vyos.utils.file import read_file
data = read_file("/proc/uptime")
seconds = search("([0-9\.]+)\s", data).group(1)
@@ -29,7 +29,7 @@ def _get_uptime_seconds():
def _get_load_averages():
from re import search
- from vyos.util import cmd
+ from vyos.utils.process import cmd
from vyos.cpu import get_core_count
data = cmd("uptime")
@@ -45,7 +45,7 @@ def _get_load_averages():
return res
def _get_raw_data():
- from vyos.util import seconds_to_human
+ from vyos.utils.convert import seconds_to_human
res = {}
res["uptime_seconds"] = _get_uptime_seconds()
diff --git a/src/op_mode/vpn_ike_sa.py b/src/op_mode/vpn_ike_sa.py
index 4b44c5c15..069c12069 100755
--- a/src/op_mode/vpn_ike_sa.py
+++ b/src/op_mode/vpn_ike_sa.py
@@ -19,7 +19,7 @@ import re
import sys
import vici
-from vyos.util import process_named_running
+from vyos.utils.process import process_named_running
ike_sa_peer_prefix = """\
Peer ID / IP Local ID / IP
@@ -39,8 +39,6 @@ def ike_sa(peer, nat):
peers = []
for conn in sas:
for name, sa in conn.items():
- if peer and not name.startswith('peer_' + peer):
- continue
if name.startswith('peer_') and name in peers:
continue
if nat and 'nat-local' not in sa:
@@ -70,7 +68,7 @@ if __name__ == '__main__':
args = parser.parse_args()
- if not process_named_running('charon'):
+ if not process_named_running('charon-systemd'):
print("IPsec Process NOT Running")
sys.exit(0)
diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py
index 68dc5bc45..ef89e605f 100755
--- a/src/op_mode/vpn_ipsec.py
+++ b/src/op_mode/vpn_ipsec.py
@@ -16,12 +16,12 @@
import re
import argparse
-from subprocess import TimeoutExpired
-from vyos.util import call
+from vyos.utils.process import call
SWANCTL_CONF = '/etc/swanctl/swanctl.conf'
+
def get_peer_connections(peer, tunnel, return_all = False):
search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*'
matches = []
@@ -34,73 +34,22 @@ def get_peer_connections(peer, tunnel, return_all = False):
matches.append(result[1])
return matches
-def reset_peer(peer, tunnel):
- if not peer:
- print('Invalid peer, aborting')
- return
-
- conns = get_peer_connections(peer, tunnel, return_all = (not tunnel or tunnel == 'all'))
-
- if not conns:
- print('Tunnel(s) not found, aborting')
- return
-
- result = True
- for conn in conns:
- try:
- call(f'sudo /usr/sbin/ipsec down {conn}{{*}}', timeout = 10)
- call(f'sudo /usr/sbin/ipsec up {conn}', timeout = 10)
- except TimeoutExpired as e:
- print(f'Timed out while resetting {conn}')
- result = False
-
-
- print('Peer reset result: ' + ('success' if result else 'failed'))
-
-def get_profile_connection(profile, tunnel = None):
- search = rf'(dmvpn-{profile}-[\w]+)' if tunnel == 'all' else rf'(dmvpn-{profile}-{tunnel})'
- with open(SWANCTL_CONF, 'r') as f:
- for line in f.readlines():
- result = re.search(search, line)
- if result:
- return result[1]
- return None
-
-def reset_profile(profile, tunnel):
- if not profile:
- print('Invalid profile, aborting')
- return
-
- if not tunnel:
- print('Invalid tunnel, aborting')
- return
-
- conn = get_profile_connection(profile)
-
- if not conn:
- print('Profile not found, aborting')
- return
-
- call(f'sudo /usr/sbin/ipsec down {conn}')
- result = call(f'sudo /usr/sbin/ipsec up {conn}')
-
- print('Profile reset result: ' + ('success' if result == 0 else 'failed'))
def debug_peer(peer, tunnel):
peer = peer.replace(':', '-')
if not peer or peer == "all":
debug_commands = [
- "sudo ipsec statusall",
- "sudo swanctl -L",
- "sudo swanctl -l",
- "sudo swanctl -P",
- "sudo ip x sa show",
- "sudo ip x policy show",
- "sudo ip tunnel show",
- "sudo ip address",
- "sudo ip rule show",
- "sudo ip route | head -100",
- "sudo ip route show table 220"
+ "ipsec statusall",
+ "swanctl -L",
+ "swanctl -l",
+ "swanctl -P",
+ "ip x sa show",
+ "ip x policy show",
+ "ip tunnel show",
+ "ip address",
+ "ip rule show",
+ "ip route | head -100",
+ "ip route show table 220"
]
for debug_cmd in debug_commands:
print(f'\n### {debug_cmd} ###')
@@ -117,7 +66,8 @@ def debug_peer(peer, tunnel):
return
for conn in conns:
- call(f'sudo /usr/sbin/ipsec statusall | grep {conn}')
+ call(f'/usr/sbin/ipsec statusall | grep {conn}')
+
if __name__ == '__main__':
parser = argparse.ArgumentParser()
@@ -127,9 +77,6 @@ if __name__ == '__main__':
args = parser.parse_args()
- if args.action == 'reset-peer':
- reset_peer(args.name, args.tunnel)
- elif args.action == "reset-profile":
- reset_profile(args.name, args.tunnel)
- elif args.action == "vpn-debug":
+
+ if args.action == "vpn-debug":
debug_peer(args.name, args.tunnel)
diff --git a/src/op_mode/vrf.py b/src/op_mode/vrf.py
index a9a416761..51032a4b5 100755
--- a/src/op_mode/vrf.py
+++ b/src/op_mode/vrf.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -20,11 +20,11 @@ import sys
import typing
from tabulate import tabulate
-from vyos.util import cmd
+from vyos.utils.network import get_vrf_members
+from vyos.utils.process import cmd
import vyos.opmode
-
def _get_raw_data(name=None):
"""
If vrf name is not set - get all VRFs
@@ -45,21 +45,6 @@ def _get_raw_data(name=None):
return data
-def _get_vrf_members(vrf: str) -> list:
- """
- Get list of interface VRF members
- :param vrf: str
- :return: list
- """
- output = cmd(f'ip --json --brief link show master {vrf}')
- answer = json.loads(output)
- interfaces = []
- for data in answer:
- if 'ifname' in data:
- interfaces.append(data.get('ifname'))
- return interfaces if len(interfaces) > 0 else ['n/a']
-
-
def _get_formatted_output(raw_data):
data_entries = []
for vrf in raw_data:
@@ -67,7 +52,9 @@ def _get_formatted_output(raw_data):
state = vrf.get('operstate').lower()
hw_address = vrf.get('address')
flags = ','.join(vrf.get('flags')).lower()
- members = ','.join(_get_vrf_members(name))
+ tmp = get_vrf_members(name)
+ if tmp: members = ','.join(get_vrf_members(name))
+ else: members = 'n/a'
data_entries.append([name, state, hw_address, flags, members])
headers = ["Name", "State", "MAC address", "Flags", "Interfaces"]
diff --git a/src/op_mode/vrrp.py b/src/op_mode/vrrp.py
index dab146d28..b3ab55cc3 100755
--- a/src/op_mode/vrrp.py
+++ b/src/op_mode/vrrp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2022 VyOS maintainers and contributors
+# Copyright (C) 2018-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -13,7 +13,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
import sys
import time
@@ -21,12 +20,10 @@ import argparse
import json
import tabulate
-import vyos.util
-
from vyos.configquery import ConfigTreeQuery
from vyos.ifconfig.vrrp import VRRP
-from vyos.ifconfig.vrrp import VRRPError, VRRPNoData
-
+from vyos.ifconfig.vrrp import VRRPError
+from vyos.ifconfig.vrrp import VRRPNoData
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
@@ -44,7 +41,7 @@ def is_configured():
return True
# Exit early if VRRP is dead or not configured
-if is_configured() == False:
+if is_configured() == False:
print('VRRP not configured!')
exit(0)
if not VRRP.is_running():
diff --git a/src/op_mode/vyos-op-cmd-wrapper.sh b/src/op_mode/vyos-op-cmd-wrapper.sh
new file mode 100755
index 000000000..a89211b2b
--- /dev/null
+++ b/src/op_mode/vyos-op-cmd-wrapper.sh
@@ -0,0 +1,6 @@
+#!/bin/vbash
+shopt -s expand_aliases
+source /etc/default/vyatta
+source /etc/bash_completion.d/vyatta-op
+_vyatta_op_init
+_vyatta_op_run "$@"
diff --git a/src/op_mode/webproxy_update_blacklist.sh b/src/op_mode/webproxy_update_blacklist.sh
index 4fb9a54c6..05ea86f9e 100755
--- a/src/op_mode/webproxy_update_blacklist.sh
+++ b/src/op_mode/webproxy_update_blacklist.sh
@@ -45,6 +45,9 @@ do
--auto-update-blacklist)
auto="yes"
;;
+ --vrf)
+ vrf="yes"
+ ;;
(-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
(*) break;;
esac
@@ -76,7 +79,11 @@ fi
if [[ -n $update ]] && [[ $update -eq "yes" ]]; then
tmp_blacklists='/tmp/blacklists.gz'
- curl -o $tmp_blacklists $blacklist_url
+ if [[ -n $vrf ]] && [[ $vrf -eq "yes" ]]; then
+ sudo ip vrf exec $1 curl -o $tmp_blacklists $blacklist_url
+ else
+ curl -o $tmp_blacklists $blacklist_url
+ fi
if [ $? -ne 0 ]; then
echo "Unable to download [$blacklist_url]!"
exit 1
diff --git a/src/op_mode/wireguard_client.py b/src/op_mode/wireguard_client.py
index 76c1ff7d1..04d8ce28c 100755
--- a/src/op_mode/wireguard_client.py
+++ b/src/op_mode/wireguard_client.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -23,8 +23,8 @@ from ipaddress import ip_interface
from vyos.ifconfig import Section
from vyos.template import is_ipv4
from vyos.template import is_ipv6
-from vyos.util import cmd
-from vyos.util import popen
+from vyos.utils.process import cmd
+from vyos.utils.process import popen
if os.geteuid() != 0:
exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.")
diff --git a/src/op_mode/zone.py b/src/op_mode/zone.py
new file mode 100755
index 000000000..17ce90396
--- /dev/null
+++ b/src/op_mode/zone.py
@@ -0,0 +1,215 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import typing
+import sys
+import vyos.opmode
+
+import tabulate
+from vyos.configquery import ConfigTreeQuery
+from vyos.utils.dict import dict_search_args
+from vyos.utils.dict import dict_search
+
+
+def get_config_zone(conf, name=None):
+ config_path = ['firewall', 'zone']
+ if name:
+ config_path += [name]
+
+ zone_policy = conf.get_config_dict(config_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ return zone_policy
+
+
+def _convert_one_zone_data(zone: str, zone_config: dict) -> dict:
+ """
+ Convert config dictionary of one zone to API dictionary
+ :param zone: Zone name
+ :type zone: str
+ :param zone_config: config dictionary
+ :type zone_config: dict
+ :return: AP dictionary
+ :rtype: dict
+ """
+ list_of_rules = []
+ intrazone_dict = {}
+ if dict_search('from', zone_config):
+ for from_zone, from_zone_config in zone_config['from'].items():
+ from_zone_dict = {'name': from_zone}
+ if dict_search('firewall.name', from_zone_config):
+ from_zone_dict['firewall'] = dict_search('firewall.name',
+ from_zone_config)
+ if dict_search('firewall.ipv6_name', from_zone_config):
+ from_zone_dict['firewall_v6'] = dict_search(
+ 'firewall.ipv6_name', from_zone_config)
+ list_of_rules.append(from_zone_dict)
+
+ zone_dict = {
+ 'name': zone,
+ 'interface': dict_search('interface', zone_config),
+ 'type': 'LOCAL' if dict_search('local_zone',
+ zone_config) is not None else None,
+ }
+ if list_of_rules:
+ zone_dict['from'] = list_of_rules
+ if dict_search('intra_zone_filtering.firewall.name', zone_config):
+ intrazone_dict['firewall'] = dict_search(
+ 'intra_zone_filtering.firewall.name', zone_config)
+ if dict_search('intra_zone_filtering.firewall.ipv6_name', zone_config):
+ intrazone_dict['firewall_v6'] = dict_search(
+ 'intra_zone_filtering.firewall.ipv6_name', zone_config)
+ if intrazone_dict:
+ zone_dict['intrazone'] = intrazone_dict
+ return zone_dict
+
+
+def _convert_zones_data(zone_policies: dict) -> list:
+ """
+ Convert all config dictionary to API list of zone dictionaries
+ :param zone_policies: config dictionary
+ :type zone_policies: dict
+ :return: API list
+ :rtype: list
+ """
+ zone_list = []
+ for zone, zone_config in zone_policies.items():
+ zone_list.append(_convert_one_zone_data(zone, zone_config))
+ return zone_list
+
+
+def _convert_config(zones_config: dict, zone: str = None) -> list:
+ """
+ convert config to API list
+ :param zones_config: zones config
+ :type zones_config:
+ :param zone: zone name
+ :type zone: str
+ :return: API list
+ :rtype: list
+ """
+ if zone:
+ if zones_config:
+ output = [_convert_one_zone_data(zone, zones_config)]
+ else:
+ raise vyos.opmode.DataUnavailable(f'Zone {zone} not found')
+ else:
+ if zones_config:
+ output = _convert_zones_data(zones_config)
+ else:
+ raise vyos.opmode.UnconfiguredSubsystem(
+ 'Zone entries are not configured')
+ return output
+
+
+def output_zone_list(zone_conf: dict) -> list:
+ """
+ Format one zone row
+ :param zone_conf: zone config
+ :type zone_conf: dict
+ :return: formatted list of zones
+ :rtype: list
+ """
+ zone_info = [zone_conf['name']]
+ if zone_conf['type'] == 'LOCAL':
+ zone_info.append('LOCAL')
+ else:
+ zone_info.append("\n".join(zone_conf['interface']))
+
+ from_zone = []
+ firewall = []
+ firewall_v6 = []
+ if 'intrazone' in zone_conf:
+ from_zone.append(zone_conf['name'])
+
+ v4_name = dict_search_args(zone_conf['intrazone'], 'firewall')
+ v6_name = dict_search_args(zone_conf['intrazone'], 'firewall_v6')
+ if v4_name:
+ firewall.append(v4_name)
+ else:
+ firewall.append('')
+ if v6_name:
+ firewall_v6.append(v6_name)
+ else:
+ firewall_v6.append('')
+
+ if 'from' in zone_conf:
+ for from_conf in zone_conf['from']:
+ from_zone.append(from_conf['name'])
+
+ v4_name = dict_search_args(from_conf, 'firewall')
+ v6_name = dict_search_args(from_conf, 'firewall_v6')
+ if v4_name:
+ firewall.append(v4_name)
+ else:
+ firewall.append('')
+ if v6_name:
+ firewall_v6.append(v6_name)
+ else:
+ firewall_v6.append('')
+
+ zone_info.append("\n".join(from_zone))
+ zone_info.append("\n".join(firewall))
+ zone_info.append("\n".join(firewall_v6))
+ return zone_info
+
+
+def get_formatted_output(zone_policy: list) -> str:
+ """
+ Formatted output of all zones
+ :param zone_policy: list of zones
+ :type zone_policy: list
+ :return: formatted table with zones
+ :rtype: str
+ """
+ headers = ["Zone",
+ "Interfaces",
+ "From Zone",
+ "Firewall IPv4",
+ "Firewall IPv6"
+ ]
+ formatted_list = []
+ for zone_conf in zone_policy:
+ formatted_list.append(output_zone_list(zone_conf))
+ tabulate.PRESERVE_WHITESPACE = True
+ output = tabulate.tabulate(formatted_list, headers, numalign="left")
+ return output
+
+
+def show(raw: bool, zone: typing.Optional[str]):
+ """
+ Show zone-policy command
+ :param raw: if API
+ :type raw: bool
+ :param zone: zone name
+ :type zone: str
+ """
+ conf: ConfigTreeQuery = ConfigTreeQuery()
+ zones_config: dict = get_config_zone(conf, zone)
+ zone_policy_api: list = _convert_config(zones_config, zone)
+ if raw:
+ return zone_policy_api
+ else:
+ return get_formatted_output(zone_policy_api)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/zone_policy.py b/src/op_mode/zone_policy.py
deleted file mode 100755
index 7b43018c2..000000000
--- a/src/op_mode/zone_policy.py
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import argparse
-import tabulate
-
-from vyos.config import Config
-from vyos.util import dict_search_args
-
-def get_config_zone(conf, name=None):
- config_path = ['zone-policy']
- if name:
- config_path += ['zone', name]
-
- zone_policy = conf.get_config_dict(config_path, key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
- return zone_policy
-
-def output_zone_name(zone, zone_conf):
- print(f'\n---------------------------------\nZone: "{zone}"\n')
-
- interfaces = ', '.join(zone_conf['interface']) if 'interface' in zone_conf else ''
- if 'local_zone' in zone_conf:
- interfaces = 'LOCAL'
-
- print(f'Interfaces: {interfaces}\n')
-
- header = ['From Zone', 'Firewall']
- rows = []
-
- if 'from' in zone_conf:
- for from_name, from_conf in zone_conf['from'].items():
- row = [from_name]
- v4_name = dict_search_args(from_conf, 'firewall', 'name')
- v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name')
-
- if v4_name:
- rows.append(row + [v4_name])
-
- if v6_name:
- rows.append(row + [f'{v6_name} [IPv6]'])
-
- if rows:
- print('From Zones:\n')
- print(tabulate.tabulate(rows, header))
-
-def show_zone_policy(zone):
- conf = Config()
- zone_policy = get_config_zone(conf, zone)
-
- if not zone_policy:
- return
-
- if 'zone' in zone_policy:
- for zone, zone_conf in zone_policy['zone'].items():
- output_zone_name(zone, zone_conf)
- elif zone:
- output_zone_name(zone, zone_policy)
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser()
- parser.add_argument('--action', help='Action', required=False)
- parser.add_argument('--name', help='Zone name', required=False, action='store', nargs='?', default='')
-
- args = parser.parse_args()
-
- if args.action == 'show':
- show_zone_policy(args.name)