Package: release.debian.org Severity: normal Tags: bookworm User: release.debian....@packages.debian.org Usertags: pu X-Debbugs-Cc: debian-b...@lists.debian.org Control: affects -1 + src:netcfg
[ Reason ] Certain kinds of network architectures in Debian deployments (e.g. https://phabricator.wikimedia.org/phame/post/view/312/ganeti_on_modern_network_design/, https://blog.fhrnet.eu/2020/03/07/dhcp-server-on-a-32-subnet/) are difficult because the installer doesn't support single-address netmasks out of the box (https://bugs.debian.org/1064005). You can sort of handle this with point-to-point preseeding, but that isn't quite identical and it doesn't work for IPv6. I've just fixed this in unstable, but it would be helpful to have it in place for installs of bookworm too. [ Impact ] It's possible to work around this without changing netcfg, but it requires messy and fragile early_command preseeding to defeat netcfg's behaviour (see #1064005). [ Tests ] The change includes automated tests for the changes to the gateway reachability test. For the changes to the routing table setup and to confirm that no changes were needed to network configuration in the installed system, I used libvirt machines with a DHCP server along the lines of https://blog.fhrnet.eu/2020/03/07/dhcp-server-on-a-32-subnet/; Wikimedia has also tested this using their own setup and confirmed that it now works out of the box. [ Risks ] I've taken care that all the new code is guarded by interface->masklen checks, so it should be obvious that it's a no-op on any system that isn't using this unusual single-address netmask setup. [ Checklist ] [x] *all* changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in (old)stable [x] the issue is verified as fixed in unstable [ Changes ] It turns out that fixing this is very straightforward: it's just a matter of special-casing the gateway reachability test and then telling the routing table to pretend that the gateway is directly attached to the relevant link. The installed system (ifupdown, NetworkManager) already gets this right; it's just the installer that had trouble with it. [ Other info ] This work was requested and sponsored by the Wikimedia Foundation. -- Colin Watson (he/him) [cjwat...@debian.org]
diff -Nru netcfg-1.187/debian/changelog netcfg-1.187+deb12u1/debian/changelog --- netcfg-1.187/debian/changelog 2023-05-23 19:24:01.000000000 +0100 +++ netcfg-1.187+deb12u1/debian/changelog 2024-05-14 11:27:14.000000000 +0100 @@ -1,3 +1,9 @@ +netcfg (1.187+deb12u1) UNRELEASED; urgency=medium + + * Handle routing for single-address netmasks (closes: #1064005). + + -- Colin Watson <cjwat...@debian.org> Tue, 14 May 2024 11:27:14 +0100 + netcfg (1.187) unstable; urgency=medium * Team upload diff -Nru netcfg-1.187/netcfg-common.c netcfg-1.187+deb12u1/netcfg-common.c --- netcfg-1.187/netcfg-common.c 2022-06-03 19:25:43.000000000 +0100 +++ netcfg-1.187+deb12u1/netcfg-common.c 2024-05-14 11:26:51.000000000 +0100 @@ -1680,8 +1680,22 @@ inet_pton(interface->address_family, interface->gateway, &gw_addr); if (interface->address_family == AF_INET) { + if (interface->masklen == 32) { + /* Special case for single-address networks. We'll handle + * routing manually in this case. + */ + return 1; + } + return (gw_addr.in4.s_addr && ((gw_addr.in4.s_addr & mask.in4.s_addr) == net.in4.s_addr)); } else if (interface->address_family == AF_INET6) { + if (interface->masklen == 128) { + /* Special case for single-address networks. We'll handle + * routing manually in this case. + */ + return 1; + } + if ((ntohs(gw_addr.in6.s6_addr16[0]) & 0xffc0) == (0xfe80 & 0xffc0)) { return 1; } diff -Nru netcfg-1.187/static.c netcfg-1.187+deb12u1/static.c --- netcfg-1.187/static.c 2021-09-22 19:07:01.000000000 +0100 +++ netcfg-1.187+deb12u1/static.c 2024-05-14 11:27:14.000000000 +0100 @@ -338,6 +338,12 @@ di_snprintfcat(buf, sizeof(buf), "%s", interface->ipaddress); rv |= di_exec_shell_log(buf); } else if (!empty_str(interface->gateway)) { + if (interface->masklen == 32) { + snprintf(buf, sizeof(buf), "route add -interface %s %s", interface->gateway, interface->ipaddress); + di_info("executing: %s", buf); + rv |= di_exec_shell_log(buf); + } + snprintf(buf, sizeof(buf), "route add default %s", interface->gateway); rv |= di_exec_shell_log(buf); } @@ -373,6 +379,8 @@ } else if (!empty_str(interface->gateway)) { snprintf(buf, sizeof(buf), "ip route add default via %s", interface->gateway); + if (interface->masklen == 32) + di_snprintfcat(buf, sizeof(buf), " dev %s onlink", interface->name); rv |= di_exec_shell_log(buf); } #endif @@ -442,6 +450,12 @@ rv |= di_exec_shell_log(buf); if (!empty_str(interface->gateway)) { + if (interface->masklen == 128) { + snprintf(buf, sizeof(buf), "/lib/freebsd/route add -inet6 -interface %s %s", interface->gateway, interface->ipaddress); + di_info("executing: %s", buf); + rv |= di_exec_shell_log(buf); + } + snprintf(buf, sizeof(buf), "/lib/freebsd/route add -inet6 default %s", interface->gateway); rv |= di_exec_shell_log(buf); } @@ -475,6 +489,8 @@ if (!empty_str(interface->gateway)) { snprintf(buf, sizeof(buf), "ip route add default via %s dev %s", interface->gateway, interface->name); + if (interface->masklen == 128) + di_snprintfcat(buf, sizeof(buf), " onlink"); di_info("executing: %s", buf); rv |= di_exec_shell_log(buf); } diff -Nru netcfg-1.187/test/test_netcfg_gateway_reachable.c netcfg-1.187+deb12u1/test/test_netcfg_gateway_reachable.c --- netcfg-1.187/test/test_netcfg_gateway_reachable.c 2021-09-22 19:07:01.000000000 +0100 +++ netcfg-1.187+deb12u1/test/test_netcfg_gateway_reachable.c 2024-05-14 11:26:51.000000000 +0100 @@ -41,6 +41,21 @@ } END_TEST +START_TEST(test_netcfg_gateway_reachable_v4_32) +{ + struct netcfg_interface iface; + netcfg_interface_init(&iface); + + strcpy(iface.ipaddress, "192.168.1.2"); + strcpy(iface.gateway, "192.168.1.1"); + iface.masklen = 32; + iface.address_family = AF_INET; + + ck_assert_msg (netcfg_gateway_reachable(&iface), + "Gateway erroneously unreachable"); +} +END_TEST + START_TEST(test_netcfg_gateway_reachable_v6_64) { struct netcfg_interface iface; @@ -81,6 +96,21 @@ } END_TEST +START_TEST(test_netcfg_gateway_reachable_v6_128) +{ + struct netcfg_interface iface; + netcfg_interface_init(&iface); + + strcpy(iface.ipaddress, "2001:3:5:7::71"); + strcpy(iface.gateway, "2001:3:5::1"); + iface.masklen = 128; + iface.address_family = AF_INET6; + + ck_assert_msg (netcfg_gateway_reachable(&iface), + "Gateway erroneously unreachable"); +} +END_TEST + START_TEST(test_netcfg_gateway_reachable_v6_fe80) { struct netcfg_interface iface; @@ -111,8 +141,10 @@ TCase *tc = tcase_create ("netcfg_gateway_reachable"); tcase_add_test (tc, test_netcfg_gateway_reachable_v4_24); tcase_add_test (tc, test_netcfg_gateway_reachable_v4_22); + tcase_add_test (tc, test_netcfg_gateway_reachable_v4_32); tcase_add_test (tc, test_netcfg_gateway_reachable_v6_64); tcase_add_test (tc, test_netcfg_gateway_reachable_v6_48); + tcase_add_test (tc, test_netcfg_gateway_reachable_v6_128); tcase_add_test (tc, test_netcfg_gateway_reachable_v6_fe80); suite_add_tcase (s, tc);