summaryrefslogtreecommitdiff
path: root/src/op_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/op_mode')
-rwxr-xr-xsrc/op_mode/bridge.py202
-rwxr-xr-xsrc/op_mode/conntrack.py (renamed from src/op_mode/show_conntrack.py)43
-rwxr-xr-xsrc/op_mode/container.py85
-rwxr-xr-xsrc/op_mode/dns.py95
-rwxr-xr-xsrc/op_mode/ipsec.py71
-rwxr-xr-xsrc/op_mode/nat.py201
-rwxr-xr-xsrc/op_mode/show_nat_rules.py126
-rwxr-xr-xsrc/op_mode/show_vrf.py66
-rwxr-xr-xsrc/op_mode/vpn_ipsec.py5
-rwxr-xr-xsrc/op_mode/vrf.py95
10 files changed, 783 insertions, 206 deletions
diff --git a/src/op_mode/bridge.py b/src/op_mode/bridge.py
new file mode 100755
index 000000000..411aa06d1
--- /dev/null
+++ b/src/op_mode/bridge.py
@@ -0,0 +1,202 @@
+#!/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/>.
+
+import jmespath
+import json
+import sys
+import typing
+
+from sys import exit
+from tabulate import tabulate
+
+from vyos.util import cmd
+from vyos.util import dict_search
+
+import vyos.opmode
+
+
+def _get_json_data():
+ """
+ Get bridge data format JSON
+ """
+ return cmd(f'sudo bridge --json link show')
+
+
+def _get_raw_data_summary():
+ """Get interested rules
+ :returns dict
+ """
+ data = _get_json_data()
+ data_dict = json.loads(data)
+ return data_dict
+
+
+def _get_raw_data_vlan():
+ """
+ :returns dict
+ """
+ json_data = cmd('sudo bridge --json --compressvlans vlan show')
+ data_dict = json.loads(json_data)
+ return data_dict
+
+
+def _get_raw_data_fdb(bridge):
+ """Get MAC-address for the bridge brX
+ :returns list
+ """
+ json_data = cmd(f'sudo bridge --json fdb show br {bridge}')
+ data_dict = json.loads(json_data)
+ return data_dict
+
+
+def _get_raw_data_mdb(bridge):
+ """Get MAC-address multicast gorup for the bridge brX
+ :return list
+ """
+ json_data = cmd(f'bridge --json mdb show br {bridge}')
+ data_dict = json.loads(json_data)
+ return data_dict
+
+
+def _get_bridge_members(bridge: str) -> list:
+ """
+ Get list of interface bridge members
+ :param bridge: str
+ :default: ['n/a']
+ :return: list
+ """
+ data = _get_raw_data_summary()
+ members = jmespath.search(f'[?master == `{bridge}`].ifname', data)
+ return [member for member in members] if members else ['n/a']
+
+
+def _get_member_options(bridge: str):
+ data = _get_raw_data_summary()
+ options = jmespath.search(f'[?master == `{bridge}`]', data)
+ return options
+
+
+def _get_formatted_output_summary(data):
+ data_entries = ''
+ bridges = set(jmespath.search('[*].master', data))
+ for bridge in bridges:
+ member_options = _get_member_options(bridge)
+ member_entries = []
+ for option in member_options:
+ interface = option.get('ifname')
+ ifindex = option.get('ifindex')
+ state = option.get('state')
+ mtu = option.get('mtu')
+ flags = ','.join(option.get('flags')).lower()
+ prio = option.get('priority')
+ member_entries.append([interface, state, mtu, flags, prio])
+ member_headers = ["Member", "State", "MTU", "Flags", "Prio"]
+ output_members = tabulate(member_entries, member_headers, numalign="left")
+ output_bridge = f"""Bridge interface {bridge}:
+{output_members}
+
+"""
+ data_entries += output_bridge
+ output = data_entries
+ return output
+
+
+def _get_formatted_output_vlan(data):
+ data_entries = []
+ for entry in data:
+ interface = entry.get('ifname')
+ vlans = entry.get('vlans')
+ for vlan_entry in vlans:
+ vlan = vlan_entry.get('vlan')
+ if vlan_entry.get('vlanEnd'):
+ vlan_end = vlan_entry.get('vlanEnd')
+ vlan = f'{vlan}-{vlan_end}'
+ flags = ', '.join(vlan_entry.get('flags')).lower()
+ data_entries.append([interface, vlan, flags])
+
+ headers = ["Interface", "Vlan", "Flags"]
+ output = tabulate(data_entries, headers)
+ return output
+
+
+def _get_formatted_output_fdb(data):
+ data_entries = []
+ for entry in data:
+ interface = entry.get('ifname')
+ mac = entry.get('mac')
+ state = entry.get('state')
+ flags = ','.join(entry['flags'])
+ data_entries.append([interface, mac, state, flags])
+
+ headers = ["Interface", "Mac address", "State", "Flags"]
+ output = tabulate(data_entries, headers, numalign="left")
+ return output
+
+
+def _get_formatted_output_mdb(data):
+ data_entries = []
+ for entry in data:
+ for mdb_entry in entry['mdb']:
+ interface = mdb_entry.get('port')
+ group = mdb_entry.get('grp')
+ state = mdb_entry.get('state')
+ flags = ','.join(mdb_entry.get('flags'))
+ data_entries.append([interface, group, state, flags])
+ headers = ["Interface", "Group", "State", "Flags"]
+ output = tabulate(data_entries, headers)
+ return output
+
+
+def show(raw: bool):
+ bridge_data = _get_raw_data_summary()
+ if raw:
+ return bridge_data
+ else:
+ return _get_formatted_output_summary(bridge_data)
+
+
+def show_vlan(raw: bool):
+ bridge_vlan = _get_raw_data_vlan()
+ if raw:
+ return bridge_vlan
+ else:
+ return _get_formatted_output_vlan(bridge_vlan)
+
+
+def show_fdb(raw: bool, interface: str):
+ fdb_data = _get_raw_data_fdb(interface)
+ if raw:
+ return fdb_data
+ else:
+ return _get_formatted_output_fdb(fdb_data)
+
+
+def show_mdb(raw: bool, interface: str):
+ mdb_data = _get_raw_data_mdb(interface)
+ if raw:
+ return mdb_data
+ else:
+ return _get_formatted_output_mdb(mdb_data)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except ValueError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/show_conntrack.py b/src/op_mode/conntrack.py
index 089a3e454..1441d110f 100755
--- a/src/op_mode/show_conntrack.py
+++ b/src/op_mode/conntrack.py
@@ -14,17 +14,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 sys
import xmltodict
from tabulate import tabulate
from vyos.util import cmd
+from vyos.util import run
+import vyos.opmode
-def _get_raw_data():
+
+def _get_xml_data(family):
"""
Get conntrack XML output
"""
- return cmd(f'sudo conntrack --dump --output xml')
+ return cmd(f'sudo conntrack --dump --family {family} --output xml')
def _xml_to_dict(xml):
@@ -32,26 +36,34 @@ def _xml_to_dict(xml):
Convert XML to dictionary
Return: dictionary
"""
- parse = xmltodict.parse(xml)
+ parse = xmltodict.parse(xml, attr_prefix='')
# If only one conntrack entry we must change dict
if 'meta' in parse['conntrack']['flow']:
return dict(conntrack={'flow': [parse['conntrack']['flow']]})
return parse
-def _get_formatted_output(xml):
+def _get_raw_data(family):
+ """
+ Return: dictionary
+ """
+ xml = _get_xml_data(family)
+ return _xml_to_dict(xml)
+
+
+def get_formatted_output(dict_data):
"""
:param xml:
:return: formatted output
"""
data_entries = []
- dict_data = _xml_to_dict(xml)
+ #dict_data = _get_raw_data(family)
for entry in dict_data['conntrack']['flow']:
orig_src, orig_dst, orig_sport, orig_dport = {}, {}, {}, {}
reply_src, reply_dst, reply_sport, reply_dport = {}, {}, {}, {}
proto = {}
for meta in entry['meta']:
- direction = meta['@direction']
+ direction = meta['direction']
if direction in ['original']:
if 'layer3' in meta:
orig_src = meta['layer3']['src']
@@ -61,7 +73,7 @@ def _get_formatted_output(xml):
orig_sport = meta['layer4']['sport']
if meta.get('layer4').get('dport'):
orig_dport = meta['layer4']['dport']
- proto = meta['layer4']['@protoname']
+ proto = meta['layer4']['protoname']
if direction in ['reply']:
if 'layer3' in meta:
reply_src = meta['layer3']['src']
@@ -71,7 +83,7 @@ def _get_formatted_output(xml):
reply_sport = meta['layer4']['sport']
if meta.get('layer4').get('dport'):
reply_dport = meta['layer4']['dport']
- proto = meta['layer4']['@protoname']
+ proto = meta['layer4']['protoname']
if direction == 'independent':
conn_id = meta['id']
timeout = meta['timeout']
@@ -90,13 +102,20 @@ def _get_formatted_output(xml):
return output
-def show(raw: bool):
- conntrack_data = _get_raw_data()
+def show(raw: bool, family: str):
+ family = 'ipv6' if family == 'inet6' else 'ipv4'
+ conntrack_data = _get_raw_data(family)
if raw:
return conntrack_data
else:
- return _get_formatted_output(conntrack_data)
+ return get_formatted_output(conntrack_data)
if __name__ == '__main__':
- print(show(raw=False))
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except ValueError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/container.py b/src/op_mode/container.py
new file mode 100755
index 000000000..78d42f800
--- /dev/null
+++ b/src/op_mode/container.py
@@ -0,0 +1,85 @@
+#!/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/>.
+
+import json
+import sys
+
+from sys import exit
+
+from vyos.util import cmd
+
+import vyos.opmode
+
+
+def _get_json_data(command: str) -> list:
+ """
+ Get container command format JSON
+ """
+ return cmd(f'{command} --format json')
+
+
+def _get_raw_data(command: str) -> list:
+ json_data = _get_json_data(command)
+ data = json.loads(json_data)
+ return data
+
+
+def show_container(raw: bool):
+ command = 'sudo podman ps --all'
+ container_data = _get_raw_data(command)
+ if raw:
+ return container_data
+ else:
+ return cmd(command)
+
+
+def show_image(raw: bool):
+ command = 'sudo podman image ls'
+ container_data = _get_raw_data('sudo podman image ls')
+ if raw:
+ return container_data
+ else:
+ return cmd(command)
+
+
+def show_network(raw: bool):
+ command = 'sudo podman network ls'
+ container_data = _get_raw_data(command)
+ if raw:
+ return container_data
+ else:
+ return cmd(command)
+
+
+def restart(name: str):
+ from vyos.util import rc_cmd
+
+ rc, output = rc_cmd(f'sudo podman restart {name}')
+ if rc != 0:
+ print(output)
+ return None
+ print(f'Container name "{name}" restarted!')
+ return output
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except ValueError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/dns.py b/src/op_mode/dns.py
new file mode 100755
index 000000000..717652b9b
--- /dev/null
+++ b/src/op_mode/dns.py
@@ -0,0 +1,95 @@
+#!/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/>.
+
+
+import sys
+
+from sys import exit
+from tabulate import tabulate
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.util import cmd
+
+import vyos.opmode
+
+
+def _data_to_dict(data, sep="\t") -> dict:
+ """
+ Return dictionary from plain text
+ separated by tab
+
+ cache-entries 73
+ cache-hits 0
+ uptime 2148
+ user-msec 172
+
+ {
+ 'cache-entries': '73',
+ 'cache-hits': '0',
+ 'uptime': '2148',
+ 'user-msec': '172'
+ }
+ """
+ dictionary = {}
+ mylist = [line for line in data.split('\n')]
+
+ for line in mylist:
+ if sep in line:
+ key, value = line.split(sep)
+ dictionary[key] = value
+ return dictionary
+
+
+def _get_raw_forwarding_statistics() -> dict:
+ command = cmd('sudo /usr/bin/rec_control --socket-dir=/run/powerdns get-all')
+ data = _data_to_dict(command)
+ data['cache-size'] = "{0:.2f}".format( int(
+ cmd('sudo /usr/bin/rec_control --socket-dir=/run/powerdns get cache-bytes')) / 1024 )
+ return data
+
+
+def _get_formatted_forwarding_statistics(data):
+ cache_entries = data.get('cache-entries')
+ max_cache_entries = data.get('max-cache-entries')
+ cache_size = data.get('cache-size')
+ data_entries = [[cache_entries, max_cache_entries, f'{cache_size} kbytes']]
+ headers = ["Cache entries", "Max cache entries" , "Cache size"]
+ output = tabulate(data_entries, headers, numalign="left")
+ return output
+
+
+def show_forwarding_statistics(raw: bool):
+
+ config = ConfigTreeQuery()
+ if not config.exists('service dns forwarding'):
+ print("DNS forwarding is not configured")
+ exit(0)
+
+ dns_data = _get_raw_forwarding_statistics()
+ if raw:
+ return dns_data
+ else:
+ return _get_formatted_forwarding_statistics(dns_data)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except ValueError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
new file mode 100755
index 000000000..432856585
--- /dev/null
+++ b/src/op_mode/ipsec.py
@@ -0,0 +1,71 @@
+#!/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/>.
+
+import re
+import sys
+from vyos.util import call
+import vyos.opmode
+
+
+SWANCTL_CONF = '/etc/swanctl/swanctl.conf'
+
+
+def get_peer_connections(peer, tunnel, return_all = False):
+ peer = peer.replace(':', '-')
+ search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*'
+ matches = []
+ with open(SWANCTL_CONF, 'r') as f:
+ for line in f.readlines():
+ result = re.match(search, line)
+ if result:
+ suffix = f'tunnel_{tunnel}' if tunnel.isnumeric() else tunnel
+ if return_all or (result[2] == suffix):
+ matches.append(result[1])
+ return matches
+
+
+def reset_peer(peer: str, tunnel:str):
+ 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'))
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except ValueError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py
new file mode 100755
index 000000000..a98fc4227
--- /dev/null
+++ b/src/op_mode/nat.py
@@ -0,0 +1,201 @@
+#!/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/>.
+
+import jmespath
+import json
+import sys
+
+from sys import exit
+from tabulate import tabulate
+
+from vyos.util import cmd
+from vyos.util import dict_search
+
+import vyos.opmode
+
+
+def _get_json_data(direction):
+ """
+ Get NAT format JSON
+ """
+ if direction == 'source':
+ chain = 'POSTROUTING'
+ if direction == 'destination':
+ chain = 'PREROUTING'
+ return cmd(f'sudo nft --json list chain ip nat {chain}')
+
+
+def _get_raw_data_rules(direction):
+ """Get interested rules
+ :returns dict
+ """
+ data = _get_json_data(direction)
+ data_dict = json.loads(data)
+ rules = []
+ for rule in data_dict['nftables']:
+ if 'rule' in rule and 'comment' in rule['rule']:
+ rules.append(rule)
+ return rules
+
+
+def _get_formatted_output_rules(data, direction):
+ # Add default values before loop
+ sport, dport, proto = 'any', 'any', 'any'
+ saddr, daddr = '0.0.0.0/0', '0.0.0.0/0'
+ data_entries = []
+ for rule in data:
+ if 'comment' in rule['rule']:
+ comment = rule.get('rule').get('comment')
+ rule_number = comment.split('-')[-1]
+ rule_number = rule_number.split(' ')[0]
+ if 'expr' in rule['rule']:
+ interface = rule.get('rule').get('expr')[0].get('match').get('right') \
+ if jmespath.search('rule.expr[*].match.left.meta', rule) else 'any'
+ for index, match in enumerate(jmespath.search('rule.expr[*].match', rule)):
+ if 'payload' in match['left']:
+ if 'prefix' in match['right'] or 'set' in match['right']:
+ # Merge dict src/dst l3_l4 parameters
+ my_dict = {**match['left']['payload'], **match['right']}
+ proto = my_dict.get('protocol').upper()
+ if my_dict['field'] == 'saddr':
+ saddr = f'{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}'
+ elif my_dict['field'] == 'daddr':
+ daddr = f'{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}'
+ elif my_dict['field'] == 'sport':
+ # Port range or single port
+ if jmespath.search('set[*].range', my_dict):
+ sport = my_dict['set'][0]['range']
+ sport = '-'.join(map(str, sport))
+ else:
+ sport = my_dict.get('set')
+ sport = ','.join(map(str, sport))
+ elif my_dict['field'] == 'dport':
+ # Port range or single port
+ if jmespath.search('set[*].range', my_dict):
+ dport = my_dict["set"][0]["range"]
+ dport = '-'.join(map(str, dport))
+ else:
+ dport = my_dict.get('set')
+ dport = ','.join(map(str, dport))
+ else:
+ if jmespath.search('left.payload.field', match) == 'saddr':
+ saddr = match.get('right')
+ if jmespath.search('left.payload.field', match) == 'daddr':
+ daddr = match.get('right')
+ else:
+ saddr = '0.0.0.0/0'
+ daddr = '0.0.0.0/0'
+ sport = 'any'
+ dport = 'any'
+ proto = 'any'
+
+ source = f'''{saddr}
+sport {sport}'''
+ destination = f'''{daddr}
+dport {dport}'''
+
+ if jmespath.search('left.payload.field', match) == 'protocol':
+ field_proto = match.get('right').upper()
+
+ for expr in rule.get('rule').get('expr'):
+ if 'snat' in expr:
+ translation = dict_search('snat.addr', expr)
+ if expr['snat'] and 'port' in expr['snat']:
+ if jmespath.search('snat.port.range', expr):
+ port = dict_search('snat.port.range', expr)
+ port = '-'.join(map(str, port))
+ else:
+ port = expr['snat']['port']
+ translation = f'''{translation}
+port {port}'''
+
+ elif 'masquerade' in expr:
+ translation = 'masquerade'
+ if expr['masquerade'] and 'port' in expr['masquerade']:
+ if jmespath.search('masquerade.port.range', expr):
+ port = dict_search('masquerade.port.range', expr)
+ port = '-'.join(map(str, port))
+ else:
+ port = expr['masquerade']['port']
+
+ translation = f'''{translation}
+port {port}'''
+ elif 'dnat' in expr:
+ translation = dict_search('dnat.addr', expr)
+ if expr['dnat'] and 'port' in expr['dnat']:
+ if jmespath.search('dnat.port.range', expr):
+ port = dict_search('dnat.port.range', expr)
+ port = '-'.join(map(str, port))
+ else:
+ port = expr['dnat']['port']
+ translation = f'''{translation}
+port {port}'''
+ else:
+ translation = 'exclude'
+ # Overwrite match loop 'proto' if specified filed 'protocol' exist
+ if 'protocol' in jmespath.search('rule.expr[*].match.left.payload.field', rule):
+ proto = jmespath.search('rule.expr[0].match.right', rule).upper()
+
+ data_entries.append([rule_number, source, destination, proto, interface, translation])
+
+ interface_header = 'Out-Int' if direction == 'source' else 'In-Int'
+ headers = ["Rule", "Source", "Destination", "Proto", interface_header, "Translation"]
+ output = tabulate(data_entries, headers, numalign="left")
+ return output
+
+
+def _get_formatted_output_statistics(data, direction):
+ data_entries = []
+ for rule in data:
+ if 'comment' in rule['rule']:
+ comment = rule.get('rule').get('comment')
+ rule_number = comment.split('-')[-1]
+ rule_number = rule_number.split(' ')[0]
+ if 'expr' in rule['rule']:
+ interface = rule.get('rule').get('expr')[0].get('match').get('right') \
+ if jmespath.search('rule.expr[*].match.left.meta', rule) else 'any'
+ packets = jmespath.search('rule.expr[*].counter.packets | [0]', rule)
+ _bytes = jmespath.search('rule.expr[*].counter.bytes | [0]', rule)
+ data_entries.append([rule_number, packets, _bytes, interface])
+ headers = ["Rule", "Packets", "Bytes", "Interface"]
+ output = tabulate(data_entries, headers, numalign="left")
+ return output
+
+
+def show_rules(raw: bool, direction: str):
+ nat_rules = _get_raw_data_rules(direction)
+ if raw:
+ return nat_rules
+ else:
+ return _get_formatted_output_rules(nat_rules, direction)
+
+
+def show_statistics(raw: bool, direction: str):
+ nat_statistics = _get_raw_data_rules(direction)
+ if raw:
+ return nat_statistics
+ else:
+ return _get_formatted_output_statistics(nat_statistics, direction)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except ValueError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/show_nat_rules.py b/src/op_mode/show_nat_rules.py
deleted file mode 100755
index 60a4bdd13..000000000
--- a/src/op_mode/show_nat_rules.py
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021-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/>.
-
-import jmespath
-import json
-
-from argparse import ArgumentParser
-from jinja2 import Template
-from sys import exit
-from vyos.util import cmd
-from vyos.util import dict_search
-
-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 nat')
- tmp = json.loads(tmp)
-
- format_nat_rule = '{0: <10} {1: <50} {2: <50} {3: <10}'
- print(format_nat_rule.format("Rule", "Source" if args.source else "Destination", "Translation", "Outbound Interface" if args.source else "Inbound Interface"))
- print(format_nat_rule.format("----", "------" if args.source else "-----------", "-----------", "------------------" if args.source else "-----------------"))
-
- data_json = jmespath.search('nftables[?rule].rule[?chain]', tmp)
- for idx in range(0, len(data_json)):
- data = data_json[idx]
-
- # The following key values must exist
- # When the rule JSON does not have some keys, this is not a rule we can work with
- continue_rule = False
- for key in ['comment', 'chain', 'expr']:
- if key not in data:
- continue_rule = True
- continue
- if continue_rule:
- continue
-
- comment = data['comment']
-
- # Check the annotation to see if the annotation format is created by VYOS
- continue_rule = True
- for comment_prefix in ['SRC-NAT-', 'DST-NAT-']:
- if comment_prefix in comment:
- continue_rule = False
- if continue_rule:
- continue
-
- rule = int(''.join(list(filter(str.isdigit, comment))))
- chain = data['chain']
- if not ((args.source and chain == 'POSTROUTING') or (not args.source and chain == 'PREROUTING')):
- continue
- interface = dict_search('match.right', data['expr'][0])
- srcdest = ''
- srcdests = []
- tran_addr = ''
- for i in range(1,len(data['expr']) ):
- srcdest_json = dict_search('match.right', data['expr'][i])
- if srcdest_json:
- if isinstance(srcdest_json,str):
- if srcdest != '':
- srcdests.append(srcdest)
- srcdest = ''
- srcdest = srcdest_json + ' '
- elif 'prefix' in srcdest_json:
- addr_tmp = dict_search('match.right.prefix.addr', data['expr'][i])
- len_tmp = dict_search('match.right.prefix.len', data['expr'][i])
- if addr_tmp and len_tmp:
- srcdest = addr_tmp + '/' + str(len_tmp) + ' '
- elif 'set' in srcdest_json:
- if isinstance(srcdest_json['set'][0],int):
- srcdest += 'port ' + str(srcdest_json['set'][0]) + ' '
- else:
- port_range = srcdest_json['set'][0]['range']
- srcdest += 'port ' + str(port_range[0]) + '-' + str(port_range[1]) + ' '
-
- tran_addr_json = dict_search('snat' if args.source else 'dnat', data['expr'][i])
- if tran_addr_json:
- if isinstance(tran_addr_json['addr'],str):
- tran_addr += tran_addr_json['addr'] + ' '
- elif 'prefix' in tran_addr_json['addr']:
- addr_tmp = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3])
- len_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3])
- if addr_tmp and len_tmp:
- tran_addr += addr_tmp + '/' + str(len_tmp) + ' '
-
- if tran_addr_json.get('port'):
- if isinstance(tran_addr_json['port'],int):
- tran_addr += 'port ' + str(tran_addr_json['port'])
-
- else:
- if 'masquerade' in data['expr'][i]:
- tran_addr = 'masquerade'
- elif 'log' in data['expr'][i]:
- continue
-
- if srcdest != '':
- srcdests.append(srcdest)
- srcdest = ''
- else:
- srcdests.append('any')
- print(format_nat_rule.format(rule, srcdests[0], tran_addr, interface))
-
- for i in range(1, len(srcdests)):
- print(format_nat_rule.format(' ', srcdests[i], ' ', ' '))
-
- exit(0)
-else:
- parser.print_help()
- exit(1)
-
diff --git a/src/op_mode/show_vrf.py b/src/op_mode/show_vrf.py
deleted file mode 100755
index 3c7a90205..000000000
--- a/src/op_mode/show_vrf.py
+++ /dev/null
@@ -1,66 +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/>.
-
-import argparse
-import jinja2
-from json import loads
-
-from vyos.util import cmd
-
-vrf_out_tmpl = """VRF name state mac address flags interfaces
--------- ----- ----------- ----- ----------
-{%- for v in vrf %}
-{{"%-16s"|format(v.ifname)}} {{ "%-8s"|format(v.operstate | lower())}} {{"%-17s"|format(v.address | lower())}} {{ v.flags|join(',')|lower()}} {{v.members|join(',')|lower()}}
-{%- endfor %}
-
-"""
-
-def list_vrfs():
- command = 'ip -j -br link show type vrf'
- answer = loads(cmd(command))
- return [_ for _ in answer if _]
-
-def list_vrf_members(vrf):
- command = f'ip -j -br link show master {vrf}'
- answer = loads(cmd(command))
- return [_ for _ in answer if _]
-
-parser = argparse.ArgumentParser()
-group = parser.add_mutually_exclusive_group()
-group.add_argument("-e", "--extensive", action="store_true",
- help="provide detailed vrf informatio")
-parser.add_argument('interface', metavar='I', type=str, nargs='?',
- help='interface to display')
-
-args = parser.parse_args()
-
-if args.extensive:
- data = { 'vrf': [] }
- for vrf in list_vrfs():
- name = vrf['ifname']
- if args.interface and name != args.interface:
- continue
-
- vrf['members'] = []
- for member in list_vrf_members(name):
- vrf['members'].append(member['ifname'])
- data['vrf'].append(vrf)
-
- tmpl = jinja2.Template(vrf_out_tmpl)
- print(tmpl.render(data))
-
-else:
- print(" ".join([vrf['ifname'] for vrf in list_vrfs()]))
diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py
index 8955e5a59..68dc5bc45 100755
--- a/src/op_mode/vpn_ipsec.py
+++ b/src/op_mode/vpn_ipsec.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-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
@@ -87,6 +87,7 @@ def reset_profile(profile, tunnel):
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",
@@ -109,7 +110,7 @@ def debug_peer(peer, tunnel):
if not tunnel or tunnel == 'all':
tunnel = ''
- conn = get_peer_connections(peer, tunnel)
+ conns = get_peer_connections(peer, tunnel, return_all = (tunnel == '' or tunnel == 'all'))
if not conns:
print('Peer not found, aborting')
diff --git a/src/op_mode/vrf.py b/src/op_mode/vrf.py
new file mode 100755
index 000000000..e3d944d90
--- /dev/null
+++ b/src/op_mode/vrf.py
@@ -0,0 +1,95 @@
+#!/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/>.
+
+import json
+import jmespath
+import sys
+import typing
+
+from tabulate import tabulate
+from vyos.util import cmd
+
+import vyos.opmode
+
+
+def _get_raw_data(name=None):
+ """
+ If vrf name is not set - get all VRFs
+ If vrf name is set - get only this name data
+ If vrf name set and not found - return []
+ """
+ output = cmd('sudo ip --json --brief link show type vrf')
+ data = json.loads(output)
+ if not data:
+ return []
+ if name:
+ is_vrf_exists = True if [vrf for vrf in data if vrf.get('ifname') == name] else False
+ if is_vrf_exists:
+ output = cmd(f'sudo ip --json --brief link show dev {name}')
+ data = json.loads(output)
+ return data
+ return []
+ return data
+
+
+def _get_vrf_members(vrf: str) -> list:
+ """
+ Get list of interface VRF members
+ :param vrf: str
+ :return: list
+ """
+ output = cmd(f'sudo 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:
+ name = vrf.get('ifname')
+ state = vrf.get('operstate').lower()
+ hw_address = vrf.get('address')
+ flags = ','.join(vrf.get('flags')).lower()
+ members = ','.join(_get_vrf_members(name))
+ data_entries.append([name, state, hw_address, flags, members])
+
+ headers = ["Name", "State", "MAC address", "Flags", "Interfaces"]
+ output = tabulate(data_entries, headers, numalign="left")
+ return output
+
+
+def show(raw: bool, name: typing.Optional[str]):
+ vrf_data = _get_raw_data(name=name)
+ if not jmespath.search('[*].ifname', vrf_data):
+ return "VRF is not configured"
+ if raw:
+ return vrf_data
+ else:
+ return _get_formatted_output(vrf_data)
+
+
+if __name__ == "__main__":
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except ValueError as e:
+ print(e)
+ sys.exit(1)