Systemd/src/network/networkd-address.c
Patrik Flykt 851c9f8273 systemd-networkd: Use IFA_F_NOPREFIXROUTE with IPv6 addresses
The IFA_F_NOPREFIXROUTE flag prevents the kernel from creating new onlink
prefixes when a DHCPv6 IPv6 address with a prefix length is set from user
space. IPv6 routing will follow the onlink status from Router Advertisment
Prefix Information options or any manually set route, which is the correct
thing to do.

As this flag has a larger value than what fits into an unsigned char, update
the flag attribute to an uint32_t and set it with an IFA_FLAGS attribute
when writing netlink messages to the kernel.
2015-03-27 13:57:13 +02:00

636 lines
21 KiB
C

/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2013 Tom Gundersen <teg@jklm.no>
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 <net/if.h>
#include "utf8.h"
#include "util.h"
#include "conf-parser.h"
#include "fw-util.h"
#include "networkd.h"
#include "networkd-link.h"
static void address_init(Address *address) {
assert(address);
address->family = AF_UNSPEC;
address->scope = RT_SCOPE_UNIVERSE;
address->cinfo.ifa_prefered = CACHE_INFO_INFINITY_LIFE_TIME;
address->cinfo.ifa_valid = CACHE_INFO_INFINITY_LIFE_TIME;
}
int address_new_static(Network *network, unsigned section, Address **ret) {
_cleanup_address_free_ Address *address = NULL;
if (section) {
address = hashmap_get(network->addresses_by_section, UINT_TO_PTR(section));
if (address) {
*ret = address;
address = NULL;
return 0;
}
}
address = new0(Address, 1);
if (!address)
return -ENOMEM;
address_init(address);
address->network = network;
LIST_APPEND(addresses, network->static_addresses, address);
if (section) {
address->section = section;
hashmap_put(network->addresses_by_section,
UINT_TO_PTR(address->section), address);
}
*ret = address;
address = NULL;
return 0;
}
int address_new_dynamic(Address **ret) {
_cleanup_address_free_ Address *address = NULL;
address = new0(Address, 1);
if (!address)
return -ENOMEM;
address_init(address);
*ret = address;
address = NULL;
return 0;
}
void address_free(Address *address) {
if (!address)
return;
if (address->network) {
LIST_REMOVE(addresses, address->network->static_addresses, address);
if (address->section)
hashmap_remove(address->network->addresses_by_section,
UINT_TO_PTR(address->section));
}
free(address);
}
int address_establish(Address *address, Link *link) {
bool masq;
int r;
assert(address);
assert(link);
masq = link->network &&
link->network->ip_masquerade &&
address->family == AF_INET &&
address->scope < RT_SCOPE_LINK;
/* Add firewall entry if this is requested */
if (address->ip_masquerade_done != masq) {
union in_addr_union masked = address->in_addr;
in_addr_mask(address->family, &masked, address->prefixlen);
r = fw_add_masquerade(masq, AF_INET, 0, &masked, address->prefixlen, NULL, NULL, 0);
if (r < 0)
log_link_warning_errno(link, r, "Could not enable IP masquerading: %m");
address->ip_masquerade_done = masq;
}
return 0;
}
int address_release(Address *address, Link *link) {
int r;
assert(address);
assert(link);
/* Remove masquerading firewall entry if it was added */
if (address->ip_masquerade_done) {
union in_addr_union masked = address->in_addr;
in_addr_mask(address->family, &masked, address->prefixlen);
r = fw_add_masquerade(false, AF_INET, 0, &masked, address->prefixlen, NULL, NULL, 0);
if (r < 0)
log_link_warning_errno(link, r, "Failed to disable IP masquerading: %m");
address->ip_masquerade_done = false;
}
return 0;
}
int address_drop(Address *address, Link *link,
sd_rtnl_message_handler_t callback) {
_cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL;
int r;
assert(address);
assert(address->family == AF_INET || address->family == AF_INET6);
assert(link);
assert(link->ifindex > 0);
assert(link->manager);
assert(link->manager->rtnl);
address_release(address, link);
r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_DELADDR,
link->ifindex, address->family);
if (r < 0)
return log_error_errno(r, "Could not allocate RTM_DELADDR message: %m");
r = sd_rtnl_message_addr_set_prefixlen(req, address->prefixlen);
if (r < 0)
return log_error_errno(r, "Could not set prefixlen: %m");
if (address->family == AF_INET)
r = sd_rtnl_message_append_in_addr(req, IFA_LOCAL, &address->in_addr.in);
else if (address->family == AF_INET6)
r = sd_rtnl_message_append_in6_addr(req, IFA_LOCAL, &address->in_addr.in6);
if (r < 0)
return log_error_errno(r, "Could not append IFA_LOCAL attribute: %m");
r = sd_rtnl_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
if (r < 0)
return log_error_errno(r, "Could not send rtnetlink message: %m");
link_ref(link);
return 0;
}
int address_update(Address *address, Link *link,
sd_rtnl_message_handler_t callback) {
_cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL;
int r;
assert(address);
assert(address->family == AF_INET || address->family == AF_INET6);
assert(link->ifindex > 0);
assert(link->manager);
assert(link->manager->rtnl);
r = sd_rtnl_message_new_addr_update(link->manager->rtnl, &req,
link->ifindex, address->family);
if (r < 0)
return log_error_errno(r, "Could not allocate RTM_NEWADDR message: %m");
r = sd_rtnl_message_addr_set_prefixlen(req, address->prefixlen);
if (r < 0)
return log_error_errno(r, "Could not set prefixlen: %m");
address->flags |= IFA_F_PERMANENT;
r = sd_rtnl_message_addr_set_flags(req, address->flags & 0xff);
if (r < 0)
return log_error_errno(r, "Could not set flags: %m");
if (address->flags & ~0xff) {
r = sd_rtnl_message_append_u32(req, IFA_FLAGS, address->flags);
if (r < 0)
return log_error_errno(r, "Could not set extended flags: %m");
}
r = sd_rtnl_message_addr_set_scope(req, address->scope);
if (r < 0)
return log_error_errno(r, "Could not set scope: %m");
if (address->family == AF_INET)
r = sd_rtnl_message_append_in_addr(req, IFA_LOCAL, &address->in_addr.in);
else if (address->family == AF_INET6)
r = sd_rtnl_message_append_in6_addr(req, IFA_LOCAL, &address->in_addr.in6);
if (r < 0)
return log_error_errno(r, "Could not append IFA_LOCAL attribute: %m");
if (address->family == AF_INET) {
r = sd_rtnl_message_append_in_addr(req, IFA_BROADCAST, &address->broadcast);
if (r < 0)
return log_error_errno(r, "Could not append IFA_BROADCAST attribute: %m");
}
if (address->label) {
r = sd_rtnl_message_append_string(req, IFA_LABEL, address->label);
if (r < 0)
return log_error_errno(r, "Could not append IFA_LABEL attribute: %m");
}
r = sd_rtnl_message_append_cache_info(req, IFA_CACHEINFO, &address->cinfo);
if (r < 0)
return log_error_errno(r, "Could not append IFA_CACHEINFO attribute: %m");
r = sd_rtnl_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
if (r < 0)
return log_error_errno(r, "Could not send rtnetlink message: %m");
link_ref(link);
return 0;
}
static int address_acquire(Link *link, Address *original, Address **ret) {
union in_addr_union in_addr = {};
struct in_addr broadcast = {};
_cleanup_address_free_ Address *na = NULL;
int r;
assert(link);
assert(original);
assert(ret);
/* Something useful was configured? just use it */
if (in_addr_is_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_link_error(link, "Failed to acquire address from pool: %s", strerror(-r));
return r;
}
if (r == 0) {
log_link_error(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)
return -ENOMEM;
}
na->broadcast = broadcast;
na->in_addr = in_addr;
LIST_PREPEND(addresses, link->pool_addresses, na);
*ret = na;
na = NULL;
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 r;
assert(address);
assert(address->family == AF_INET || address->family == AF_INET6);
assert(link);
assert(link->ifindex > 0);
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)
return log_error_errno(r, "Could not allocate RTM_NEWADDR message: %m");
r = sd_rtnl_message_addr_set_prefixlen(req, address->prefixlen);
if (r < 0)
return log_error_errno(r, "Could not set prefixlen: %m");
address->flags |= IFA_F_PERMANENT;
r = sd_rtnl_message_addr_set_flags(req, (address->flags & 0xff));
if (r < 0)
return log_error_errno(r, "Could not set flags: %m");
if (address->flags & ~0xff) {
r = sd_rtnl_message_append_u32(req, IFA_FLAGS, address->flags);
if (r < 0)
return log_error_errno(r, "Could not set extended flags: %m");
}
r = sd_rtnl_message_addr_set_scope(req, address->scope);
if (r < 0)
return log_error_errno(r, "Could not set scope: %m");
if (address->family == AF_INET)
r = sd_rtnl_message_append_in_addr(req, IFA_LOCAL, &address->in_addr.in);
else if (address->family == AF_INET6)
r = sd_rtnl_message_append_in6_addr(req, IFA_LOCAL, &address->in_addr.in6);
if (r < 0)
return log_error_errno(r, "Could not append IFA_LOCAL attribute: %m");
if (!in_addr_is_null(address->family, &address->in_addr_peer)) {
if (address->family == AF_INET)
r = sd_rtnl_message_append_in_addr(req, IFA_ADDRESS, &address->in_addr_peer.in);
else if (address->family == AF_INET6)
r = sd_rtnl_message_append_in6_addr(req, IFA_ADDRESS, &address->in_addr_peer.in6);
if (r < 0)
return log_error_errno(r, "Could not append IFA_ADDRESS attribute: %m");
} else {
if (address->family == AF_INET) {
r = sd_rtnl_message_append_in_addr(req, IFA_BROADCAST, &address->broadcast);
if (r < 0)
return log_error_errno(r, "Could not append IFA_BROADCAST attribute: %m");
}
}
if (address->label) {
r = sd_rtnl_message_append_string(req, IFA_LABEL, address->label);
if (r < 0)
return log_error_errno(r, "Could not append IFA_LABEL attribute: %m");
}
r = sd_rtnl_message_append_cache_info(req, IFA_CACHEINFO,
&address->cinfo);
if (r < 0)
return log_error_errno(r, "Could not append IFA_CACHEINFO attribute: %m");
r = sd_rtnl_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
if (r < 0)
return log_error_errno(r, "Could not send rtnetlink message: %m");
link_ref(link);
address_establish(address, link);
return 0;
}
int config_parse_broadcast(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
_cleanup_address_free_ Address *n = NULL;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
r = address_new_static(network, section_line, &n);
if (r < 0)
return r;
if (n->family == AF_INET6) {
log_syntax(unit, LOG_ERR, filename, line, EINVAL,
"Broadcast is not valid for IPv6 addresses, ignoring assignment: %s", rvalue);
return 0;
}
r = in_addr_from_string(AF_INET, rvalue, (union in_addr_union*) &n->broadcast);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, EINVAL,
"Broadcast is invalid, ignoring assignment: %s", rvalue);
return 0;
}
n->family = AF_INET;
n = NULL;
return 0;
}
int config_parse_address(const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
_cleanup_address_free_ Address *n = NULL;
const char *address, *e;
union in_addr_union buffer;
int r, f;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
if (streq(section, "Network")) {
/* we are not in an Address section, so treat
* this as the special '0' section */
section_line = 0;
}
r = address_new_static(network, section_line, &n);
if (r < 0)
return r;
/* Address=address/prefixlen */
/* prefixlen */
e = strchr(rvalue, '/');
if (e) {
unsigned i;
r = safe_atou(e + 1, &i);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, EINVAL,
"Prefix length is invalid, ignoring assignment: %s", e + 1);
return 0;
}
n->prefixlen = (unsigned char) i;
address = strndupa(rvalue, e - rvalue);
} else
address = rvalue;
r = in_addr_from_string_auto(address, &f, &buffer);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, EINVAL,
"Address is invalid, ignoring assignment: %s", address);
return 0;
}
if (!e && f == AF_INET) {
r = in_addr_default_prefixlen(&buffer.in, &n->prefixlen);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, EINVAL,
"Prefix length not specified, and a default one can not be deduced for '%s', ignoring assignment", address);
return 0;
}
}
if (n->family != AF_UNSPEC && f != n->family) {
log_syntax(unit, LOG_ERR, filename, line, EINVAL,
"Address is incompatible, ignoring assignment: %s", address);
return 0;
}
n->family = f;
if (streq(lvalue, "Address"))
n->in_addr = buffer;
else
n->in_addr_peer = buffer;
if (n->family == AF_INET && n->broadcast.s_addr == 0)
n->broadcast.s_addr = n->in_addr.in.s_addr | htonl(0xfffffffflu >> n->prefixlen);
n = NULL;
return 0;
}
int config_parse_label(const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
_cleanup_address_free_ Address *n = NULL;
char *label;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
r = address_new_static(network, section_line, &n);
if (r < 0)
return r;
label = strdup(rvalue);
if (!label)
return log_oom();
if (!ascii_is_valid(label) || strlen(label) >= IFNAMSIZ) {
log_syntax(unit, LOG_ERR, filename, line, EINVAL,
"Interface label is not ASCII clean or is too"
" long, ignoring assignment: %s", rvalue);
free(label);
return 0;
}
free(n->label);
if (*label)
n->label = label;
else {
free(label);
n->label = NULL;
}
n = NULL;
return 0;
}
bool address_equal(Address *a1, Address *a2) {
/* same object */
if (a1 == a2)
return true;
/* one, but not both, is NULL */
if (!a1 || !a2)
return false;
if (a1->family != a2->family)
return false;
switch (a1->family) {
/* use the same notion of equality as the kernel does */
case AF_UNSPEC:
return true;
case AF_INET:
if (a1->prefixlen != a2->prefixlen)
return false;
else if (a1->prefixlen == 0)
/* make sure we don't try to shift by 32.
* See ISO/IEC 9899:TC3 § 6.5.7.3. */
return true;
else {
uint32_t b1, b2;
b1 = be32toh(a1->in_addr.in.s_addr);
b2 = be32toh(a2->in_addr.in.s_addr);
return (b1 >> (32 - a1->prefixlen)) == (b2 >> (32 - a1->prefixlen));
}
case AF_INET6: {
uint64_t *b1, *b2;
b1 = (uint64_t*)&a1->in_addr.in6;
b2 = (uint64_t*)&a2->in_addr.in6;
return (((b1[0] ^ b2[0]) | (b1[1] ^ b2[1])) == 0UL);
}
default:
assert_not_reached("Invalid address family");
}
}