Systemd/src/network/networkd-ndisc.c
Lennart Poettering 8e766630f0 tree-wide: drop redundant _cleanup_ macros (#8810)
This drops a good number of type-specific _cleanup_ macros, and patches
all users to just use the generic ones.

In most recent code we abstained from defining type-specific macros, and
this basically removes all those added already, with the exception of
the really low-level ones.

Having explicit macros for this is not too useful, as the expression
without the extra macro is generally just 2ch wider. We should generally
emphesize generic code, unless there are really good reasons for
specific code, hence let's follow this in this case too.

Note that _cleanup_free_ and similar really low-level, libc'ish, Linux
API'ish macros continue to be defined, only the really high-level OO
ones are dropped. From now on this should really be the rule: for really
low-level stuff, such as memory allocation, fd handling and so one, go
ahead and define explicit per-type macros, but for high-level, specific
program code, just use the generic _cleanup_() macro directly, in order
to keep things simple and as readable as possible for the uninitiated.

Note that before this patch some of the APIs (notable libudev ones) were
already used with the high-level macros at some places and with the
generic _cleanup_ macro at others. With this patch we hence unify on the
latter.
2018-04-25 12:31:45 +02:00

733 lines
24 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
Copyright (C) 2014 Intel Corporation. All rights reserved.
***/
#include <netinet/icmp6.h>
#include <arpa/inet.h>
#include "sd-ndisc.h"
#include "networkd-ndisc.h"
#include "networkd-route.h"
#define NDISC_DNSSL_MAX 64U
#define NDISC_RDNSS_MAX 64U
#define NDISC_PREFIX_LFT_MIN 7200U
static int ndisc_netlink_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) {
_cleanup_(link_unrefp) Link *link = userdata;
int r;
assert(link);
assert(link->ndisc_messages > 0);
link->ndisc_messages--;
r = sd_netlink_message_get_errno(m);
if (r < 0 && r != -EEXIST)
log_link_error_errno(link, r, "Could not set NDisc route or address: %m");
if (link->ndisc_messages == 0) {
link->ndisc_configured = true;
link_check_ready(link);
}
return 1;
}
static void ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
_cleanup_(route_freep) Route *route = NULL;
struct in6_addr gateway;
uint16_t lifetime;
unsigned preference;
uint32_t mtu;
usec_t time_now;
int r;
Address *address;
Iterator i;
assert(link);
assert(rt);
r = sd_ndisc_router_get_lifetime(rt, &lifetime);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
return;
}
if (lifetime == 0) /* not a default router */
return;
r = sd_ndisc_router_get_address(rt, &gateway);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
return;
}
SET_FOREACH(address, link->addresses, i) {
if (!memcmp(&gateway, &address->in_addr.in6,
sizeof(address->in_addr.in6))) {
char buffer[INET6_ADDRSTRLEN];
log_link_debug(link, "No NDisc route added, gateway %s matches local address",
inet_ntop(AF_INET6,
&address->in_addr.in6,
buffer, sizeof(buffer)));
return;
}
}
SET_FOREACH(address, link->addresses_foreign, i) {
if (!memcmp(&gateway, &address->in_addr.in6,
sizeof(address->in_addr.in6))) {
char buffer[INET6_ADDRSTRLEN];
log_link_debug(link, "No NDisc route added, gateway %s matches local address",
inet_ntop(AF_INET6,
&address->in_addr.in6,
buffer, sizeof(buffer)));
return;
}
}
r = sd_ndisc_router_get_preference(rt, &preference);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m");
return;
}
r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
}
r = sd_ndisc_router_get_mtu(rt, &mtu);
if (r == -ENODATA)
mtu = 0;
else if (r < 0) {
log_link_warning_errno(link, r, "Failed to get default router MTU from RA: %m");
return;
}
r = route_new(&route);
if (r < 0) {
log_link_error_errno(link, r, "Could not allocate route: %m");
return;
}
route->family = AF_INET6;
route->table = link->network->ipv6_accept_ra_route_table;
route->priority = link->network->dhcp_route_metric;
route->protocol = RTPROT_RA;
route->pref = preference;
route->gw.in6 = gateway;
route->lifetime = time_now + lifetime * USEC_PER_SEC;
route->mtu = mtu;
r = route_configure(route, link, ndisc_netlink_handler);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set default route: %m");
link_enter_failed(link);
return;
}
link->ndisc_messages++;
}
static void ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) {
_cleanup_(address_freep) Address *address = NULL;
Address *existing_address;
uint32_t lifetime_valid, lifetime_preferred, lifetime_remaining;
usec_t time_now;
unsigned prefixlen;
int r;
assert(link);
assert(rt);
r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
}
r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix length: %m");
return;
}
r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix valid lifetime: %m");
return;
}
r = sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix preferred lifetime: %m");
return;
}
/* The preferred lifetime is never greater than the valid lifetime */
if (lifetime_preferred > lifetime_valid)
return;
r = address_new(&address);
if (r < 0) {
log_link_error_errno(link, r, "Could not allocate address: %m");
return;
}
address->family = AF_INET6;
r = sd_ndisc_router_prefix_get_address(rt, &address->in_addr.in6);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix address: %m");
return;
}
if (in_addr_is_null(AF_INET6, (const union in_addr_union *) &link->network->ipv6_token) == 0)
memcpy(((char *)&address->in_addr.in6) + 8, ((char *)&link->network->ipv6_token) + 8, 8);
else {
/* see RFC4291 section 2.5.1 */
address->in_addr.in6.s6_addr[8] = link->mac.ether_addr_octet[0];
address->in_addr.in6.s6_addr[8] ^= 1 << 1;
address->in_addr.in6.s6_addr[9] = link->mac.ether_addr_octet[1];
address->in_addr.in6.s6_addr[10] = link->mac.ether_addr_octet[2];
address->in_addr.in6.s6_addr[11] = 0xff;
address->in_addr.in6.s6_addr[12] = 0xfe;
address->in_addr.in6.s6_addr[13] = link->mac.ether_addr_octet[3];
address->in_addr.in6.s6_addr[14] = link->mac.ether_addr_octet[4];
address->in_addr.in6.s6_addr[15] = link->mac.ether_addr_octet[5];
}
address->prefixlen = prefixlen;
address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR;
address->cinfo.ifa_prefered = lifetime_preferred;
/* see RFC4862 section 5.5.3.e */
r = address_get(link, address->family, &address->in_addr, address->prefixlen, &existing_address);
if (r > 0) {
lifetime_remaining = existing_address->cinfo.tstamp / 100 + existing_address->cinfo.ifa_valid - time_now / USEC_PER_SEC;
if (lifetime_valid > NDISC_PREFIX_LFT_MIN || lifetime_valid > lifetime_remaining)
address->cinfo.ifa_valid = lifetime_valid;
else if (lifetime_remaining <= NDISC_PREFIX_LFT_MIN)
address->cinfo.ifa_valid = lifetime_remaining;
else
address->cinfo.ifa_valid = NDISC_PREFIX_LFT_MIN;
} else if (lifetime_valid > 0)
address->cinfo.ifa_valid = lifetime_valid;
else
return; /* see RFC4862 section 5.5.3.d */
if (address->cinfo.ifa_valid == 0)
return;
r = address_configure(address, link, ndisc_netlink_handler, true);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set SLAAC address: %m");
link_enter_failed(link);
return;
}
link->ndisc_messages++;
}
static void ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
_cleanup_(route_freep) Route *route = NULL;
usec_t time_now;
uint32_t lifetime;
unsigned prefixlen;
int r;
assert(link);
assert(rt);
r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
}
r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix length: %m");
return;
}
r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix lifetime: %m");
return;
}
r = route_new(&route);
if (r < 0) {
log_link_error_errno(link, r, "Could not allocate route: %m");
return;
}
route->family = AF_INET6;
route->table = link->network->ipv6_accept_ra_route_table;
route->priority = link->network->dhcp_route_metric;
route->protocol = RTPROT_RA;
route->flags = RTM_F_PREFIX;
route->dst_prefixlen = prefixlen;
route->lifetime = time_now + lifetime * USEC_PER_SEC;
r = sd_ndisc_router_prefix_get_address(rt, &route->dst.in6);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix address: %m");
return;
}
r = route_configure(route, link, ndisc_netlink_handler);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set prefix route: %m");
link_enter_failed(link);
return;
}
link->ndisc_messages++;
}
static void ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
_cleanup_(route_freep) Route *route = NULL;
struct in6_addr gateway;
uint32_t lifetime;
unsigned preference, prefixlen;
usec_t time_now;
int r;
assert(link);
r = sd_ndisc_router_route_get_lifetime(rt, &lifetime);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
return;
}
if (lifetime == 0)
return;
r = sd_ndisc_router_get_address(rt, &gateway);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
return;
}
r = sd_ndisc_router_route_get_prefixlen(rt, &prefixlen);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get route prefix length: %m");
return;
}
r = sd_ndisc_router_route_get_preference(rt, &preference);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m");
return;
}
r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
}
r = route_new(&route);
if (r < 0) {
log_link_error_errno(link, r, "Could not allocate route: %m");
return;
}
route->family = AF_INET6;
route->table = link->network->ipv6_accept_ra_route_table;
route->protocol = RTPROT_RA;
route->pref = preference;
route->gw.in6 = gateway;
route->dst_prefixlen = prefixlen;
route->lifetime = time_now + lifetime * USEC_PER_SEC;
r = sd_ndisc_router_route_get_address(rt, &route->dst.in6);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get route address: %m");
return;
}
r = route_configure(route, link, ndisc_netlink_handler);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set additional route: %m");
link_enter_failed(link);
return;
}
link->ndisc_messages++;
}
static void ndisc_rdnss_hash_func(const void *p, struct siphash *state) {
const NDiscRDNSS *x = p;
siphash24_compress(&x->address, sizeof(x->address), state);
}
static int ndisc_rdnss_compare_func(const void *_a, const void *_b) {
const NDiscRDNSS *a = _a, *b = _b;
return memcmp(&a->address, &b->address, sizeof(a->address));
}
static const struct hash_ops ndisc_rdnss_hash_ops = {
.hash = ndisc_rdnss_hash_func,
.compare = ndisc_rdnss_compare_func
};
static void ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) {
uint32_t lifetime;
const struct in6_addr *a;
usec_t time_now;
int i, n, r;
assert(link);
assert(rt);
r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
}
r = sd_ndisc_router_rdnss_get_lifetime(rt, &lifetime);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m");
return;
}
n = sd_ndisc_router_rdnss_get_addresses(rt, &a);
if (n < 0) {
log_link_warning_errno(link, n, "Failed to get RDNSS addresses: %m");
return;
}
for (i = 0; i < n; i++) {
NDiscRDNSS d = {
.address = a[i]
}, *x;
if (lifetime == 0) {
(void) set_remove(link->ndisc_rdnss, &d);
link_dirty(link);
continue;
}
x = set_get(link->ndisc_rdnss, &d);
if (x) {
x->valid_until = time_now + lifetime * USEC_PER_SEC;
continue;
}
ndisc_vacuum(link);
if (set_size(link->ndisc_rdnss) >= NDISC_RDNSS_MAX) {
log_link_warning(link, "Too many RDNSS records per link, ignoring.");
continue;
}
r = set_ensure_allocated(&link->ndisc_rdnss, &ndisc_rdnss_hash_ops);
if (r < 0) {
log_oom();
return;
}
x = new0(NDiscRDNSS, 1);
if (!x) {
log_oom();
return;
}
x->address = a[i];
x->valid_until = time_now + lifetime * USEC_PER_SEC;
r = set_put(link->ndisc_rdnss, x);
if (r < 0) {
free(x);
log_oom();
return;
}
assert(r > 0);
link_dirty(link);
}
}
static void ndisc_dnssl_hash_func(const void *p, struct siphash *state) {
const NDiscDNSSL *x = p;
siphash24_compress(NDISC_DNSSL_DOMAIN(x), strlen(NDISC_DNSSL_DOMAIN(x)), state);
}
static int ndisc_dnssl_compare_func(const void *_a, const void *_b) {
const NDiscDNSSL *a = _a, *b = _b;
return strcmp(NDISC_DNSSL_DOMAIN(a), NDISC_DNSSL_DOMAIN(b));
}
static const struct hash_ops ndisc_dnssl_hash_ops = {
.hash = ndisc_dnssl_hash_func,
.compare = ndisc_dnssl_compare_func
};
static void ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) {
_cleanup_strv_free_ char **l = NULL;
uint32_t lifetime;
usec_t time_now;
char **i;
int r;
assert(link);
assert(rt);
r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
}
r = sd_ndisc_router_dnssl_get_lifetime(rt, &lifetime);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m");
return;
}
r = sd_ndisc_router_dnssl_get_domains(rt, &l);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RDNSS addresses: %m");
return;
}
STRV_FOREACH(i, l) {
_cleanup_free_ NDiscDNSSL *s;
NDiscDNSSL *x;
s = malloc0(ALIGN(sizeof(NDiscDNSSL)) + strlen(*i) + 1);
if (!s) {
log_oom();
return;
}
strcpy(NDISC_DNSSL_DOMAIN(s), *i);
if (lifetime == 0) {
(void) set_remove(link->ndisc_dnssl, s);
link_dirty(link);
continue;
}
x = set_get(link->ndisc_dnssl, s);
if (x) {
x->valid_until = time_now + lifetime * USEC_PER_SEC;
continue;
}
ndisc_vacuum(link);
if (set_size(link->ndisc_dnssl) >= NDISC_DNSSL_MAX) {
log_link_warning(link, "Too many DNSSL records per link, ignoring.");
continue;
}
r = set_ensure_allocated(&link->ndisc_dnssl, &ndisc_dnssl_hash_ops);
if (r < 0) {
log_oom();
return;
}
s->valid_until = time_now + lifetime * USEC_PER_SEC;
r = set_put(link->ndisc_dnssl, s);
if (r < 0) {
log_oom();
return;
}
s = NULL;
assert(r > 0);
link_dirty(link);
}
}
static void ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
int r;
assert(link);
assert(rt);
r = sd_ndisc_router_option_rewind(rt);
for (;;) {
uint8_t type;
if (r < 0) {
log_link_warning_errno(link, r, "Failed to iterate through options: %m");
return;
}
if (r == 0) /* EOF */
break;
r = sd_ndisc_router_option_get_type(rt, &type);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA option type: %m");
return;
}
switch (type) {
case SD_NDISC_OPTION_PREFIX_INFORMATION: {
uint8_t flags;
r = sd_ndisc_router_prefix_get_flags(rt, &flags);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA prefix flags: %m");
return;
}
if (flags & ND_OPT_PI_FLAG_ONLINK)
ndisc_router_process_onlink_prefix(link, rt);
if (flags & ND_OPT_PI_FLAG_AUTO)
ndisc_router_process_autonomous_prefix(link, rt);
break;
}
case SD_NDISC_OPTION_ROUTE_INFORMATION:
ndisc_router_process_route(link, rt);
break;
case SD_NDISC_OPTION_RDNSS:
if (link->network->ipv6_accept_ra_use_dns)
ndisc_router_process_rdnss(link, rt);
break;
case SD_NDISC_OPTION_DNSSL:
if (link->network->ipv6_accept_ra_use_dns)
ndisc_router_process_dnssl(link, rt);
break;
}
r = sd_ndisc_router_option_next(rt);
}
}
static void ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
uint64_t flags;
int r;
assert(link);
assert(link->network);
assert(link->manager);
assert(rt);
r = sd_ndisc_router_get_flags(rt, &flags);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA flags: %m");
return;
}
if (flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)) {
/* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */
r = dhcp6_request_address(link, !(flags & ND_RA_FLAG_MANAGED));
if (r < 0 && r != -EBUSY)
log_link_warning_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m");
else
log_link_debug(link, "Acquiring DHCPv6 lease on NDisc request");
}
ndisc_router_process_default(link, rt);
ndisc_router_process_options(link, rt);
}
static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) {
Link *link = userdata;
assert(link);
if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
return;
switch (event) {
case SD_NDISC_EVENT_ROUTER:
ndisc_router_handler(link, rt);
break;
case SD_NDISC_EVENT_TIMEOUT:
link->ndisc_configured = true;
link_check_ready(link);
break;
default:
log_link_warning(link, "IPv6 Neighbor Discovery unknown event: %d", event);
}
}
int ndisc_configure(Link *link) {
int r;
assert(link);
r = sd_ndisc_new(&link->ndisc);
if (r < 0)
return r;
r = sd_ndisc_attach_event(link->ndisc, NULL, 0);
if (r < 0)
return r;
r = sd_ndisc_set_mac(link->ndisc, &link->mac);
if (r < 0)
return r;
r = sd_ndisc_set_ifindex(link->ndisc, link->ifindex);
if (r < 0)
return r;
r = sd_ndisc_set_callback(link->ndisc, ndisc_handler, link);
if (r < 0)
return r;
return 0;
}
void ndisc_vacuum(Link *link) {
NDiscRDNSS *r;
NDiscDNSSL *d;
Iterator i;
usec_t time_now;
assert(link);
/* Removes all RDNSS and DNSSL entries whose validity time has passed */
time_now = now(clock_boottime_or_monotonic());
SET_FOREACH(r, link->ndisc_rdnss, i)
if (r->valid_until < time_now) {
free(set_remove(link->ndisc_rdnss, r));
link_dirty(link);
}
SET_FOREACH(d, link->ndisc_dnssl, i)
if (d->valid_until < time_now) {
free(set_remove(link->ndisc_dnssl, d));
link_dirty(link);
}
}
void ndisc_flush(Link *link) {
assert(link);
/* Removes all RDNSS and DNSSL entries, without exception */
link->ndisc_rdnss = set_free_free(link->ndisc_rdnss);
link->ndisc_dnssl = set_free_free(link->ndisc_dnssl);
}