diff --git a/.github/workflows/package-smoketest.yml b/.github/workflows/package-smoketest.yml index 91c968c822..d352bd3cb2 100644 --- a/.github/workflows/package-smoketest.yml +++ b/.github/workflows/package-smoketest.yml @@ -17,6 +17,7 @@ env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed for PR comments BUILD_BY: autobuild@vyos.net DEBIAN_MIRROR: http://deb.debian.org/debian/ + DEBIAN_SECURITY_MIRROR: http://deb.debian.org/debian-security VYOS_MIRROR: https://packages.vyos.net/repositories/current/ jobs: @@ -56,6 +57,7 @@ jobs: --build-type release \ --custom-package vyos-1x-smoketest \ --debian-mirror $DEBIAN_MIRROR \ + --debian-security-mirror $DEBIAN_SECURITY_MIRROR \ --version ${{ steps.version.outputs.build_version }} \ --vyos-mirror $VYOS_MIRROR \ generic diff --git a/CODEOWNERS b/CODEOWNERS index 1913942985..4891a03252 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1,2 @@ -* @vyos/reviewers \ No newline at end of file +# Users from reviewers github team +* @dmbaturin @sarthurdev @jestabro @sever-sever @c-po @fett0 @nicolas-fort @zdc diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2 index a10dcf2c16..34dfa529a2 100644 --- a/data/templates/accel-ppp/ipoe.config.j2 +++ b/data/templates/accel-ppp/ipoe.config.j2 @@ -59,7 +59,9 @@ lua-file={{ lua_file }} {% set relay = ',' ~ 'relay=' ~ iface_config.external_dhcp.dhcp_relay if iface_config.external_dhcp.dhcp_relay is vyos_defined else '' %} {% set giaddr = ',' ~ 'giaddr=' ~ iface_config.external_dhcp.giaddr if iface_config.external_dhcp.giaddr is vyos_defined else '' %} {% set username = ',' ~ 'username=lua:' ~ iface_config.lua_username if iface_config.lua_username is vyos_defined else '' %} -{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,{{ range }}start=dhcpv4,ipv6=1{{ relay }}{{ giaddr }}{{ username }} +{% set start_map = {'dhcp': 'dhcpv4', 'unclassified-packet': 'up', 'auto': 'auto'} %} +{% set start = start_map[iface_config.start_session] %} +{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,{{ range }}start={{ start }},ipv6=1{{ relay }}{{ giaddr }}{{ username }} {% if iface_config.vlan_mon is vyos_defined %} vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }} {% endif %} diff --git a/data/templates/chrony/chrony.conf.j2 b/data/templates/chrony/chrony.conf.j2 index 2838f55242..cc80e4d643 100644 --- a/data/templates/chrony/chrony.conf.j2 +++ b/data/templates/chrony/chrony.conf.j2 @@ -67,9 +67,9 @@ binddevice {{ interface }} {% endif %} {% endif %} -{% if ptp.timestamp.interface is vyos_defined %} +{% if timestamp.interface is vyos_defined %} # Enable hardware timestamping on the specified interfaces -{% for iface, iface_config in ptp.timestamp.interface.items() %} +{% for iface, iface_config in timestamp.interface.items() %} {% if iface == "all" %} {% set iface = "*" %} {% endif %} diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in index bd2ff820da..ad18156047 100644 --- a/interface-definitions/container.xml.in +++ b/interface-definitions/container.xml.in @@ -275,6 +275,7 @@ 64 + #include Attach user defined network to container diff --git a/interface-definitions/include/interface/default-route-distance.xml.i b/interface-definitions/include/interface/default-route-distance.xml.i index 6eda52c919..7a226a538b 100644 --- a/interface-definitions/include/interface/default-route-distance.xml.i +++ b/interface-definitions/include/interface/default-route-distance.xml.i @@ -4,7 +4,7 @@ Distance for installed default route u32:1-255 - Distance for the default route from DHCP server + Distance for the default route received from the server diff --git a/interface-definitions/interfaces_pppoe.xml.in b/interface-definitions/interfaces_pppoe.xml.in index 56660bc154..f24bc41d8c 100644 --- a/interface-definitions/interfaces_pppoe.xml.in +++ b/interface-definitions/interfaces_pppoe.xml.in @@ -21,6 +21,9 @@ #include #include #include + + 1 + #include #include #include diff --git a/interface-definitions/pki.xml.in b/interface-definitions/pki.xml.in index b922771c17..5c0b735ef6 100644 --- a/interface-definitions/pki.xml.in +++ b/interface-definitions/pki.xml.in @@ -35,6 +35,12 @@ + + + Install into CA certificate store on router + + + #include @@ -74,7 +80,7 @@ - #include + #include Size of the RSA key diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in index 27a654f926..39cfb78893 100644 --- a/interface-definitions/service_ipoe-server.xml.in +++ b/interface-definitions/service_ipoe-server.xml.in @@ -131,6 +131,30 @@ shared + + + Start session options + + auto dhcp unclassified-packet + + + auto + Start session with username as the interface name + + + dhcp + Start session on DHCPv4 Discover + + + unclassified-packet + Start session on unclassified-packet + + + (auto|dhcp|unclassified-packet) + + + dhcp + Client address pool diff --git a/interface-definitions/service_ntp.xml.in b/interface-definitions/service_ntp.xml.in index 5dc0cd2951..c31b572bd9 100644 --- a/interface-definitions/service_ntp.xml.in +++ b/interface-definitions/service_ntp.xml.in @@ -13,72 +13,72 @@ #include #include #include - + - Enable Precision Time Protocol (PTP) transport + Enable timestamping of packets in the NIC hardware - #include - - 319 - - + - Enable timestamping of packets in the NIC hardware + Interface to enable timestamping on + + + all + + + all + Select all interfaces + + + txt + Interface name + + + #include + all + - + - Interface to enable timestamping on + Selects which inbound packets are timestamped by the NIC - - all + all ntp ptp none all - Select all interfaces + All packets are timestamped - txt - Interface name + ntp + Only NTP packets are timestamped + + + ptp + Only PTP or NTP packets using the PTP transport are timestamped + + + none + No packet is timestamped - #include - all + (all|ntp|ptp|none) - - - - Selects which inbound packets are timestamped by the NIC - - all ntp ptp none - - - all - All packets are timestamped - - - ntp - Only NTP packets are timestamped - - - ptp - Only PTP or NTP packets using the PTP transport are timestamped - - - none - No packet is timestamped - - - (all|ntp|ptp|none) - - - - - + - + + + + + + Enable Precision Time Protocol (PTP) transport + + + #include + + 319 + diff --git a/op-mode-definitions/show-bridge.xml.in b/op-mode-definitions/show-bridge.xml.in index 5d8cc38475..1212ab1f93 100644 --- a/op-mode-definitions/show-bridge.xml.in +++ b/op-mode-definitions/show-bridge.xml.in @@ -66,7 +66,7 @@ Display bridge interface nexthop-group - ${vyos_op_scripts_dir}/bridge.py show_detail --nexthop_group --interface=$3 + ${vyos_op_scripts_dir}/bridge.py show_detail --nexthop-group --interface=$3 diff --git a/op-mode-definitions/show-monitoring.xml.in b/op-mode-definitions/show-monitoring.xml.in index 2651b34387..9fd60e45ad 100644 --- a/op-mode-definitions/show-monitoring.xml.in +++ b/op-mode-definitions/show-monitoring.xml.in @@ -2,12 +2,68 @@ - + Show currently monitored services - vtysh -c "show debugging" - + + + + Show currently monitored FRR services + + vtysh -c "show debugging" + + + + Show Zebra routing information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh "show ${@:4}" + + + + Client information + + + + + Brief summary + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh "show ${@:4}" + + + + + + Zebra dataplane information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh "show ${@:4}" + + + + Zebra router information + + + + + Zebra routing table information + + + + + Summary information + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh "show ${@:4}" + + + + + + + + + + + diff --git a/op-mode-definitions/show-zebra.xml.in b/op-mode-definitions/show-zebra.xml.in deleted file mode 100644 index 69991a1d57..0000000000 --- a/op-mode-definitions/show-zebra.xml.in +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - Show Zebra routing information - - ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ - - - - Client information - - - - - Brief Summary - - ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ - - - - - - Zebra dataplane information - - ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ - - - - Zebra Router Information - - - - - Table Information about this Zebra Router - - - - - Summary Information - - ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ - - - - - - - - - - diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index 3e02fbba61..fb79e84598 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -469,15 +469,15 @@ def mask_inclusive(left, right, libpath=LIBPATH): return tree -def reference_tree_to_json(from_dir, to_file, libpath=LIBPATH): +def reference_tree_to_json(from_dir, to_file, internal_cache="", libpath=LIBPATH): try: __lib = cdll.LoadLibrary(libpath) __reference_tree_to_json = __lib.reference_tree_to_json - __reference_tree_to_json.argtypes = [c_char_p, c_char_p] + __reference_tree_to_json.argtypes = [c_char_p, c_char_p, c_char_p] __get_error = __lib.get_error __get_error.argtypes = [] __get_error.restype = c_char_p - res = __reference_tree_to_json(from_dir.encode(), to_file.encode()) + res = __reference_tree_to_json(internal_cache.encode(), from_dir.encode(), to_file.encode()) except Exception as e: raise ConfigTreeError(e) if res == 1: diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index dec619d3eb..4259909676 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -36,7 +36,8 @@ 'isc_dhclient_dir' : '/run/dhclient', 'dhcp6_client_dir' : '/run/dhcp6c', 'vyos_configdir' : '/opt/vyatta/config', - 'completion_dir' : f'{base_dir}/completion' + 'completion_dir' : f'{base_dir}/completion', + 'ca_certificates' : '/usr/local/share/ca-certificates/vyos' } config_status = '/tmp/vyos-config-status' diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 61da7b74b9..50dd0f3961 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -310,13 +310,15 @@ def set_rps(self, state): rps_cpus = 0 queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*')) if state: + cpu_count = os.cpu_count() + # Enable RPS on all available CPUs except CPU0 which we will not # utilize so the system has one spare core when it's under high # preasure to server other means. Linux sysfs excepts a bitmask # representation of the CPUs which should participate on RPS, we # can enable more CPUs that are physically present on the system, # Linux will clip that internally! - rps_cpus = (1 << os.cpu_count()) -1 + rps_cpus = (1 << cpu_count) - 1 # XXX: we should probably reserve one core when the system is under # high preasure so we can still have a core left for housekeeping. @@ -324,8 +326,19 @@ def set_rps(self, state): # receive packet steering. rps_cpus &= ~1 - for i in range(0, queues): - self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', f'{rps_cpus:x}') + # Convert the bitmask to hexadecimal chunks of 32 bits + # Split the bitmask into chunks of up to 32 bits each + hex_chunks = [] + for i in range(0, cpu_count, 32): + # Extract the next 32-bit chunk + chunk = (rps_cpus >> i) & 0xFFFFFFFF + hex_chunks.append(f"{chunk:08x}") + + # Join the chunks with commas + rps_cpus = ",".join(hex_chunks) + + for i in range(queues): + self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', rps_cpus) # send bitmask representation as hex string without leading '0x' return True diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py index 0541384da9..51559a7c67 100755 --- a/smoketest/scripts/cli/test_container.py +++ b/smoketest/scripts/cli/test_container.py @@ -96,6 +96,31 @@ def test_basic(self): tmp = cmd(f'sudo podman exec -it {cont_name} sysctl kernel.msgmax') self.assertEqual(tmp, 'kernel.msgmax = 4096') + def test_name_server(self): + cont_name = 'dns-test' + net_name = 'net-test' + name_server = '192.168.0.1' + prefix = '192.0.2.0/24' + + self.cli_set(base_path + ['network', net_name, 'prefix', prefix]) + + self.cli_set(base_path + ['name', cont_name, 'image', cont_image]) + self.cli_set(base_path + ['name', cont_name, 'name-server', name_server]) + self.cli_set(base_path + ['name', cont_name, 'network', net_name, 'address', str(ip_interface(prefix).ip + 2)]) + + # verify() - name server has no effect when container network has dns enabled + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['network', net_name, 'no-name-server']) + self.cli_commit() + + n = cmd_to_json(f'sudo podman inspect {cont_name}') + self.assertEqual(n['HostConfig']['Dns'][0], name_server) + + tmp = cmd(f'sudo podman exec -it {cont_name} cat /etc/resolv.conf') + self.assertIn(name_server, tmp) + def test_cpu_limit(self): cont_name = 'c2' diff --git a/smoketest/scripts/cli/test_service_ntp.py b/smoketest/scripts/cli/test_service_ntp.py index 07af4f5eba..469d44eaab 100755 --- a/smoketest/scripts/cli/test_service_ntp.py +++ b/smoketest/scripts/cli/test_service_ntp.py @@ -203,7 +203,7 @@ def test_offload_timestamp_default(self): self.cli_set(base_path + ['server', server, 'ptp']) self.cli_set(base_path + ['ptp', 'port', ptp_port]) - self.cli_set(base_path + ['ptp', 'timestamp', 'interface', 'all']) + self.cli_set(base_path + ['timestamp', 'interface', 'all']) # commit changes self.cli_commit() diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index a7dc33d9d4..594de3eb0b 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -148,6 +148,9 @@ def verify(container): if network_name not in container.get('network', {}): raise ConfigError(f'Container network "{network_name}" does not exist!') + if 'name_server' in container_config and 'no_name_server' not in container['network'][network_name]: + raise ConfigError(f'Setting name server has no effect when attached container network has DNS enabled!') + if 'address' in container_config['network'][network_name]: cnt_ipv4 = 0 cnt_ipv6 = 0 @@ -363,9 +366,14 @@ def generate_run_arguments(name, container_config): if 'allow_host_pid' in container_config: host_pid = '--pid host' + name_server = '' + if 'name_server' in container_config: + for ns in container_config['name_server']: + name_server += f'--dns {ns}' + container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} {sysctl_opt} ' \ f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \ - f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label} {uid} {host_pid}' + f'--name {name} {hostname} {device} {port} {name_server} {volume} {env_opt} {label} {uid} {host_pid}' entrypoint = '' if 'entrypoint' in container_config: diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py index 45e0129a33..acea2c9be1 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -50,6 +50,7 @@ airbag.enable() vyos_certbot_dir = directories['certbot'] +vyos_ca_certificates_dir = directories['ca_certificates'] # keys to recursively search for under specified path sync_search = [ @@ -149,35 +150,15 @@ def get_config(config=None): if len(argv) > 1 and argv[1] == 'certbot_renew': pki['certbot_renew'] = {} - tmp = node_changed(conf, base + ['ca'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) - if tmp: - if 'changed' not in pki: pki.update({'changed':{}}) - pki['changed'].update({'ca' : tmp}) - - tmp = node_changed(conf, base + ['certificate'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) - if tmp: - if 'changed' not in pki: pki.update({'changed':{}}) - pki['changed'].update({'certificate' : tmp}) + changed_keys = ['ca', 'certificate', 'dh', 'key-pair', 'openssh', 'openvpn'] - tmp = node_changed(conf, base + ['dh'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) - if tmp: - if 'changed' not in pki: pki.update({'changed':{}}) - pki['changed'].update({'dh' : tmp}) + for key in changed_keys: + tmp = node_changed(conf, base + [key], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) - tmp = node_changed(conf, base + ['key-pair'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) - if tmp: - if 'changed' not in pki: pki.update({'changed':{}}) - pki['changed'].update({'key_pair' : tmp}) - - tmp = node_changed(conf, base + ['openssh'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) - if tmp: - if 'changed' not in pki: pki.update({'changed':{}}) - pki['changed'].update({'openssh' : tmp}) + if 'changed' not in pki: + pki.update({'changed':{}}) - tmp = node_changed(conf, base + ['openvpn', 'shared-secret'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD) - if tmp: - if 'changed' not in pki: pki.update({'changed':{}}) - pki['changed'].update({'openvpn' : tmp}) + pki['changed'].update({key.replace('-', '_') : tmp}) # We only merge on the defaults of there is a configuration at all if conf.exists(base): @@ -417,10 +398,33 @@ def verify(pki): return None +def cleanup_system_ca(): + if not os.path.exists(vyos_ca_certificates_dir): + os.mkdir(vyos_ca_certificates_dir) + else: + for filename in os.listdir(vyos_ca_certificates_dir): + full_path = os.path.join(vyos_ca_certificates_dir, filename) + if os.path.isfile(full_path): + os.unlink(full_path) + def generate(pki): if not pki: + cleanup_system_ca() return None + # Create or cleanup CA install directory + if 'changed' in pki and 'ca' in pki['changed']: + cleanup_system_ca() + + if 'ca' in pki: + for ca, ca_conf in pki['ca'].items(): + if 'system_install' in ca_conf: + ca_obj = load_certificate(ca_conf['certificate']) + ca_path = os.path.join(vyos_ca_certificates_dir, f'{ca}.crt') + + with open(ca_path, 'w') as f: + f.write(encode_certificate(ca_obj)) + # Certbot renewal only needs to re-trigger the services to load up the # new PEM file if 'certbot_renew' in pki: @@ -487,6 +491,7 @@ def apply(pki): systemd_certbot_name = 'certbot.timer' if not pki: call(f'systemctl stop {systemd_certbot_name}') + call('update-ca-certificates') return None has_certbot = False @@ -504,6 +509,10 @@ def apply(pki): if 'changed' in pki: call_dependents() + # Rebuild ca-certificates bundle + if 'ca' in pki['changed']: + call('update-ca-certificates') + return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_static_multicast.py b/src/conf_mode/protocols_static_multicast.py index d323ceb4f9..c8894fd417 100755 --- a/src/conf_mode/protocols_static_multicast.py +++ b/src/conf_mode/protocols_static_multicast.py @@ -86,10 +86,10 @@ def verify(mroute): if mroute is None: return None - for route in mroute['mroute']: - route = route.split('/') + for mcast_route in mroute['mroute']: + route = mcast_route.split('/') if IPv4Address(route[0]) < IPv4Address('224.0.0.0'): - raise ConfigError(route + " not a multicast network") + raise ConfigError(f'{mcast_route} not a multicast network') def generate(mroute): diff --git a/src/conf_mode/system_config-management.py b/src/conf_mode/system_config-management.py index 8de4e53429..a3ce665120 100755 --- a/src/conf_mode/system_config-management.py +++ b/src/conf_mode/system_config-management.py @@ -39,6 +39,9 @@ def get_config(config=None): def verify(mgmt): + if mgmt is None: + return + d = mgmt.config_dict confirm = d.get('commit_confirm', {}) if confirm.get('action', '') == 'reload' and 'commit_revisions' not in d: diff --git a/src/etc/sudoers.d/vyos b/src/etc/sudoers.d/vyos index 67d7babc44..198b9b9aab 100644 --- a/src/etc/sudoers.d/vyos +++ b/src/etc/sudoers.d/vyos @@ -1,7 +1,8 @@ # # VyOS modifications to sudo configuration # -Defaults syslog_goodpri=info +Defaults !syslog +Defaults !pam_session Defaults env_keep+=VYATTA_* # diff --git a/src/init/vyos-router b/src/init/vyos-router index 8825cc16aa..f8cc875078 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -471,6 +471,12 @@ start () touch /tmp/vyos.smoketest.debug fi + # Cleanup PKI CAs + if [ -d /usr/local/share/ca-certificates/vyos ]; then + rm -f /usr/local/share/ca-certificates/vyos/*.crt + update-ca-certificates >/dev/null 2>&1 + fi + log_action_begin_msg "Mounting VyOS Config" # ensure the vyatta_configdir supports a large number of inodes since # the config hierarchy is often inode-bound (instead of size). diff --git a/src/op_mode/bridge.py b/src/op_mode/bridge.py index e80b1c21d6..c4293a77c3 100755 --- a/src/op_mode/bridge.py +++ b/src/op_mode/bridge.py @@ -23,10 +23,11 @@ from vyos.utils.process import cmd from vyos.utils.process import rc_cmd -from vyos.utils.process import call +from vyos.utils.process import call import vyos.opmode + def _get_json_data(): """ Get bridge data format JSON @@ -43,7 +44,7 @@ def _get_raw_data_summary(): return data_dict -def _get_raw_data_vlan(tunnel:bool=False): +def _get_raw_data_vlan(tunnel: bool = False): """ :returns dict """ @@ -54,14 +55,18 @@ def _get_raw_data_vlan(tunnel:bool=False): data_dict = json.loads(json_data) return data_dict + def _get_raw_data_vni() -> dict: """ :returns dict """ - json_data = cmd(f'bridge --json vni show') + code, json_data = rc_cmd(f'bridge --json vni show') + if code != 0: + raise vyos.opmode.UnconfiguredObject('VNI is not configured') data_dict = json.loads(json_data) return data_dict + def _get_raw_data_fdb(bridge): """Get MAC-address for the bridge brX :returns list @@ -70,7 +75,9 @@ def _get_raw_data_fdb(bridge): # From iproute2 fdb.c, fdb_show() will only exit(-1) in case of # non-existent bridge device; raise error. if code == 255: - raise vyos.opmode.UnconfiguredObject(f"bridge {bridge} does not exist in the system") + raise vyos.opmode.UnconfiguredObject( + f'bridge {bridge} does not exist in the system' + ) data_dict = json.loads(json_data) return data_dict @@ -116,8 +123,8 @@ def _get_formatted_output_summary(data): 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") + member_headers = ['Member', 'State', 'MTU', 'Flags', 'Prio'] + output_members = tabulate(member_entries, member_headers, numalign='left') output_bridge = f"""Bridge interface {bridge}: {output_members} @@ -138,13 +145,14 @@ def _get_formatted_output_vlan(data): vlan_end = vlan_entry.get('vlanEnd') vlan = f'{vlan}-{vlan_end}' flags_raw = vlan_entry.get('flags') - flags = ', '.join(flags_raw if isinstance(flags_raw,list) else "").lower() + 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: @@ -166,10 +174,11 @@ def _get_formatted_output_vlan_tunnel(data): # 200 200 data_entries.append(['', vlan, vni]) - headers = ["Interface", "VLAN", "VNI"] + headers = ['Interface', 'VLAN', 'VNI'] output = tabulate(data_entries, headers) return output + def _get_formatted_output_vni(data): data_entries = [] for entry in data: @@ -182,10 +191,11 @@ def _get_formatted_output_vni(data): vlan = f'{vlan}-{vlan_end}' data_entries.append([interface, vlan]) - headers = ["Interface", "VNI"] + headers = ['Interface', 'VNI'] output = tabulate(data_entries, headers) return output + def _get_formatted_output_fdb(data): data_entries = [] for entry in data: @@ -195,8 +205,8 @@ def _get_formatted_output_fdb(data): flags = ','.join(entry['flags']) data_entries.append([interface, mac, state, flags]) - headers = ["Interface", "Mac address", "State", "Flags"] - output = tabulate(data_entries, headers, numalign="left") + headers = ['Interface', 'Mac address', 'State', 'Flags'] + output = tabulate(data_entries, headers, numalign='left') return output @@ -209,28 +219,33 @@ def _get_formatted_output_mdb(data): state = mdb_entry.get('state') flags = ','.join(mdb_entry.get('flags')) data_entries.append([interface, group, state, flags]) - headers = ["Interface", "Group", "State", "Flags"] + headers = ['Interface', 'Group', 'State', 'Flags'] 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 = 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() if raw: @@ -249,6 +264,7 @@ def show_vlan(raw: bool, tunnel: typing.Optional[bool]): else: return _get_formatted_output_vlan(bridge_vlan) + def show_vni(raw: bool): bridge_vni = _get_raw_data_vni() if raw: @@ -256,6 +272,7 @@ def show_vni(raw: bool): else: return _get_formatted_output_vni(bridge_vni) + def show_fdb(raw: bool, interface: str): fdb_data = _get_raw_data_fdb(interface) if raw: @@ -271,6 +288,7 @@ 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: @@ -283,6 +301,7 @@ def show_detail(raw: bool, nexthop_group: typing.Optional[bool], interface: str) else: return _get_bridge_detail(interface) + if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__])