summaryrefslogtreecommitdiff
path: root/src/libsystemd-network
diff options
context:
space:
mode:
Diffstat (limited to 'src/libsystemd-network')
-rw-r--r--src/libsystemd-network/icmp6-util.c7
-rw-r--r--src/libsystemd-network/ndisc-internal.h49
-rw-r--r--src/libsystemd-network/ndisc-router.c779
-rw-r--r--src/libsystemd-network/ndisc-router.h62
-rw-r--r--src/libsystemd-network/sd-ndisc.c551
-rw-r--r--src/libsystemd-network/test-ndisc-rs.c163
6 files changed, 1185 insertions, 426 deletions
diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c
index d81e9ebd88..c2e4b0e9e3 100644
--- a/src/libsystemd-network/icmp6-util.c
+++ b/src/libsystemd-network/icmp6-util.c
@@ -49,7 +49,8 @@ int icmp6_bind_router_solicitation(int index) {
};
_cleanup_close_ int s = -1;
char ifname[IF_NAMESIZE] = "";
- int r, zero = 0, one = 1, hops = 255;
+ static const int zero = 0, one = 1, hops = 255;
+ int r;
s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6);
if (s < 0)
@@ -85,6 +86,10 @@ int icmp6_bind_router_solicitation(int index) {
if (r < 0)
return -errno;
+ r = setsockopt(s, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
+ if (r < 0)
+ return -errno;
+
if (if_indextoname(index, ifname) == 0)
return -errno;
diff --git a/src/libsystemd-network/ndisc-internal.h b/src/libsystemd-network/ndisc-internal.h
new file mode 100644
index 0000000000..60e183ff8c
--- /dev/null
+++ b/src/libsystemd-network/ndisc-internal.h
@@ -0,0 +1,49 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ 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 "log.h"
+
+#include "sd-ndisc.h"
+
+struct sd_ndisc {
+ unsigned n_ref;
+
+ int ifindex;
+ int fd;
+
+ sd_event *event;
+ int event_priority;
+
+ struct ether_addr mac_addr;
+ uint8_t hop_limit;
+ uint32_t mtu;
+
+ sd_event_source *recv_event_source;
+ sd_event_source *timeout_event_source;
+
+ unsigned nd_sent;
+
+ sd_ndisc_callback_t callback;
+ void *userdata;
+};
+
+#define log_ndisc_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "NDISC: " fmt, ##__VA_ARGS__)
+#define log_ndisc(fmt, ...) log_ndisc_errno(0, fmt, ##__VA_ARGS__)
diff --git a/src/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c
new file mode 100644
index 0000000000..d9950b638c
--- /dev/null
+++ b/src/libsystemd-network/ndisc-router.c
@@ -0,0 +1,779 @@
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ 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 <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;
+
+ free(rt);
+ return NULL;
+}
+
+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 = rt;
+ rt = NULL;
+
+ 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_addr_is_null(&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;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ *ret = pi->nd_opt_pi_flags_reserved;
+ 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 = l;
+ l = NULL;
+
+ 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;
+}
diff --git a/src/libsystemd-network/ndisc-router.h b/src/libsystemd-network/ndisc-router.h
new file mode 100644
index 0000000000..1fe703da63
--- /dev/null
+++ b/src/libsystemd-network/ndisc-router.h
@@ -0,0 +1,62 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ 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 "sd-ndisc.h"
+
+#include "time-util.h"
+
+struct sd_ndisc_router {
+ unsigned n_ref;
+
+ triple_timestamp timestamp;
+ struct in6_addr address;
+
+ /* The raw packet size. The data is appended to the object, accessible via NDIS_ROUTER_RAW() */
+ size_t raw_size;
+
+ /* The current read index for the iterative option interface */
+ size_t rindex;
+
+ uint64_t flags;
+ unsigned preference;
+ uint16_t lifetime;
+
+ uint8_t hop_limit;
+ uint32_t mtu;
+};
+
+static inline void* NDISC_ROUTER_RAW(const sd_ndisc_router *rt) {
+ return (uint8_t*) rt + ALIGN(sizeof(sd_ndisc_router));
+}
+
+static inline void *NDISC_ROUTER_OPTION_DATA(const sd_ndisc_router *rt) {
+ return ((uint8_t*) NDISC_ROUTER_RAW(rt)) + rt->rindex;
+}
+
+static inline uint8_t NDISC_ROUTER_OPTION_TYPE(const sd_ndisc_router *rt) {
+ return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[0];
+}
+static inline size_t NDISC_ROUTER_OPTION_LENGTH(const sd_ndisc_router *rt) {
+ return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[1] * 8;
+}
+
+sd_ndisc_router *ndisc_router_new(size_t raw_size);
+int ndisc_router_parse(sd_ndisc_router *rt);
diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c
index ccb8002173..ea3fe369ce 100644
--- a/src/libsystemd-network/sd-ndisc.c
+++ b/src/libsystemd-network/sd-ndisc.c
@@ -19,165 +19,71 @@
#include <netinet/icmp6.h>
#include <netinet/in.h>
-#include <netinet/ip6.h>
-#include <stdbool.h>
-#include <string.h>
-#include <sys/ioctl.h>
#include "sd-ndisc.h"
#include "alloc-util.h"
-#include "async.h"
+#include "fd-util.h"
#include "icmp6-util.h"
#include "in-addr-util.h"
-#include "list.h"
+#include "ndisc-internal.h"
+#include "ndisc-router.h"
#include "socket-util.h"
#include "string-util.h"
+#include "util.h"
#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
-#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
+#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
-enum NDiscState {
- NDISC_STATE_IDLE,
- NDISC_STATE_SOLICITATION_SENT,
- NDISC_STATE_ADVERTISEMENT_LISTEN,
- _NDISC_STATE_MAX,
- _NDISC_STATE_INVALID = -1,
-};
+static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) {
+ assert(ndisc);
-#define IP6_MIN_MTU 1280U
-#define ICMP6_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
-#define NDISC_OPT_LEN_UNITS 8U
+ log_ndisc("Invoking callback for '%c'.", event);
-#define ND_RA_FLAG_PREF 0x18
-#define ND_RA_FLAG_PREF_LOW 0x03
-#define ND_RA_FLAG_PREF_MEDIUM 0x0
-#define ND_RA_FLAG_PREF_HIGH 0x1
-#define ND_RA_FLAG_PREF_INVALID 0x2
+ if (!ndisc->callback)
+ return;
-typedef struct NDiscPrefix NDiscPrefix;
-
-struct NDiscPrefix {
- unsigned n_ref;
-
- sd_ndisc *nd;
-
- LIST_FIELDS(NDiscPrefix, prefixes);
-
- uint8_t len;
- usec_t valid_until;
- struct in6_addr addr;
-};
-
-struct sd_ndisc {
- unsigned n_ref;
-
- enum NDiscState state;
- int ifindex;
- int fd;
-
- sd_event *event;
- int event_priority;
-
- struct ether_addr mac_addr;
- uint32_t mtu;
-
- LIST_HEAD(NDiscPrefix, prefixes);
-
- sd_event_source *recv_event_source;
- sd_event_source *timeout_event_source;
-
- unsigned nd_sent;
-
- sd_ndisc_router_callback_t router_callback;
- sd_ndisc_prefix_autonomous_callback_t prefix_autonomous_callback;
- sd_ndisc_prefix_onlink_callback_t prefix_onlink_callback;
- sd_ndisc_callback_t callback;
- void *userdata;
-};
-
-#define log_ndisc_errno(p, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "NDisc CLIENT: " fmt, ##__VA_ARGS__)
-#define log_ndisc(p, fmt, ...) log_ndisc_errno(p, 0, fmt, ##__VA_ARGS__)
-
-static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) {
-
- if (!prefix)
- return NULL;
-
- assert(prefix->n_ref > 0);
- prefix->n_ref--;
-
- if (prefix->n_ref > 0)
- return NULL;
-
- if (prefix->nd)
- LIST_REMOVE(prefixes, prefix->nd->prefixes, prefix);
-
- free(prefix);
-
- return NULL;
-}
-
-static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) {
- NDiscPrefix *prefix;
-
- assert(ret);
-
- prefix = new0(NDiscPrefix, 1);
- if (!prefix)
- return -ENOMEM;
-
- prefix->n_ref = 1;
- LIST_INIT(prefixes, prefix);
- prefix->nd = nd;
-
- *ret = prefix;
- return 0;
+ ndisc->callback(ndisc, event, rt, ndisc->userdata);
}
-int sd_ndisc_set_callback(
+_public_ int sd_ndisc_set_callback(
sd_ndisc *nd,
- sd_ndisc_router_callback_t router_callback,
- sd_ndisc_prefix_onlink_callback_t prefix_onlink_callback,
- sd_ndisc_prefix_autonomous_callback_t prefix_autonomous_callback,
sd_ndisc_callback_t callback,
void *userdata) {
assert_return(nd, -EINVAL);
- nd->router_callback = router_callback;
- nd->prefix_onlink_callback = prefix_onlink_callback;
- nd->prefix_autonomous_callback = prefix_autonomous_callback;
nd->callback = callback;
nd->userdata = userdata;
return 0;
}
-int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
+_public_ int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
assert_return(nd, -EINVAL);
assert_return(ifindex > 0, -EINVAL);
+ assert_return(nd->fd < 0, -EBUSY);
nd->ifindex = ifindex;
return 0;
}
-int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
+_public_ int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
assert_return(nd, -EINVAL);
if (mac_addr)
- memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
+ nd->mac_addr = *mac_addr;
else
zero(nd->mac_addr);
return 0;
-
}
-int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
+_public_ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
int r;
assert_return(nd, -EINVAL);
+ assert_return(nd->fd < 0, -EBUSY);
assert_return(!nd->event, -EBUSY);
if (event)
@@ -193,21 +99,22 @@ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
return 0;
}
-int sd_ndisc_detach_event(sd_ndisc *nd) {
+_public_ int sd_ndisc_detach_event(sd_ndisc *nd) {
+
assert_return(nd, -EINVAL);
+ assert_return(nd->fd < 0, -EBUSY);
nd->event = sd_event_unref(nd->event);
-
return 0;
}
-sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
+_public_ sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
assert_return(nd, NULL);
return nd->event;
}
-sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
+_public_ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
if (!nd)
return NULL;
@@ -221,15 +128,14 @@ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
static int ndisc_reset(sd_ndisc *nd) {
assert(nd);
- nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
- nd->fd = asynchronous_close(nd->fd);
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
+ nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
+ nd->fd = safe_close(nd->fd);
return 0;
}
-sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
- NDiscPrefix *prefix, *p;
+_public_ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
if (!nd)
return NULL;
@@ -242,16 +148,12 @@ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
ndisc_reset(nd);
sd_ndisc_detach_event(nd);
-
- LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes)
- prefix = ndisc_prefix_unref(prefix);
-
free(nd);
return NULL;
}
-int sd_ndisc_new(sd_ndisc **ret) {
+_public_ int sd_ndisc_new(sd_ndisc **ret) {
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
assert_return(ret, -EINVAL);
@@ -261,223 +163,70 @@ int sd_ndisc_new(sd_ndisc **ret) {
return -ENOMEM;
nd->n_ref = 1;
- nd->ifindex = -1;
nd->fd = -1;
- LIST_HEAD_INIT(nd->prefixes);
-
*ret = nd;
nd = NULL;
return 0;
}
-int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
+_public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
assert_return(nd, -EINVAL);
assert_return(mtu, -EINVAL);
if (nd->mtu == 0)
- return -ENOMSG;
+ return -ENODATA;
*mtu = nd->mtu;
return 0;
}
-static int prefix_match(const struct in6_addr *prefix, uint8_t prefixlen,
- const struct in6_addr *addr,
- uint8_t addr_prefixlen) {
- uint8_t bytes, mask, len;
-
- assert(prefix);
- assert(addr);
-
- len = MIN(prefixlen, addr_prefixlen);
-
- bytes = len / 8;
- mask = 0xff << (8 - len % 8);
+_public_ int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret) {
+ assert_return(nd, -EINVAL);
+ assert_return(ret, -EINVAL);
- if (memcmp(prefix, addr, bytes) != 0 ||
- (prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask))
- return -EADDRNOTAVAIL;
+ if (nd->hop_limit == 0)
+ return -ENODATA;
+ *ret = nd->hop_limit;
return 0;
}
-static int ndisc_prefix_match(sd_ndisc *nd, const struct in6_addr *addr,
- uint8_t addr_len, NDiscPrefix **result) {
- NDiscPrefix *prefix, *p;
- usec_t time_now;
- int r;
-
- assert(nd);
-
- r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
- if (r < 0)
- return r;
-
- LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
- if (prefix->valid_until < time_now) {
- prefix = ndisc_prefix_unref(prefix);
- continue;
- }
-
- if (prefix_match(&prefix->addr, prefix->len, addr, addr_len) >= 0) {
- *result = prefix;
- return 0;
- }
- }
-
- return -EADDRNOTAVAIL;
-}
-
-static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
- const struct nd_opt_prefix_info *prefix_opt) {
- NDiscPrefix *prefix;
- uint32_t lifetime_valid, lifetime_preferred;
- usec_t time_now;
- char time_string[FORMAT_TIMESPAN_MAX];
+static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
int r;
assert(nd);
- assert(prefix_opt);
+ assert(rt);
- if (len < prefix_opt->nd_opt_pi_len)
- return -EBADMSG;
-
- if (!(prefix_opt->nd_opt_pi_flags_reserved & (ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO)))
+ r = ndisc_router_parse(rt);
+ if (r == -EBADMSG) /* Bad packet */
return 0;
-
- if (in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &prefix_opt->nd_opt_pi_prefix) > 0)
- return 0;
-
- lifetime_valid = be32toh(prefix_opt->nd_opt_pi_valid_time);
- lifetime_preferred = be32toh(prefix_opt->nd_opt_pi_preferred_time);
-
- if (lifetime_valid < lifetime_preferred)
- return 0;
-
- r = ndisc_prefix_match(nd, &prefix_opt->nd_opt_pi_prefix,
- prefix_opt->nd_opt_pi_prefix_len, &prefix);
- if (r < 0) {
- if (r != -EADDRNOTAVAIL)
- return r;
-
- /* if router advertisement prefix valid timeout is zero, the timeout
- callback will be called immediately to clean up the prefix */
-
- r = ndisc_prefix_new(nd, &prefix);
- if (r < 0)
- return r;
-
- prefix->len = prefix_opt->nd_opt_pi_prefix_len;
-
- memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix,
- sizeof(prefix->addr));
-
- log_ndisc(nd, "New prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
- SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
- prefix->len, lifetime_valid,
- format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
-
- LIST_PREPEND(prefixes, nd->prefixes, prefix);
-
- } else {
- if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) {
- uint8_t prefixlen;
-
- prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
-
- log_ndisc(nd, "Prefix length mismatch %d/%d using %d",
- prefix->len,
- prefix_opt->nd_opt_pi_prefix_len,
- prefixlen);
-
- prefix->len = prefixlen;
- }
-
- log_ndisc(nd, "Update prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
- SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
- prefix->len, lifetime_valid,
- format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
- }
-
- r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
if (r < 0)
- return r;
-
- prefix->valid_until = time_now + lifetime_valid * USEC_PER_SEC;
-
- if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) && nd->prefix_onlink_callback)
- nd->prefix_onlink_callback(nd, &prefix->addr, prefix->len, prefix->valid_until, nd->userdata);
-
- if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) && nd->prefix_autonomous_callback)
- nd->prefix_autonomous_callback(nd, &prefix->addr, prefix->len, lifetime_preferred, lifetime_valid,
- nd->userdata);
-
- return 0;
-}
-
-static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra, size_t len) {
- struct nd_opt_hdr *opt_hdr;
- void *opt;
-
- assert(nd);
- assert(ra);
-
- if (len < sizeof(struct nd_router_advert) + NDISC_OPT_LEN_UNITS) {
- log_ndisc(nd, "Router Advertisement below minimum length");
- return -EBADMSG;
- }
-
- len -= sizeof(struct nd_router_advert);
- opt = ra + 1;
- opt_hdr = opt;
-
- while (len != 0 && len >= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS) {
- struct nd_opt_mtu *opt_mtu;
- struct nd_opt_prefix_info *opt_prefix;
- uint32_t mtu;
-
- if (opt_hdr->nd_opt_len == 0)
- return -EBADMSG;
-
- switch (opt_hdr->nd_opt_type) {
-
- case ND_OPT_MTU:
- opt_mtu = opt;
-
- mtu = be32toh(opt_mtu->nd_opt_mtu_mtu);
-
- if (mtu != nd->mtu) {
- nd->mtu = MAX(mtu, IP6_MIN_MTU);
- log_ndisc(nd, "Router Advertisement link MTU %d using %d", mtu, nd->mtu);
- }
-
- break;
-
- case ND_OPT_PREFIX_INFORMATION:
- opt_prefix = opt;
- ndisc_prefix_update(nd, len, opt_prefix);
- break;
- }
+ return 0;
- len -= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS;
- opt = (void*) ((uint8_t*) opt + opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS);
- opt_hdr = opt;
- }
+ /* Update global variables we keep */
+ if (rt->mtu > 0)
+ nd->mtu = rt->mtu;
+ if (rt->hop_limit > 0)
+ nd->hop_limit = rt->hop_limit;
- if (len > 0)
- log_ndisc(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
+ log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec",
+ rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none",
+ rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium",
+ rt->lifetime);
+ ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
return 0;
}
-static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
- _cleanup_free_ struct nd_router_advert *ra = NULL;
+static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
sd_ndisc *nd = userdata;
union {
struct cmsghdr cmsghdr;
- uint8_t buf[CMSG_LEN(sizeof(int))];
+ uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */
+ CMSG_SPACE(sizeof(struct timeval))];
} control = {};
struct iovec iov = {};
union sockaddr_union sa = {};
@@ -490,10 +239,7 @@ static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t
.msg_controllen = sizeof(control),
};
struct cmsghdr *cmsg;
- struct in6_addr *gw;
- unsigned lifetime;
ssize_t len, buflen;
- int r, pref, stateful;
assert(s);
assert(nd);
@@ -501,35 +247,47 @@ static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t
buflen = next_datagram_size_fd(fd);
if (buflen < 0)
- return buflen;
-
- iov.iov_len = buflen;
+ return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m");
- ra = malloc(iov.iov_len);
- if (!ra)
+ rt = ndisc_router_new(buflen);
+ if (!rt)
return -ENOMEM;
- iov.iov_base = ra;
+ iov.iov_base = NDISC_ROUTER_RAW(rt);
+ iov.iov_len = rt->raw_size;
- len = recvmsg(fd, &msg, 0);
+ len = recvmsg(fd, &msg, MSG_DONTWAIT);
if (len < 0) {
if (errno == EAGAIN || errno == EINTR)
return 0;
- return log_ndisc_errno(nd, errno, "Could not receive message from ICMPv6 socket: %m");
+ return log_ndisc_errno(errno, "Could not receive message from ICMPv6 socket: %m");
}
- if ((size_t) len < sizeof(struct nd_router_advert)) {
- log_ndisc(nd, "Too small to be a router advertisement: ignoring");
- return 0;
+
+ if ((size_t) len != rt->raw_size) {
+ log_ndisc("Packet size mismatch.");
+ return -EINVAL;
}
- if (msg.msg_namelen == 0)
- gw = NULL; /* only happens when running the test-suite over a socketpair */
- else if (msg.msg_namelen != sizeof(sa.in6)) {
- log_ndisc(nd, "Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t)msg.msg_namelen);
- return 0;
- } else
- gw = &sa.in6.sin6_addr;
+ if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
+ sa.in6.sin6_family == AF_INET6) {
+
+ if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr) <= 0) {
+ _cleanup_free_ char *addr = NULL;
+
+ (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr, &addr);
+ log_ndisc("Received RA from non-link-local address %s. Ignoring.", strna(addr));
+ return 0;
+ }
+
+ rt->address = sa.in6.sin6_addr;
+
+ } else if (msg.msg_namelen > 0) {
+ log_ndisc("Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t) msg.msg_namelen);
+ return -EINVAL;
+ }
+
+ /* namelen == 0 only happens when running the test-suite over a socketpair */
assert(!(msg.msg_flags & MSG_CTRUNC));
assert(!(msg.msg_flags & MSG_TRUNC));
@@ -538,61 +296,29 @@ static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t
if (cmsg->cmsg_level == SOL_IPV6 &&
cmsg->cmsg_type == IPV6_HOPLIMIT &&
cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
- int hops = *(int*)CMSG_DATA(cmsg);
+ int hops = *(int*) CMSG_DATA(cmsg);
if (hops != 255) {
- log_ndisc(nd, "Received RA with invalid hop limit %d. Ignoring.", hops);
+ log_ndisc("Received RA with invalid hop limit %d. Ignoring.", hops);
return 0;
}
-
- break;
}
- }
- if (gw && !in_addr_is_link_local(AF_INET6, (const union in_addr_union*) gw)) {
- _cleanup_free_ char *addr = NULL;
-
- (void)in_addr_to_string(AF_INET6, (const union in_addr_union*) gw, &addr);
-
- log_ndisc(nd, "Received RA from non-link-local address %s. Ignoring.", strna(addr));
- return 0;
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SO_TIMESTAMP &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
+ triple_timestamp_from_realtime(&rt->timestamp, timeval_load(CMSG_DATA(cmsg)));
}
- if (ra->nd_ra_type != ND_ROUTER_ADVERT)
- return 0;
-
- if (ra->nd_ra_code != 0)
- return 0;
+ if (!triple_timestamp_is_set(&rt->timestamp))
+ triple_timestamp_get(&rt->timestamp);
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
- nd->state = NDISC_STATE_ADVERTISEMENT_LISTEN;
-
- stateful = ra->nd_ra_flags_reserved & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER);
- pref = (ra->nd_ra_flags_reserved & ND_RA_FLAG_PREF) >> 3;
-
- if (!IN_SET(pref, ND_RA_FLAG_PREF_LOW, ND_RA_FLAG_PREF_HIGH))
- pref = ND_RA_FLAG_PREF_MEDIUM;
-
- lifetime = be16toh(ra->nd_ra_router_lifetime);
-
- log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %u sec",
- stateful & ND_RA_FLAG_MANAGED ? "MANAGED" : stateful & ND_RA_FLAG_OTHER ? "OTHER" : "none",
- pref == ND_RA_FLAG_PREF_HIGH ? "high" : pref == ND_RA_FLAG_PREF_LOW ? "low" : "medium",
- lifetime);
- r = ndisc_ra_parse(nd, ra, (size_t) len);
- if (r < 0) {
- log_ndisc_errno(nd, r, "Could not parse Router Advertisement: %m");
- return 0;
- }
-
- if (nd->router_callback)
- nd->router_callback(nd, stateful, gw, lifetime, pref, nd->userdata);
-
- return 0;
+ return ndisc_handle_datagram(nd, rt);
}
-static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
sd_ndisc *nd = userdata;
usec_t time_now, next_timeout;
int r;
@@ -601,43 +327,34 @@ static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec,
assert(nd);
assert(nd->event);
- nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
-
if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) {
- if (nd->callback)
- nd->callback(nd, SD_NDISC_EVENT_TIMEOUT, nd->userdata);
- nd->state = NDISC_STATE_ADVERTISEMENT_LISTEN;
- } else {
- r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
- if (r < 0) {
- log_ndisc_errno(nd, r, "Error sending Router Solicitation: %m");
- goto fail;
- } else {
- nd->state = NDISC_STATE_SOLICITATION_SENT;
- log_ndisc(nd, "Sent Router Solicitation");
- }
-
- nd->nd_sent++;
+ nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
+ ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
+ return 0;
+ }
- assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
+ r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
+ if (r < 0) {
+ log_ndisc_errno(r, "Error sending Router Solicitation: %m");
+ goto fail;
+ }
- next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
+ log_ndisc("Sent Router Solicitation");
+ nd->nd_sent++;
- r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(),
- next_timeout, 0,
- ndisc_router_solicitation_timeout, nd);
- if (r < 0) {
- log_ndisc_errno(nd, r, "Failed to allocate timer event: %m");
- goto fail;
- }
+ assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
+ next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
- r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
- if (r < 0) {
- log_ndisc_errno(nd, r, "Cannot set timer priority: %m");
- goto fail;
- }
+ r = sd_event_source_set_time(nd->timeout_event_source, next_timeout);
+ if (r < 0) {
+ log_ndisc_errno(r, "Error updating timer: %m");
+ goto fail;
+ }
- (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
+ r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT);
+ if (r < 0) {
+ log_ndisc_errno(r, "Error reenabling timer: %m");
+ goto fail;
}
return 0;
@@ -647,38 +364,36 @@ fail:
return 0;
}
-int sd_ndisc_stop(sd_ndisc *nd) {
+_public_ int sd_ndisc_stop(sd_ndisc *nd) {
assert_return(nd, -EINVAL);
- if (nd->state == NDISC_STATE_IDLE)
+ if (nd->fd < 0)
return 0;
- log_ndisc(nd, "Stopping IPv6 Router Solicitation client");
+ log_ndisc("Stopping IPv6 Router Solicitation client");
ndisc_reset(nd);
- nd->state = NDISC_STATE_IDLE;
-
- if (nd->callback)
- nd->callback(nd, SD_NDISC_EVENT_STOP, nd->userdata);
-
- return 0;
+ return 1;
}
-int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
+_public_ int sd_ndisc_start(sd_ndisc *nd) {
int r;
assert_return(nd, -EINVAL);
assert_return(nd->event, -EINVAL);
assert_return(nd->ifindex > 0, -EINVAL);
- assert_return(nd->state == NDISC_STATE_IDLE, -EBUSY);
- r = icmp6_bind_router_solicitation(nd->ifindex);
- if (r < 0)
- return r;
+ if (nd->fd >= 0)
+ return 0;
- nd->fd = r;
+ assert(!nd->recv_event_source);
+ assert(!nd->timeout_event_source);
- r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_router_advertisement_recv, nd);
+ nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
+ if (nd->fd < 0)
+ return nd->fd;
+
+ r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
if (r < 0)
goto fail;
@@ -688,7 +403,7 @@ int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
(void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
- r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_router_solicitation_timeout, nd);
+ r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_timeout, nd);
if (r < 0)
goto fail;
@@ -698,8 +413,8 @@ int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
(void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
- log_ndisc(ns, "Started IPv6 Router Solicitation client");
- return 0;
+ log_ndisc("Started IPv6 Router Solicitation client");
+ return 1;
fail:
ndisc_reset(nd);
diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c
index 4817c968ac..d9669488be 100644
--- a/src/libsystemd-network/test-ndisc-rs.c
+++ b/src/libsystemd-network/test-ndisc-rs.c
@@ -18,11 +18,15 @@
***/
#include <netinet/icmp6.h>
+#include <arpa/inet.h>
#include "sd-ndisc.h"
+#include "alloc-util.h"
+#include "hexdecoct.h"
#include "icmp6-util.h"
#include "socket-util.h"
+#include "strv.h"
static struct ether_addr mac_addr = {
.ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
@@ -35,6 +39,144 @@ static int test_fd[2];
typedef int (*send_ra_t)(uint8_t flags);
static send_ra_t send_ra_function;
+static void router_dump(sd_ndisc_router *rt) {
+ struct in6_addr addr;
+ char buf[FORMAT_TIMESTAMP_MAX];
+ uint8_t hop_limit;
+ uint64_t t, flags;
+ uint32_t mtu;
+ uint16_t lifetime;
+ unsigned preference;
+ int r;
+
+ assert_se(rt);
+
+ log_info("--");
+ assert_se(sd_ndisc_router_get_address(rt, &addr) == -ENODATA);
+
+ assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
+ log_info("Timestamp: %s", format_timestamp(buf, sizeof(buf), t));
+
+ assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_MONOTONIC, &t) >= 0);
+ log_info("Monotonic: %" PRIu64, t);
+
+ if (sd_ndisc_router_get_hop_limit(rt, &hop_limit) < 0)
+ log_info("No hop limit set");
+ else
+ log_info("Hop limit: %u", hop_limit);
+
+ assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0);
+ log_info("Flags: <%s|%s>",
+ flags & ND_RA_FLAG_OTHER ? "OTHER" : "",
+ flags & ND_RA_FLAG_MANAGED ? "MANAGED" : "");
+
+ assert_se(sd_ndisc_router_get_preference(rt, &preference) >= 0);
+ log_info("Preference: %s",
+ preference == SD_NDISC_PREFERENCE_LOW ? "low" :
+ preference == SD_NDISC_PREFERENCE_HIGH ? "high" : "medium");
+
+ assert_se(sd_ndisc_router_get_lifetime(rt, &lifetime) >= 0);
+ log_info("Lifetime: %" PRIu16, lifetime);
+
+ if (sd_ndisc_router_get_mtu(rt, &mtu) < 0)
+ log_info("No MTU set");
+ else
+ log_info("MTU: %" PRIu32, mtu);
+
+ r = sd_ndisc_router_option_rewind(rt);
+ for (;;) {
+ uint8_t type;
+
+ assert_se(r >= 0);
+
+ if (r == 0)
+ break;
+
+ assert_se(sd_ndisc_router_option_get_type(rt, &type) >= 0);
+
+ log_info(">> Option %u", type);
+
+ switch (type) {
+
+ case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
+ case SD_NDISC_OPTION_TARGET_LL_ADDRESS: {
+ _cleanup_free_ char *c = NULL;
+ const void *p;
+ size_t n;
+
+ assert_se(sd_ndisc_router_option_get_raw(rt, &p, &n) >= 0);
+ assert_se(n > 2);
+ assert_se(c = hexmem((uint8_t*) p + 2, n - 2));
+
+ log_info("Address: %s", c);
+ break;
+ }
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION: {
+ uint32_t lifetime_valid, lifetime_preferred;
+ unsigned prefix_len;
+ uint8_t pfl;
+ struct in6_addr a;
+ char buff[INET6_ADDRSTRLEN];
+
+ assert_se(sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid) >= 0);
+ log_info("Valid Lifetime: %" PRIu32, lifetime_valid);
+
+ assert_se(sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred) >= 0);
+ log_info("Preferred Lifetime: %" PRIu32, lifetime_preferred);
+
+ assert_se(sd_ndisc_router_prefix_get_flags(rt, &pfl) >= 0);
+ log_info("Flags: <%s|%s>",
+ pfl & ND_OPT_PI_FLAG_ONLINK ? "ONLINK" : "",
+ pfl & ND_OPT_PI_FLAG_AUTO ? "AUTO" : "");
+
+ assert_se(sd_ndisc_router_prefix_get_prefixlen(rt, &prefix_len) >= 0);
+ log_info("Prefix Length: %u", prefix_len);
+
+ assert_se(sd_ndisc_router_prefix_get_address(rt, &a) >= 0);
+ log_info("Prefix: %s", inet_ntop(AF_INET6, &a, buff, sizeof(buff)));
+
+ break;
+ }
+
+ case SD_NDISC_OPTION_RDNSS: {
+ const struct in6_addr *a;
+ uint32_t lt;
+ int n, i;
+
+ n = sd_ndisc_router_rdnss_get_addresses(rt, &a);
+ assert_se(n > 0);
+
+ for (i = 0; i < n; i++) {
+ char buff[INET6_ADDRSTRLEN];
+ log_info("DNS: %s", inet_ntop(AF_INET6, a + i, buff, sizeof(buff)));
+ }
+
+ assert_se(sd_ndisc_router_rdnss_get_lifetime(rt, &lt) >= 0);
+ log_info("Lifetime: %" PRIu32, lt);
+ break;
+ }
+
+ case SD_NDISC_OPTION_DNSSL: {
+ _cleanup_strv_free_ char **l = NULL;
+ uint32_t lt;
+ int n, i;
+
+ n = sd_ndisc_router_dnssl_get_domains(rt, &l);
+ assert_se(n > 0);
+
+ for (i = 0; i < n; i++)
+ log_info("Domain: %s", l[i]);
+
+ assert_se(sd_ndisc_router_dnssl_get_lifetime(rt, &lt) >= 0);
+ log_info("Lifetime: %" PRIu32, lt);
+ break;
+ }}
+
+ r = sd_ndisc_router_option_next(rt);
+ }
+}
+
static int test_rs_hangcheck(sd_event_source *s, uint64_t usec,
void *userdata) {
assert_se(false);
@@ -83,32 +225,39 @@ int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
return send_ra_function(0);
}
-static void test_rs_done(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata) {
+static void test_callback(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) {
sd_event *e = userdata;
static unsigned idx = 0;
- uint8_t flags_array[] = {
+ uint64_t flags_array[] = {
0,
0,
0,
ND_RA_FLAG_OTHER,
ND_RA_FLAG_MANAGED
};
+ uint64_t flags;
uint32_t mtu;
assert_se(nd);
+ if (event != SD_NDISC_EVENT_ROUTER)
+ return;
+
+ router_dump(rt);
+
+ assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0);
assert_se(flags == flags_array[idx]);
idx++;
if (verbose)
- printf(" got event 0x%02x\n", flags);
+ printf(" got event 0x%02" PRIx64 "\n", flags);
if (idx < ELEMENTSOF(flags_array)) {
send_ra(flags_array[idx]);
return;
}
- assert_se(sd_ndisc_get_mtu(nd, &mtu) == -ENOMSG);
+ assert_se(sd_ndisc_get_mtu(nd, &mtu) == -ENODATA);
sd_event_exit(e, 0);
}
@@ -132,17 +281,17 @@ static void test_rs(void) {
assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0);
assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0);
- assert_se(sd_ndisc_set_callback(nd, test_rs_done, NULL, NULL, NULL, e) >= 0);
+ assert_se(sd_ndisc_set_callback(nd, test_callback, e) >= 0);
assert_se(sd_event_add_time(e, &test_hangcheck, clock_boottime_or_monotonic(),
time_now + 2 *USEC_PER_SEC, 0,
test_rs_hangcheck, NULL) >= 0);
assert_se(sd_ndisc_stop(nd) >= 0);
- assert_se(sd_ndisc_router_discovery_start(nd) >= 0);
+ assert_se(sd_ndisc_start(nd) >= 0);
assert_se(sd_ndisc_stop(nd) >= 0);
- assert_se(sd_ndisc_router_discovery_start(nd) >= 0);
+ assert_se(sd_ndisc_start(nd) >= 0);
sd_event_loop(e);