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 dea0633a95246c9c95658860018f5b0608fa18c1..82145c6765825b7321e2bedddd1ecba6af3bbfe6 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-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 \
 
 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 afd36a0dfcce8b4368d3c62a22affb487ce087c5..ce015004de7e6209ca25f1cbcf2606d12dce26a9 100644 (file)
@@ -228,6 +228,68 @@ int address_update(Address *address, Link *link,
         return 0;
 }
 
         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;
 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);
 
         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) {
         r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_NEWADDR,
                                      link->ifindex, address->family);
         if (r < 0) {
index d81a3bf630ff33b8eee02a66eb8fe8d1f3a6e956..e7753dc98cea309cd08610332ec1ba03808e8f47 100644 (file)
@@ -112,6 +112,11 @@ static void link_free(Link *link) {
                 address_free(address);
         }
 
                 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);
 
         sd_dhcp_client_unref(link->dhcp_client);
         sd_dhcp_lease_unref(link->dhcp_lease);
 
index 2a0d5342daeb4672f84bc9aa3fda69e71d0cc2ea..5cc88723e5215265ad0dc98638fc2483fb4ecd20 100644 (file)
@@ -75,6 +75,33 @@ static int setup_signals(Manager *m) {
         return 0;
 }
 
         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;
 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);
 
 
         LIST_HEAD_INIT(m->networks);
 
+        r = setup_default_address_pool(m);
+        if (r < 0)
+                return r;
+
         *ret = m;
         m = NULL;
 
         *ret = m;
         m = NULL;
 
@@ -139,6 +170,7 @@ void manager_free(Manager *m) {
         Network *network;
         NetDev *netdev;
         Link *link;
         Network *network;
         NetDev *netdev;
         Link *link;
+        AddressPool *pool;
 
         if (!m)
                 return;
 
         if (!m)
                 return;
@@ -164,6 +196,9 @@ void manager_free(Manager *m) {
                 netdev_unref(netdev);
         hashmap_free(m->netdevs);
 
                 netdev_unref(netdev);
         hashmap_free(m->netdevs);
 
+        while ((pool = m->address_pools))
+                address_pool_free(pool);
+
         sd_rtnl_unref(m->rtnl);
 
         free(m);
         sd_rtnl_unref(m->rtnl);
 
         free(m);
@@ -460,3 +495,23 @@ finish:
 
         return r;
 }
 
         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 308be832e16fe3cee7d41ebf6f3e26d0635358ba..7069a11d4cdb20fb951f6e06efc62c344e41b014 100644 (file)
@@ -47,6 +47,7 @@ typedef struct Link Link;
 typedef struct Address Address;
 typedef struct Route Route;
 typedef struct Manager Manager;
 typedef struct Address Address;
 typedef struct Route Route;
 typedef struct Manager Manager;
+typedef struct AddressPool AddressPool;
 
 typedef struct netdev_enslave_callback netdev_enslave_callback;
 
 
 typedef struct netdev_enslave_callback netdev_enslave_callback;
 
@@ -259,9 +260,22 @@ struct Link {
         uint16_t original_mtu;
         sd_ipv4ll *ipv4ll;
 
         uint16_t original_mtu;
         sd_ipv4ll *ipv4ll;
 
+        LIST_HEAD(Address, pool_addresses);
+
         sd_dhcp_server *dhcp_server;
 };
 
         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;
 struct Manager {
         sd_rtnl *rtnl;
         sd_event *event;
@@ -277,6 +291,7 @@ struct Manager {
         Hashmap *links;
         Hashmap *netdevs;
         LIST_HEAD(Network, networks);
         Hashmap *links;
         Hashmap *netdevs;
         LIST_HEAD(Network, networks);
+        LIST_HEAD(AddressPool, address_pools);
 
         usec_t network_dirs_ts_usec;
 };
 
         usec_t network_dirs_ts_usec;
 };
@@ -299,6 +314,8 @@ int manager_bus_listen(Manager *m);
 
 int manager_save(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)
 
 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)
 
 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__)
 /* 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__)