chiark / gitweb /
sd-ipv4ll/networkd: generate predictable addresses
authorUmut Tezduyar Lindskog <umut.tezduyar@axis.com>
Fri, 21 Mar 2014 18:23:35 +0000 (19:23 +0100)
committerTom Gundersen <teg@jklm.no>
Fri, 21 Mar 2014 19:24:10 +0000 (20:24 +0100)
Increase the chance of using the same link local address between reboots. The
pseudo random sequence of addresses we attempt is now seeded with data that is
very likely to stay the same between reboots, but at the same time be unique
to the specific machine/nic.

First we try to use the ID_NET_NAME_* data from the udev db combined with the
machin-id, which is guaranteed to be unique and persistent, if available. If
that is not possible (e.g., in containers where we don't have access to the
udev db) we fallback to using the MAC address of the interface, which is
guaranteed to be unique, and likely to be persistent.

[tomegun: three minor changes:

 - don't expose HASH_KEY in the siphash24 header
 - get rid of some compile-warnings (and some casts at the same time),
   by using uint8_t[8] rather than uint64_t in the api
 - added commit message]

TODO
src/libsystemd-network/sd-ipv4ll.c
src/network/networkd-link.c
src/network/networkd.h
src/shared/net-util.c
src/shared/net-util.h
src/systemd/sd-ipv4ll.h
src/udev/net/link-config.c

diff --git a/TODO b/TODO
index 50d67f727303f92e02b5c366c606f62ccfde9e1f..b894e235ed89f381d16b36d0114ac9aaf944041c 100644 (file)
--- a/TODO
+++ b/TODO
@@ -660,7 +660,6 @@ Features:
    - add reduced [Link] support to .network files
    - add IPv4LL tests (inspire by DHCP)
    - add Scope= parsing option for [Network]
-   - change LL address generation and make it predictable like get_mac() (link-config.c)
    - have smooth transition from LL to routable address, without disconnecting clients.
 
 * sd-network:
index ad8b4e3b4334974b66cd344d1a1ea5df0a00b070..7431af729e6cbcc999f3d52f62121197d0e01a25 100644 (file)
@@ -24,6 +24,7 @@
 #include <arpa/inet.h>
 
 #include "util.h"
+#include "siphash24.h"
 #include "list.h"
 
 #include "ipv4ll-internal.h"
@@ -76,6 +77,8 @@ struct sd_ipv4ll {
         usec_t defend_window;
         int next_wakeup_valid;
         be32_t address;
+        struct random_data *random_data;
+        char *random_data_state;
         /* External */
         be32_t claimed_address;
         struct ether_addr mac_addr;
@@ -128,30 +131,27 @@ static int ipv4ll_stop(sd_ipv4ll *ll, int event) {
         return 0;
 }
 
-static be32_t ipv4ll_pick_address(sd_ipv4ll *ll) {
+static int ipv4ll_pick_address(sd_ipv4ll *ll, be32_t *address) {
         be32_t addr;
+        int r;
+        int32_t random;
 
         assert(ll);
+        assert(address);
+        assert(ll->random_data);
 
-        if (ll->address) {
-                do {
-                        uint32_t r = random_u32() & 0x0000FFFF;
-                        addr = htonl(IPV4LL_NETWORK | r);
-                } while (addr == ll->address ||
-                        (ntohl(addr) & IPV4LL_NETMASK) != IPV4LL_NETWORK ||
-                        (ntohl(addr) & 0x0000FF00) == 0x0000 ||
-                        (ntohl(addr) & 0x0000FF00) == 0xFF00);
-        } else {
-                uint32_t a = 1;
-                int i;
-
-                for (i = 0; i < ETH_ALEN; i++)
-                        a += ll->mac_addr.ether_addr_octet[i]*i;
-                a = (a % 0xFE00) + 0x0100;
-                addr = htonl(IPV4LL_NETWORK | (uint32_t) a);
-        }
+        do {
+                r = random_r(ll->random_data, &random);
+                if (r < 0)
+                        return r;
+                addr = htonl((random & 0x0000FFFF) | IPV4LL_NETWORK);
+        } while (addr == ll->address ||
+                (ntohl(addr) & IPV4LL_NETMASK) != IPV4LL_NETWORK ||
+                (ntohl(addr) & 0x0000FF00) == 0x0000 ||
+                (ntohl(addr) & 0x0000FF00) == 0xFF00);
 
-        return addr;
+        *address = addr;
+        return 0;
 }
 
 static int ipv4ll_timer(sd_event_source *s, uint64_t usec, void *userdata) {
@@ -304,7 +304,9 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void
                         ll->claimed_address = 0;
 
                         /* Pick a new address */
-                        ll->address = ipv4ll_pick_address(ll);
+                        r = ipv4ll_pick_address(ll, &ll->address);
+                        if (r < 0)
+                                goto out;
                         ll->conflict++;
                         ll->defend_window = 0;
                         ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, 1);
@@ -448,6 +450,39 @@ int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address){
         return 0;
 }
 
+int sd_ipv4ll_set_address_seed (sd_ipv4ll *ll, uint8_t seed[8]) {
+        unsigned int entropy = *seed;
+        int r;
+
+        assert_return(ll, -EINVAL);
+
+        free(ll->random_data);
+        free(ll->random_data_state);
+
+        ll->random_data = new0(struct random_data, 1);
+        ll->random_data_state = new0(char, 128);
+
+        if (!ll->random_data || !ll->random_data_state) {
+                r = -ENOMEM;
+                goto error;
+        }
+
+        r = initstate_r((unsigned int)entropy, ll->random_data_state, 128, ll->random_data);
+        if (r < 0)
+                goto error;
+
+error:
+        if (r < 0){
+                free(ll->random_data);
+                free(ll->random_data_state);
+                ll->random_data = NULL;
+                ll->random_data_state = NULL;
+        }
+        return r;
+}
+
+#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) {
         int r;
 
@@ -466,8 +501,23 @@ int sd_ipv4ll_start (sd_ipv4ll *ll) {
         ll->defend_window = 0;
         ll->claimed_address = 0;
 
-        if (ll->address == 0)
-                ll->address = ipv4ll_pick_address(ll);
+        if (!ll->random_data) {
+                uint8_t seed[8];
+
+                /* Fallback to mac */
+                siphash24(seed, &ll->mac_addr.ether_addr_octet,
+                          ETH_ALEN, HASH_KEY.bytes);
+
+                r = sd_ipv4ll_set_address_seed(ll, seed);
+                if (r < 0)
+                        goto out;
+        }
+
+        if (ll->address == 0) {
+                r = ipv4ll_pick_address(ll, &ll->address);
+                if (r < 0)
+                        goto out;
+        }
 
         ipv4ll_set_state (ll, IPV4LL_STATE_INIT, 1);
 
@@ -506,6 +556,8 @@ void sd_ipv4ll_free (sd_ipv4ll *ll) {
         sd_ipv4ll_stop(ll);
         sd_ipv4ll_detach_event(ll);
 
+        free(ll->random_data);
+        free(ll->random_data_state);
         free(ll);
 }
 
index 14cc8715ce844224ae2cd30b3c865504e5a4bdcf..4044f0b5a52e65dfd6e0febd4957b809c80dd797 100644 (file)
@@ -63,6 +63,8 @@ int link_new(Manager *manager, struct udev_device *device, Link **ret) {
         if (r < 0)
                 return r;
 
+        link->udev_device = udev_device_ref(device);
+
         *ret = link;
         link = NULL;
 
@@ -85,6 +87,8 @@ void link_free(Link *link) {
         free(link->ifname);
         free(link->state_file);
 
+        udev_device_unref(link->udev_device);
+
         free(link);
 }
 
@@ -1283,10 +1287,18 @@ int link_add(Manager *m, struct udev_device *device, Link **ret) {
                 return r;
 
         if (link->network->ipv4ll) {
+                uint8_t seed[8];
                 r = sd_ipv4ll_new(&link->ipv4ll);
                 if (r < 0)
                         return r;
 
+                r = net_get_unique_predictable_data(link->udev_device, seed);
+                if (r >= 0) {
+                        r = sd_ipv4ll_set_address_seed(link->ipv4ll, seed);
+                        if (r < 0)
+                                return r;
+                }
+
                 r = sd_ipv4ll_attach_event(link->ipv4ll, NULL, 0);
                 if (r < 0)
                         return r;
index 311350c1e2e0b559cdd01b026f7a27f2e3ec7ef0..239ef1cd38b96652bb300bc474897b311d707e95 100644 (file)
@@ -198,6 +198,7 @@ struct Link {
         char *ifname;
         char *state_file;
         struct ether_addr mac;
+        struct udev_device *udev_device;
 
         unsigned flags;
 
index 50cfa2c3f3a5a791f903859167523ebda43da131..1c223117b88dcb83a2bf4f5b7d83acd756c474ef 100644 (file)
@@ -24,6 +24,9 @@
 #include <arpa/inet.h>
 #include <fnmatch.h>
 
+#include "strv.h"
+#include "siphash24.h"
+#include "libudev-private.h"
 #include "net-util.h"
 #include "log.h"
 #include "utf8.h"
 #include "conf-parser.h"
 #include "condition.h"
 
+#define HASH_KEY SD_ID128_MAKE(d3,1e,48,fa,90,fe,4b,4c,9d,af,d5,d7,a1,b1,2e,8a)
+
+int net_get_unique_predictable_data(struct udev_device *device, uint8_t result[8]) {
+        size_t l, sz = 0;
+        const char *name, *field = NULL;
+        int r;
+        uint8_t *v;
+
+        /* fetch some persistent data unique (on this machine) to this device */
+        FOREACH_STRING(field, "ID_NET_NAME_ONBOARD", "ID_NET_NAME_SLOT", "ID_NET_NAME_PATH", "ID_NET_NAME_MAC") {
+                name = udev_device_get_property_value(device, field);
+                if (name)
+                        break;
+        }
+
+        if (!name)
+                return -ENOENT;
+
+        l = strlen(name);
+        sz = sizeof(sd_id128_t) + l;
+        v = alloca(sz);
+
+        /* fetch some persistent data unique to this machine */
+        r = sd_id128_get_machine((sd_id128_t*) v);
+        if (r < 0)
+                 return r;
+        memcpy(v + sizeof(sd_id128_t), name, l);
+
+        /* Let's hash the machine ID plus the device name. We
+        * use a fixed, but originally randomly created hash
+        * key here. */
+        siphash24(result, v, sz, HASH_KEY.bytes);
+
+        return 0;
+}
+
 bool net_match_config(const struct ether_addr *match_mac,
                       const char *match_path,
                       const char *match_driver,
index 99479e1f5cbe32f2a5a31e405540a4a1362501a4..4a8d3f9fdfde03430eb5e6499f92525f959d6fda 100644 (file)
@@ -62,3 +62,5 @@ int config_parse_ifalias(const char *unit, const char *filename, unsigned line,
                          int ltype, const char *rvalue, void *data, void *userdata);
 
 int net_parse_inaddr(const char *address, unsigned char *family, void *dst);
+
+int net_get_unique_predictable_data(struct udev_device *device, uint8_t result[8]);
index 0207006ff796a14fe450922d3ccdb59f4d4f2efe..6273c8917aee69f2d70c52a575c86caf0b914420 100644 (file)
@@ -42,6 +42,7 @@ 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);
index 62439c0c71a680f858fbf48dc740da0345604a4f..472a4150c3d4dcf832c2a61a6d3a0ea523893d90 100644 (file)
@@ -294,44 +294,17 @@ static bool mac_is_permanent(struct udev_device *device) {
         return type == 0;
 }
 
-#define HASH_KEY SD_ID128_MAKE(d3,1e,48,fa,90,fe,4b,4c,9d,af,d5,d7,a1,b1,2e,8a)
-
 static int get_mac(struct udev_device *device, bool want_random, struct ether_addr *mac) {
         int r;
 
         if (want_random)
                 random_bytes(mac->ether_addr_octet, ETH_ALEN);
         else {
-                const char *name;
                 uint8_t result[8];
-                size_t l, sz;
-                uint8_t *v;
-
-                /* fetch some persistent data unique (on this machine) to this device */
-                name = udev_device_get_property_value(device, "ID_NET_NAME_ONBOARD");
-                if (!name) {
-                        name = udev_device_get_property_value(device, "ID_NET_NAME_SLOT");
-                        if (!name) {
-                                name = udev_device_get_property_value(device, "ID_NET_NAME_PATH");
-                                if (!name)
-                                        return -ENOENT;
-                        }
-                }
 
-                l = strlen(name);
-                sz = sizeof(sd_id128_t) + l;
-                v = alloca(sz);
-
-                /* fetch some persistent data unique to this machine */
-                r = sd_id128_get_machine((sd_id128_t*) v);
+                r = net_get_unique_predictable_data(device, result);
                 if (r < 0)
                         return r;
-                memcpy(v + sizeof(sd_id128_t), name, l);
-
-                /* Let's hash the machine ID plus the device name. We
-                 * use a fixed, but originally randomly created hash
-                 * key here. */
-                siphash24(result, v, sz, HASH_KEY.bytes);
 
                 assert_cc(ETH_ALEN <= sizeof(result));
                 memcpy(mac->ether_addr_octet, result, ETH_ALEN);