Systemd/src/libsystemd-network/ndisc-router.c
Zbigniew Jędrzejewski-Szmek 11a1589223 tree-wide: drop license boilerplate
Files which are installed as-is (any .service and other unit files, .conf
files, .policy files, etc), are left as is. My assumption is that SPDX
identifiers are not yet that well known, so it's better to retain the
extended header to avoid any doubt.

I also kept any copyright lines. We can probably remove them, but it'd nice to
obtain explicit acks from all involved authors before doing that.
2018-04-06 18:58:55 +02:00

773 lines
21 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 "sd-ndisc.h"
#include "alloc-util.h"
#include "dns-domain.h"
#include "hostname-util.h"
#include "missing.h"
#include "ndisc-internal.h"
#include "ndisc-router.h"
#include "strv.h"
_public_ sd_ndisc_router* sd_ndisc_router_ref(sd_ndisc_router *rt) {
if (!rt)
return NULL;
assert(rt->n_ref > 0);
rt->n_ref++;
return rt;
}
_public_ sd_ndisc_router* sd_ndisc_router_unref(sd_ndisc_router *rt) {
if (!rt)
return NULL;
assert(rt->n_ref > 0);
rt->n_ref--;
if (rt->n_ref > 0)
return NULL;
return mfree(rt);
}
sd_ndisc_router *ndisc_router_new(size_t raw_size) {
sd_ndisc_router *rt;
rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size);
if (!rt)
return NULL;
rt->raw_size = raw_size;
rt->n_ref = 1;
return rt;
}
_public_ int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size) {
_cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
int r;
assert_return(ret, -EINVAL);
assert_return(raw || raw_size <= 0, -EINVAL);
rt = ndisc_router_new(raw_size);
if (!rt)
return -ENOMEM;
memcpy(NDISC_ROUTER_RAW(rt), raw, raw_size);
r = ndisc_router_parse(rt);
if (r < 0)
return r;
*ret = TAKE_PTR(rt);
return r;
}
_public_ int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
assert_return(rt, -EINVAL);
assert_return(ret_addr, -EINVAL);
if (IN6_IS_ADDR_UNSPECIFIED(&rt->address))
return -ENODATA;
*ret_addr = rt->address;
return 0;
}
_public_ int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) {
assert_return(rt, -EINVAL);
assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
assert_return(clock_supported(clock), -EOPNOTSUPP);
assert_return(ret, -EINVAL);
if (!triple_timestamp_is_set(&rt->timestamp))
return -ENODATA;
*ret = triple_timestamp_by_clock(&rt->timestamp, clock);
return 0;
}
_public_ int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(size, -EINVAL);
*ret = NDISC_ROUTER_RAW(rt);
*size = rt->raw_size;
return 0;
}
int ndisc_router_parse(sd_ndisc_router *rt) {
struct nd_router_advert *a;
const uint8_t *p;
bool has_mtu = false, has_flag_extension = false;
size_t left;
assert(rt);
if (rt->raw_size < sizeof(struct nd_router_advert)) {
log_ndisc("Too small to be a router advertisement, ignoring.");
return -EBADMSG;
}
/* Router advertisement packets are neatly aligned to 64bit boundaries, hence we can access them directly */
a = NDISC_ROUTER_RAW(rt);
if (a->nd_ra_type != ND_ROUTER_ADVERT) {
log_ndisc("Received ND packet that is not a router advertisement, ignoring.");
return -EBADMSG;
}
if (a->nd_ra_code != 0) {
log_ndisc("Received ND packet with wrong RA code, ignoring.");
return -EBADMSG;
}
rt->hop_limit = a->nd_ra_curhoplimit;
rt->flags = a->nd_ra_flags_reserved; /* the first 8bit */
rt->lifetime = be16toh(a->nd_ra_router_lifetime);
rt->preference = (rt->flags >> 3) & 3;
if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
rt->preference = SD_NDISC_PREFERENCE_MEDIUM;
p = (const uint8_t*) NDISC_ROUTER_RAW(rt) + sizeof(struct nd_router_advert);
left = rt->raw_size - sizeof(struct nd_router_advert);
for (;;) {
uint8_t type;
size_t length;
if (left == 0)
break;
if (left < 2) {
log_ndisc("Option lacks header, ignoring datagram.");
return -EBADMSG;
}
type = p[0];
length = p[1] * 8;
if (length == 0) {
log_ndisc("Zero-length option, ignoring datagram.");
return -EBADMSG;
}
if (left < length) {
log_ndisc("Option truncated, ignoring datagram.");
return -EBADMSG;
}
switch (type) {
case SD_NDISC_OPTION_PREFIX_INFORMATION:
if (length != 4*8) {
log_ndisc("Prefix option of invalid size, ignoring datagram.");
return -EBADMSG;
}
if (p[2] > 128) {
log_ndisc("Bad prefix length, ignoring datagram.");
return -EBADMSG;
}
break;
case SD_NDISC_OPTION_MTU: {
uint32_t m;
if (has_mtu) {
log_ndisc("MTU option specified twice, ignoring.");
continue;
}
if (length != 8) {
log_ndisc("MTU option of invalid size, ignoring datagram.");
return -EBADMSG;
}
m = be32toh(*(uint32_t*) (p + 4));
if (m >= IPV6_MIN_MTU) /* ignore invalidly small MTUs */
rt->mtu = m;
has_mtu = true;
break;
}
case SD_NDISC_OPTION_ROUTE_INFORMATION:
if (length < 1*8 || length > 3*8) {
log_ndisc("Route information option of invalid size, ignoring datagram.");
return -EBADMSG;
}
if (p[2] > 128) {
log_ndisc("Bad route prefix length, ignoring datagram.");
return -EBADMSG;
}
break;
case SD_NDISC_OPTION_RDNSS:
if (length < 3*8 || (length % (2*8)) != 1*8) {
log_ndisc("RDNSS option has invalid size.");
return -EBADMSG;
}
break;
case SD_NDISC_OPTION_FLAGS_EXTENSION:
if (has_flag_extension) {
log_ndisc("Flags extension option specified twice, ignoring.");
continue;
}
if (length < 1*8) {
log_ndisc("Flags extension option has invalid size.");
return -EBADMSG;
}
/* Add in the additional flags bits */
rt->flags |=
((uint64_t) p[2] << 8) |
((uint64_t) p[3] << 16) |
((uint64_t) p[4] << 24) |
((uint64_t) p[5] << 32) |
((uint64_t) p[6] << 40) |
((uint64_t) p[7] << 48);
has_flag_extension = true;
break;
case SD_NDISC_OPTION_DNSSL:
if (length < 2*8) {
log_ndisc("DNSSL option has invalid size.");
return -EBADMSG;
}
break;
}
p += length, left -= length;
}
rt->rindex = sizeof(struct nd_router_advert);
return 0;
}
_public_ int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
*ret = rt->hop_limit;
return 0;
}
_public_ int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags) {
assert_return(rt, -EINVAL);
assert_return(ret_flags, -EINVAL);
*ret_flags = rt->flags;
return 0;
}
_public_ int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime) {
assert_return(rt, -EINVAL);
assert_return(ret_lifetime, -EINVAL);
*ret_lifetime = rt->lifetime;
return 0;
}
_public_ int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
*ret = rt->preference;
return 0;
}
_public_ int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
if (rt->mtu <= 0)
return -ENODATA;
*ret = rt->mtu;
return 0;
}
_public_ int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) {
assert_return(rt, -EINVAL);
assert(rt->raw_size >= sizeof(struct nd_router_advert));
rt->rindex = sizeof(struct nd_router_advert);
return rt->rindex < rt->raw_size;
}
_public_ int sd_ndisc_router_option_next(sd_ndisc_router *rt) {
size_t length;
assert_return(rt, -EINVAL);
if (rt->rindex == rt->raw_size) /* EOF */
return -ESPIPE;
if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
return -EBADMSG;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (rt->rindex + length > rt->raw_size)
return -EBADMSG;
rt->rindex += length;
return rt->rindex < rt->raw_size;
}
_public_ int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
if (rt->rindex == rt->raw_size) /* EOF */
return -ESPIPE;
if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
return -EBADMSG;
*ret = NDISC_ROUTER_OPTION_TYPE(rt);
return 0;
}
_public_ int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) {
uint8_t k;
int r;
assert_return(rt, -EINVAL);
r = sd_ndisc_router_option_get_type(rt, &k);
if (r < 0)
return r;
return type == k;
}
_public_ int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) {
size_t length;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(size, -EINVAL);
/* Note that this returns the full option, including the option header */
if (rt->rindex + 2 > rt->raw_size)
return -EBADMSG;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (rt->rindex + length > rt->raw_size)
return -EBADMSG;
*ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
*size = length;
return 0;
}
static int get_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix_info **ret) {
struct nd_opt_prefix_info *ri;
size_t length;
int r;
assert(rt);
assert(ret);
r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREFIX_INFORMATION);
if (r < 0)
return r;
if (r == 0)
return -EMEDIUMTYPE;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (length != sizeof(struct nd_opt_prefix_info))
return -EBADMSG;
ri = (struct nd_opt_prefix_info*) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex);
if (ri->nd_opt_pi_prefix_len > 128)
return -EBADMSG;
*ret = ri;
return 0;
}
_public_ int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
struct nd_opt_prefix_info *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_prefix_info(rt, &ri);
if (r < 0)
return r;
*ret = be32toh(ri->nd_opt_pi_valid_time);
return 0;
}
_public_ int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
struct nd_opt_prefix_info *pi;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_prefix_info(rt, &pi);
if (r < 0)
return r;
*ret = be32toh(pi->nd_opt_pi_preferred_time);
return 0;
}
_public_ int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret) {
struct nd_opt_prefix_info *pi;
uint8_t flags;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_prefix_info(rt, &pi);
if (r < 0)
return r;
flags = pi->nd_opt_pi_flags_reserved;
if ((flags & ND_OPT_PI_FLAG_AUTO) && (pi->nd_opt_pi_prefix_len != 64)) {
log_ndisc("Invalid prefix length, ignoring prefix for stateless autoconfiguration.");
flags &= ~ND_OPT_PI_FLAG_AUTO;
}
*ret = flags;
return 0;
}
_public_ int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
struct nd_opt_prefix_info *pi;
int r;
assert_return(rt, -EINVAL);
assert_return(ret_addr, -EINVAL);
r = get_prefix_info(rt, &pi);
if (r < 0)
return r;
*ret_addr = pi->nd_opt_pi_prefix;
return 0;
}
_public_ int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
struct nd_opt_prefix_info *pi;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_prefix_info(rt, &pi);
if (r < 0)
return r;
if (pi->nd_opt_pi_prefix_len > 128)
return -EBADMSG;
*ret = pi->nd_opt_pi_prefix_len;
return 0;
}
static int get_route_info(sd_ndisc_router *rt, uint8_t **ret) {
uint8_t *ri;
size_t length;
int r;
assert(rt);
assert(ret);
r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_ROUTE_INFORMATION);
if (r < 0)
return r;
if (r == 0)
return -EMEDIUMTYPE;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (length < 1*8 || length > 3*8)
return -EBADMSG;
ri = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
if (ri[2] > 128)
return -EBADMSG;
*ret = ri;
return 0;
}
_public_ int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_route_info(rt, &ri);
if (r < 0)
return r;
*ret = be32toh(*(uint32_t*) (ri + 4));
return 0;
}
_public_ int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret_addr, -EINVAL);
r = get_route_info(rt, &ri);
if (r < 0)
return r;
zero(*ret_addr);
memcpy(ret_addr, ri + 8, NDISC_ROUTER_OPTION_LENGTH(rt) - 8);
return 0;
}
_public_ int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_route_info(rt, &ri);
if (r < 0)
return r;
*ret = ri[2];
return 0;
}
_public_ int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_route_info(rt, &ri);
if (r < 0)
return r;
*ret = (ri[3] >> 3) & 3;
if (!IN_SET(*ret, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
*ret = SD_NDISC_PREFERENCE_MEDIUM;
return 0;
}
static int get_rdnss_info(sd_ndisc_router *rt, uint8_t **ret) {
size_t length;
int r;
assert(rt);
assert(ret);
r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS);
if (r < 0)
return r;
if (r == 0)
return -EMEDIUMTYPE;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (length < 3*8 || (length % (2*8)) != 1*8)
return -EBADMSG;
*ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
return 0;
}
_public_ int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_rdnss_info(rt, &ri);
if (r < 0)
return r;
*ret = (const struct in6_addr*) (ri + 8);
return (NDISC_ROUTER_OPTION_LENGTH(rt) - 8) / 16;
}
_public_ int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_rdnss_info(rt, &ri);
if (r < 0)
return r;
*ret = be32toh(*(uint32_t*) (ri + 4));
return 0;
}
static int get_dnssl_info(sd_ndisc_router *rt, uint8_t **ret) {
size_t length;
int r;
assert(rt);
assert(ret);
r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_DNSSL);
if (r < 0)
return r;
if (r == 0)
return -EMEDIUMTYPE;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (length < 2*8)
return -EBADMSG;
*ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
return 0;
}
_public_ int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) {
_cleanup_strv_free_ char **l = NULL;
_cleanup_free_ char *e = NULL;
size_t allocated = 0, n = 0, left;
uint8_t *ri, *p;
bool first = true;
int r;
unsigned k = 0;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_dnssl_info(rt, &ri);
if (r < 0)
return r;
p = ri + 8;
left = NDISC_ROUTER_OPTION_LENGTH(rt) - 8;
for (;;) {
if (left == 0) {
if (n > 0) /* Not properly NUL terminated */
return -EBADMSG;
break;
}
if (*p == 0) {
/* Found NUL termination */
if (n > 0) {
_cleanup_free_ char *normalized = NULL;
e[n] = 0;
r = dns_name_normalize(e, &normalized);
if (r < 0)
return r;
/* Ignore the root domain name or "localhost" and friends */
if (!is_localhost(normalized) &&
!dns_name_is_root(normalized)) {
if (strv_push(&l, normalized) < 0)
return -ENOMEM;
normalized = NULL;
k++;
}
}
n = 0;
first = true;
p++, left--;
continue;
}
/* Check for compression (which is not allowed) */
if (*p > 63)
return -EBADMSG;
if (1U + *p + 1U > left)
return -EBADMSG;
if (!GREEDY_REALLOC(e, allocated, n + !first + DNS_LABEL_ESCAPED_MAX + 1U))
return -ENOMEM;
if (first)
first = false;
else
e[n++] = '.';
r = dns_label_escape((char*) p+1, *p, e + n, DNS_LABEL_ESCAPED_MAX);
if (r < 0)
return r;
n += r;
left -= 1 + *p;
p += 1 + *p;
}
if (strv_isempty(l)) {
*ret = NULL;
return 0;
}
*ret = TAKE_PTR(l);
return k;
}
_public_ int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret_sec) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret_sec, -EINVAL);
r = get_dnssl_info(rt, &ri);
if (r < 0)
return r;
*ret_sec = be32toh(*(uint32_t*) (ri + 4));
return 0;
}