diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 356b9e9d31..19743d2037 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -18,7 +18,9 @@ static int dhcp_remove_routes(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all); static int dhcp_remove_router(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all); static int dhcp_remove_dns_routes(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all); -static int dhcp_remove_address(Link *link, sd_dhcp_lease *lease, const struct in_addr *address); +static int dhcp_remove_address(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, link_netlink_message_handler_t callback); +static int dhcp_remove_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link); +static int dhcp_lease_renew(sd_dhcp_client *client, Link *link); void dhcp4_release_old_lease(Link *link) { struct in_addr address = {}, address_old = {}; @@ -38,7 +40,7 @@ void dhcp4_release_old_lease(Link *link) { (void) dhcp_remove_dns_routes(link, link->dhcp_lease_old, &address_old, false); if (!in4_addr_equal(&address_old, &address)) - (void) dhcp_remove_address(link, link->dhcp_lease_old, &address_old); + (void) dhcp_remove_address(link, link->dhcp_lease_old, &address_old, NULL); link->dhcp_lease_old = sd_dhcp_lease_unref(link->dhcp_lease_old); link_dirty(link); @@ -56,13 +58,35 @@ static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *li return 1; r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -EEXIST) { + if (r == -ENETUNREACH && !link->dhcp4_route_retrying) { + + /* It seems kernel does not support that the prefix route cannot be configured with + * route table. Let's once drop the config and reconfigure them later. */ + + log_link_debug_errno(link, r, "Could not set DHCPv4 route, retrying later: %m"); + link->dhcp4_route_failed = true; + link->manager->dhcp4_prefix_root_cannot_set_table = true; + } else if (r < 0 && r != -EEXIST) { log_link_error_errno(link, r, "Could not set DHCPv4 route: %m"); link_enter_failed(link); return 1; } if (link->dhcp4_messages == 0) { + if (link->dhcp4_route_failed) { + struct in_addr address = {}; + + link->dhcp4_route_failed = false; + link->dhcp4_route_retrying = true; + + (void) sd_dhcp_lease_get_address(link->dhcp_lease, &address); + (void) dhcp_remove_routes(link, link->dhcp_lease, &address, true); + (void) dhcp_remove_router(link, link->dhcp_lease, &address, true); + (void) dhcp_remove_dns_routes(link, link->dhcp_lease, &address, true); + (void) dhcp_remove_address(link, link->dhcp_lease, &address, dhcp_remove_address_handler); + + return 1; + } link->dhcp4_configured = true; /* New address and routes are configured now. Let's release old lease. */ dhcp4_release_old_lease(link); @@ -87,7 +111,8 @@ static int route_scope_from_address(const Route *route, const struct in_addr *se static bool link_noprefixroute(Link *link) { return link->network->dhcp_route_table_set && - link->network->dhcp_route_table != RT_TABLE_MAIN; + link->network->dhcp_route_table != RT_TABLE_MAIN && + !link->manager->dhcp4_prefix_root_cannot_set_table; } static int dhcp_route_configure(Route **route, Link *link) { @@ -500,7 +525,32 @@ static int dhcp_remove_dns_routes(Link *link, sd_dhcp_lease *lease, const struct return 0; } -static int dhcp_remove_address(Link *link, sd_dhcp_lease *lease, const struct in_addr *address) { +static int dhcp_remove_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + + /* This is only used when retrying to assign the address received from DHCPv4 server. + * See dhcp4_route_handler(). */ + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + log_link_debug_errno(link, r, "Failed to remove DHCPv4 address, ignoring: %m"); + else + (void) manager_rtnl_process_address(rtnl, m, link->manager); + + (void) dhcp_lease_renew(link->dhcp_client, link); + return 1; +} + +static int dhcp_remove_address( + Link *link, sd_dhcp_lease *lease, + const struct in_addr *address, + link_netlink_message_handler_t callback) { + _cleanup_(address_freep) Address *a = NULL; struct in_addr netmask; int r; @@ -521,7 +571,7 @@ static int dhcp_remove_address(Link *link, sd_dhcp_lease *lease, const struct in if (sd_dhcp_lease_get_netmask(lease, &netmask) >= 0) a->prefixlen = in4_addr_netmask_to_prefixlen(&netmask); - (void) address_remove(a, link, NULL); + (void) address_remove(a, link, callback); return 0; } @@ -590,7 +640,7 @@ static int dhcp_lease_lost(Link *link) { (void) dhcp_remove_routes(link, link->dhcp_lease, &address, true); (void) dhcp_remove_router(link, link->dhcp_lease, &address, true); (void) dhcp_remove_dns_routes(link, link->dhcp_lease, &address, true); - (void) dhcp_remove_address(link, link->dhcp_lease, &address); + (void) dhcp_remove_address(link, link->dhcp_lease, &address, NULL); (void) dhcp_reset_mtu(link); (void) dhcp_reset_hostname(link); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 91e2bf06b5..429a053fdf 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -87,6 +87,8 @@ typedef struct Link { char *lease_file; uint32_t original_mtu; unsigned dhcp4_messages; + bool dhcp4_route_failed:1; + bool dhcp4_route_retrying:1; bool dhcp4_configured:1; bool dhcp6_configured:1; diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index 5422b56dc2..12cf08b5ec 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -64,6 +64,8 @@ struct Manager { usec_t speed_meter_interval_usec; usec_t speed_meter_usec_new; usec_t speed_meter_usec_old; + + bool dhcp4_prefix_root_cannot_set_table; }; int manager_new(Manager **ret); diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 171df79eca..b7afdaa911 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -2469,13 +2469,16 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): output = check_output('ip route show table main dev veth99') print(output) # See issue #8726 - self.assertEqual(output, '') + main_table_is_empty = output == '' + if not main_table_is_empty: + self.assertNotRegex(output, 'proto dhcp') print('## ip route show table 211 dev veth99') output = check_output('ip route show table 211 dev veth99') print(output) self.assertRegex(output, 'default via 192.168.5.1 proto dhcp') - self.assertRegex(output, '192.168.5.0/24 proto dhcp') + if main_table_is_empty: + self.assertRegex(output, '192.168.5.0/24 proto dhcp') self.assertRegex(output, '192.168.5.0/24 via 192.168.5.5 proto dhcp') self.assertRegex(output, '192.168.5.1 proto dhcp scope link')