networkd: smooth transition from ipv4ll to dhcp address
authorUmut Tezduyar Lindskog <umut.tezduyar@axis.com>
Wed, 2 Apr 2014 19:31:12 +0000 (21:31 +0200)
committerTom Gundersen <teg@jklm.no>
Thu, 3 Apr 2014 14:00:25 +0000 (16:00 +0200)
Currently when both ipv4ll and dhcp are enabled, ipv4ll
address (if one has been claimed) is removed when dhcp
address is aquired. This is not the best thing to do
since there might be clients unaware of the removal
trying to communicate.

This patch provides a smooth transition between ipv4ll
and dhcp. If ipv4ll address was claimed [1] before dhcp,
address is marked as deprecated. Deprecated address is still
a valid address and packets can be received on it but address
cannot be selected as a source address. If dhcp lease cannot
be extended, then ipv4ll address is marked as valid again.

[1] If there is no collision, claiming IPv4LL takes between 4 to
7 seconds.

12 files changed:
TODO
man/systemd.network.xml
src/libsystemd-network/sd-ipv4ll.c
src/libsystemd/sd-rtnl/rtnl-message.c
src/libsystemd/sd-rtnl/rtnl-types.c
src/libsystemd/sd-rtnl/rtnl-types.h
src/libsystemd/sd-rtnl/test-rtnl.c
src/network/networkd-address.c
src/network/networkd-link.c
src/network/networkd.h
src/systemd/sd-ipv4ll.h
src/systemd/sd-rtnl.h

diff --git a/TODO b/TODO
index 2d56e81..4ff69ff 100644 (file)
--- a/TODO
+++ b/TODO
@@ -665,7 +665,6 @@ Features:
    - add reduced [Link] support to .network files
    - add IPv4LL tests (inspire by DHCP)
    - add Scope= parsing option for [Network]
-   - have smooth transition from LL to routable address, without disconnecting clients.
 
 * sd-network:
    - make sure ipv4ll and dhcp clients can handle changing mac addresses while running
index f3b3b31..f49de17 100644 (file)
                                         <term><varname>IPv4LL=</varname></term>
                                         <listitem>
                                                 <para>A boolean. When true, enables IPv4 link-local support.
-                                                If <literal>DHCP=</literal> is also true, IPv4 link-local
-                                                address will be removed upon acquiring a DHCP lease.
+                                                If <literal>DHCP=</literal> is also true, acquiring DHCP address
+                                                will deprecate previously acquired IPv4 link-local address or
+                                                stop acquiring process if there hasn't been one acquired before.
                                                 </para>
                                         </listitem>
                                 </varlistentry>
index a201139..81fe85b 100644 (file)
@@ -481,6 +481,12 @@ error:
         return r;
 }
 
+bool sd_ipv4ll_is_running(sd_ipv4ll *ll) {
+        assert_return(ll, -EINVAL);
+
+        return ll->state != IPV4LL_STATE_INIT;
+}
+
 #define HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2)
 
 int sd_ipv4ll_start (sd_ipv4ll *ll) {
index 4ace94c..e5854de 100644 (file)
@@ -286,6 +286,19 @@ int sd_rtnl_message_new_addr(sd_rtnl *rtnl, sd_rtnl_message **ret,
         return 0;
 }
 
+int sd_rtnl_message_new_addr_update(sd_rtnl *rtnl, sd_rtnl_message **ret,
+                             int index, unsigned char family) {
+        int r;
+
+        r = sd_rtnl_message_new_addr(rtnl, ret, RTM_NEWADDR, index, family);
+        if (r < 0)
+                return r;
+
+        (*ret)->hdr->nlmsg_flags |= NLM_F_REPLACE;
+
+        return 0;
+}
+
 sd_rtnl_message *sd_rtnl_message_ref(sd_rtnl_message *m) {
         if (m)
                 assert_se(REFCNT_INC(m->n_ref) >= 2);
@@ -559,6 +572,24 @@ int sd_rtnl_message_append_ether_addr(sd_rtnl_message *m, unsigned short type, c
         return 0;
 }
 
+int sd_rtnl_message_append_cache_info(sd_rtnl_message *m, unsigned short type, const struct ifa_cacheinfo *info) {
+        int r;
+
+        assert_return(m, -EINVAL);
+        assert_return(!m->sealed, -EPERM);
+        assert_return(info, -EINVAL);
+
+        r = message_attribute_has_type(m, type, NLA_CACHE_INFO);
+        if (r < 0)
+                return r;
+
+        r = add_rtattr(m, type, info, sizeof(struct ifa_cacheinfo));
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 int sd_rtnl_message_open_container(sd_rtnl_message *m, unsigned short type) {
         size_t size;
         int r;
@@ -741,6 +772,25 @@ int sd_rtnl_message_read_ether_addr(sd_rtnl_message *m, unsigned short type, str
         return 0;
 }
 
+int sd_rtnl_message_read_cache_info(sd_rtnl_message *m, unsigned short type, struct ifa_cacheinfo *info) {
+        int r;
+        void *attr_data;
+
+        r = message_attribute_has_type(m, type, NLA_CACHE_INFO);
+        if (r < 0)
+                return r;
+
+        r = rtnl_message_read_internal(m, type, &attr_data);
+        if (r < 0)
+                return r;
+        else if ((size_t)r < sizeof(struct ifa_cacheinfo))
+                return -EIO;
+
+        memcpy(info, attr_data, sizeof(struct ifa_cacheinfo));
+
+        return 0;
+}
+
 int sd_rtnl_message_read_in_addr(sd_rtnl_message *m, unsigned short type, struct in_addr *data) {
         int r;
         void *attr_data;
index 29ee5bc..4e70c95 100644 (file)
@@ -216,9 +216,9 @@ static const NLType rtnl_address_types[IFA_MAX + 1] = {
         [IFA_LOCAL]             = { .type = NLA_IN_ADDR },
         [IFA_LABEL]             = { .type = NLA_STRING, .size = IFNAMSIZ - 1 },
         [IFA_BROADCAST]         = { .type = NLA_IN_ADDR }, /* 6? */
+        [IFA_CACHEINFO]         = { .type = NLA_CACHE_INFO, .size = sizeof(struct ifa_cacheinfo) },
 /*
         [IFA_ANYCAST],
-        [IFA_CACHEINFO]         = { .len = sizeof(struct ifa_cacheinfo) },
         [IFA_MULTICAST],
 */
 #ifdef IFA_FLAGS
index 2425dc9..7ce9597 100644 (file)
@@ -31,6 +31,7 @@ enum {
         NLA_STRING,
         NLA_IN_ADDR,
         NLA_ETHER_ADDR,
+        NLA_CACHE_INFO,
         NLA_NESTED,
         NLA_UNION,
 };
index 4436962..529231a 100644 (file)
@@ -106,6 +106,7 @@ static void test_address_get(sd_rtnl *rtnl, int ifindex) {
         sd_rtnl_message *m;
         sd_rtnl_message *r;
         struct in_addr in_data;
+        struct ifa_cacheinfo cache;
         char *label;
 
         assert_se(sd_rtnl_message_new_addr(rtnl, &m, RTM_GETADDR, ifindex, AF_INET) >= 0);
@@ -116,6 +117,7 @@ static void test_address_get(sd_rtnl *rtnl, int ifindex) {
         assert_se(sd_rtnl_message_read_in_addr(r, IFA_LOCAL, &in_data) == 0);
         assert_se(sd_rtnl_message_read_in_addr(r, IFA_ADDRESS, &in_data) == 0);
         assert_se(sd_rtnl_message_read_string(r, IFA_LABEL, &label) == 0);
+        assert_se(sd_rtnl_message_read_cache_info(r, IFA_CACHEINFO, &cache) == 0);
 
         assert_se(sd_rtnl_flush(rtnl) >= 0);
         assert_se((m = sd_rtnl_message_unref(m)) == NULL);
index dd4c822..87688a5 100644 (file)
 #include "conf-parser.h"
 #include "network-internal.h"
 
+static void address_init(Address *address) {
+        assert(address);
+
+        address->family = AF_UNSPEC;
+        address->scope = RT_SCOPE_UNIVERSE;
+        address->cinfo.ifa_prefered = CACHE_INFO_INFINITY_LIFE_TIME;
+        address->cinfo.ifa_valid = CACHE_INFO_INFINITY_LIFE_TIME;
+}
+
 int address_new_static(Network *network, unsigned section, Address **ret) {
         _cleanup_address_free_ Address *address = NULL;
 
@@ -46,8 +55,7 @@ int address_new_static(Network *network, unsigned section, Address **ret) {
         if (!address)
                 return -ENOMEM;
 
-        address->family = AF_UNSPEC;
-        address->scope = RT_SCOPE_UNIVERSE;
+        address_init(address);
 
         address->network = network;
 
@@ -71,8 +79,7 @@ int address_new_dynamic(Address **ret) {
         if (!address)
                 return -ENOMEM;
 
-        address->family = AF_UNSPEC;
-        address->scope = RT_SCOPE_UNIVERSE;
+        address_init(address);
 
         *ret = address;
         address = NULL;
@@ -140,6 +147,87 @@ int address_drop(Address *address, Link *link,
         return 0;
 }
 
+int address_update(Address *address, Link *link,
+                   sd_rtnl_message_handler_t callback) {
+        _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL;
+        int r;
+
+        assert(address);
+        assert(address->family == AF_INET || address->family == AF_INET6);
+        assert(link->ifindex > 0);
+        assert(link->manager);
+        assert(link->manager->rtnl);
+
+        r = sd_rtnl_message_new_addr_update(link->manager->rtnl, &req,
+                                     link->ifindex, address->family);
+        if (r < 0) {
+                log_error("Could not allocate RTM_NEWADDR message: %s",
+                          strerror(-r));
+                return r;
+        }
+
+        r = sd_rtnl_message_addr_set_prefixlen(req, address->prefixlen);
+        if (r < 0) {
+                log_error("Could not set prefixlen: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_rtnl_message_addr_set_flags(req, IFA_F_PERMANENT);
+        if (r < 0) {
+                log_error("Could not set flags: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_rtnl_message_addr_set_scope(req, address->scope);
+        if (r < 0) {
+                log_error("Could not set scope: %s", strerror(-r));
+                return r;
+        }
+
+        if (address->family == AF_INET)
+                r = sd_rtnl_message_append_in_addr(req, IFA_LOCAL, &address->in_addr.in);
+        else if (address->family == AF_INET6)
+                r = sd_rtnl_message_append_in6_addr(req, IFA_LOCAL, &address->in_addr.in6);
+        if (r < 0) {
+                log_error("Could not append IFA_LOCAL attribute: %s",
+                          strerror(-r));
+                return r;
+        }
+
+        if (address->family == AF_INET) {
+                r = sd_rtnl_message_append_in_addr(req, IFA_BROADCAST, &address->broadcast);
+                if (r < 0) {
+                        log_error("Could not append IFA_BROADCAST attribute: %s",
+                                  strerror(-r));
+                        return r;
+                }
+        }
+
+        if (address->label) {
+                r = sd_rtnl_message_append_string(req, IFA_LABEL, address->label);
+                if (r < 0) {
+                        log_error("Could not append IFA_LABEL attribute: %s",
+                                  strerror(-r));
+                        return r;
+                }
+        }
+
+        r = sd_rtnl_message_append_cache_info(req, IFA_CACHEINFO, &address->cinfo);
+        if (r < 0) {
+                log_error("Could not append IFA_CACHEINFO attribute: %s",
+                          strerror(-r));
+                return r;
+        }
+
+        r = sd_rtnl_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
+        if (r < 0) {
+                log_error("Could not send rtnetlink message: %s", strerror(-r));
+                return r;
+        }
+
+        return 0;
+}
+
 int address_configure(Address *address, Link *link,
                       sd_rtnl_message_handler_t callback) {
         _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL;
index 4e0fe0a..63d253d 100644 (file)
@@ -30,6 +30,9 @@
 
 #include "dhcp-lease-internal.h"
 
+static int ipv4ll_address_update(Link *link, bool deprecate);
+static bool ipv4ll_is_bound(sd_ipv4ll *ll);
+
 int link_new(Manager *manager, struct udev_device *device, Link **ret) {
         _cleanup_link_free_ Link *link = NULL;
         const char *ifname;
@@ -168,7 +171,6 @@ static int route_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
 
 static int link_enter_set_routes(Link *link) {
         Route *rt;
-        struct in_addr a;
         int r;
 
         assert(link);
@@ -178,7 +180,7 @@ static int link_enter_set_routes(Link *link) {
         link->state = LINK_STATE_SETTING_ROUTES;
 
         if (!link->network->static_routes && !link->dhcp_lease &&
-                (!link->ipv4ll || sd_ipv4ll_get_address(link->ipv4ll, &a) < 0))
+                (!link->ipv4ll || ipv4ll_is_bound(link->ipv4ll) == false))
                 return link_enter_configured(link);
 
         log_debug_link(link, "setting routes");
@@ -345,7 +347,6 @@ static int address_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
 
 static int link_enter_set_addresses(Link *link) {
         Address *ad;
-        struct in_addr a;
         int r;
 
         assert(link);
@@ -355,7 +356,7 @@ static int link_enter_set_addresses(Link *link) {
         link->state = LINK_STATE_SETTING_ADDRESSES;
 
         if (!link->network->static_addresses && !link->dhcp_lease &&
-                (!link->ipv4ll || sd_ipv4ll_get_address(link->ipv4ll, &a) < 0))
+                (!link->ipv4ll || ipv4ll_is_bound(link->ipv4ll) == false))
                 return link_enter_set_routes(link);
 
         log_debug_link(link, "setting addresses");
@@ -456,6 +457,28 @@ static int link_enter_set_addresses(Link *link) {
         return 0;
 }
 
+static int address_update_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
+        Link *link = userdata;
+        int r;
+
+        assert(m);
+        assert(link);
+        assert(link->ifname);
+
+        if (link->state == LINK_STATE_FAILED)
+                return 1;
+
+        r = sd_rtnl_message_get_errno(m);
+        if (r < 0 && r != -ENOENT)
+                log_struct_link(LOG_WARNING, link,
+                                "MESSAGE=%s: could not update address: %s",
+                                link->ifname, strerror(-r),
+                                "ERRNO=%d", -r,
+                                NULL);
+
+        return 0;
+}
+
 static int address_drop_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
         Link *link = userdata;
         int r;
@@ -753,7 +776,7 @@ static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) {
 
 static void dhcp_handler(sd_dhcp_client *client, int event, void *userdata) {
         Link *link = userdata;
-        int r;
+        int r = 0;
 
         assert(link);
         assert(link->network);
@@ -792,7 +815,10 @@ static void dhcp_handler(sd_dhcp_client *client, int event, void *userdata) {
                         }
 
                         if (event == DHCP_EVENT_EXPIRED && link->network->ipv4ll) {
-                                r = sd_ipv4ll_start (link->ipv4ll);
+                                if (!sd_ipv4ll_is_running(link->ipv4ll))
+                                        r = sd_ipv4ll_start(link->ipv4ll);
+                                else if (ipv4ll_is_bound(link->ipv4ll))
+                                        r = ipv4ll_address_update(link, false);
                                 if (r < 0) {
                                         link_enter_failed(link);
                                         return;
@@ -807,7 +833,10 @@ static void dhcp_handler(sd_dhcp_client *client, int event, void *userdata) {
                                 return;
                         }
                         if (link->ipv4ll) {
-                                r = sd_ipv4ll_stop(link->ipv4ll);
+                                if (ipv4ll_is_bound(link->ipv4ll))
+                                        r = ipv4ll_address_update(link, true);
+                                else
+                                        r = sd_ipv4ll_stop(link->ipv4ll);
                                 if (r < 0) {
                                         link_enter_failed(link);
                                         return;
@@ -825,11 +854,44 @@ static void dhcp_handler(sd_dhcp_client *client, int event, void *userdata) {
         return;
 }
 
-static int ipv4ll_address_lost(sd_ipv4ll *ll, Link *link) {
+static int ipv4ll_address_update(Link *link, bool deprecate) {
+        int r;
+        struct in_addr addr;
+
+        assert(link);
+
+        r = sd_ipv4ll_get_address(link->ipv4ll, &addr);
+        if (r >= 0) {
+                _cleanup_address_free_ Address *address = NULL;
+
+                log_debug_link(link, "IPv4 link-local %s %u.%u.%u.%u",
+                               deprecate ? "deprecate" : "approve",
+                               ADDRESS_FMT_VAL(addr));
+
+                r = address_new_dynamic(&address);
+                if (r < 0) {
+                        log_error_link(link, "Could not allocate address: %s", strerror(-r));
+                        return r;
+                }
+
+                address->family = AF_INET;
+                address->in_addr.in = addr;
+                address->prefixlen = 16;
+                address->scope = RT_SCOPE_LINK;
+                address->cinfo.ifa_prefered = deprecate ? 0 : CACHE_INFO_INFINITY_LIFE_TIME;
+                address->broadcast.s_addr = address->in_addr.in.s_addr | htonl(0xfffffffflu >> address->prefixlen);
+
+                address_update(address, link, &address_update_handler);
+        }
+
+        return 0;
+
+}
+
+static int ipv4ll_address_lost(Link *link) {
         int r;
         struct in_addr addr;
 
-        assert(ll);
         assert(link);
 
         r = sd_ipv4ll_get_address(link->ipv4ll, &addr);
@@ -870,6 +932,18 @@ static int ipv4ll_address_lost(sd_ipv4ll *ll, Link *link) {
         return 0;
 }
 
+static bool ipv4ll_is_bound(sd_ipv4ll *ll) {
+        int r;
+        struct in_addr addr;
+
+        assert(ll);
+
+        r = sd_ipv4ll_get_address(ll, &addr);
+        if (r < 0)
+                return false;
+        return true;
+}
+
 static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) {
         struct in_addr address;
         int r;
@@ -903,7 +977,7 @@ static void ipv4ll_handler(sd_ipv4ll *ll, int event, void *userdata){
         switch(event) {
                 case IPV4LL_EVENT_STOP:
                 case IPV4LL_EVENT_CONFLICT:
-                        r = ipv4ll_address_lost(ll, link);
+                        r = ipv4ll_address_lost(link);
                         if (r < 0) {
                                 link_enter_failed(link);
                                 return;
index 8144031..36902e3 100644 (file)
@@ -36,6 +36,8 @@
 #include "set.h"
 #include "condition-util.h"
 
+#define CACHE_INFO_INFINITY_LIFE_TIME 0xFFFFFFFFU
+
 typedef struct NetDev NetDev;
 typedef struct Network Network;
 typedef struct Link Link;
@@ -150,6 +152,7 @@ struct Address {
         char *label;
 
         struct in_addr broadcast;
+        struct ifa_cacheinfo cinfo;
 
         union {
                 struct in_addr in;
@@ -335,6 +338,7 @@ int address_new_static(Network *network, unsigned section, Address **ret);
 int address_new_dynamic(Address **ret);
 void address_free(Address *address);
 int address_configure(Address *address, Link *link, sd_rtnl_message_handler_t callback);
+int address_update(Address *address, Link *link, sd_rtnl_message_handler_t callback);
 int address_drop(Address *address, Link *link, sd_rtnl_message_handler_t callback);
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(Address*, address_free);
index 6273c89..28405a1 100644 (file)
@@ -22,6 +22,7 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <stdbool.h>
 #include <netinet/in.h>
 #include <net/ethernet.h>
 
@@ -42,10 +43,11 @@ int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address);
 int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_cb_t cb, void *userdata);
 int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr);
 int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index);
-int sd_ipv4ll_set_address_seed (sd_ipv4ll *ll, uint8_t seed[8]);
-int sd_ipv4ll_start (sd_ipv4ll *ll);
-int sd_ipv4ll_stop (sd_ipv4ll *ll);
-void sd_ipv4ll_free (sd_ipv4ll *ll);
-int sd_ipv4ll_new (sd_ipv4ll **ret);
+int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint8_t seed[8]);
+bool sd_ipv4ll_is_running(sd_ipv4ll *ll);
+int sd_ipv4ll_start(sd_ipv4ll *ll);
+int sd_ipv4ll_stop(sd_ipv4ll *ll);
+void sd_ipv4ll_free(sd_ipv4ll *ll);
+int sd_ipv4ll_new(sd_ipv4ll **ret);
 
 #endif
index 80e88e3..f7f7074 100644 (file)
@@ -68,6 +68,7 @@ int sd_rtnl_detach_event(sd_rtnl *nl);
 
 /* messages */
 int sd_rtnl_message_new_link(sd_rtnl *rtnl, sd_rtnl_message **ret, uint16_t msg_type, int index);
+int sd_rtnl_message_new_addr_update(sd_rtnl *rtnl, sd_rtnl_message **ret, int index, unsigned char family);
 int sd_rtnl_message_new_addr(sd_rtnl *rtnl, sd_rtnl_message **ret, uint16_t msg_type, int index,
                              unsigned char family);
 int sd_rtnl_message_new_route(sd_rtnl *rtnl, sd_rtnl_message **ret, uint16_t nlmsg_type,
@@ -99,6 +100,7 @@ int sd_rtnl_message_append_u32(sd_rtnl_message *m, unsigned short type, uint32_t
 int sd_rtnl_message_append_in_addr(sd_rtnl_message *m, unsigned short type, const struct in_addr *data);
 int sd_rtnl_message_append_in6_addr(sd_rtnl_message *m, unsigned short type, const struct in6_addr *data);
 int sd_rtnl_message_append_ether_addr(sd_rtnl_message *m, unsigned short type, const struct ether_addr *data);
+int sd_rtnl_message_append_cache_info(sd_rtnl_message *m, unsigned short type, const struct ifa_cacheinfo *info);
 
 int sd_rtnl_message_open_container(sd_rtnl_message *m, unsigned short type);
 int sd_rtnl_message_open_container_union(sd_rtnl_message *m, unsigned short type, const char *key);
@@ -109,6 +111,7 @@ int sd_rtnl_message_read_u8(sd_rtnl_message *m, unsigned short type, uint8_t *da
 int sd_rtnl_message_read_u16(sd_rtnl_message *m, unsigned short type, uint16_t *data);
 int sd_rtnl_message_read_u32(sd_rtnl_message *m, unsigned short type, uint32_t *data);
 int sd_rtnl_message_read_ether_addr(sd_rtnl_message *m, unsigned short type, struct ether_addr *data);
+int sd_rtnl_message_read_cache_info(sd_rtnl_message *m, unsigned short type, struct ifa_cacheinfo *info);
 int sd_rtnl_message_read_in_addr(sd_rtnl_message *m, unsigned short type, struct in_addr *data);
 int sd_rtnl_message_read_in6_addr(sd_rtnl_message *m, unsigned short type, struct in6_addr *data);
 int sd_rtnl_message_enter_container(sd_rtnl_message *m, unsigned short type);