]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/libsystemd-network/sd-ipv4ll.c
network: return 1 on start and 0 if ipv4ll is already started
[thirdparty/systemd.git] / src / libsystemd-network / sd-ipv4ll.c
index f9779a8601effdb0cbcc09c38cdc4a6c82479d6d..4f4d9ebe87719add28630b590dcc4057f4d61349 100644 (file)
@@ -1,43 +1,29 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Axis Communications AB. All rights reserved.
-  Copyright (C) 2015 Tom Gundersen
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+  Copyright © 2014 Axis Communications AB. All rights reserved.
 ***/
 
 #include <arpa/inet.h>
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 
+#include "sd-id128.h"
 #include "sd-ipv4acd.h"
 #include "sd-ipv4ll.h"
 
 #include "alloc-util.h"
+#include "ether-addr-util.h"
 #include "in-addr-util.h"
 #include "list.h"
 #include "random-util.h"
-#include "refcnt.h"
 #include "siphash24.h"
 #include "sparse-endian.h"
+#include "string-util.h"
 #include "util.h"
 
-#define IPV4LL_NETWORK 0xA9FE0000L
-#define IPV4LL_NETMASK 0xFFFF0000L
+#define IPV4LL_NETWORK UINT32_C(0xA9FE0000)
+#define IPV4LL_NETMASK UINT32_C(0xFFFF0000)
 
 #define IPV4LL_DONT_DESTROY(ll) \
         _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll)
@@ -46,46 +32,36 @@ struct sd_ipv4ll {
         unsigned n_ref;
 
         sd_ipv4acd *acd;
+
         be32_t address; /* the address pushed to ACD */
-        struct random_data *random_data;
-        char *random_data_state;
+        struct ether_addr mac;
+
+        struct {
+                le64_t value;
+                le64_t generation;
+        } seed;
+        bool seed_set;
 
         /* External */
         be32_t claimed_address;
+
         sd_ipv4ll_callback_t callback;
         void* userdata;
 };
 
-sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll) {
-        if (!ll)
-                return NULL;
-
-        assert(ll->n_ref >= 1);
-        ll->n_ref++;
-
-        return ll;
-}
-
-sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) {
-        if (!ll)
-                return NULL;
+#define log_ipv4ll_errno(ll, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "IPV4LL: " fmt, ##__VA_ARGS__)
+#define log_ipv4ll(ll, fmt, ...) log_ipv4ll_errno(ll, 0, fmt, ##__VA_ARGS__)
 
-        assert(ll->n_ref >= 1);
-        ll->n_ref--;
+static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
 
-        if (ll->n_ref > 0)
-                return NULL;
+static sd_ipv4ll *ipv4ll_free(sd_ipv4ll *ll) {
+        assert(ll);
 
         sd_ipv4acd_unref(ll->acd);
-
-        free(ll->random_data);
-        free(ll->random_data_state);
-        free(ll);
-
-        return NULL;
+        return mfree(ll);
 }
 
-static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4ll, sd_ipv4ll, ipv4ll_free);
 
 int sd_ipv4ll_new(sd_ipv4ll **ret) {
         _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL;
@@ -107,52 +83,38 @@ int sd_ipv4ll_new(sd_ipv4ll **ret) {
         if (r < 0)
                 return r;
 
-        *ret = ll;
-        ll = NULL;
+        *ret = TAKE_PTR(ll);
 
         return 0;
 }
 
 int sd_ipv4ll_stop(sd_ipv4ll *ll) {
-        int r;
-
         assert_return(ll, -EINVAL);
 
-        r = sd_ipv4acd_stop(ll->acd);
-        if (r < 0)
-                return r;
-
-        return 0;
+        return sd_ipv4acd_stop(ll->acd);
 }
 
 int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) {
         assert_return(ll, -EINVAL);
         assert_return(ifindex > 0, -EINVAL);
+        assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
 
         return sd_ipv4acd_set_ifindex(ll->acd, ifindex);
 }
 
-#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_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
         int r;
 
         assert_return(ll, -EINVAL);
+        assert_return(addr, -EINVAL);
+        assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
 
-        if (!ll->random_data) {
-                uint64_t seed;
-
-                /* If no random data is set, generate some from the MAC */
-                seed = siphash24(&addr->ether_addr_octet, ETH_ALEN, HASH_KEY.bytes);
-
-                assert_cc(sizeof(unsigned) <= 8);
-
-                r = sd_ipv4ll_set_address_seed(ll, (unsigned) htole64(seed));
-                if (r < 0)
-                        return r;
-        }
+        r = sd_ipv4acd_set_mac(ll->acd, addr);
+        if (r < 0)
+                return r;
 
-        return sd_ipv4acd_set_mac(ll->acd, addr);
+        ll->mac = *addr;
+        return 0;
 }
 
 int sd_ipv4ll_detach_event(sd_ipv4ll *ll) {
@@ -162,15 +124,9 @@ int sd_ipv4ll_detach_event(sd_ipv4ll *ll) {
 }
 
 int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) {
-        int r;
-
         assert_return(ll, -EINVAL);
 
-        r = sd_ipv4acd_attach_event(ll->acd, event, priority);
-        if (r < 0)
-                return r;
-
-        return 0;
+        return sd_ipv4acd_attach_event(ll->acd, event, priority);
 }
 
 int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) {
@@ -194,32 +150,12 @@ int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) {
         return 0;
 }
 
-int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, unsigned seed) {
-        _cleanup_free_ struct random_data *random_data = NULL;
-        _cleanup_free_ char *random_data_state = NULL;
-        int r;
-
+int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) {
         assert_return(ll, -EINVAL);
+        assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
 
-        random_data = new0(struct random_data, 1);
-        if (!random_data)
-                return -ENOMEM;
-
-        random_data_state = new0(char, 128);
-        if (!random_data_state)
-                return -ENOMEM;
-
-        r = initstate_r(seed, random_data_state, 128, random_data);
-        if (r < 0)
-                return r;
-
-        free(ll->random_data);
-        ll->random_data = random_data;
-        random_data = NULL;
-
-        free(ll->random_data_state);
-        ll->random_data_state = random_data_state;
-        random_data_state = NULL;
+        ll->seed.value = htole64(seed);
+        ll->seed_set = true;
 
         return 0;
 }
@@ -231,20 +167,12 @@ int sd_ipv4ll_is_running(sd_ipv4ll *ll) {
 }
 
 static bool ipv4ll_address_is_valid(const struct in_addr *address) {
-        uint32_t addr;
-
         assert(address);
 
         if (!in_addr_is_link_local(AF_INET, (const union in_addr_union *) address))
                 return false;
 
-        addr = be32toh(address->s_addr);
-
-        if ((addr & 0x0000FF00) == 0x0000 ||
-            (addr & 0x0000FF00) == 0xFF00)
-                return false;
-
-        return true;
+        return !IN_SET(be32toh(address->s_addr) & 0x0000FF00U, 0x0000U, 0xFF00U);
 }
 
 int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) {
@@ -263,50 +191,83 @@ int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) {
         return 0;
 }
 
+#define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b)
+
 static int ipv4ll_pick_address(sd_ipv4ll *ll) {
-        struct in_addr in_addr;
+        _cleanup_free_ char *address = NULL;
         be32_t addr;
-        int r;
-        int32_t random;
 
         assert(ll);
-        assert(ll->random_data);
 
         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) & 0x0000FF00) == 0x0000 ||
-                (ntohl(addr) & 0x0000FF00) == 0xFF00);
+                uint64_t h;
 
-        in_addr.s_addr = addr;
+                h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes);
 
-        r = sd_ipv4ll_set_address(ll, &in_addr);
-        if (r < 0)
-                return r;
+                /* Increase the generation counter by one */
+                ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1);
 
-        return 0;
+                addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK);
+        } while (addr == ll->address ||
+                 IN_SET(be32toh(addr) & 0x0000FF00U, 0x0000U, 0xFF00U));
+
+        (void) in_addr_to_string(AF_INET, &(union in_addr_union) { .in.s_addr = addr }, &address);
+        log_ipv4ll(ll, "Picked new IP address %s.", strna(address));
+
+        return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr });
 }
 
-int sd_ipv4ll_start(sd_ipv4ll *ll) {
+#define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2)
+
+static int ipv4ll_start_internal(sd_ipv4ll *ll, bool reset_generation) {
         int r;
+        bool picked_address = false;
 
         assert_return(ll, -EINVAL);
-        assert_return(ll->random_data, -EINVAL);
+        assert_return(!ether_addr_is_null(&ll->mac), -EINVAL);
+
+        /* If no random seed is set, generate some from the MAC address */
+        if (!ll->seed_set)
+                ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes));
+
+        if (reset_generation)
+                ll->seed.generation = 0;
 
         if (ll->address == 0) {
                 r = ipv4ll_pick_address(ll);
                 if (r < 0)
                         return r;
+
+                picked_address = true;
         }
 
-        r = sd_ipv4acd_start(ll->acd);
-        if (r < 0)
+        r = sd_ipv4acd_start(ll->acd, reset_generation);
+        if (r < 0) {
+
+                /* We couldn't start? If so, let's forget the picked address again, the user might make a change and
+                 * retry, and we want the new data to take effect when picking an address. */
+                if (picked_address)
+                        ll->address = 0;
+
                 return r;
+        }
 
-        return 0;
+        return 1;
+}
+
+int sd_ipv4ll_start(sd_ipv4ll *ll) {
+        assert_return(ll, -EINVAL);
+
+        if (sd_ipv4ll_is_running(ll))
+                return 0;
+
+        return ipv4ll_start_internal(ll, true);
+}
+
+int sd_ipv4ll_restart(sd_ipv4ll *ll) {
+        ll->address = 0;
+
+        return ipv4ll_start_internal(ll, false);
 }
 
 static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) {
@@ -325,17 +286,17 @@ void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
         assert(ll);
 
         switch (event) {
+
         case SD_IPV4ACD_EVENT_STOP:
                 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
-
                 ll->claimed_address = 0;
-
                 break;
+
         case SD_IPV4ACD_EVENT_BIND:
                 ll->claimed_address = ll->address;
                 ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_BIND);
-
                 break;
+
         case SD_IPV4ACD_EVENT_CONFLICT:
                 /* if an address was already bound we must call up to the
                    user to handle this, otherwise we just try again */
@@ -344,16 +305,13 @@ void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
 
                         ll->claimed_address = 0;
                 } else {
-                        r = ipv4ll_pick_address(ll);
-                        if (r < 0)
-                                goto error;
-
-                        r = sd_ipv4acd_start(ll->acd);
+                        r = sd_ipv4ll_restart(ll);
                         if (r < 0)
                                 goto error;
                 }
 
                 break;
+
         default:
                 assert_not_reached("Invalid IPv4ACD event.");
         }