diff options
| -rw-r--r-- | data/templates/mdns-repeater/avahi-daemon.j2 | 6 | ||||
| -rw-r--r-- | interface-definitions/service-mdns-repeater.xml.in | 25 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_service_mdns-repeater.py | 74 | ||||
| -rwxr-xr-x | src/conf_mode/service_mdns-repeater.py | 22 | 
4 files changed, 111 insertions, 16 deletions
| diff --git a/data/templates/mdns-repeater/avahi-daemon.j2 b/data/templates/mdns-repeater/avahi-daemon.j2 index e0dfd897e..d562c048f 100644 --- a/data/templates/mdns-repeater/avahi-daemon.j2 +++ b/data/templates/mdns-repeater/avahi-daemon.j2 @@ -1,7 +1,7 @@  ### Autogenerated by service_mdns-repeater.py ###  [server] -use-ipv4=yes -use-ipv6=yes +use-ipv4={{ 'yes' if ip_version in ['ipv4', 'both'] else 'no' }} +use-ipv6={{ 'yes' if ip_version in ['ipv6', 'both'] else 'no' }}  allow-interfaces={{ interface | join(', ') }}  {% if browse_domain is vyos_defined and browse_domain | length %}  browse-domains={{ browse_domain | join(', ') }} @@ -17,6 +17,8 @@ disable-user-service-publishing=yes  publish-addresses=no  publish-hinfo=no  publish-workstation=no +publish-aaaa-on-ipv4=no +publish-a-on-ipv6=no  [reflector]  enable-reflector=yes diff --git a/interface-definitions/service-mdns-repeater.xml.in b/interface-definitions/service-mdns-repeater.xml.in index 653dbbbe4..67870946c 100644 --- a/interface-definitions/service-mdns-repeater.xml.in +++ b/interface-definitions/service-mdns-repeater.xml.in @@ -15,6 +15,31 @@              <children>                #include <include/generic-disable-node.xml.i>                #include <include/generic-interface-multi.xml.i> +              <leafNode name="ip-version"> +                <properties> +                  <help>IP address version to use</help> +                  <valueHelp> +                    <format>_ipv4</format> +                    <description>Use only IPv4 address</description> +                  </valueHelp> +                  <valueHelp> +                    <format>_ipv6</format> +                    <description>Use only IPv6 address</description> +                  </valueHelp> +                  <valueHelp> +                    <format>both</format> +                    <description>Use both IPv4 and IPv6 address</description> +                  </valueHelp> +                  <completionHelp> +                    <list>ipv4 ipv6 both</list> +                  </completionHelp> +                  <constraint> +                    <regex>(ipv[46]|both)</regex> +                  </constraint> +                  <constraintErrorMessage>IP Version must be literal 'ipv4', 'ipv6' or 'both'</constraintErrorMessage> +                </properties> +                <defaultValue>both</defaultValue> +              </leafNode>                <leafNode name="browse-domain">                  <properties>                    <help>mDNS browsing domains in addition to the default one</help> diff --git a/smoketest/scripts/cli/test_service_mdns-repeater.py b/smoketest/scripts/cli/test_service_mdns-repeater.py index 9a9839025..f2fb3b509 100755 --- a/smoketest/scripts/cli/test_service_mdns-repeater.py +++ b/smoketest/scripts/cli/test_service_mdns-repeater.py @@ -19,6 +19,7 @@ import unittest  from base_vyostest_shim import VyOSUnitTestSHIM  from configparser import ConfigParser +from vyos.configsession import ConfigSessionError  from vyos.utils.process import process_named_running  base_path = ['service', 'mdns', 'repeater'] @@ -27,6 +28,20 @@ config_file = '/run/avahi-daemon/avahi-daemon.conf'  class TestServiceMDNSrepeater(VyOSUnitTestSHIM.TestCase): +    def setUp(self): +        # Start with a clean CLI instance +        self.cli_delete(base_path) + +        # Service required a configured IP address on the interface +        self.cli_set(intf_base + ['dum10', 'address', '192.0.2.1/30']) +        self.cli_set(intf_base + ['dum10', 'ipv6', 'address', 'no-default-link-local']) +        self.cli_set(intf_base + ['dum20', 'address', '192.0.2.5/30']) +        self.cli_set(intf_base + ['dum20', 'address', '2001:db8:0:2::5/64']) +        self.cli_set(intf_base + ['dum30', 'address', '192.0.2.9/30']) +        self.cli_set(intf_base + ['dum30', 'address', '2001:db8:0:2::9/64']) +        self.cli_set(intf_base + ['dum40', 'address', '2001:db8:0:2::11/64']) +        self.cli_commit() +      def tearDown(self):          # Check for running process          self.assertTrue(process_named_running('avahi-daemon')) @@ -34,24 +49,23 @@ class TestServiceMDNSrepeater(VyOSUnitTestSHIM.TestCase):          self.cli_delete(base_path)          self.cli_delete(intf_base + ['dum10'])          self.cli_delete(intf_base + ['dum20']) +        self.cli_delete(intf_base + ['dum30']) +        self.cli_delete(intf_base + ['dum40'])          self.cli_commit()          # Check that there is no longer a running process          self.assertFalse(process_named_running('avahi-daemon')) -    def test_service(self): +    def test_service_dual_stack(self):          # mDNS browsing domains in addition to the default one (local)          domains = ['dom1.home.arpa', 'dom2.home.arpa']          # mDNS services to be repeated          services = ['_ipp._tcp', '_smb._tcp', '_ssh._tcp'] -        # Service required a configured IP address on the interface -        self.cli_set(intf_base + ['dum10', 'address', '192.0.2.1/30']) -        self.cli_set(intf_base + ['dum20', 'address', '192.0.2.5/30']) - -        self.cli_set(base_path + ['interface', 'dum10']) +        self.cli_set(base_path + ['ip-version', 'both'])          self.cli_set(base_path + ['interface', 'dum20']) +        self.cli_set(base_path + ['interface', 'dum30'])          for domain in domains:              self.cli_set(base_path + ['browse-domain', domain]) @@ -65,10 +79,56 @@ class TestServiceMDNSrepeater(VyOSUnitTestSHIM.TestCase):          conf = ConfigParser(delimiters='=')          conf.read(config_file) -        self.assertEqual(conf['server']['allow-interfaces'], 'dum10, dum20') +        self.assertEqual(conf['server']['use-ipv4'], 'yes') +        self.assertEqual(conf['server']['use-ipv6'], 'yes') +        self.assertEqual(conf['server']['allow-interfaces'], 'dum20, dum30')          self.assertEqual(conf['server']['browse-domains'], ', '.join(domains))          self.assertEqual(conf['reflector']['enable-reflector'], 'yes')          self.assertEqual(conf['reflector']['reflect-filters'], ', '.join(services)) +    def test_service_ipv4(self): +        # partcipating interfaces should have IPv4 addresses +        self.cli_set(base_path + ['ip-version', 'ipv4']) +        self.cli_set(base_path + ['interface', 'dum10']) +        self.cli_set(base_path + ['interface', 'dum40']) + +        # exception is raised if partcipating interfaces do not have IPv4 address +        with self.assertRaises(ConfigSessionError): +            self.cli_commit() +        self.cli_delete(base_path + ['interface', 'dum40']) +        self.cli_set(base_path + ['interface', 'dum20']) +        self.cli_commit() + +        # Validate configuration values +        conf = ConfigParser(delimiters='=') +        conf.read(config_file) + +        self.assertEqual(conf['server']['use-ipv4'], 'yes') +        self.assertEqual(conf['server']['use-ipv6'], 'no') +        self.assertEqual(conf['server']['allow-interfaces'], 'dum10, dum20') +        self.assertEqual(conf['reflector']['enable-reflector'], 'yes') + +    def test_service_ipv6(self): +        # partcipating interfaces should have IPv6 addresses +        self.cli_set(base_path + ['ip-version', 'ipv6']) +        self.cli_set(base_path + ['interface', 'dum10']) +        self.cli_set(base_path + ['interface', 'dum30']) + +        # exception is raised if partcipating interfaces do not have IPv4 address +        with self.assertRaises(ConfigSessionError): +            self.cli_commit() +        self.cli_delete(base_path + ['interface', 'dum10']) +        self.cli_set(base_path + ['interface', 'dum40']) +        self.cli_commit() + +        # Validate configuration values +        conf = ConfigParser(delimiters='=') +        conf.read(config_file) + +        self.assertEqual(conf['server']['use-ipv4'], 'no') +        self.assertEqual(conf['server']['use-ipv6'], 'yes') +        self.assertEqual(conf['server']['allow-interfaces'], 'dum30, dum40') +        self.assertEqual(conf['reflector']['enable-reflector'], 'yes') +  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/src/conf_mode/service_mdns-repeater.py b/src/conf_mode/service_mdns-repeater.py index a2c90b537..d4b8ef8c4 100755 --- a/src/conf_mode/service_mdns-repeater.py +++ b/src/conf_mode/service_mdns-repeater.py @@ -18,7 +18,7 @@ import os  from json import loads  from sys import exit -from netifaces import ifaddresses, interfaces, AF_INET +from netifaces import ifaddresses, interfaces, AF_INET, AF_INET6  from vyos.config import Config  from vyos.ifconfig.vrrp import VRRP @@ -36,18 +36,22 @@ def get_config(config=None):          conf = config      else:          conf = Config() +      base = ['service', 'mdns', 'repeater'] -    mdns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) +    if not conf.exists(base): +        return None + +    mdns = conf.get_config_dict(base, key_mangling=('-', '_'), +                                no_tag_node_value_mangle=True, +                                get_first_key=True, +                                with_recursive_defaults=True)      if mdns:          mdns['vrrp_exists'] = conf.exists('high-availability vrrp')      return mdns  def verify(mdns): -    if not mdns: -        return None - -    if 'disable' in mdns: +    if not mdns or 'disable' in mdns:          return None      # We need at least two interfaces to repeat mDNS advertisments @@ -60,10 +64,14 @@ def verify(mdns):          if interface not in interfaces():              raise ConfigError(f'Interface "{interface}" does not exist!') -        if AF_INET not in ifaddresses(interface): +        if mdns['ip_version'] in ['ipv4', 'both'] and AF_INET not in ifaddresses(interface):              raise ConfigError('mDNS repeater requires an IPv4 address to be '                                    f'configured on interface "{interface}"') +        if mdns['ip_version'] in ['ipv6', 'both'] and AF_INET6 not in ifaddresses(interface): +            raise ConfigError('mDNS repeater requires an IPv6 address to be ' +                                  f'configured on interface "{interface}"') +      return None  # Get VRRP states from interfaces, returns only interfaces where state is MASTER | 
