diff options
-rw-r--r-- | interface-definitions/service_upnp.xml.in | 44 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_service_upnp.py | 68 | ||||
-rwxr-xr-x | src/conf_mode/service_upnp.py | 44 | ||||
-rw-r--r-- | src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper | 16 | ||||
-rw-r--r-- | src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup | 2 |
5 files changed, 94 insertions, 80 deletions
diff --git a/interface-definitions/service_upnp.xml.in b/interface-definitions/service_upnp.xml.in index 8d0a14d4e..7cfe1f02e 100644 --- a/interface-definitions/service_upnp.xml.in +++ b/interface-definitions/service_upnp.xml.in @@ -19,7 +19,7 @@ </leafNode> <leafNode name="wan-interface"> <properties> - <help>WAN network interface (REQUIRE)</help> + <help>WAN network interface</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> @@ -139,49 +139,27 @@ <format>txt</format> <description>The STUN server host address</description> </valueHelp> - <valueHelp> - <format>stun.stunprotocol.org</format> - <description>stunprotocol</description> - </valueHelp> - <valueHelp> - <format>stun.sipgate.net</format> - <description>sipgate</description> - </valueHelp> - <valueHelp> - <format>stun.xten.com</format> - <description>xten</description> - </valueHelp> - <valueHelp> - <format>txt</format> - <description>other STUN Server</description> - </valueHelp> - </properties> - </leafNode> - <leafNode name="port"> - <properties> - <help>The STUN server port</help> - <valueHelp> - <format>txt</format> - <description>The STUN server port</description> - </valueHelp> + <constraint> + <validator name="fqdn"/> + </constraint> </properties> </leafNode> + #include <include/port-number.xml.i> </children> </node> - <tagNode name="rules"> + <tagNode name="rule"> <properties> <help>UPnP Rule</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Rule number</description> + </valueHelp> <constraint> <validator name="numeric" argument="--range 0-65535"/> </constraint> </properties> <children> - <leafNode name="disable"> - <properties> - <help>Disable Rule</help> - <valueless /> - </properties> - </leafNode> + #include <include/generic-disable-node.xml.i> <leafNode name="external-port-range"> <properties> <help>Port range (REQUIRE)</help> diff --git a/smoketest/scripts/cli/test_service_upnp.py b/smoketest/scripts/cli/test_service_upnp.py index 9fbbdaff9..c3e9b600f 100755 --- a/smoketest/scripts/cli/test_service_upnp.py +++ b/smoketest/scripts/cli/test_service_upnp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -20,52 +20,86 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.template import ip_from_cidr from vyos.util import read_file from vyos.util import process_named_running UPNP_CONF = '/run/upnp/miniupnp.conf' +DAEMON = 'miniupnpd' interface = 'eth0' base_path = ['service', 'upnp'] address_base = ['interfaces', 'ethernet', interface, 'address'] +ipv4_addr = '100.64.0.1/24' +ipv6_addr = '2001:db8::1/64' + class TestServiceUPnP(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(cls, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, address_base + [ipv4_addr]) + cls.cli_set(cls, address_base + [ipv6_addr]) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, address_base) + cls._session.commit() + + super(cls, cls).tearDownClass() + def tearDown(self): - self.cli_delete(address_base) + # Check for running process + self.assertTrue(process_named_running(DAEMON)) + self.cli_delete(base_path) self.cli_commit() - + + # Check for running process + self.assertFalse(process_named_running(DAEMON)) + def test_ipv4_base(self): - self.cli_set(address_base + ['100.64.0.1/24']) self.cli_set(base_path + ['nat-pmp']) - self.cli_set(base_path + ['wan-interface', interface]) self.cli_set(base_path + ['listen', interface]) + + # check validate() - WAN interface is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['wan-interface', interface]) + self.cli_commit() - + config = read_file(UPNP_CONF) self.assertIn(f'ext_ifname={interface}', config) self.assertIn(f'listening_ip={interface}', config) self.assertIn(f'enable_natpmp=yes', config) self.assertIn(f'enable_upnp=yes', config) - - # Check for running process - self.assertTrue(process_named_running('miniupnpd')) - + def test_ipv6_base(self): - self.cli_set(address_base + ['2001:db8::1/64']) + v6_addr = ip_from_cidr(ipv6_addr) + self.cli_set(base_path + ['nat-pmp']) - self.cli_set(base_path + ['wan-interface', interface]) self.cli_set(base_path + ['listen', interface]) - self.cli_set(base_path + ['listen', '2001:db8::1']) + self.cli_set(base_path + ['listen', v6_addr]) + + # check validate() - WAN interface is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['wan-interface', interface]) + self.cli_commit() - + config = read_file(UPNP_CONF) self.assertIn(f'ext_ifname={interface}', config) self.assertIn(f'listening_ip={interface}', config) + self.assertIn(f'ipv6_listening_ip={v6_addr}', config) self.assertIn(f'enable_natpmp=yes', config) self.assertIn(f'enable_upnp=yes', config) - - # Check for running process - self.assertTrue(process_named_running('miniupnpd')) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py index 638296f45..d21b31990 100755 --- a/src/conf_mode/service_upnp.py +++ b/src/conf_mode/service_upnp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -24,7 +24,6 @@ from ipaddress import IPv6Network from vyos.config import Config from vyos.configdict import dict_merge -from vyos.configdict import dict_search from vyos.configdict import get_interface_dict from vyos.configverify import verify_vrf from vyos.util import call @@ -43,17 +42,18 @@ def get_config(config=None): conf = config else: conf = Config() + base = ['service', 'upnp'] upnpd = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - + if not upnpd: return None - - if dict_search('rule', upnpd): + + if 'rule' in upnpd: default_member_values = defaults(base + ['rule']) for rule,rule_config in upnpd['rule'].items(): upnpd['rule'][rule] = dict_merge(default_member_values, upnpd['rule'][rule]) - + uuidgen = uuid.uuid1() upnpd.update({'uuid': uuidgen}) @@ -62,7 +62,7 @@ def get_config(config=None): def get_all_interface_addr(prefix, filter_dev, filter_family): list_addr = [] interfaces = netifaces.interfaces() - + for interface in interfaces: if filter_dev and interface in filter_dev: continue @@ -87,27 +87,28 @@ def get_all_interface_addr(prefix, filter_dev, filter_family): list_addr.append(addr['addr'] + prefix) else: list_addr.append(addr['addr']) - + return list_addr def verify(upnpd): if not upnpd: return None - + if 'wan_interface' not in upnpd: raise ConfigError('To enable UPNP, you must have the "wan-interface" option!') - - if dict_search('rules', upnpd): - for rule,rule_config in upnpd['rule'].items(): + + if 'rule' in upnpd: + for rule, rule_config in upnpd['rule'].items(): for option in ['external_port_range', 'internal_port_range', 'ip', 'action']: if option not in rule_config: - raise ConfigError(f'A UPNP rule must have an "{option}" option!') - - if dict_search('stun', upnpd): + tmp = option.replace('_', '-') + raise ConfigError(f'Every UPNP rule requires "{tmp}" to be set!') + + if 'stun' in upnpd: for option in ['host', 'port']: if option not in upnpd['stun']: raise ConfigError(f'A UPNP stun support must have an "{option}" option!') - + # Check the validity of the IP address listen_dev = [] system_addrs_cidr = get_all_interface_addr(True, [], [netifaces.AF_INET, netifaces.AF_INET6]) @@ -120,7 +121,7 @@ def verify(upnpd): raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!') if is_ipv6(listen_if_or_addr) and IPv6Network(listen_if_or_addr).is_multicast: raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!') - + system_listening_dev_addrs_cidr = get_all_interface_addr(True, listen_dev, [netifaces.AF_INET6]) system_listening_dev_addrs = get_all_interface_addr(False, listen_dev, [netifaces.AF_INET6]) for listen_if_or_addr in upnpd['listen']: @@ -130,19 +131,20 @@ def verify(upnpd): def generate(upnpd): if not upnpd: return None - + if os.path.isfile(config_file): os.unlink(config_file) - + render(config_file, 'firewall/upnpd.conf.tmpl', upnpd) def apply(upnpd): + systemd_service_name = 'miniupnpd.service' if not upnpd: # Stop the UPNP service - call('systemctl stop miniupnpd.service') + call(f'systemctl stop {systemd_service_name}') else: # Start the UPNP service - call('systemctl restart miniupnpd.service') + call(f'systemctl restart {systemd_service_name}') if __name__ == '__main__': try: diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper index 9d5505758..74a7e83bf 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -4,7 +4,7 @@ IF_METRIC=${IF_METRIC:-210} # Check if interface is inside a VRF -VRF_OPTION=$(ip -j -d link show ${interface} | awk '{if(match($0, /.*"master":"(\w+)".*"info_slave_kind":"vrf"/, IFACE_DETAILS)) printf("vrf %s", IFACE_DETAILS[1])}') +VRF_OPTION=$(/usr/sbin/ip -j -d link show ${interface} | awk '{if(match($0, /.*"master":"(\w+)".*"info_slave_kind":"vrf"/, IFACE_DETAILS)) printf("vrf %s", IFACE_DETAILS[1])}') # get status of FRR function frr_alive () { @@ -66,9 +66,9 @@ function iptovtysh () { # delete the same route from kernel before adding new one function delroute () { logmsg info "Checking if the route presented in kernel: $@ $VRF_OPTION" - if ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then - logmsg info "Deleting IP route: \"ip route del $@ $VRF_OPTION\"" - ip route del $@ $VRF_OPTION + if /usr/sbin/ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then + logmsg info "Deleting IP route: \"/usr/sbin/ip route del $@ $VRF_OPTION\"" + /usr/sbin/ip route del $@ $VRF_OPTION fi } @@ -76,8 +76,8 @@ function delroute () { function ip () { # pass comand to system `ip` if this is not related to routes change if [ "$2" != "route" ] ; then - logmsg info "Passing command to iproute2: \"$@\"" - ip $@ + logmsg info "Passing command to /usr/sbin/ip: \"$@\"" + /usr/sbin/ip $@ else # if we want to work with routes, try to use FRR first if frr_alive ; then @@ -87,8 +87,8 @@ function ip () { vtysh -c "conf t" -c "$VTYSH_CMD" else # add ip route to kernel - logmsg info "Modifying routes in kernel: \"ip $@\"" - ip $@ $VRF_OPTION + logmsg info "Modifying routes in kernel: \"/usr/sbin/ip $@\"" + /usr/sbin/ip $@ $VRF_OPTION fi fi } diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup index a6989441b..ad6a1d5eb 100644 --- a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup +++ b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup @@ -1,7 +1,7 @@ ## ## VyOS cleanup ## -# NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via ip or vtysh, according to the system state +# NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via /usr/sbin/ip or vtysh, according to the system state hostsd_client="/usr/bin/vyos-hostsd-client" hostsd_changes= # check vyos-hostsd status |