]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: assign new DHCP address before removing old lease address 12685/head
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 7 Jun 2019 06:31:07 +0000 (15:31 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 7 Jun 2019 07:22:00 +0000 (16:22 +0900)
Closes #12676.

src/network/networkd-dhcp4.c
src/network/networkd-link.c
src/network/networkd-link.h

index ce1e77429b0c735ed7c90a1fba3ea5ffa6beb9b0..978378d7b8217336fc344733e16533618f04b065 100644 (file)
 #include "string-util.h"
 #include "sysctl-util.h"
 
+static int dhcp_remove_routes(Link *link, sd_dhcp_lease *lease, sd_dhcp_lease *new_lease, struct in_addr *address);
+static int dhcp_remove_router(Link *link, sd_dhcp_lease *lease, struct in_addr *address);
+static int dhcp_remove_address(Link *link, sd_dhcp_lease *lease, struct in_addr *address);
+
+void dhcp4_release_old_lease(Link *link) {
+        union in_addr_union address = IN_ADDR_NULL, address_old = IN_ADDR_NULL;
+
+        assert(link);
+
+        if (!link->dhcp_lease_old)
+                return;
+
+        assert(link->dhcp_lease);
+
+        (void) sd_dhcp_lease_get_address(link->dhcp_lease_old, &address_old.in);
+        (void) sd_dhcp_lease_get_address(link->dhcp_lease, &address.in);
+
+        (void) dhcp_remove_routes(link, link->dhcp_lease_old, link->dhcp_lease, &address_old.in);
+
+        if (!in_addr_equal(AF_INET, &address_old, &address)) {
+                (void) dhcp_remove_router(link, link->dhcp_lease_old, &address_old.in);
+                (void) dhcp_remove_address(link, link->dhcp_lease_old, &address_old.in);
+        }
+
+        link->dhcp_lease_old = sd_dhcp_lease_unref(link->dhcp_lease_old);
+        link_dirty(link);
+}
+
 static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
         int r;
 
@@ -33,6 +61,8 @@ static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *li
 
         if (link->dhcp4_messages == 0) {
                 link->dhcp4_configured = true;
+                /* New address and routes are configured now. Let's release old lease. */
+                dhcp4_release_old_lease(link);
                 link_check_ready(link);
         }
 
@@ -182,10 +212,32 @@ static int link_set_dhcp_routes(Link *link) {
         return 0;
 }
 
-static int dhcp_remove_routes(Link *link, struct in_addr *address) {
-        _cleanup_free_ sd_dhcp_route **routes = NULL;
+static bool route_present_in_routes(const Route *route, sd_dhcp_route **routes, unsigned n_routes) {
+        assert(n_routes == 0 || routes);
+
+        for (unsigned j = 0; j < n_routes; j++) {
+                union in_addr_union a;
+                unsigned char l;
+
+                assert_se(sd_dhcp_route_get_gateway(routes[j], &a.in) >= 0);
+                if (!in_addr_equal(AF_INET, &a, &route->gw))
+                        continue;
+                assert_se(sd_dhcp_route_get_destination(routes[j], &a.in) >= 0);
+                if (!in_addr_equal(AF_INET, &a, &route->dst))
+                        continue;
+                assert_se(sd_dhcp_route_get_destination_prefix_length(routes[j], &l) >= 0);
+                if (l != route->dst_prefixlen)
+                        continue;
+                return true;
+        }
+
+        return false;
+}
+
+static int dhcp_remove_routes(Link *link, sd_dhcp_lease *lease, sd_dhcp_lease *new_lease, struct in_addr *address) {
+        _cleanup_free_ sd_dhcp_route **routes = NULL, **new_routes = NULL;
         uint32_t table;
-        int n, i, r;
+        int m = 0, n, i, r;
 
         assert(link);
         assert(address);
@@ -193,12 +245,19 @@ static int dhcp_remove_routes(Link *link, struct in_addr *address) {
         if (!link->network->dhcp_use_routes)
                 return 0;
 
-        n = sd_dhcp_lease_get_routes(link->dhcp_lease, &routes);
-        if (IN_SET(n, 0, -ENODATA)) {
-                log_link_debug(link, "DHCP: No routes received from DHCP server: %m");
+        n = sd_dhcp_lease_get_routes(lease, &routes);
+        if (IN_SET(n, 0, -ENODATA))
                 return 0;
-        } else if (n < 0)
-                return log_link_error_errno(link, n, "DHCP error: could not get routes: %m");
+        else if (n < 0)
+                return log_link_error_errno(link, n, "DHCP error: Failed to get routes: %m");
+
+        if (new_lease) {
+                m = sd_dhcp_lease_get_routes(new_lease, &new_routes);
+                if (m == -ENODATA)
+                        m = 0;
+                else if (m < 0)
+                        return log_link_error_errno(link, m, "DHCP error: Failed to get routes: %m");
+        }
 
         table = link_get_dhcp_route_table(link);
 
@@ -217,13 +276,16 @@ static int dhcp_remove_routes(Link *link, struct in_addr *address) {
                 route->table = table;
                 route->scope = route_scope_from_address(route, address);
 
+                if (route_present_in_routes(route, new_routes, m))
+                        continue;
+
                 (void) route_remove(route, link, NULL);
         }
 
         return n;
 }
 
-static int dhcp_remove_router(Link *link, struct in_addr *address) {
+static int dhcp_remove_router(Link *link, sd_dhcp_lease *lease, struct in_addr *address) {
         _cleanup_(route_freep) Route *route_gw = NULL, *route = NULL;
         const struct in_addr *router;
         uint32_t table;
@@ -235,7 +297,7 @@ static int dhcp_remove_router(Link *link, struct in_addr *address) {
         if (!link->network->dhcp_use_routes)
                 return 0;
 
-        r = sd_dhcp_lease_get_router(link->dhcp_lease, &router);
+        r = sd_dhcp_lease_get_router(lease, &router);
         if (IN_SET(r, 0, -ENODATA)) {
                 log_link_debug(link, "DHCP: No gateway received from DHCP server.");
                 return 0;
@@ -279,7 +341,7 @@ static int dhcp_remove_router(Link *link, struct in_addr *address) {
         return 0;
 }
 
-static int dhcp_remove_address(Link *link, struct in_addr *address) {
+static int dhcp_remove_address(Link *link, sd_dhcp_lease *lease, struct in_addr *address) {
         _cleanup_(address_freep) Address *a = NULL;
         struct in_addr netmask;
         int r;
@@ -297,7 +359,7 @@ static int dhcp_remove_address(Link *link, struct in_addr *address) {
         a->family = AF_INET;
         a->in_addr.in = *address;
 
-        if (sd_dhcp_lease_get_netmask(link->dhcp_lease, &netmask) >= 0)
+        if (sd_dhcp_lease_get_netmask(lease, &netmask) >= 0)
                 a->prefixlen = in4_addr_netmask_to_prefixlen(&netmask);
 
         (void) address_remove(a, link, NULL);
@@ -366,9 +428,9 @@ static int dhcp_lease_lost(Link *link) {
         link->dhcp4_configured = false;
 
         (void) sd_dhcp_lease_get_address(link->dhcp_lease, &address);
-        (void) dhcp_remove_routes(link, &address);
-        (void) dhcp_remove_router(link, &address);
-        (void) dhcp_remove_address(link, &address);
+        (void) dhcp_remove_routes(link, link->dhcp_lease, NULL, &address);
+        (void) dhcp_remove_router(link, link->dhcp_lease, &address);
+        (void) dhcp_remove_address(link, link->dhcp_lease, &address);
         (void) dhcp_reset_mtu(link);
         (void) dhcp_reset_hostname(link);
 
@@ -406,6 +468,9 @@ static int dhcp4_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *
 
         if (link->dhcp4_messages == 0) {
                 link->dhcp4_configured = true;
+                /* The new address is configured, and no route is requested.
+                 * Let's drop the old lease. */
+                dhcp4_release_old_lease(link);
                 link_check_ready(link);
         }
 
@@ -601,6 +666,37 @@ static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) {
         return 0;
 }
 
+static int dhcp_lease_ip_change(sd_dhcp_client *client, Link *link) {
+        int r;
+
+        link->dhcp_lease_old = TAKE_PTR(link->dhcp_lease);
+
+        /* On ip address change, to keep the connectability, we would like to assign new address and
+         * routes, and then release old lease. There are two possible success paths:
+         *
+         * 1. new address and routes are configured.
+         *    -> handled by dhcp_release_old_lease() in dhcp4_route_handler().
+         * 2. new address is configured and no route is requested.
+         *    -> handled by dhcp_release_old_lease() in dhcp4_address_handler().
+         *
+         * On error in assigning new address and routes, then the link always enters to the failed
+         * state. And link_enter_failed() leads to the DHCP client to be stopped. So,
+         * dhcp_release_old_lease() will be also called by link_stop_clients().
+         */
+
+        r = dhcp_lease_acquired(client, link);
+        if (r < 0) {
+                /* If it fails, then the new address is not configured yet.
+                 * So, let's simply drop the old lease. */
+                sd_dhcp_lease_unref(link->dhcp_lease);
+                link->dhcp_lease = TAKE_PTR(link->dhcp_lease_old);
+                (void) dhcp_lease_lost(link);
+                return r;
+        }
+
+        return 0;
+}
+
 static int dhcp_server_is_black_listed(Link *link, sd_dhcp_client *client) {
         sd_dhcp_lease *lease;
         struct in_addr addr;
@@ -671,8 +767,6 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
 
                         break;
                 case SD_DHCP_CLIENT_EVENT_EXPIRED:
-                case SD_DHCP_CLIENT_EVENT_IP_CHANGE:
-
                         if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) {
                                 log_link_notice(link, "DHCPv4 connection considered critical, ignoring request to reconfigure it.");
                                 return 0;
@@ -686,12 +780,17 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
                                 }
                         }
 
-                        if (event == SD_DHCP_CLIENT_EVENT_IP_CHANGE) {
-                                r = dhcp_lease_acquired(client, link);
-                                if (r < 0) {
-                                        link_enter_failed(link);
-                                        return r;
-                                }
+                        break;
+                case SD_DHCP_CLIENT_EVENT_IP_CHANGE:
+                        if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) {
+                                log_link_notice(link, "DHCPv4 connection considered critical, ignoring request to reconfigure it.");
+                                return 0;
+                        }
+
+                        r = dhcp_lease_ip_change(client, link);
+                        if (r < 0) {
+                                link_enter_failed(link);
+                                return r;
                         }
 
                         break;
index 8ebe4b2bcb8a60f22bf5bd96605de7e28d5228d9..7181cdd0d70dd61e8f05539fb714bcb75bd05c4a 100644 (file)
@@ -687,6 +687,8 @@ int link_stop_clients(Link *link, bool may_keep_dhcp) {
         assert(link->manager);
         assert(link->manager->event);
 
+        dhcp4_release_old_lease(link);
+
         if (link->dhcp_client && (!may_keep_dhcp || !link->network ||
                                   !FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP_ON_STOP))) {
                 k = sd_dhcp_client_stop(link->dhcp_client);
index 03db91bbd97e92100035842d7a0be8c915867e73..f2aa504f1ff88a2ea467b89994a561c826056b8f 100644 (file)
@@ -78,7 +78,7 @@ typedef struct Link {
         bool addresses_ready;
 
         sd_dhcp_client *dhcp_client;
-        sd_dhcp_lease *dhcp_lease;
+        sd_dhcp_lease *dhcp_lease, *dhcp_lease_old;
         char *lease_file;
         uint32_t original_mtu;
         unsigned dhcp4_messages;
@@ -167,6 +167,7 @@ int link_set_mtu(Link *link, uint32_t mtu);
 int ipv4ll_configure(Link *link);
 bool link_ipv4ll_enabled(Link *link, AddressFamilyBoolean mask);
 
+void dhcp4_release_old_lease(Link *link);
 int dhcp4_configure(Link *link);
 int dhcp4_set_client_identifier(Link *link);
 int dhcp4_set_promote_secondaries(Link *link);