diff options
Diffstat (limited to 'src/op_mode')
-rwxr-xr-x | src/op_mode/bridge.py | 202 | ||||
-rwxr-xr-x | src/op_mode/conntrack.py (renamed from src/op_mode/show_conntrack.py) | 43 | ||||
-rwxr-xr-x | src/op_mode/container.py | 85 | ||||
-rwxr-xr-x | src/op_mode/cpu.py | 82 | ||||
-rwxr-xr-x | src/op_mode/cpu_summary.py | 48 | ||||
-rwxr-xr-x | src/op_mode/dns.py | 95 | ||||
-rwxr-xr-x | src/op_mode/ipsec.py | 71 | ||||
-rwxr-xr-x | src/op_mode/memory.py (renamed from src/op_mode/show_ram.py) | 39 | ||||
-rwxr-xr-x | src/op_mode/nat.py | 201 | ||||
-rwxr-xr-x | src/op_mode/neighbor.py (renamed from src/op_mode/show_neigh.py) | 60 | ||||
-rw-r--r-- | src/op_mode/route.py | 98 | ||||
-rwxr-xr-x | src/op_mode/show_cpu.py | 72 | ||||
-rwxr-xr-x | src/op_mode/show_nat_rules.py | 126 | ||||
-rwxr-xr-x | src/op_mode/show_vrf.py | 66 | ||||
-rwxr-xr-x | src/op_mode/version.py (renamed from src/op_mode/show_version.py) | 52 | ||||
-rwxr-xr-x | src/op_mode/vpn_ipsec.py | 5 | ||||
-rwxr-xr-x | src/op_mode/vrf.py | 95 |
17 files changed, 1062 insertions, 378 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/cpu.py b/src/op_mode/cpu.py new file mode 100755 index 000000000..f9c425826 --- /dev/null +++ b/src/op_mode/cpu.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2016-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 + +import vyos.cpu +import vyos.opmode + +from jinja2 import Template + +cpu_template = Template(""" +{% for cpu in cpus %} +{% if 'physical id' in cpu %}CPU socket: {{cpu['physical id']}}{% endif %} +{% if 'vendor_id' in cpu %}CPU Vendor: {{cpu['vendor_id']}}{% endif %} +{% if 'model name' in cpu %}Model: {{cpu['model name']}}{% endif %} +{% if 'cpu cores' in cpu %}Cores: {{cpu['cpu cores']}}{% endif %} +{% if 'cpu MHz' in cpu %}Current MHz: {{cpu['cpu MHz']}}{% endif %} +{% endfor %} +""") + +cpu_summary_template = Template(""" +Physical CPU cores: {{count}} +CPU model(s): {{models | join(", ")}} +""") + +def _get_raw_data(): + return vyos.cpu.get_cpus() + +def _format_cpus(cpu_data): + env = {'cpus': cpu_data} + return cpu_template.render(env).strip() + +def _get_summary_data(): + count = vyos.cpu.get_core_count() + cpu_data = vyos.cpu.get_cpus() + models = [c['model name'] for c in cpu_data] + env = {'count': count, "models": models} + + return env + +def _format_cpu_summary(summary_data): + return cpu_summary_template.render(summary_data).strip() + +def show(raw: bool): + cpu_data = _get_raw_data() + + if raw: + return cpu_data + else: + return _format_cpus(cpu_data) + +def show_summary(raw: bool): + cpu_summary_data = _get_summary_data() + + if raw: + return cpu_summary_data + else: + return _format_cpu_summary(cpu_summary_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/cpu_summary.py b/src/op_mode/cpu_summary.py deleted file mode 100755 index 3bdf5a718..000000000 --- a/src/op_mode/cpu_summary.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-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 -from vyos.util import colon_separated_to_dict - -FILE_NAME = '/proc/cpuinfo' - -def get_raw_data(): - with open(FILE_NAME, 'r') as f: - data_raw = f.read() - - data = colon_separated_to_dict(data_raw) - - # Accumulate all data in a dict for future support for machine-readable output - cpu_data = {} - cpu_data['cpu_number'] = len(data['processor']) - cpu_data['models'] = list(set(data['model name'])) - - # Strip extra whitespace from CPU model names, /proc/cpuinfo is prone to that - cpu_data['models'] = list(map(lambda s: re.sub(r'\s+', ' ', s), cpu_data['models'])) - - return cpu_data - -def get_formatted_output(): - cpu_data = get_raw_data() - - out = "CPU(s): {0}\n".format(cpu_data['cpu_number']) - out += "CPU model(s): {0}".format(",".join(cpu_data['models'])) - - return out - -if __name__ == '__main__': - print(get_formatted_output()) - 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/show_ram.py b/src/op_mode/memory.py index 2b0be3965..a3870e498 100755 --- a/src/op_mode/show_ram.py +++ b/src/op_mode/memory.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 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 @@ -15,7 +15,12 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # -def get_system_memory(): +import sys + +import vyos.opmode + + +def _get_system_memory(): from re import search as re_search def find_value(keyword, mem_data): @@ -43,10 +48,10 @@ def get_system_memory(): return res -def get_system_memory_human(): +def _get_system_memory_human(): from vyos.util import bytes_to_human - mem = get_system_memory() + mem = _get_system_memory() for key in mem: # The Linux kernel exposes memory values in kilobytes, @@ -55,17 +60,31 @@ def get_system_memory_human(): return mem -def get_raw_data(): - return get_system_memory_human() - -def get_formatted_output(): - mem = get_raw_data() +def _get_raw_data(): + return _get_system_memory_human() +def _get_formatted_output(mem): out = "Total: {}\n".format(mem["total"]) out += "Free: {}\n".format(mem["free"]) out += "Used: {}".format(mem["used"]) return out +def show(raw: bool): + ram_data = _get_raw_data() + + if raw: + return ram_data + else: + return _get_formatted_output(ram_data) + + if __name__ == '__main__': - print(get_formatted_output()) + 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_neigh.py b/src/op_mode/neighbor.py index d874bd544..d86a372ac 100755 --- a/src/op_mode/show_neigh.py +++ b/src/op_mode/neighbor.py @@ -28,29 +28,37 @@ # ] import sys +import typing +import vyos.opmode -def get_raw_data(family, device=None, state=None): +def interface_exists(interface): + import os + return os.path.exists(f'/sys/class/net/{interface}') + +def get_raw_data(family, interface=None, state=None): from json import loads from vyos.util import cmd - if device: - device = f"dev {device}" + if interface: + if not interface_exists(interface): + raise ValueError(f"Interface '{interface}' does not exist in the system") + interface = f"dev {interface}" else: - device = "" + interface = "" if state: state = f"nud {state}" else: state = "" - neigh_cmd = f"ip --family {family} --json neighbor list {device} {state}" + neigh_cmd = f"ip --family {family} --json neighbor list {interface} {state}" data = loads(cmd(neigh_cmd)) return data -def get_formatted_output(family, device=None, state=None): +def format_neighbors(neighs, interface=None): from tabulate import tabulate def entry_to_list(e, intf=None): @@ -68,35 +76,47 @@ def get_formatted_output(family, device=None, state=None): # Device field is absent from outputs of `ip neigh list dev ...` if "dev" in e: dev = e["dev"] - elif device: - dev = device + elif interface: + dev = interface else: raise ValueError("interface is not defined") return [dst, dev, lladdr, state] - neighs = get_raw_data(family, device=device, state=state) neighs = map(entry_to_list, neighs) headers = ["Address", "Interface", "Link layer address", "State"] return tabulate(neighs, headers) -if __name__ == '__main__': - from argparse import ArgumentParser +def show(raw: bool, family: str, interface: typing.Optional[str], state: typing.Optional[str]): + """ Display neighbor table contents """ + data = get_raw_data(family, interface, state=state) - parser = ArgumentParser() - parser.add_argument("-f", "--family", type=str, default="inet", help="Address family") - parser.add_argument("-i", "--interface", type=str, help="Network interface") - parser.add_argument("-s", "--state", type=str, help="Neighbor table entry state") + if raw: + return data + else: + return format_neighbors(data, interface) - args = parser.parse_args() +def reset(family: str, interface: typing.Optional[str], address: typing.Optional[str]): + from vyos.util import run - if args.state: - if args.state not in ["reachable", "failed", "stale", "permanent"]: - raise ValueError(f"""Incorrect state "{args.state}"! Must be one of: reachable, stale, failed, permanent""") + if address and interface: + raise ValueError("interface and address parameters are mutually exclusive") + elif address: + run(f"""ip --family {family} neighbor flush to {address}""") + elif interface: + run(f"""ip --family {family} neighbor flush dev {interface}""") + else: + # Flush an entire neighbor table + run(f"""ip --family {family} neighbor flush""") + +if __name__ == '__main__': try: - print(get_formatted_output(args.family, device=args.interface, state=args.state)) + 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/route.py b/src/op_mode/route.py new file mode 100644 index 000000000..3bb06adac --- /dev/null +++ b/src/op_mode/route.py @@ -0,0 +1,98 @@ +#!/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/>. +# +# Purpose: +# Displays routing table information. +# Used by the "run <ip|ipv6> route *" commands. + +import re +import sys +import typing + +from jinja2 import Template + +import vyos.opmode + +frr_command_template = Template(""" +{% if family == "inet" %} + show ip route +{% else %} + show ipv6 route +{% endif %} + +{% if table %} + table {{table}} +{% endif %} + +{% if vrf %} + vrf {{table}} +{% endif %} + +{% if tag %} + tag {{tag}} +{% elif net %} + {{net}} +{% elif protocol %} + {{protocol}} +{% endif %} + +{% if raw %} + json +{% endif %} +""") + +def show(raw: bool, + family: str, + net: typing.Optional[str], + table: typing.Optional[int], + protocol: typing.Optional[str], + vrf: typing.Optional[str], + tag: typing.Optional[str]): + if net and protocol: + raise ValueError("net and protocol are mutually exclusive") + elif table and vrf: + raise ValueError("table and vrf are mutually exclusive") + elif (family == 'inet6') and (protocol == 'rip'): + raise ValueError("rip is not a valid protocol for family inet6") + elif (family == 'inet') and (protocol == 'ripng'): + raise ValueError("rip is not a valid protocol for family inet6") + else: + if (family == 'inet6') and (protocol == 'ospf'): + protocol = 'ospf6' + + 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}'") + + if raw: + from json import loads + return loads(output) + else: + 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/show_cpu.py b/src/op_mode/show_cpu.py deleted file mode 100755 index 9973d9789..000000000 --- a/src/op_mode/show_cpu.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2016-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 json - -from jinja2 import Template -from sys import exit -from vyos.util import popen, DEVNULL - -OUT_TMPL_SRC = """ -{%- if cpu -%} -{% if 'vendor' in cpu %}CPU Vendor: {{cpu.vendor}}{% endif %} -{% if 'model' in cpu %}Model: {{cpu.model}}{% endif %} -{% if 'cpus' in cpu %}Total CPUs: {{cpu.cpus}}{% endif %} -{% if 'sockets' in cpu %}Sockets: {{cpu.sockets}}{% endif %} -{% if 'cores' in cpu %}Cores: {{cpu.cores}}{% endif %} -{% if 'threads' in cpu %}Threads: {{cpu.threads}}{% endif %} -{% if 'mhz' in cpu %}Current MHz: {{cpu.mhz}}{% endif %} -{% if 'mhz_min' in cpu %}Minimum MHz: {{cpu.mhz_min}}{% endif %} -{% if 'mhz_max' in cpu %}Maximum MHz: {{cpu.mhz_max}}{% endif %} -{%- endif -%} -""" - -def get_raw_data(): - cpu = {} - cpu_json, code = popen('lscpu -J', stderr=DEVNULL) - - if code == 0: - cpu_info = json.loads(cpu_json) - if len(cpu_info) > 0 and 'lscpu' in cpu_info: - for prop in cpu_info['lscpu']: - if (prop['field'].find('Thread(s)') > -1): cpu['threads'] = prop['data'] - if (prop['field'].find('Core(s)')) > -1: cpu['cores'] = prop['data'] - if (prop['field'].find('Socket(s)')) > -1: cpu['sockets'] = prop['data'] - if (prop['field'].find('CPU(s):')) > -1: cpu['cpus'] = prop['data'] - if (prop['field'].find('CPU MHz')) > -1: cpu['mhz'] = prop['data'] - if (prop['field'].find('CPU min MHz')) > -1: cpu['mhz_min'] = prop['data'] - if (prop['field'].find('CPU max MHz')) > -1: cpu['mhz_max'] = prop['data'] - if (prop['field'].find('Vendor ID')) > -1: cpu['vendor'] = prop['data'] - if (prop['field'].find('Model name')) > -1: cpu['model'] = prop['data'] - - return cpu - -def get_formatted_output(): - cpu = get_raw_data() - - tmp = {'cpu':cpu} - tmpl = Template(OUT_TMPL_SRC) - return tmpl.render(tmp) - -if __name__ == '__main__': - cpu = get_raw_data() - - if len(cpu) > 0: - print(get_formatted_output()) - else: - print('CPU information could not be determined\n') - 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/show_version.py b/src/op_mode/version.py index b82ab6eca..06208c3e5 100755 --- a/src/op_mode/show_version.py +++ b/src/op_mode/version.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2016-2020 VyOS maintainers and contributors +# Copyright (C) 2016-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 @@ -18,13 +18,14 @@ # Displays image version and system information. # Used by the "run show version" command. -import argparse +import sys +import typing + +import vyos.opmode import vyos.version import vyos.limericks from jinja2 import Template -from sys import exit -from vyos.util import call version_output_tmpl = """ Version: VyOS {{version}} @@ -45,32 +46,39 @@ Hardware S/N: {{hardware_serial}} Hardware UUID: {{hardware_uuid}} Copyright: VyOS maintainers and contributors +{%- if limerick %} +{{limerick}} +{% endif -%} """ -def get_raw_data(): +def _get_raw_data(funny=False): version_data = vyos.version.get_full_version_data() + + if funny: + version_data["limerick"] = vyos.limericks.get_random() + return version_data -def get_formatted_output(): - version_data = get_raw_data() +def _get_formatted_output(version_data): tmpl = Template(version_output_tmpl) - return tmpl.render(version_data) + return tmpl.render(version_data).strip() -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument("-f", "--funny", action="store_true", help="Add something funny to the output") - parser.add_argument("-j", "--json", action="store_true", help="Produce JSON output") +def show(raw: bool, funny: typing.Optional[bool]): + """ Display neighbor table contents """ + version_data = _get_raw_data(funny=funny) - args = parser.parse_args() + if raw: + return version_data + else: + return _get_formatted_output(version_data) - version_data = vyos.version.get_full_version_data() - if args.json: - import json - print(json.dumps(version_data)) - exit(0) - else: - print(get_formatted_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) - if args.funny: - print(vyos.limericks.get_random()) 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) |