#!/usr/bin/env python3 # # Copyright (C) 2024 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 . import sys import json import vyos.opmode from vyos.utils.process import cmd def _get_version_data(): from vyos.version import get_version_data return get_version_data() def _get_uptime(): from vyos.utils.system import get_uptime_seconds return get_uptime_seconds() def _get_load_average(): from vyos.utils.system import get_load_averages return get_load_averages() def _get_cpus(): from vyos.utils.cpu import get_cpus return get_cpus() def _get_process_stats(): return cmd('top --iterations 1 --batch-mode --accum-time-toggle') def _get_storage(): from vyos.utils.disk import get_persistent_storage_stats return get_persistent_storage_stats() def _get_devices(): devices = {} devices["pci"] = cmd("lspci") devices["usb"] = cmd("lsusb") return devices def _get_memory(): from vyos.utils.file import read_file return read_file("/proc/meminfo") def _get_processes(): res = cmd("ps aux") return res def _get_interrupts(): from vyos.utils.file import read_file interrupts = read_file("/proc/interrupts") softirqs = read_file("/proc/softirqs") return (interrupts, softirqs) def _get_partitions(): # XXX: as of parted 3.5, --json is completely broken # and cannot be used (outputs malformed JSON syntax) res = cmd(f"parted --list") return res def _get_running_config(): from os import getpid from vyos.configsession import ConfigSession from vyos.utils.strip_config import strip_config_source c = ConfigSession(getpid()) return strip_config_source(c.show_config([])) def _get_boot_config(): from vyos.utils.file import read_file from vyos.utils.strip_config import strip_config_source config = read_file('/opt/vyatta/etc/config.boot.default') return strip_config_source(config) def _get_config_scripts(): from os import listdir from os.path import join from vyos.utils.file import read_file scripts = [] dir = '/config/scripts' for f in listdir(dir): script = {} path = join(dir, f) data = read_file(path) script["path"] = path script["data"] = data scripts.append(script) return scripts def _get_nic_data(): from vyos.utils.process import ip_cmd link_data = ip_cmd("link show") addr_data = ip_cmd("address show") return link_data, addr_data def _get_routes(proto): from json import loads from vyos.utils.process import ip_cmd # Only include complete routing tables if they are not too large # At the moment "too large" is arbitrarily set to 1000 MAX_ROUTES = 1000 data = {} summary = cmd(f"vtysh -c 'show {proto} route summary json'") summary = loads(summary) data["summary"] = summary if summary["routesTotal"] < MAX_ROUTES: rib_routes = cmd(f"vtysh -c 'show {proto} route json'") data["routes"] = loads(rib_routes) if summary["routesTotalFib"] < MAX_ROUTES: ip_proto = "-4" if proto == "ip" else "-6" fib_routes = ip_cmd(f"{ip_proto} route show") data["fib_routes"] = fib_routes return data def _get_ip_routes(): return _get_routes("ip") def _get_ipv6_routes(): return _get_routes("ipv6") def _get_ospfv2(): # XXX: OSPF output when it's not configured is an empty string, # which is not a valid JSON output = cmd("vtysh -c 'show ip ospf json'") if output: return json.loads(output) else: return {} def _get_ospfv3(): output = cmd("vtysh -c 'show ipv6 ospf6 json'") if output: return json.loads(output) else: return {} def _get_bgp_summary(): output = cmd("vtysh -c 'show bgp summary json'") return json.loads(output) def _get_isis(): output = cmd("vtysh -c 'show isis summary json'") if output: return json.loads(output) else: return {} def _get_arp_table(): from json import loads from vyos.utils.process import cmd arp_table = cmd("ip --json -4 neighbor show") return loads(arp_table) def _get_ndp_table(): from json import loads arp_table = cmd("ip --json -6 neighbor show") return loads(arp_table) def _get_nftables_rules(): nft_rules = cmd("nft list ruleset") return nft_rules def _get_connections(): from vyos.utils.process import cmd return cmd("ss -apO") def _get_system_packages(): from re import split from vyos.utils.process import cmd dpkg_out = cmd(''' dpkg-query -W -f='${Package} ${Version} ${Architecture} ${db:Status-Abbrev}\n' ''') pkg_lines = split(r'\n+', dpkg_out) # Discard the header, it's five lines long pkg_lines = pkg_lines[5:] pkgs = [] for pl in pkg_lines: parts = split(r'\s+', pl) pkg = {} pkg["name"] = parts[0] pkg["version"] = parts[1] pkg["architecture"] = parts[2] pkg["status"] = parts[3] pkgs.append(pkg) return pkgs def _get_image_info(): from vyos.system.image import get_images_details return get_images_details() def _get_kernel_modules(): from vyos.utils.kernel import lsmod return lsmod() def _get_last_logs(max): from systemd import journal r = journal.Reader() # Set the reader to use logs from the current boot r.this_boot() # Jump to the last logs r.seek_tail() # Only get logs of INFO level or more urgent r.log_level(journal.LOG_INFO) # Retrieve the entries entries = [] # I couldn't find a way to just get last/first N entries, # so we'll use the cursor directly. num = max while num >= 0: je = r.get_previous() entry = {} # Extract the most useful and serializable fields entry["timestamp"] = je.get("SYSLOG_TIMESTAMP") entry["pid"] = je.get("SYSLOG_PID") entry["identifier"] = je.get("SYSLOG_IDENTIFIER") entry["facility"] = je.get("SYSLOG_FACILITY") entry["systemd_unit"] = je.get("_SYSTEMD_UNIT") entry["message"] = je.get("MESSAGE") entries.append(entry) num = num - 1 return entries def _get_raw_data(): data = {} # VyOS-specific information data["vyos"] = {} ## The equivalent of "show version" from vyos.version import get_version_data data["vyos"]["version"] = _get_version_data() ## Installed images data["vyos"]["images"] = _get_image_info() # System information data["system"] = {} ## Uptime and load averages data["system"]["uptime"] = _get_uptime() data["system"]["load_average"] = _get_load_average() data["system"]["process_stats"] = _get_process_stats() ## Debian packages data["system"]["packages"] = _get_system_packages() ## Kernel modules data["system"]["kernel"] = {} data["system"]["kernel"]["modules"] = _get_kernel_modules() ## Processes data["system"]["processes"] = _get_processes() ## Interrupts interrupts, softirqs = _get_interrupts() data["system"]["interrupts"] = interrupts data["system"]["softirqs"] = softirqs # Hardware data["hardware"] = {} data["hardware"]["cpu"] = _get_cpus() data["hardware"]["storage"] = _get_storage() data["hardware"]["partitions"] = _get_partitions() data["hardware"]["devices"] = _get_devices() data["hardware"]["memory"] = _get_memory() # Configuration data data["vyos"]["config"] = {} ## Running config text ## We do not encode it so that it's possible to ## see exactly what the user sees and detect any syntax/rendering anomalies — ## exporting the config to JSON could obscure them data["vyos"]["config"]["running"] = _get_running_config() ## Default boot config, exactly as in /config/config.boot ## It may be different from the running config ## _and_ may have its own syntax quirks that may point at bugs data["vyos"]["config"]["boot"] = _get_boot_config() ## Config scripts data["vyos"]["config"]["scripts"] = _get_config_scripts() # Network interfaces data["network_interfaces"] = {} # Interface data from iproute2 link_data, addr_data = _get_nic_data() data["network_interfaces"]["links"] = link_data data["network_interfaces"]["addresses"] = addr_data # Routing table data data["routing"] = {} data["routing"]["ip"] = _get_ip_routes() data["routing"]["ipv6"] = _get_ipv6_routes() # Routing protocols data["routing"]["ip"]["ospf"] = _get_ospfv2() data["routing"]["ipv6"]["ospfv3"] = _get_ospfv3() data["routing"]["bgp"] = {} data["routing"]["bgp"]["summary"] = _get_bgp_summary() data["routing"]["isis"] = _get_isis() # ARP and NDP neighbor tables data["neighbor_tables"] = {} data["neighbor_tables"]["arp"] = _get_arp_table() data["neighbor_tables"]["ndp"] = _get_ndp_table() # nftables config data["nftables_rules"] = _get_nftables_rules() # All connections data["connections"] = _get_connections() # Logs data["last_logs"] = _get_last_logs(1000) return data def show(raw: bool): data = _get_raw_data() if raw: return data else: raise vyos.opmode.UnsupportedOperation("Formatted output is not implemented yet") if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) if res: print(res) except (ValueError, vyos.opmode.Error) as e: print(e) sys.exit(1) except (KeyboardInterrupt, BrokenPipeError): sys.exit(1)