chiark / gitweb /
networkd: add address pool support
authorLennart Poettering <lennart@poettering.net>
Wed, 18 Jun 2014 16:22:14 +0000 (18:22 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 18 Jun 2014 16:28:29 +0000 (18:28 +0200)
When an address is configured to be all zeroes, networkd will now
automatically find a locally unused network of the right size from a
list of pre-configured pools. Currently those pools are 10.0.0.0/8,
172.16.0.0/12, 192.168.0.0/16 and fc00::/7, i.e. the network ranges for
private networks. They are compiled in, but should be configurable
eventually.

This allows applying the same configuration to a large number of
interfaces with each time a different IP range block, and management of
these IP ranges is fully automatic.

When allocating an address range from the pool it is made sure the range
is not used otherwise.

Makefile.am
src/network/networkd-address-pool.c [new file with mode: 0644]
src/network/networkd-address.c
src/network/networkd-link.c
src/network/networkd-manager.c
src/network/networkd.h

index dea0633..82145c6 100644 (file)
@@ -4384,7 +4384,8 @@ libsystemd_networkd_core_la_SOURCES = \
        src/network/networkd-network.c \
        src/network/networkd-address.c \
        src/network/networkd-route.c \
-       src/network/networkd-manager.c
+       src/network/networkd-manager.c \
+       src/network/networkd-address-pool.c
 
 nodist_libsystemd_networkd_core_la_SOURCES = \
        src/network/networkd-network-gperf.c \
diff --git a/src/network/networkd-address-pool.c b/src/network/networkd-address-pool.c
new file mode 100644 (file)
index 0000000..a5079ad
--- /dev/null
@@ -0,0 +1,166 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Lennart Poettering
+
+  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/>.
+***/
+
+#include <arpa/inet.h>
+
+#include "networkd.h"
+
+int address_pool_new(
+                Manager *m,
+                AddressPool **ret,
+                unsigned family,
+                const union in_addr_union *u,
+                unsigned prefixlen) {
+
+        AddressPool *p;
+
+        assert(m);
+        assert(ret);
+        assert(u);
+
+        p = new0(AddressPool, 1);
+        if (!p)
+                return -ENOMEM;
+
+        p->manager = m;
+        p->family = family;
+        p->prefixlen = prefixlen;
+        p->in_addr = *u;
+
+        LIST_PREPEND(address_pools, m->address_pools, p);
+
+        *ret = p;
+        return 0;
+}
+
+int address_pool_new_from_string(
+                Manager *m,
+                AddressPool **ret,
+                unsigned family,
+                const char *p,
+                unsigned prefixlen) {
+
+        union in_addr_union u;
+        int r;
+
+        assert(m);
+        assert(ret);
+        assert(p);
+
+        r = in_addr_from_string(family, p, &u);
+        if (r < 0)
+                return r;
+
+        return address_pool_new(m, ret, family, &u, prefixlen);
+}
+
+void address_pool_free(AddressPool *p) {
+
+        if (!p)
+                return;
+
+        if (p->manager)
+                LIST_REMOVE(address_pools, p->manager->address_pools, p);
+
+        free(p);
+}
+
+static bool address_pool_prefix_is_taken(
+                AddressPool *p,
+                const union in_addr_union *u,
+                unsigned prefixlen) {
+
+        Iterator i;
+        Link *l;
+        Network *n;
+
+        assert(p);
+        assert(u);
+
+        HASHMAP_FOREACH(l, p->manager->links, i) {
+                Address *a;
+
+                /* Don't clash with assigned addresses */
+                LIST_FOREACH(addresses, a, l->addresses) {
+                        if (a->family != p->family)
+                                continue;
+
+                        if (in_addr_prefix_intersect(p->family, u, prefixlen, &a->in_addr, a->prefixlen))
+                                return true;
+                }
+
+                /* Don't clash with addresses already pulled from the pool, but not assigned yet */
+                LIST_FOREACH(addresses, a, l->pool_addresses) {
+                        if (a->family != p->family)
+                                continue;
+
+                        if (in_addr_prefix_intersect(p->family, u, prefixlen, &a->in_addr, a->prefixlen))
+                                return true;
+                }
+        }
+
+        /* And don't clash with configured but un-assigned addresses either */
+        LIST_FOREACH(networks, n, p->manager->networks) {
+                Address *a;
+
+                LIST_FOREACH(addresses, a, n->static_addresses) {
+                        if (a->family != p->family)
+                                continue;
+
+                        if (in_addr_prefix_intersect(p->family, u, prefixlen, &a->in_addr, a->prefixlen))
+                                return true;
+                }
+        }
+
+        return false;
+}
+
+int address_pool_acquire(AddressPool *p, unsigned prefixlen, union in_addr_union *found) {
+        union in_addr_union u;
+
+        assert(p);
+        assert(prefixlen > 0);
+        assert(found);
+
+        if (p->prefixlen > prefixlen)
+                return 0;
+
+        u = p->in_addr;
+        for (;;) {
+                if (!address_pool_prefix_is_taken(p, &u, prefixlen)) {
+                        _cleanup_free_ char *s = NULL;
+
+                        in_addr_to_string(p->family, &u, &s);
+                        log_debug("Found range %s/%u", strna(s), prefixlen);
+
+                        *found = u;
+                        return 1;
+                }
+
+                if (!in_addr_prefix_next(p->family, &u, prefixlen))
+                        return 0;
+
+                if (!in_addr_prefix_intersect(p->family, &p->in_addr, p->prefixlen, &u, prefixlen))
+                        return 0;
+        }
+
+        return 0;
+}
index afd36a0..ce01500 100644 (file)
@@ -228,6 +228,68 @@ int address_update(Address *address, Link *link,
         return 0;
 }
 
+static int address_acquire(Link *link, Address *original, Address **ret) {
+        union in_addr_union in_addr = {};
+        struct in_addr broadcast = {};
+        Address *na = NULL;
+        int r;
+
+        assert(link);
+        assert(original);
+        assert(ret);
+
+        /* Something useful was configured? just use it */
+        if (in_addr_null(original->family, &original->in_addr) <= 0)
+                return 0;
+
+        /* The address is configured to be 0.0.0.0 or [::] by the user?
+         * Then let's acquire something more useful from the pool. */
+        r = manager_address_pool_acquire(link->manager, original->family, original->prefixlen, &in_addr);
+        if (r < 0) {
+                log_error_link(link, "Failed to acquire address from pool: %s", strerror(-r));
+                return r;
+        }
+        if (r == 0) {
+                log_error_link(link, "Couldn't find free address for interface, all taken.");
+                return -EBUSY;
+        }
+
+        if (original->family == AF_INET) {
+                /* Pick first address in range for ourselves ...*/
+                in_addr.in.s_addr = in_addr.in.s_addr | htobe32(1);
+
+                /* .. and use last as broadcast address */
+                broadcast.s_addr = in_addr.in.s_addr | htobe32(0xFFFFFFFFUL >> original->prefixlen);
+        } else if (original->family == AF_INET6)
+                in_addr.in6.s6_addr[15] |= 1;
+
+        r = address_new_dynamic(&na);
+        if (r < 0)
+                return r;
+
+        na->family = original->family;
+        na->prefixlen = original->prefixlen;
+        na->scope = original->scope;
+        na->cinfo = original->cinfo;
+
+        if (original->label) {
+                na->label = strdup(original->label);
+
+                if (!na->label) {
+                        free(na);
+                        return -ENOMEM;
+                }
+        }
+
+        na->broadcast = broadcast;
+        na->in_addr = in_addr;
+
+        LIST_PREPEND(addresses, link->pool_addresses, na);
+
+        *ret = na;
+        return 0;
+}
+
 int address_configure(Address *address, Link *link,
                       sd_rtnl_message_handler_t callback) {
         _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL;
@@ -240,6 +302,10 @@ int address_configure(Address *address, Link *link,
         assert(link->manager);
         assert(link->manager->rtnl);
 
+        r = address_acquire(link, address, &address);
+        if (r < 0)
+                return r;
+
         r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_NEWADDR,
                                      link->ifindex, address->family);
         if (r < 0) {
index d81a3bf..e7753dc 100644 (file)
@@ -112,6 +112,11 @@ static void link_free(Link *link) {
                 address_free(address);
         }
 
+        while ((address = link->pool_addresses)) {
+                LIST_REMOVE(addresses, link->pool_addresses, address);
+                address_free(address);
+        }
+
         sd_dhcp_client_unref(link->dhcp_client);
         sd_dhcp_lease_unref(link->dhcp_lease);
 
index 2a0d534..5cc8872 100644 (file)
@@ -75,6 +75,33 @@ static int setup_signals(Manager *m) {
         return 0;
 }
 
+static int setup_default_address_pool(Manager *m) {
+        AddressPool *p;
+        int r;
+
+        assert(m);
+
+        /* Add in the well-known private address ranges. */
+
+        r = address_pool_new_from_string(m, &p, AF_INET6, "fc00::", 7);
+        if (r < 0)
+                return r;
+
+        r = address_pool_new_from_string(m, &p, AF_INET, "192.168.0.0", 16);
+        if (r < 0)
+                return r;
+
+        r = address_pool_new_from_string(m, &p, AF_INET, "172.16.0.0", 12);
+        if (r < 0)
+                return r;
+
+        r = address_pool_new_from_string(m, &p, AF_INET, "10.0.0.0", 8);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 int manager_new(Manager **ret) {
         _cleanup_manager_free_ Manager *m = NULL;
         int r;
@@ -129,6 +156,10 @@ int manager_new(Manager **ret) {
 
         LIST_HEAD_INIT(m->networks);
 
+        r = setup_default_address_pool(m);
+        if (r < 0)
+                return r;
+
         *ret = m;
         m = NULL;
 
@@ -139,6 +170,7 @@ void manager_free(Manager *m) {
         Network *network;
         NetDev *netdev;
         Link *link;
+        AddressPool *pool;
 
         if (!m)
                 return;
@@ -164,6 +196,9 @@ void manager_free(Manager *m) {
                 netdev_unref(netdev);
         hashmap_free(m->netdevs);
 
+        while ((pool = m->address_pools))
+                address_pool_free(pool);
+
         sd_rtnl_unref(m->rtnl);
 
         free(m);
@@ -460,3 +495,23 @@ finish:
 
         return r;
 }
+
+int manager_address_pool_acquire(Manager *m, unsigned family, unsigned prefixlen, union in_addr_union *found) {
+        AddressPool *p;
+        int r;
+
+        assert(m);
+        assert(prefixlen > 0);
+        assert(found);
+
+        LIST_FOREACH(address_pools, p, m->address_pools) {
+                if (p->family != family)
+                        continue;
+
+                r = address_pool_acquire(p, prefixlen, found);
+                if (r != 0)
+                        return r;
+        }
+
+        return 0;
+}
index 308be83..7069a11 100644 (file)
@@ -47,6 +47,7 @@ typedef struct Link Link;
 typedef struct Address Address;
 typedef struct Route Route;
 typedef struct Manager Manager;
+typedef struct AddressPool AddressPool;
 
 typedef struct netdev_enslave_callback netdev_enslave_callback;
 
@@ -259,9 +260,22 @@ struct Link {
         uint16_t original_mtu;
         sd_ipv4ll *ipv4ll;
 
+        LIST_HEAD(Address, pool_addresses);
+
         sd_dhcp_server *dhcp_server;
 };
 
+struct AddressPool {
+        Manager *manager;
+
+        unsigned family;
+        unsigned prefixlen;
+
+        union in_addr_union in_addr;
+
+        LIST_FIELDS(AddressPool, address_pools);
+};
+
 struct Manager {
         sd_rtnl *rtnl;
         sd_event *event;
@@ -277,6 +291,7 @@ struct Manager {
         Hashmap *links;
         Hashmap *netdevs;
         LIST_HEAD(Network, networks);
+        LIST_HEAD(AddressPool, address_pools);
 
         usec_t network_dirs_ts_usec;
 };
@@ -299,6 +314,8 @@ int manager_bus_listen(Manager *m);
 
 int manager_save(Manager *m);
 
+int manager_address_pool_acquire(Manager *m, unsigned family, unsigned prefixlen, union in_addr_union *found);
+
 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
 #define _cleanup_manager_free_ _cleanup_(manager_freep)
 
@@ -449,6 +466,14 @@ LinkOperationalState link_operstate_from_string(const char *s) _pure_;
 DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_unref);
 #define _cleanup_link_unref_ _cleanup_(link_unrefp)
 
+/* Address Pool */
+
+int address_pool_new(Manager *m, AddressPool **ret, unsigned family, const union in_addr_union *u, unsigned prefixlen);
+int address_pool_new_from_string(Manager *m, AddressPool **ret, unsigned family, const char *p, unsigned prefixlen);
+void address_pool_free(AddressPool *p);
+
+int address_pool_acquire(AddressPool *p, unsigned prefixlen, union in_addr_union *found);
+
 /* Macros which append INTERFACE= to the message */
 
 #define log_full_link(level, link, fmt, ...) log_meta_object(level, __FILE__, __LINE__, __func__, "INTERFACE=", link->ifname, "%-*s: " fmt, IFNAMSIZ, link->ifname, ##__VA_ARGS__)