diff options
31 files changed, 651 insertions, 257 deletions
diff --git a/data/templates/ids/fastnetmon.j2 b/data/templates/ids/fastnetmon.j2 index c482002fa..005338836 100644 --- a/data/templates/ids/fastnetmon.j2 +++ b/data/templates/ids/fastnetmon.j2 @@ -1,21 +1,22 @@ # enable this option if you want to send logs to local syslog facility +logging:logging_level = debug logging:local_syslog_logging = on # list of all your networks in CIDR format -networks_list_path = /etc/networks_list - -# list networks in CIDR format which will be not monitored for attacks -white_list_path = /etc/networks_whitelist +networks_list_path = /run/fastnetmon/networks_list # Enable/Disable any actions in case of attack enable_ban = on +enable_ban_ipv6 = on ## How many packets will be collected from attack traffic ban_details_records_count = 500 ## How long (in seconds) we should keep an IP in blocked state ## If you set 0 here it completely disables unban capability -ban_time = 1900 +{% if ban_time is vyos_defined %} +ban_time = {{ ban_time }} +{% endif %} # Check if the attack is still active, before triggering an unban callback with this option # If the attack is still active, check each run of the unban watchdog diff --git a/data/templates/ids/fastnetmon_networks_list.j2 b/data/templates/ids/fastnetmon_networks_list.j2 index 1c81180be..5f1b3ba4d 100644 --- a/data/templates/ids/fastnetmon_networks_list.j2 +++ b/data/templates/ids/fastnetmon_networks_list.j2 @@ -1,6 +1,4 @@ -{% if network is vyos_defined(var_type=str) %} -{{ network }} -{% else %} +{% if network is vyos_defined() %} {% for net in network %} {{ net }} {% endfor %} diff --git a/data/templates/router-advert/radvd.conf.j2 b/data/templates/router-advert/radvd.conf.j2 index ed15b32f0..a464795ad 100644 --- a/data/templates/router-advert/radvd.conf.j2 +++ b/data/templates/router-advert/radvd.conf.j2 @@ -50,6 +50,8 @@ interface {{ iface }} { AdvValidLifetime {{ prefix_options.valid_lifetime }}; AdvOnLink {{ 'off' if prefix_options.no_on_link_flag is vyos_defined else 'on' }}; AdvPreferredLifetime {{ prefix_options.preferred_lifetime }}; + DeprecatePrefix {{ 'on' if prefix_options.deprecate_prefix is vyos_defined else 'off' }}; + DecrementLifetimes {{ 'on' if prefix_options.decrement_lifetime is vyos_defined else 'off' }}; }; {% endfor %} {% endif %} diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index da935bd4c..81121bfe9 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -88,8 +88,10 @@ fi # Remove unwanted daemon files from /etc # conntackd +# pmacct +# fastnetmon DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/conntrackd - /etc/default/pmacctd /etc/pmacct" + /etc/default/pmacctd /etc/pmacct /etc/networks_list /etc/fastnetmon.conf" for file in $DELETE; do if [ -f ${file} ]; then rm -f ${file} diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index 0d0ada591..15c2beefa 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -639,7 +639,7 @@ </leafNode> <leafNode name="prefix-len"> <properties> - <help>IP prefix-length to match</help> + <help>IP prefix-length to match (cannot be used for BGP routes)</help> <valueHelp> <format>u32:0-32</format> <description>Prefix length</description> @@ -809,7 +809,7 @@ </leafNode> <leafNode name="prefix-len"> <properties> - <help>IPv6 prefix-length to match</help> + <help>IPv6 prefix-length to match (cannot be used for BGP routes)</help> <valueHelp> <format>u32:0-128</format> <description>Prefix length</description> diff --git a/interface-definitions/service-ids-ddos-protection.xml.in b/interface-definitions/service-ids-ddos-protection.xml.in index 5e65d3106..a176d6fff 100644 --- a/interface-definitions/service-ids-ddos-protection.xml.in +++ b/interface-definitions/service-ids-ddos-protection.xml.in @@ -18,6 +18,19 @@ <help>Path to fastnetmon alert script</help> </properties> </leafNode> + <leafNode name="ban-time"> + <properties> + <help>How long we should keep an IP in blocked state</help> + <valueHelp> + <format>u32:1-4294967294</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967294"/> + </constraint> + </properties> + <defaultValue>1900</defaultValue> + </leafNode> <leafNode name="direction"> <properties> <help>Direction for processing traffic</help> @@ -55,13 +68,18 @@ </node> <leafNode name="network"> <properties> - <help>Define monitoring networks</help> + <help>Specify IPv4 and IPv6 networks which belong to you</help> <valueHelp> <format>ipv4net</format> - <description>Processed network</description> + <description>Your IPv4 prefix(es)</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>Your IPv6 prefix(es)</description> </valueHelp> <constraint> <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> </constraint> <multi/> </properties> diff --git a/interface-definitions/service-router-advert.xml.in b/interface-definitions/service-router-advert.xml.in index 258b7b749..87ec512d6 100644 --- a/interface-definitions/service-router-advert.xml.in +++ b/interface-definitions/service-router-advert.xml.in @@ -249,6 +249,18 @@ <valueless/> </properties> </leafNode> + <leafNode name="deprecate-prefix"> + <properties> + <help>Upon shutdown, this option will deprecate the prefix by announcing it in the shutdown RA</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="decrement-lifetime"> + <properties> + <help>Lifetime is decremented by the number of seconds since the last RA - use in conjunction with a DHCPv6-PD prefix</help> + <valueless/> + </properties> + </leafNode> <leafNode name="preferred-lifetime"> <properties> <help>Time in seconds that the prefix will remain preferred</help> diff --git a/op-mode-definitions/ipv4-route.xml.in b/op-mode-definitions/ipv4-route.xml.in index 8f001d5bb..660b34496 100644 --- a/op-mode-definitions/ipv4-route.xml.in +++ b/op-mode-definitions/ipv4-route.xml.in @@ -39,7 +39,7 @@ <list><x.x.x.x></list> </completionHelp> </properties> - <command>sudo ip neigh flush to "$5"</command> + <command>sudo ${vyos_op_scripts_dir}/neighbor.py reset --family inet --address "$5"</command> </tagNode> <tagNode name="interface"> <properties> @@ -48,8 +48,14 @@ <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> </properties> - <command>sudo ip neigh flush dev "$5"</command> + <command>sudo ${vyos_op_scripts_dir}/neighbor.py reset --family inet --interface "$5"</command> </tagNode> + <node name="table"> + <properties> + <help>Flush the ARP cache completely</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/neighbor.py reset --family inet</command> + </node> </children> </node> <node name="route"> diff --git a/op-mode-definitions/ipv6-route.xml.in b/op-mode-definitions/ipv6-route.xml.in index 4f8792f9f..d75caf308 100644 --- a/op-mode-definitions/ipv6-route.xml.in +++ b/op-mode-definitions/ipv6-route.xml.in @@ -20,7 +20,7 @@ <properties> <help>Show IPv6 neighbor (NDP) table</help> </properties> - <command>${vyos_op_scripts_dir}/show_neigh.py --family inet6</command> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet6</command> <children> <tagNode name="interface"> <properties> @@ -29,7 +29,7 @@ <script>${vyos_completion_dir}/list_interfaces.py -b</script> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_neigh.py --family inet6 --interface "$5"</command> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet6 --interface "$5"</command> </tagNode> <tagNode name="state"> <properties> @@ -38,7 +38,7 @@ <list>reachable stale failed permanent</list> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_neigh.py --family inet6 --state "$5"</command> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet6 --state "$5"</command> </tagNode> </children> </node> diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index f5e0ede59..99d64acb3 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -14,6 +14,19 @@ </properties> <command>grc journalctl --no-hostname --follow --boot</command> </node> + <node name="ids"> + <properties> + <help>Monitor log for Intrusion Detection System</help> + </properties> + <children> + <leafNode name="ddos-protection"> + <properties> + <help>Monitor last lines of DDOS protection</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit fastnetmon.service</command> + </leafNode> + </children> + </node> <node name="dhcp"> <properties> <help>Monitor last lines of Dynamic Host Control Protocol (DHCP)</help> @@ -111,7 +124,7 @@ </node> <node name="protocol"> <properties> - <help>Monitor log for Routing Protocols</help> + <help>Monitor log for Routing Protocol</help> </properties> <children> <leafNode name="ospf"> diff --git a/op-mode-definitions/show-arp.xml.in b/op-mode-definitions/show-arp.xml.in index 58cc6e45e..8662549fc 100644 --- a/op-mode-definitions/show-arp.xml.in +++ b/op-mode-definitions/show-arp.xml.in @@ -6,7 +6,7 @@ <properties> <help>Show Address Resolution Protocol (ARP) information</help> </properties> - <command>${vyos_op_scripts_dir}/show_neigh.py --family inet</command> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet</command> <children> <tagNode name="interface"> <properties> @@ -15,7 +15,7 @@ <script>${vyos_completion_dir}/list_interfaces.py -b</script> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_neigh.py --family inet --interface "$4"</command> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet --interface "$4"</command> </tagNode> </children> </node> diff --git a/op-mode-definitions/show-conntrack.xml.in b/op-mode-definitions/show-conntrack.xml.in index 792623d7d..8d921e6a5 100644 --- a/op-mode-definitions/show-conntrack.xml.in +++ b/op-mode-definitions/show-conntrack.xml.in @@ -16,7 +16,13 @@ <properties> <help>Show conntrack entries for IPv4 protocol</help> </properties> - <command>sudo ${vyos_op_scripts_dir}/show_conntrack.py</command> + <command>sudo ${vyos_op_scripts_dir}/conntrack.py show --family inet</command> + </node> + <node name="ipv6"> + <properties> + <help>Show conntrack entries for IPv6 protocol</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/conntrack.py show --family inet6</command> </node> </children> </node> diff --git a/op-mode-definitions/show-hardware.xml.in b/op-mode-definitions/show-hardware.xml.in index 20fdd753d..ebd806ba5 100644 --- a/op-mode-definitions/show-hardware.xml.in +++ b/op-mode-definitions/show-hardware.xml.in @@ -9,21 +9,21 @@ <children> <node name="cpu"> <properties> - <help>Show CPU info</help> + <help>Show CPU informaion</help> </properties> - <command>lscpu</command> + <command>${vyos_op_scripts_dir}/cpu.py show</command> <children> <node name="detail"> <properties> - <help> Show system CPU details</help> + <help>Show system CPU details</help> </properties> <command>cat /proc/cpuinfo</command> </node> <node name="summary"> <properties> - <help>Show system CPUs</help> + <help>Show system CPUs summary</help> </properties> - <command>${vyos_op_scripts_dir}/cpu_summary.py</command> + <command>${vyos_op_scripts_dir}/cpu.py show_summary</command> </node> </children> </node> diff --git a/op-mode-definitions/show-ip.xml.in b/op-mode-definitions/show-ip.xml.in index d342ac192..0751c50cb 100644 --- a/op-mode-definitions/show-ip.xml.in +++ b/op-mode-definitions/show-ip.xml.in @@ -11,7 +11,7 @@ <properties> <help>Show IPv4 neighbor (ARP) table</help> </properties> - <command>${vyos_op_scripts_dir}/show_neigh.py --family inet</command> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet</command> <children> <tagNode name="interface"> <properties> @@ -20,7 +20,7 @@ <script>${vyos_completion_dir}/list_interfaces.py -b</script> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_neigh.py --family inet --interface "$5"</command> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet --interface "$5"</command> </tagNode> <tagNode name="state"> <properties> @@ -29,7 +29,7 @@ <list>reachable stale failed permanent</list> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_neigh.py --family inet --state "$5"</command> + <command>${vyos_op_scripts_dir}/neighbor.py show --family inet --state "$5"</command> </tagNode> </children> </node> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 76879e5d6..ab9e128a8 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -32,6 +32,19 @@ </properties> <command>journalctl --no-hostname --boot --unit conntrackd.service</command> </leafNode> + <node name="ids"> + <properties> + <help>Show log for for Intrusion Detection System</help> + </properties> + <children> + <leafNode name="ddos-protection"> + <properties> + <help>Show log for DDOS protection</help> + </properties> + <command>journalctl --no-hostname --boot --unit fastnetmon.service</command> + </leafNode> + </children> + </node> <node name="dhcp"> <properties> <help>Show log for Dynamic Host Control Protocol (DHCP)</help> @@ -243,7 +256,7 @@ </node> <node name="protocol"> <properties> - <help>Show log for Routing Protocols</help> + <help>Show log for Routing Protocol</help> </properties> <children> <leafNode name="ospf"> diff --git a/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in index 68b473bc1..6f05d0c12 100644 --- a/op-mode-definitions/show-system.xml.in +++ b/op-mode-definitions/show-system.xml.in @@ -104,7 +104,7 @@ <properties> <help>Show system memory usage</help> </properties> - <command>${vyos_op_scripts_dir}/show_ram.py</command> + <command>${vyos_op_scripts_dir}/memory.py show</command> <children> <leafNode name="cache"> <properties> diff --git a/op-mode-definitions/show-version.xml.in b/op-mode-definitions/show-version.xml.in index 8b7cc7e58..d9c4738af 100644 --- a/op-mode-definitions/show-version.xml.in +++ b/op-mode-definitions/show-version.xml.in @@ -6,13 +6,13 @@ <properties> <help>Show system version information</help> </properties> - <command>sudo ${vyos_op_scripts_dir}/show_version.py</command> + <command>sudo ${vyos_op_scripts_dir}/version.py show</command> <children> <leafNode name="funny"> <properties> <help>Show system version and some fun stuff</help> </properties> - <command>sudo ${vyos_op_scripts_dir}/show_version.py --funny</command> + <command>sudo ${vyos_op_scripts_dir}/version.py show --funny</command> </leafNode> <leafNode name="all"> <properties> diff --git a/python/vyos/cpu.py b/python/vyos/cpu.py index a0ef864be..488ae79fb 100644 --- a/python/vyos/cpu.py +++ b/python/vyos/cpu.py @@ -32,7 +32,8 @@ import re def _read_cpuinfo(): with open('/proc/cpuinfo', 'r') as f: - return f.readlines() + lines = f.read().strip() + return re.split(r'\n+', lines) def _split_line(l): l = l.strip() diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py new file mode 100644 index 000000000..0af4359c6 --- /dev/null +++ b/python/vyos/opmode.py @@ -0,0 +1,128 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import re +import sys +import typing + + +def _is_op_mode_function_name(name): + if re.match(r"^(show|clear|reset|restart)", name): + return True + else: + return False + +def _is_show(name): + if re.match(r"^show", name): + return True + else: + return False + +def _get_op_mode_functions(module): + from inspect import getmembers, isfunction + + # Get all functions in that module + funcs = getmembers(module, isfunction) + + # getmembers returns (name, func) tuples + funcs = list(filter(lambda ft: _is_op_mode_function_name(ft[0]), funcs)) + + funcs_dict = {} + for (name, thunk) in funcs: + funcs_dict[name] = thunk + + return funcs_dict + +def _is_optional_type(t): + # Optional[t] is internally an alias for Union[t, NoneType] + # and there's no easy way to get union members it seems + if (type(t) == typing._UnionGenericAlias): + if (len(t.__args__) == 2): + if t.__args__[1] == type(None): + return True + + return False + +def _get_arg_type(t): + """ Returns the type itself if it's a primitive type, + or the "real" type of typing.Optional + + Doesn't work with anything else at the moment! + """ + if _is_optional_type(t): + return t.__args__[0] + else: + return t + +def run(module): + from argparse import ArgumentParser + + functions = _get_op_mode_functions(module) + + parser = ArgumentParser() + subparsers = parser.add_subparsers(dest="subcommand") + + for function_name in functions: + subparser = subparsers.add_parser(function_name, help=functions[function_name].__doc__) + + type_hints = typing.get_type_hints(functions[function_name]) + for opt in type_hints: + th = type_hints[opt] + + if _get_arg_type(th) == bool: + subparser.add_argument(f"--{opt}", action='store_true') + else: + if _is_optional_type(th): + subparser.add_argument(f"--{opt}", type=_get_arg_type(th), default=None) + else: + subparser.add_argument(f"--{opt}", type=_get_arg_type(th), required=True) + + # Get options as a dict rather than a namespace, + # so that we can modify it and pack for passing to functions + args = vars(parser.parse_args()) + + if not args["subcommand"]: + print("Subcommand required!") + parser.print_usage() + sys.exit(1) + + function_name = args["subcommand"] + func = functions[function_name] + + # Remove the subcommand from the arguments, + # it would cause an extra argument error when we pass the dict to a function + del args["subcommand"] + + # Show commands must always get the "raw" argument, + # but other commands (clear/reset/restart) should not, + # because they produce no output and it makes no sense for them. + if ("raw" not in args) and _is_show(function_name): + args["raw"] = False + + if re.match(r"^show", function_name): + # Show commands are slightly special: + # they may return human-formatted output + # or a raw dict that we need to serialize in JSON for printing + res = func(**args) + if not args["raw"]: + return res + else: + from json import dumps + return dumps(res, indent=4) + else: + # Other functions should not return anything, + # although they may print their own warnings or status messages + func(**args) + diff --git a/smoketest/scripts/cli/test_service_ids.py b/smoketest/scripts/cli/test_service_ids.py index 18f1b8ec5..8720362ba 100755 --- a/smoketest/scripts/cli/test_service_ids.py +++ b/smoketest/scripts/cli/test_service_ids.py @@ -24,7 +24,8 @@ from vyos.util import process_named_running from vyos.util import read_file PROCESS_NAME = 'fastnetmon' -FASTNETMON_CONF = '/etc/fastnetmon.conf' +FASTNETMON_CONF = '/run/fastnetmon/fastnetmon.conf' +NETWORKS_CONF = '/run/fastnetmon/networks_list' base_path = ['service', 'ids', 'ddos-protection'] class TestServiceIDS(VyOSUnitTestSHIM.TestCase): @@ -48,7 +49,7 @@ class TestServiceIDS(VyOSUnitTestSHIM.TestCase): self.assertFalse(process_named_running(PROCESS_NAME)) def test_fastnetmon(self): - networks = ['10.0.0.0/24', '10.5.5.0/24'] + networks = ['10.0.0.0/24', '10.5.5.0/24', '2001:db8:10::/64', '2001:db8:20::/64'] interfaces = ['eth0', 'eth1'] fps = '3500' mbps = '300' @@ -86,9 +87,18 @@ class TestServiceIDS(VyOSUnitTestSHIM.TestCase): self.assertIn(f'threshold_mbps = {mbps}', config) self.assertIn(f'ban_for_pps = on', config) self.assertIn(f'threshold_pps = {pps}', config) + # default + self.assertIn(f'enable_ban = on', config) + self.assertIn(f'enable_ban_ipv6 = on', config) + self.assertIn(f'ban_time = 1900', config) tmp = ','.join(interfaces) self.assertIn(f'interfaces = {tmp}', config) + + network_config = read_file(NETWORKS_CONF) + for tmp in networks: + self.assertIn(f'{tmp}', network_config) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py index 1168c05cd..873be7df0 100755 --- a/smoketest/scripts/cli/test_service_router-advert.py +++ b/smoketest/scripts/cli/test_service_router-advert.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-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 @@ -23,11 +23,13 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.util import read_file from vyos.util import process_named_running +PROCESS_NAME = 'radvd' RADVD_CONF = '/run/radvd/radvd.conf' interface = 'eth1' base_path = ['service', 'router-advert', 'interface', interface] address_base = ['interfaces', 'ethernet', interface, 'address'] +prefix = '::/64' def get_config_value(key): tmp = read_file(RADVD_CONF) @@ -35,18 +37,36 @@ def get_config_value(key): return tmp[0].split()[0].replace(';','') class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self.cli_set(address_base + ['2001:db8::1/64']) + + @classmethod + def setUpClass(cls): + super(TestServiceRADVD, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, ['service', 'router-advert']) + + cls.cli_set(cls, address_base + ['2001:db8::1/64']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, address_base) + super(TestServiceRADVD, cls).tearDownClass() def tearDown(self): - self.cli_delete(address_base) + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(base_path) self.cli_commit() + # Check for no longer running process + self.assertFalse(process_named_running(PROCESS_NAME)) + def test_common(self): - self.cli_set(base_path + ['prefix', '::/64', 'no-on-link-flag']) - self.cli_set(base_path + ['prefix', '::/64', 'no-autonomous-flag']) - self.cli_set(base_path + ['prefix', '::/64', 'valid-lifetime', 'infinity']) + self.cli_set(base_path + ['prefix', prefix, 'no-on-link-flag']) + self.cli_set(base_path + ['prefix', prefix, 'no-autonomous-flag']) + self.cli_set(base_path + ['prefix', prefix, 'valid-lifetime', 'infinity']) self.cli_set(base_path + ['other-config-flag']) # commit changes @@ -57,7 +77,7 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): self.assertEqual(tmp, interface) tmp = get_config_value('prefix') - self.assertEqual(tmp, '::/64') + self.assertEqual(tmp, prefix) tmp = get_config_value('AdvOtherConfigFlag') self.assertEqual(tmp, 'on') @@ -88,15 +108,19 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): tmp = get_config_value('AdvOnLink') self.assertEqual(tmp, 'off') - # Check for running process - self.assertTrue(process_named_running('radvd')) + tmp = get_config_value('DeprecatePrefix') + self.assertEqual(tmp, 'off') + + tmp = get_config_value('DecrementLifetimes') + self.assertEqual(tmp, 'off') + def test_dns(self): nameserver = ['2001:db8::1', '2001:db8::2'] dnssl = ['vyos.net', 'vyos.io'] ns_lifetime = '599' - self.cli_set(base_path + ['prefix', '::/64', 'valid-lifetime', 'infinity']) + self.cli_set(base_path + ['prefix', prefix, 'valid-lifetime', 'infinity']) self.cli_set(base_path + ['other-config-flag']) for ns in nameserver: @@ -127,5 +151,21 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): self.assertIn(tmp, config) + def test_deprecate_prefix(self): + self.cli_set(base_path + ['prefix', prefix, 'valid-lifetime', 'infinity']) + self.cli_set(base_path + ['prefix', prefix, 'deprecate-prefix']) + self.cli_set(base_path + ['prefix', prefix, 'decrement-lifetime']) + + # commit changes + self.cli_commit() + + config = read_file(RADVD_CONF) + + tmp = get_config_value('DeprecatePrefix') + self.assertEqual(tmp, 'on') + + tmp = get_config_value('DecrementLifetimes') + self.assertEqual(tmp, 'on') + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py index ae7e582ec..615658c84 100755 --- a/src/conf_mode/service_ids_fastnetmon.py +++ b/src/conf_mode/service_ids_fastnetmon.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# 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 @@ -19,14 +19,16 @@ import os from sys import exit from vyos.config import Config -from vyos import ConfigError -from vyos.util import call +from vyos.configdict import dict_merge from vyos.template import render +from vyos.util import call +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() -config_file = r'/etc/fastnetmon.conf' -networks_list = r'/etc/networks_list' +config_file = r'/run/fastnetmon/fastnetmon.conf' +networks_list = r'/run/fastnetmon/networks_list' def get_config(config=None): if config: @@ -34,50 +36,54 @@ def get_config(config=None): else: conf = Config() base = ['service', 'ids', 'ddos-protection'] + if not conf.exists(base): + return None + fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = defaults(base) + fastnetmon = dict_merge(default_values, fastnetmon) + return fastnetmon def verify(fastnetmon): if not fastnetmon: return None - if not "mode" in fastnetmon: - raise ConfigError('ddos-protection mode is mandatory!') - - if not "network" in fastnetmon: - raise ConfigError('Required define network!') + if 'mode' not in fastnetmon: + raise ConfigError('Specify operating mode!') - if not "listen_interface" in fastnetmon: - raise ConfigError('Define listen-interface is mandatory!') + if 'listen_interface' not in fastnetmon: + raise ConfigError('Specify interface(s) for traffic capture') - if "alert_script" in fastnetmon: - if os.path.isfile(fastnetmon["alert_script"]): + if 'alert_script' in fastnetmon: + if os.path.isfile(fastnetmon['alert_script']): # Check script permissions - if not os.access(fastnetmon["alert_script"], os.X_OK): - raise ConfigError('Script {0} does not have permissions for execution'.format(fastnetmon["alert_script"])) + if not os.access(fastnetmon['alert_script'], os.X_OK): + raise ConfigError('Script "{alert_script}" is not executable!'.format(fastnetmon['alert_script'])) else: - raise ConfigError('File {0} does not exists!'.format(fastnetmon["alert_script"])) + raise ConfigError('File "{alert_script}" does not exists!'.format(fastnetmon)) def generate(fastnetmon): if not fastnetmon: - if os.path.isfile(config_file): - os.unlink(config_file) - if os.path.isfile(networks_list): - os.unlink(networks_list) + for file in [config_file, networks_list]: + if os.path.isfile(file): + os.unlink(file) - return + return None render(config_file, 'ids/fastnetmon.j2', fastnetmon) render(networks_list, 'ids/fastnetmon_networks_list.j2', fastnetmon) - return None def apply(fastnetmon): + systemd_service = 'fastnetmon.service' if not fastnetmon: # Stop fastnetmon service if removed - call('systemctl stop fastnetmon.service') + call(f'systemctl stop {systemd_service}') else: - call('systemctl restart fastnetmon.service') + call(f'systemctl reload-or-restart {systemd_service}') return None diff --git a/src/etc/systemd/system/fastnetmon.service.d/override.conf b/src/etc/systemd/system/fastnetmon.service.d/override.conf new file mode 100644 index 000000000..8f7f3774f --- /dev/null +++ b/src/etc/systemd/system/fastnetmon.service.d/override.conf @@ -0,0 +1,12 @@ +[Unit] +RequiresMountsFor=/run +ConditionPathExists=/run/fastnetmon/fastnetmon.conf +After= +After=vyos-router.service + +[Service] +Type=simple +WorkingDirectory=/run/fastnetmon +PIDFile=/run/fastnetmon/fastnetmon.pid +ExecStart= +ExecStart=/usr/sbin/fastnetmon --configuration_file /run/fastnetmon/fastnetmon.conf 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/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/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/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_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()) |