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: