Skip to content

Commit f16d201

Browse files
committed
Filter default gateway routes by address family in dual-stack config
When a network adapter has both IPv4 and IPv6 addresses with separate default gateways, _process_networks picks the first route with prefixlen == 0 regardless of address family. This causes IPv6 addresses to receive the IPv4 gateway (e.g., 169.254.0.1 instead of fe80::1), and the IPv6 default route is never configured. Filter the default gateway route candidates by matching the route's address family (0.0.0.0/0 vs ::/0) against the network address being configured. Signed-off-by: Max Makarov <maxpain@linux.com>
1 parent d63509f commit f16d201

2 files changed

Lines changed: 69 additions & 2 deletions

File tree

cloudbaseinit/plugins/common/networkconfig.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,13 @@ def _process_networks(osutils, network_details):
263263
ip_address, prefix_len = net.address_cidr.split("/")
264264

265265
gateway = None
266+
is_ipv6 = netaddr.valid_ipv6(ip_address)
267+
expected_version = 6 if is_ipv6 else 4
266268
default_gw_route = [
267269
r for r in net.routes if
268-
netaddr.IPNetwork(r.network_cidr).prefixlen == 0]
270+
netaddr.IPNetwork(r.network_cidr).prefixlen == 0 and
271+
netaddr.IPNetwork(r.network_cidr).version ==
272+
expected_version]
269273
if default_gw_route:
270274
gateway = default_gw_route[0].gateway
271275

cloudbaseinit/tests/plugins/common/test_networkconfig.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,9 +458,14 @@ def _test_execute_network_details_v2(self, mock_get_os_utils,
458458
any_order=False)
459459

460460
ip_address, prefix_len = mock.sentinel.address_cidr1.split("/")
461+
# When address is IPv6 but gateway is IPv4-only, gateway should
462+
# be None because the address family filter excludes it.
463+
expected_gateway = mock.sentinel.gateway1
464+
if ":" in ip_address:
465+
expected_gateway = None
461466
mock_os_utils.set_static_network_config.assert_called_once_with(
462467
mock.sentinel.link_id1, ip_address, prefix_len,
463-
mock.sentinel.gateway1, expected_dns_list)
468+
expected_gateway, expected_dns_list)
464469

465470
def test_execute_network_details_v2(self):
466471
self._test_execute_network_details_v2()
@@ -473,3 +478,61 @@ def test_execute_network_details_v2_ipv4_dns_list(self):
473478

474479
def test_execute_network_details_v2_ipv6_dns_list(self):
475480
self._test_execute_network_details_v2(both_ipv6_dns_list=True)
481+
482+
@mock.patch("cloudbaseinit.osutils.factory.get_os_utils")
483+
def test_execute_network_details_v2_dual_stack_gateways(
484+
self, mock_get_os_utils):
485+
"""IPv4 address gets IPv4 gateway, IPv6 address gets IPv6 gateway."""
486+
link1 = network_model.Link(
487+
id="eth0",
488+
name="eth0",
489+
type=network_model.LINK_TYPE_PHYSICAL,
490+
enabled=True,
491+
mac_address=u"00:00:00:00:00:01",
492+
mtu=None,
493+
bond=None,
494+
vlan_link=None,
495+
vlan_id=None)
496+
497+
route_v4 = network_model.Route(
498+
network_cidr=u"0.0.0.0/0",
499+
gateway=u"169.254.0.1")
500+
route_v6 = network_model.Route(
501+
network_cidr=u"::/0",
502+
gateway=u"fe80::1")
503+
504+
network_v4 = network_model.Network(
505+
link="eth0",
506+
address_cidr=u"10.0.0.1/32",
507+
dns_nameservers=["1.1.1.1"],
508+
routes=[route_v4, route_v6])
509+
network_v6 = network_model.Network(
510+
link="eth0",
511+
address_cidr=u"2001:db8::1/96",
512+
dns_nameservers=["1.1.1.1"],
513+
routes=[route_v4, route_v6])
514+
515+
network_details = network_model.NetworkDetailsV2(
516+
links=[link1],
517+
networks=[network_v4, network_v6],
518+
services=[])
519+
520+
service = mock.Mock()
521+
service.get_network_details_v2.return_value = network_details
522+
523+
mock_os_utils = mock.Mock()
524+
mock_get_os_utils.return_value = mock_os_utils
525+
mock_os_utils.get_network_adapter_name_by_mac_address.return_value = \
526+
"eth0"
527+
528+
plugin = networkconfig.NetworkConfigPlugin()
529+
plugin.execute(service, {})
530+
531+
calls = mock_os_utils.set_static_network_config.call_args_list
532+
self.assertEqual(len(calls), 2)
533+
# IPv4 address should get IPv4 gateway
534+
self.assertEqual(calls[0], mock.call(
535+
"eth0", "10.0.0.1", "32", "169.254.0.1", ["1.1.1.1"]))
536+
# IPv6 address should get IPv6 gateway
537+
self.assertEqual(calls[1], mock.call(
538+
"eth0", "2001:db8::1", "96", "fe80::1", ["1.1.1.1"]))

0 commit comments

Comments
 (0)