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.
This commit is contained in:
Lennart Poettering 2014-06-18 18:22:14 +02:00
parent 059f6c42b7
commit 11bf3cced1
6 changed files with 319 additions and 1 deletions

View 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 \

View File

@ -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;
}

View 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) {

View 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);

View 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;
}

View 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__)