chiark / gitweb /
dhcp-lease: refactor lease parsing
[elogind.git] / src / libsystemd-network / sd-ipv4ll.c
index c6f6e014310a2c7fbafd29717735cabf0c0d8464..fd39c12eb22df0c028fc1721001627e9a67ea697 100644 (file)
@@ -24,7 +24,9 @@
 #include <arpa/inet.h>
 
 #include "util.h"
+#include "siphash24.h"
 #include "list.h"
+#include "refcnt.h"
 
 #include "ipv4ll-internal.h"
 #include "sd-ipv4ll.h"
@@ -59,11 +61,14 @@ typedef enum IPv4LLState {
         IPV4LL_STATE_WAITING_ANNOUNCE,
         IPV4LL_STATE_ANNOUNCING,
         IPV4LL_STATE_RUNNING,
+        IPV4LL_STATE_STOPPED,
         _IPV4LL_STATE_MAX,
         _IPV4LL_STATE_INVALID = -1
 } IPv4LLState;
 
 struct sd_ipv4ll {
+        RefCount n_ref;
+
         IPv4LLState state;
         int index;
         int fd;
@@ -76,6 +81,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;
@@ -100,60 +107,59 @@ static void ipv4ll_set_state(sd_ipv4ll *ll, IPv4LLState st, int reset_counter) {
         }
 }
 
-static int ipv4ll_client_notify(sd_ipv4ll *ll, int event) {
+static sd_ipv4ll *ipv4ll_client_notify(sd_ipv4ll *ll, int event) {
         assert(ll);
 
-        if (ll->cb)
+        if (ll->cb) {
+                ll = sd_ipv4ll_ref(ll);
                 ll->cb(ll, event, ll->userdata);
+                ll = sd_ipv4ll_unref(ll);
+        }
 
-        return 0;
+        return ll;
 }
 
-static int ipv4ll_stop(sd_ipv4ll *ll, int event) {
+static sd_ipv4ll *ipv4ll_stop(sd_ipv4ll *ll, int event) {
         assert(ll);
 
         ll->receive_message = sd_event_source_unref(ll->receive_message);
-        if (ll->fd >= 0)
-                close_nointr_nofail(ll->fd);
-        ll->fd = -1;
+        ll->fd = safe_close(ll->fd);
 
         ll->timer = sd_event_source_unref(ll->timer);
 
-        ipv4ll_client_notify(ll, event);
-
-        ll->claimed_address = 0;
+        log_ipv4ll(ll, "STOPPED");
 
-        ipv4ll_set_state (ll, IPV4LL_STATE_INIT, 1);
+        ll = ipv4ll_client_notify(ll, event);
 
-        log_ipv4ll(ll, "STOPPED");
+        if (ll) {
+                ll->claimed_address = 0;
+                ipv4ll_set_state (ll, IPV4LL_STATE_INIT, 1);
+        }
 
-        return 0;
+        return ll;
 }
 
-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) {
@@ -167,7 +173,7 @@ static int ipv4ll_timer(sd_event_source *s, uint64_t usec, void *userdata) {
         return 0;
 }
 
-static void ipv4ll_set_next_wakeup (sd_ipv4ll *ll, int sec, int random_sec) {
+static void ipv4ll_set_next_wakeup(sd_ipv4ll *ll, int sec, int random_sec) {
         usec_t next_timeout = 0;
         usec_t time_now = 0;
 
@@ -180,7 +186,7 @@ static void ipv4ll_set_next_wakeup (sd_ipv4ll *ll, int sec, int random_sec) {
         if (random_sec)
                 next_timeout += random_u32() % (random_sec * USEC_PER_SEC);
 
-        if (sd_event_get_now_monotonic(ll->event, &time_now) < 0)
+        if (sd_event_now(ll->event, CLOCK_MONOTONIC, &time_now) < 0)
                 time_now = now(CLOCK_MONOTONIC);
 
         ll->next_wakeup = time_now + next_timeout;
@@ -258,7 +264,10 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void
                 if (ll->iteration == 0) {
                         log_ipv4ll(ll, "ANNOUNCE");
                         ll->claimed_address = ll->address;
-                        r = ipv4ll_client_notify(ll, IPV4LL_EVENT_BIND);
+                        ll = ipv4ll_client_notify(ll, IPV4LL_EVENT_BIND);
+                        if (!ll || ll->state == IPV4LL_STATE_STOPPED)
+                                goto out;
+
                         ll->conflict = 0;
                 }
 
@@ -280,7 +289,7 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void
 
                         if (ipv4ll_arp_conflict(ll, in_packet)) {
 
-                                r = sd_event_get_now_monotonic(ll->event, &time_now);
+                                r = sd_event_now(ll->event, CLOCK_MONOTONIC, &time_now);
                                 if (r < 0)
                                         goto out;
 
@@ -302,11 +311,16 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void
 
                 if (conflicted) {
                         log_ipv4ll(ll, "CONFLICT");
-                        r = ipv4ll_client_notify(ll, IPV4LL_EVENT_CONFLICT);
+                        ll = ipv4ll_client_notify(ll, IPV4LL_EVENT_CONFLICT);
+                        if (!ll || ll->state == IPV4LL_STATE_STOPPED)
+                                goto out;
+
                         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);
@@ -330,8 +344,8 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void
 
         if (ll->next_wakeup_valid) {
                 ll->timer = sd_event_source_unref(ll->timer);
-                r = sd_event_add_monotonic(ll->event, &ll->timer,
-                                   ll->next_wakeup, 0, ipv4ll_timer, ll);
+                r = sd_event_add_time(ll->event, &ll->timer, CLOCK_MONOTONIC,
+                                      ll->next_wakeup, 0, ipv4ll_timer, ll);
                 if (r < 0)
                         goto out;
 
@@ -341,7 +355,7 @@ static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void
         }
 
 out:
-        if (r < 0)
+        if (r < 0 && ll)
                 ipv4ll_stop(ll, r);
 }
 
@@ -368,8 +382,9 @@ static int ipv4ll_receive_message(sd_event_source *s, int fd,
 
 int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index) {
         assert_return(ll, -EINVAL);
-        assert_return(interface_index >= -1, -EINVAL);
-        assert_return(ll->state == IPV4LL_STATE_INIT, -EBUSY);
+        assert_return(interface_index > 0, -EINVAL);
+        assert_return(IN_SET(ll->state, IPV4LL_STATE_INIT,
+                             IPV4LL_STATE_STOPPED), -EBUSY);
 
         ll->index = interface_index;
 
@@ -377,10 +392,28 @@ int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index) {
 }
 
 int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
+        bool need_restart = false;
+
         assert_return(ll, -EINVAL);
-        assert_return(ll->state == IPV4LL_STATE_INIT, -EBUSY);
+        assert_return(addr, -EINVAL);
 
-        memcpy(&ll->mac_addr.ether_addr_octet, addr, ETH_ALEN);
+        if (memcmp(&ll->mac_addr, addr, ETH_ALEN) == 0)
+                return 0;
+
+        if (!IN_SET(ll->state, IPV4LL_STATE_INIT, IPV4LL_STATE_STOPPED)) {
+                log_ipv4ll(ll, "Changing MAC address on running IPv4LL "
+                           "client, restarting");
+                ll = ipv4ll_stop(ll, IPV4LL_EVENT_STOP);
+                need_restart = true;
+        }
+
+        if (!ll)
+                return 0;
+
+        memcpy(&ll->mac_addr, addr, ETH_ALEN);
+
+        if (need_restart)
+                sd_ipv4ll_start(ll);
 
         return 0;
 }
@@ -435,13 +468,58 @@ 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;
+        int r;
+
+        assert_return(ll, -EINVAL);
+        assert_return(seed, -EINVAL);
+
+        entropy = *seed;
+
+        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;
+}
+
+bool sd_ipv4ll_is_running(sd_ipv4ll *ll) {
+        assert_return(ll, -EINVAL);
+
+        return !IN_SET(ll->state, IPV4LL_STATE_INIT, IPV4LL_STATE_STOPPED);
+}
+
+#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;
 
         assert_return(ll, -EINVAL);
         assert_return(ll->event, -EINVAL);
         assert_return(ll->index > 0, -EINVAL);
-        assert_return(ll->state == IPV4LL_STATE_INIT, -EBUSY);
+        assert_return(IN_SET(ll->state, IPV4LL_STATE_INIT,
+                             IPV4LL_STATE_STOPPED), -EBUSY);
+
+        ll->state = IPV4LL_STATE_INIT;
 
         r = arp_network_bind_raw_socket(ll->index, &ll->link);
 
@@ -453,8 +531,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);
 
@@ -467,8 +560,11 @@ int sd_ipv4ll_start (sd_ipv4ll *ll) {
         if (r < 0)
                 goto out;
 
-        r = sd_event_add_monotonic(ll->event, &ll->timer, now(CLOCK_MONOTONIC), 0,
-                                   ipv4ll_timer, ll);
+        r = sd_event_add_time(ll->event,
+                              &ll->timer,
+                              CLOCK_MONOTONIC,
+                              now(CLOCK_MONOTONIC), 0,
+                              ipv4ll_timer, ll);
 
         if (r < 0)
                 goto out;
@@ -483,21 +579,42 @@ out:
 }
 
 int sd_ipv4ll_stop(sd_ipv4ll *ll) {
-        return ipv4ll_stop(ll, IPV4LL_EVENT_STOP);
+        ipv4ll_stop(ll, IPV4LL_EVENT_STOP);
+        if (ll)
+                ipv4ll_set_state(ll, IPV4LL_STATE_STOPPED, 1);
+
+        return 0;
 }
 
-void sd_ipv4ll_free (sd_ipv4ll *ll) {
-        if (!ll)
-                return;
+sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll) {
+        if (ll)
+                assert_se(REFCNT_INC(ll->n_ref) >= 2);
 
-        sd_ipv4ll_stop(ll);
-        sd_ipv4ll_detach_event(ll);
+        return ll;
+}
+
+sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) {
+        if (ll && REFCNT_DEC(ll->n_ref) <= 0) {
+                ll->receive_message =
+                        sd_event_source_unref(ll->receive_message);
+                ll->fd = safe_close(ll->fd);
+
+                ll->timer = sd_event_source_unref(ll->timer);
+
+                sd_ipv4ll_detach_event(ll);
+
+                free(ll->random_data);
+                free(ll->random_data_state);
+                free(ll);
+
+                return NULL;
+        }
 
-        free(ll);
+        return ll;
 }
 
-DEFINE_TRIVIAL_CLEANUP_FUNC(sd_ipv4ll*, sd_ipv4ll_free);
-#define _cleanup_ipv4ll_free_ _cleanup_(sd_ipv4ll_freep)
+DEFINE_TRIVIAL_CLEANUP_FUNC(sd_ipv4ll*, sd_ipv4ll_unref);
+#define _cleanup_ipv4ll_free_ _cleanup_(sd_ipv4ll_unrefp)
 
 int sd_ipv4ll_new(sd_ipv4ll **ret) {
         _cleanup_ipv4ll_free_ sd_ipv4ll *ll = NULL;
@@ -508,6 +625,7 @@ int sd_ipv4ll_new(sd_ipv4ll **ret) {
         if (!ll)
                 return -ENOMEM;
 
+        ll->n_ref = REFCNT_INIT;
         ll->state = IPV4LL_STATE_INIT;
         ll->index = -1;
         ll->fd = -1;