diff options
author | Indrajit Raychaudhuri <irc@indrajit.com> | 2023-09-23 23:44:08 -0500 |
---|---|---|
committer | Indrajit Raychaudhuri <irc@indrajit.com> | 2023-09-28 10:40:57 -0500 |
commit | e66f7075ee12ae3107d29efaf683442c3535e8b9 (patch) | |
tree | 1ea8a4c22b72cd63f214471d64daaeca58075ece | |
parent | 852e9c3328e61f5d0b92a9efca376aec94533f2b (diff) | |
download | vyos-1x-e66f7075ee12ae3107d29efaf683442c3535e8b9.tar.gz vyos-1x-e66f7075ee12ae3107d29efaf683442c3535e8b9.zip |
mdns: T5615: Allow controlling IP version to use for mDNS repeater
This commit adds a new configuration option to the mDNS repeater service
to allow controlling which IP version to use for mDNS repeater.
Additionally, publishing AAAA record over IPv4 and A record over IPv6 is
disabled as suggested.
See:
- https://github.com/lathiat/avahi/issues/117#issuecomment-1651475104
- https://bugzilla.redhat.com/show_bug.cgi?id=669627#c2
-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 |