diff options
21 files changed, 321 insertions, 44 deletions
| diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json index abf562984..3b2599790 100644 --- a/data/op-mode-standardized.json +++ b/data/op-mode-standardized.json @@ -14,6 +14,7 @@  "memory.py",  "nat.py",  "neighbor.py", +"nhrp.py",  "openconnect.py",  "openvpn.py",  "route.py", diff --git a/data/templates/accel-ppp/config_ip_pool.j2 b/data/templates/accel-ppp/config_ip_pool.j2 index 0bef4ad69..f7511e445 100644 --- a/data/templates/accel-ppp/config_ip_pool.j2 +++ b/data/templates/accel-ppp/config_ip_pool.j2 @@ -11,4 +11,14 @@ gw-ip-address={{ gateway_address }}  {{ subnet }}  {%         endfor %}  {%     endif %} -{% endif %} +{%     if client_ip_pool.name is vyos_defined %} +{%         for pool, pool_config in client_ip_pool.name.items() %} +{%             if pool_config.subnet is vyos_defined %} +{{ pool_config.subnet }},name={{ pool }} +{%             endif %} +{%             if pool_config.gateway_address is vyos_defined %} +gw-ip-address={{ pool_config.gateway_address }} +{%             endif %} +{%         endfor %} +{%     endif %} +{% endif %}
\ No newline at end of file diff --git a/data/templates/accel-ppp/config_shaper_radius.j2 b/data/templates/accel-ppp/config_shaper_radius.j2 index 942cdf132..0cf6a6a92 100644 --- a/data/templates/accel-ppp/config_shaper_radius.j2 +++ b/data/templates/accel-ppp/config_shaper_radius.j2 @@ -1,7 +1,7 @@ -{% if authentication.mode is vyos_defined('radius') %} -{%     if authentication.radius.rate_limit.enable is vyos_defined %} +{% if authentication.mode is vyos_defined('radius') or shaper is vyos_defined %}  [shaper]  verbose=1 +{%     if authentication.radius.rate_limit.enable is vyos_defined %}  attr={{ authentication.radius.rate_limit.attribute }}  {%         if authentication.radius.rate_limit.vendor is vyos_defined %}  vendor={{ authentication.radius.rate_limit.vendor }} @@ -10,4 +10,10 @@ vendor={{ authentication.radius.rate_limit.vendor }}  rate-multiplier={{ authentication.radius.rate_limit.multiplier }}  {%         endif %}  {%     endif %} -{% endif %} +{%     if shaper is vyos_defined %} +{%         if shaper.fwmark is vyos_defined %} +fwmark={{ shaper.fwmark }} +down-limiter=htb +{%         endif %} +{%     endif %} +{% endif %}
\ No newline at end of file diff --git a/data/templates/accel-ppp/pppoe.config.j2 b/data/templates/accel-ppp/pppoe.config.j2 index f4129d3e2..811c4ccc0 100644 --- a/data/templates/accel-ppp/pppoe.config.j2 +++ b/data/templates/accel-ppp/pppoe.config.j2 @@ -69,8 +69,6 @@ ccp={{ "1" if ppp_options.ccp is vyos_defined else "0" }}  unit-preallocate={{ "1" if authentication.radius.preallocate_vif is vyos_defined else "0" }}  {% if ppp_options.min_mtu is vyos_defined %}  min-mtu={{ ppp_options.min_mtu }} -{% else %} -min-mtu={{ mtu }}  {% endif %}  {% if ppp_options.mru is vyos_defined %}  mru={{ ppp_options.mru }} @@ -135,6 +133,19 @@ pado-delay={{ pado_delay_param.value }}  called-sid={{ authentication.radius.called_sid_format }}  {% endif %} +{% if authentication.mode is vyos_defined("local") %} +{%     if client_ip_pool.name is vyos_defined %} +{%         for pool, pool_config in client_ip_pool.name.items() %} +{%             if pool_config.subnet is vyos_defined %} +ip-pool={{ pool }} +{%             endif %} +{%             if pool_config.gateway_address is vyos_defined %} +gw-ip-address={{ pool_config.gateway_address }}/{{ pool_config.subnet.split('/')[1] }} +{%             endif %} +{%         endfor %} +{%     endif %} +{% endif %} +  {% if limits is vyos_defined %}  [connlimit]  {%     if limits.connection_limit is vyos_defined %} diff --git a/data/templates/pppoe/peer.j2 b/data/templates/pppoe/peer.j2 index f433a9b03..5e650fa3b 100644 --- a/data/templates/pppoe/peer.j2 +++ b/data/templates/pppoe/peer.j2 @@ -53,7 +53,7 @@ mtu {{ mtu }}  mru {{ mtu }}  {% if authentication is vyos_defined %} -{{ 'user "' + authentication.user + '"' if authentication.user is vyos_defined }} +{{ 'user "' + authentication.username + '"' if authentication.username is vyos_defined }}  {{ 'password "' + authentication.password + '"' if authentication.password is vyos_defined }}  {% endif %} diff --git a/data/templates/sstp-client/peer.j2 b/data/templates/sstp-client/peer.j2 index 7a0b0e1f7..745a09e14 100644 --- a/data/templates/sstp-client/peer.j2 +++ b/data/templates/sstp-client/peer.j2 @@ -45,7 +45,7 @@ lock  nodeflate  {% if authentication is vyos_defined %} -{{ 'user "' + authentication.user + '"' if authentication.user is vyos_defined }} +{{ 'user "' + authentication.username + '"' if authentication.username is vyos_defined }}  {{ 'password "' + authentication.password + '"' if authentication.password is vyos_defined }}  {% endif %} diff --git a/interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i b/interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i new file mode 100644 index 000000000..654b6727e --- /dev/null +++ b/interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i @@ -0,0 +1,18 @@ +<!-- include start from accel-ppp/client-ip-pool-name.xml.i --> +<tagNode name="name"> +  <properties> +    <help>Pool name</help> +    <valueHelp> +      <format>txt</format> +      <description>Name of IP pool</description> +    </valueHelp> +    <constraint> +      <regex>[-_a-zA-Z0-9.]+</regex> +    </constraint> +  </properties> +  <children> +    #include <include/accel-ppp/gateway-address.xml.i> +    #include <include/accel-ppp/client-ip-pool-subnet-single.xml.i> +  </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/shaper.xml.i b/interface-definitions/include/accel-ppp/shaper.xml.i new file mode 100644 index 000000000..b4f9536d2 --- /dev/null +++ b/interface-definitions/include/accel-ppp/shaper.xml.i @@ -0,0 +1,21 @@ +<!-- include start from accel-ppp/shaper.xml.i --> +<node name="shaper"> +  <properties> +    <help>Traffic shaper bandwidth parameters</help> +  </properties> +  <children> +    <leafNode name="fwmark"> +      <properties> +        <help>Firewall mark value for traffic that excludes from shaping</help> +        <valueHelp> +          <format>u32:1-2147483647</format> +          <description>Match firewall mark value</description> +        </valueHelp> +        <constraint> +          <validator name="numeric" argument="--range 1-2147483647"/> +        </constraint> +      </properties> +    </leafNode> +  </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/interface/authentication.xml.i b/interface-definitions/include/interface/authentication.xml.i index c097ca9dd..8bb094da7 100644 --- a/interface-definitions/include/interface/authentication.xml.i +++ b/interface-definitions/include/interface/authentication.xml.i @@ -4,22 +4,30 @@      <help>Authentication settings</help>    </properties>    <children> -    <leafNode name="user"> +    <leafNode name="username">        <properties> -        <help>User name</help> +        <help>Username used for authentication</help>          <valueHelp>            <format>txt</format> -          <description>Username used for connection</description> +          <description>Username</description>          </valueHelp> +        <constraint> +          <regex>[[:alnum:]][-_#@[:alnum:]]{0,127}</regex> +        </constraint> +        <constraintErrorMessage>Username is limited to alphanumerical characters, -, _, #, and @ with a total lenght of 128</constraintErrorMessage>        </properties>      </leafNode>      <leafNode name="password">        <properties> -        <help>Password</help> +        <help>Password used for authentication</help>          <valueHelp>            <format>txt</format> -          <description>Password used for connection</description> +          <description>Password</description>          </valueHelp> +        <constraint> +          <regex>[[:ascii:]]{1,128}</regex> +        </constraint> +        <constraintErrorMessage>Password is limited to ASCII characters only, with a total lenght of 128</constraintErrorMessage>        </properties>      </leafNode>    </children> diff --git a/interface-definitions/include/version/interfaces-version.xml.i b/interface-definitions/include/version/interfaces-version.xml.i index 0a209bc3a..a351515f1 100644 --- a/interface-definitions/include/version/interfaces-version.xml.i +++ b/interface-definitions/include/version/interfaces-version.xml.i @@ -1,3 +1,3 @@  <!-- include start from include/version/interfaces-version.xml.i --> -<syntaxVersion component='interfaces' version='26'></syntaxVersion> +<syntaxVersion component='interfaces' version='27'></syntaxVersion>  <!-- include end --> diff --git a/interface-definitions/service-ipoe-server.xml.in b/interface-definitions/service-ipoe-server.xml.in index d778f9de0..ca4929249 100644 --- a/interface-definitions/service-ipoe-server.xml.in +++ b/interface-definitions/service-ipoe-server.xml.in @@ -108,22 +108,7 @@                <help>Client IP pools and gateway setting</help>              </properties>              <children> -              <tagNode name="name"> -                <properties> -                  <help>Pool name</help> -                  <valueHelp> -                    <format>txt</format> -                    <description>Name of IP pool</description> -                  </valueHelp> -                  <constraint> -                    <regex>[-_a-zA-Z0-9.]+</regex> -                  </constraint> -                </properties> -                <children> -                  #include <include/accel-ppp/gateway-address.xml.i> -                  #include <include/accel-ppp/client-ip-pool-subnet-single.xml.i> -                </children> -              </tagNode> +              #include <include/accel-ppp/client-ip-pool-name.xml.i>              </children>            </node>            #include <include/accel-ppp/client-ipv6-pool.xml.i> diff --git a/interface-definitions/service-pppoe-server.xml.in b/interface-definitions/service-pppoe-server.xml.in index 68592b96b..3fde07019 100644 --- a/interface-definitions/service-pppoe-server.xml.in +++ b/interface-definitions/service-pppoe-server.xml.in @@ -56,6 +56,7 @@              <children>                #include <include/accel-ppp/client-ip-pool-start-stop.xml.i>                #include <include/accel-ppp/client-ip-pool-subnet.xml.i> +              #include <include/accel-ppp/client-ip-pool-name.xml.i>              </children>            </node>            #include <include/accel-ppp/client-ipv6-pool.xml.i> @@ -122,6 +123,7 @@                      <validator name="numeric" argument="--range 68-65535"/>                    </constraint>                  </properties> +                <defaultValue>1280</defaultValue>                </leafNode>                <leafNode name="mru">                  <properties> @@ -226,6 +228,7 @@              </properties>              <defaultValue>replace</defaultValue>            </leafNode> +          #include <include/accel-ppp/shaper.xml.i>            <node name="snmp">              <properties>                <help>Enable SNMP</help> diff --git a/op-mode-definitions/nhrp.xml.in b/op-mode-definitions/nhrp.xml.in index c10b111a7..11a4b8814 100644 --- a/op-mode-definitions/nhrp.xml.in +++ b/op-mode-definitions/nhrp.xml.in @@ -50,13 +50,13 @@              <properties>                <help>Show NHRP interface connection information</help>              </properties> -            <command>if pgrep opennhrp >/dev/null; then sudo opennhrpctl interface show; else echo OpenNHRP is not running; fi</command> +            <command>${vyos_op_scripts_dir}/nhrp.py show_interface</command>            </leafNode>            <leafNode name="tunnel">              <properties>                <help>Show NHRP tunnel connection information</help>              </properties> -            <command>if pgrep opennhrp >/dev/null; then sudo opennhrpctl show ; else echo OpenNHRP is not running; fi</command> +            <command>${vyos_op_scripts_dir}/nhrp.py show_tunnel</command>            </leafNode>          </children>        </node> diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 8e0ce701e..30bdd2d47 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -1,4 +1,4 @@ -# Copyright 2020-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2020-2023 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 @@ -23,6 +23,7 @@  from vyos import ConfigError  from vyos.util import dict_search +from vyos.util import dict_search_recursive  def verify_mtu(config):      """ @@ -232,7 +233,7 @@ def verify_authentication(config):      """      if 'authentication' not in config:          return -    if not {'user', 'password'} <= set(config['authentication']): +    if not {'username', 'password'} <= set(config['authentication']):          raise ConfigError('Authentication requires both username and ' \                            'password to be set!') @@ -414,7 +415,17 @@ def verify_accel_ppp_base_service(config, local_users=True):              if 'key' not in radius_config:                  raise ConfigError(f'Missing RADIUS secret key for server "{server}"') -    if 'gateway_address' not in config: +    # Check global gateway or gateway in named pool +    gateway = False +    if 'gateway_address' in config: +        gateway = True +    else: +        if dict_search_recursive(config, 'gateway_address', ['client_ip_pool', 'name']): +            for _, v in config['client_ip_pool']['name'].items(): +                if 'gateway_address' in v: +                    gateway = True +                    break +    if not gateway:          raise ConfigError('Server requires gateway-address to be configured!')      if 'name_server_ipv4' in config: diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index c50ead89f..795df2462 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -751,8 +751,8 @@ class Interface(Control):              elif all_rp_filter == 2: global_setting = 'loose'              from vyos.base import Warning -            Warning(f'Global source-validation is set to "{global_setting} '\ -                    f'this overrides per interface setting!') +            Warning(f'Global source-validation is set to "{global_setting}", this '\ +                    f'overrides per interface setting on "{self.ifname}"!')          tmp = self.get_interface('rp_filter')          if int(tmp) == value: diff --git a/python/vyos/template.py b/python/vyos/template.py index 15240f815..6367f51e5 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -1,4 +1,4 @@ -# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2023 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 @@ -158,6 +158,24 @@ def force_to_list(value):      else:          return [value] +@register_filter('seconds_to_human') +def seconds_to_human(seconds, separator=""): +    """ Convert seconds to human-readable values like 1d6h15m23s """ +    from vyos.util import seconds_to_human +    return seconds_to_human(seconds, separator=separator) + +@register_filter('bytes_to_human') +def bytes_to_human(bytes, initial_exponent=0, precision=2): +    """ Convert bytes to human-readable values like 1.44M """ +    from vyos.util import bytes_to_human +    return bytes_to_human(bytes, initial_exponent=initial_exponent, precision=precision) + +@register_filter('human_to_bytes') +def human_to_bytes(value): +    """ Convert a data amount with a unit suffix to bytes, like 2K to 2048 """ +    from vyos.util import human_to_bytes +    return human_to_bytes(value) +  @register_filter('ip_from_cidr')  def ip_from_cidr(prefix):      """ Take an IPv4/IPv6 CIDR host and strip cidr mask. diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py index 08b7f2f46..f4efed641 100755 --- a/smoketest/scripts/cli/test_interfaces_pppoe.py +++ b/smoketest/scripts/cli/test_interfaces_pppoe.py @@ -61,7 +61,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):              passwd = f'VyOS-passwd-{interface}'              mtu = '1400' -            self.cli_set(base_path + [interface, 'authentication', 'user', user]) +            self.cli_set(base_path + [interface, 'authentication', 'username', user])              self.cli_set(base_path + [interface, 'authentication', 'password', passwd])              self.cli_set(base_path + [interface, 'mtu', mtu])              self.cli_set(base_path + [interface, 'no-peer-dns']) @@ -94,7 +94,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):              user = f'VyOS-user-{interface}'              passwd = f'VyOS-passwd-{interface}' -            self.cli_set(base_path + [interface, 'authentication', 'user', user]) +            self.cli_set(base_path + [interface, 'authentication', 'username', user])              self.cli_set(base_path + [interface, 'authentication', 'password', passwd])              self.cli_set(base_path + [interface, 'source-interface', self._source_interface])              self.cli_set(base_path + [interface, 'disable']) @@ -123,7 +123,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):              user = f'VyOS-user-{interface}'              passwd = f'VyOS-passwd-{interface}' -            self.cli_set(base_path + [interface, 'authentication', 'user', user]) +            self.cli_set(base_path + [interface, 'authentication', 'username', user])              self.cli_set(base_path + [interface, 'source-interface', self._source_interface])              self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf']) @@ -145,7 +145,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):              user = f'VyOS-user-{interface}'              passwd = f'VyOS-passwd-{interface}' -            self.cli_set(base_path + [interface, 'authentication', 'user', user]) +            self.cli_set(base_path + [interface, 'authentication', 'username', user])              self.cli_set(base_path + [interface, 'authentication', 'password', passwd])              self.cli_set(base_path + [interface, 'no-default-route'])              self.cli_set(base_path + [interface, 'no-peer-dns']) @@ -184,7 +184,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):              service_name = f'SRV{interface}'              host_uniq = 'cafebeefBABE123456' -            self.cli_set(base_path + [interface, 'authentication', 'user', user]) +            self.cli_set(base_path + [interface, 'authentication', 'username', user])              self.cli_set(base_path + [interface, 'authentication', 'password', passwd])              self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py index 7546c2e3d..4f9181704 100755 --- a/smoketest/scripts/cli/test_service_pppoe-server.py +++ b/smoketest/scripts/cli/test_service_pppoe-server.py @@ -143,6 +143,9 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):          self.basic_config()          subnet = '172.18.0.0/24' +        fwmark = '223' +        limiter = 'htb' +          self.set(['client-ip-pool', 'subnet', subnet])          start = '192.0.2.10' @@ -151,6 +154,7 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):          start_stop = f'{start}-{stop_octet}'          self.set(['client-ip-pool', 'start', start])          self.set(['client-ip-pool', 'stop', stop]) +        self.set(['shaper', 'fwmark', fwmark])          # commit changes          self.cli_commit() @@ -163,6 +167,37 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):          self.assertEqual(conf['ip-pool'][subnet], None)          self.assertEqual(conf['ip-pool'][start_stop], None)          self.assertEqual(conf['ip-pool']['gw-ip-address'], self._gateway) +        self.assertEqual(conf['shaper']['fwmark'], fwmark) +        self.assertEqual(conf['shaper']['down-limiter'], limiter) + + +    def test_pppoe_server_client_ip_pool_name(self): +        # Test configuration of named client pools +        self.basic_config() + +        subnet = '192.0.2.0/24' +        gateway = '192.0.2.1' +        pool = 'VYOS' + +        subnet_name = f'{subnet},name' +        gw_ip_prefix = f'{gateway}/24' + +        self.set(['client-ip-pool', 'name', pool, 'subnet', subnet]) +        self.set(['client-ip-pool', 'name', pool, 'gateway-address', gateway]) +        self.cli_delete(self._base_path + ['gateway-address']) + +        # commit changes +        self.cli_commit() + +        # Validate configuration values +        conf = ConfigParser(allow_no_value=True, delimiters='=') +        conf.read(self._config_file) + +        # Validate configuration +        self.assertEqual(conf['ip-pool'][subnet_name], pool) +        self.assertEqual(conf['ip-pool']['gw-ip-address'], gateway) +        self.assertEqual(conf['pppoe']['ip-pool'], pool) +        self.assertEqual(conf['pppoe']['gw-ip-address'], gw_ip_prefix)      def test_pppoe_server_client_ipv6_pool(self): diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index a14a992ae..9ca495476 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -171,7 +171,7 @@ def apply(wwan):          options = f'ip-type={ip_type},apn=' + wwan['apn']          if 'authentication' in wwan: -            options += ',user={user},password={password}'.format(**wwan['authentication']) +            options += ',user={username},password={password}'.format(**wwan['authentication'])          command = f'{base_cmd} --simple-connect="{options}"'          call(command, stdout=DEVNULL) diff --git a/src/migration-scripts/interfaces/26-to-27 b/src/migration-scripts/interfaces/26-to-27 new file mode 100755 index 000000000..949cc55b6 --- /dev/null +++ b/src/migration-scripts/interfaces/26-to-27 @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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/>. + +# T4995: pppoe, wwan, sstpc-client rename "authentication user" CLI node +#        to "authentication username" + +from sys import argv + +from vyos.ethtool import Ethtool +from vyos.configtree import ConfigTree + +if (len(argv) < 1): +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) + +for type in ['pppoe', 'sstpc-client', 'wwam']: +    base = ['interfaces', type] +    if not config.exists(base): +        continue +    for interface in config.list_nodes(base): +        auth_base = base + [interface, 'authentication', 'user'] +        if config.exists(auth_base): +            config.rename(auth_base, 'username') + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print(f'Failed to save the modified config: {e}') +    exit(1) diff --git a/src/op_mode/nhrp.py b/src/op_mode/nhrp.py new file mode 100755 index 000000000..5ff91a59c --- /dev/null +++ b/src/op_mode/nhrp.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 tabulate +import vyos.opmode + +from vyos.util import cmd +from vyos.util import process_named_running +from vyos.util import colon_separated_to_dict + + +def _get_formatted_output(output_dict: dict) -> str: +    """ +    Create formatted table for CLI output +    :param output_dict: dictionary for API +    :type output_dict: dict +    :return: tabulate string +    :rtype: str +    """ +    print(f"Status: {output_dict['Status']}") +    output: str = tabulate.tabulate(output_dict['routes'], headers='keys', +                                    numalign="left") +    return output + + +def _get_formatted_dict(output_string: str) -> dict: +    """ +    Format string returned from CMD to API list +    :param output_string: String received by CMD +    :type output_string: str +    :return: dictionary for API +    :rtype: dict +    """ +    formatted_dict: dict = { +        'Status': '', +        'routes': [] +    } +    output_list: list = output_string.split('\n\n') +    for list_a in output_list: +        output_dict = colon_separated_to_dict(list_a, True) +        if 'Status' in output_dict: +            formatted_dict['Status'] = output_dict['Status'] +        else: +            formatted_dict['routes'].append(output_dict) +    return formatted_dict + + +def show_interface(raw: bool): +    """ +    Command 'show nhrp interface' +    :param raw: if API +    :type raw: bool +    """ +    if not process_named_running('opennhrp'): +        raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.') +    interface_string: str = cmd('sudo opennhrpctl interface show') +    interface_dict: dict = _get_formatted_dict(interface_string) +    if raw: +        return interface_dict +    else: +        return _get_formatted_output(interface_dict) + + +def show_tunnel(raw: bool): +    """ +    Command 'show nhrp tunnel' +    :param raw: if API +    :type raw: bool +    """ +    if not process_named_running('opennhrp'): +        raise vyos.opmode.UnconfiguredSubsystem('OpenNHRP is not running.') +    tunnel_string: str = cmd('sudo opennhrpctl show') +    tunnel_dict: list = _get_formatted_dict(tunnel_string) +    if raw: +        return tunnel_dict +    else: +        return _get_formatted_output(tunnel_dict) + + +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) | 
