From 11bf3cced13c885ca215c108cb0bdb7a148520d6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 18 Jun 2014 18:22:14 +0200 Subject: [PATCH 1/1] networkd: add address pool support 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 | 3 +- src/network/networkd-address-pool.c | 166 ++++++++++++++++++++++++++++ src/network/networkd-address.c | 66 +++++++++++ src/network/networkd-link.c | 5 + src/network/networkd-manager.c | 55 +++++++++ src/network/networkd.h | 25 +++++ 6 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 src/network/networkd-address-pool.c diff --git a/Makefile.am b/Makefile.am index dea0633a9..82145c676 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 index 000000000..a5079add0 --- /dev/null +++ b/src/network/networkd-address-pool.c @@ -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 . +***/ + +#include + +#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; +} diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index afd36a0df..ce015004d 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -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) { diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index d81a3bf63..e7753dc98 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -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); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 2a0d5342d..5cc88723e 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -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; +} diff --git a/src/network/networkd.h b/src/network/networkd.h index 308be832e..7069a11d4 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -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__) -- 2.30.2