diff --git a/data/templates/conntrackd/conntrackd.conf.j2 b/data/templates/conntrackd/conntrackd.conf.j2 index d662f126227..c8eaeb58e76 100644 --- a/data/templates/conntrackd/conntrackd.conf.j2 +++ b/data/templates/conntrackd/conntrackd.conf.j2 @@ -12,10 +12,10 @@ Sync { UDP { {% if listen_address is vyos_defined %} {% for address in listen_address %} - IPv4_address {{ address }} + {{ 'IPv4_address' if address | is_ipv4 else 'IPv6_address' }} {{ address }} {% endfor %} {% endif %} - IPv4_Destination_Address {{ iface_config.peer }} + {{ 'IPv4_Destination_Address' if iface_config.peer | is_ipv4 else 'IPv6_Destination_Address' }} {{ iface_config.peer }} Port {{ iface_config.port if iface_config.port is vyos_defined else '3780' }} Interface {{ iface }} SndSocketBuffer {{ sync_queue_size | int *1024 *1024 }} @@ -24,10 +24,14 @@ Sync { } {% else %} Multicast { -{% set ip_address = iface | get_ipv4 %} +{% if mcast_group | is_ipv6 %} + IPv6_address {{ mcast_group }} +{% else %} +{% set ip_address = iface | get_ipv4 %} IPv4_address {{ mcast_group }} - Group {{ iface_config.port if iface_config.port is vyos_defined else '3780' }} IPv4_interface {{ ip_address[0] | ip_from_cidr }} +{% endif %} + Group {{ iface_config.port if iface_config.port is vyos_defined else '3780' }} Interface {{ iface }} SndSocketBuffer {{ sync_queue_size | int *1024 *1024 }} RcvSocketBuffer {{ sync_queue_size | int *1024 *1024 }} diff --git a/interface-definitions/include/dhcp/dhcp-server-common-config.xml.i b/interface-definitions/include/dhcp/dhcp-server-common-config.xml.i index 64f9707f722..4fc5a4b5bd4 100644 --- a/interface-definitions/include/dhcp/dhcp-server-common-config.xml.i +++ b/interface-definitions/include/dhcp/dhcp-server-common-config.xml.i @@ -155,7 +155,7 @@ DHCP high availability configuration - #include + #include Configure high availability mode @@ -179,13 +179,17 @@ - IPv4 remote address used for connection + Remote address for high availability peer ipv4 IPv4 address of high availability peer + + ipv6 + IPv6 address of high availability peer + - + diff --git a/interface-definitions/service_conntrack-sync.xml.in b/interface-definitions/service_conntrack-sync.xml.in index dc771d64a0e..9483c809b3e 100644 --- a/interface-definitions/service_conntrack-sync.xml.in +++ b/interface-definitions/service_conntrack-sync.xml.in @@ -148,23 +148,37 @@ IP address of the peer to send the UDP conntrack info to. This disables multicast. ipv4 - IP address to listen for incoming connections + IPv4 address of conntrack synchronization peer + + + ipv6 + IPv6 address of conntrack synchronization peer - + #include - #include + #include Multicast group to use for syncing conntrack entries + + ipv4 + Multicast IPv4 group address + + + ipv6 + Multicast IPv6 group address + + + Multicast IPv4/IPv6 address required 225.0.0.50 diff --git a/python/vyos/template.py b/python/vyos/template.py index 5b228603cf5..dbbc4df8484 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -908,18 +908,19 @@ def kea_high_availability_json(config): 'max-ack-delay': 5000, 'max-unacked-clients': 10, 'peers': [ - { - 'name': os.uname()[1], - 'url': f'http://{source_addr}:647/', - 'role': peer1_role, - 'auto-failover': True - }, - { - 'name': config['name'], - 'url': f'http://{remote_addr}:647/', - 'role': peer2_role, - 'auto-failover': True - }] + { + 'name': os.uname()[1], + 'url': f'http://{bracketize_ipv6(source_addr)}:647/', + 'role': peer1_role, + 'auto-failover': True, + }, + { + 'name': config['name'], + 'url': f'http://{bracketize_ipv6(remote_addr)}:647/', + 'role': peer2_role, + 'auto-failover': True, + }, + ], } if 'ca_cert_file' in config: diff --git a/smoketest/scripts/cli/test_config_dependency.py b/smoketest/scripts/cli/test_config_dependency.py index ea5ba4a5c6e..1e1f8333242 100755 --- a/smoketest/scripts/cli/test_config_dependency.py +++ b/smoketest/scripts/cli/test_config_dependency.py @@ -19,6 +19,7 @@ from vyos.utils.process import is_systemd_service_running from vyos.utils.process import cmdl +from vyos.utils.file import read_file from vyos.configsession import ConfigSessionError from base_vyostest_shim import VyOSUnitTestSHIM @@ -77,12 +78,14 @@ def test_configdep_prio_queue(self): bonding_base = ['interfaces', 'bonding'] bond_interface = 'bond0' bond_address = '192.0.2.1/24' + bond_ipv6_address = '2001:db8:9166::1/64' vrrp_group_base = ['high-availability', 'vrrp', 'group'] vrrp_sync_group_base = ['high-availability', 'vrrp', 'sync-group'] vrrp_group = 'ETH2' vrrp_sync_group = 'GROUP' conntrack_sync_base = ['service', 'conntrack-sync'] conntrack_peer = '192.0.2.77' + conntrack_ipv6_peer = '2001:db8:9166::2' # simple set to trigger in-session conntrack -> conntrack-sync # dependency; note that this is triggered on boot in 1.4 due to @@ -92,10 +95,9 @@ def test_configdep_prio_queue(self): self.cli_set(['interfaces', 'ethernet', 'eth2', 'address', '198.51.100.2/24']) - self.cli_set(bonding_base + [bond_interface, 'address', - bond_address]) - self.cli_set(bonding_base + [bond_interface, 'member', 'interface', - 'eth3']) + self.cli_set(bonding_base + [bond_interface, 'address', bond_address]) + self.cli_set(bonding_base + [bond_interface, 'address', bond_ipv6_address]) + self.cli_set(bonding_base + [bond_interface, 'member', 'interface', 'eth3']) self.cli_set(vrrp_group_base + [vrrp_group, 'address', '198.51.100.200/24']) @@ -115,6 +117,39 @@ def test_configdep_prio_queue(self): self.cli_commit() + config = read_file('/run/conntrackd/conntrackd.conf') + self.assertIn(f'IPv4_Destination_Address {conntrack_peer}', config) + self.assertTrue(is_systemd_service_running('conntrackd.service')) + + # Test the IPv6 case + self.cli_delete(conntrack_sync_base + ['interface', bond_interface, 'peer']) + self.cli_set( + conntrack_sync_base + + ['interface', bond_interface, 'peer', conntrack_ipv6_peer] + ) + self.cli_set( + conntrack_sync_base + ['listen-address', bond_ipv6_address.split('/')[0]] + ) + + self.cli_commit() + + config = read_file('/run/conntrackd/conntrackd.conf') + self.assertIn(f'IPv6_address {bond_ipv6_address.split("/")[0]}', config) + self.assertIn(f'IPv6_Destination_Address {conntrack_ipv6_peer}', config) + self.assertTrue(is_systemd_service_running('conntrackd.service')) + + # Test IPv6 multicast + conntrack_ipv6_mcast_group = 'ff12::9166' + self.cli_delete(conntrack_sync_base + ['interface', bond_interface, 'peer']) + self.cli_delete(conntrack_sync_base + ['listen-address']) + self.cli_set(conntrack_sync_base + ['mcast-group', conntrack_ipv6_mcast_group]) + + self.cli_commit() + + config = read_file('/run/conntrackd/conntrackd.conf') + self.assertIn(f'IPv6_address {conntrack_ipv6_mcast_group}', config) + self.assertTrue(is_systemd_service_running('conntrackd.service')) + # clean up self.cli_delete(bonding_base) self.cli_delete(vrrp_group_base) diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index a544198c630..b911c4ef5d6 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -44,6 +44,8 @@ dns_1 = inc_ip(subnet, 2) dns_2 = inc_ip(subnet, 3) domain_name = 'vyos.net' +ha_ipv6_local = '2001:db8:9166::1' +ha_ipv6_remote = '2001:db8:9166::2' class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): @@ -57,6 +59,9 @@ def setUpClass(cls): cls.cli_set( cls, ['interfaces', 'dummy', interface, 'address', f'{router}/{cidr_mask}'] ) + cls.cli_set( + cls, ['interfaces', 'dummy', interface, 'address', f'{ha_ipv6_local}/64'] + ) @classmethod def tearDownClass(cls): @@ -1234,6 +1239,16 @@ def test_dhcp_high_availability(self): # Check for running process self.verify_service_running() + # Test the IPv6 case + self.cli_set(base_path + ['high-availability', 'source-address', ha_ipv6_local]) + self.cli_set(base_path + ['high-availability', 'remote', ha_ipv6_remote]) + self.cli_commit() + + config = read_file(KEA4_CONF) + self.assertIn(f'http://[{ha_ipv6_local}]:647/', config) + self.assertIn(f'http://[{ha_ipv6_remote}]:647/', config) + self.verify_service_running() + def test_dhcp_high_availability_standby(self): shared_net_name = 'FAILOVER' failover_name = 'VyOS-Failover' diff --git a/src/conf_mode/service_conntrack-sync.py b/src/conf_mode/service_conntrack-sync.py index b069378c338..455385e816e 100755 --- a/src/conf_mode/service_conntrack-sync.py +++ b/src/conf_mode/service_conntrack-sync.py @@ -26,6 +26,8 @@ from vyos.utils.process import run from vyos.template import render from vyos.template import get_ipv4 +from vyos.template import get_ipv6 +from vyos.template import is_ipv4 from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag @@ -68,9 +70,6 @@ def verify(conntrack): has_peer = False for interface, interface_config in conntrack['interface'].items(): verify_interface_exists(conntrack, interface) - # Interface must not only exist, it must also carry an IP address - if len(get_ipv4(interface)) < 1: - raise ConfigError(f'Interface {interface} requires an IP address!') if 'peer' in interface_config: has_peer = True @@ -82,6 +81,21 @@ def verify(conntrack): if 'peer' not in interface_config: raise ConfigError('Cannot mix unicast and multicast mode!') + # The synchronization interface must have an address matching the address + # family selected by its peer or multicast group. + for interface, interface_config in conntrack['interface'].items(): + sync_address = ( + interface_config['peer'] if has_peer else conntrack['mcast_group'] + ) + address_family = 'IPv4' if is_ipv4(sync_address) else 'IPv6' + interface_addresses = ( + get_ipv4(interface) if is_ipv4(sync_address) else get_ipv6(interface) + ) + if not interface_addresses: + raise ConfigError( + f'Interface {interface} requires an {address_family} address!' + ) + if 'expect_sync' in conntrack: if len(conntrack['expect_sync']) > 1 and 'all' in conntrack['expect_sync']: raise ConfigError('Cannot configure expect-sync "all" with other protocols!') @@ -90,6 +104,14 @@ def verify(conntrack): for address in conntrack['listen_address']: if not is_addr_assigned(address): raise ConfigError(f'Specified listen-address {address} not assigned to any interface!') + if has_peer: + for interface_config in conntrack['interface'].values(): + peer = interface_config['peer'] + if is_ipv4(address) != is_ipv4(peer): + raise ConfigError( + f'Listen-address {address} does not match ' + f'address-family of peer {peer}!' + ) vrrp_group = dict_search('failover_mechanism.vrrp.sync_group', conntrack) if vrrp_group == None: