diff options
author | Lennart Poettering <lennart@poettering.net> | 2016-06-07 11:13:39 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2016-06-07 11:13:39 +0200 |
commit | a849538e3bf0097595fc1fd86bcda608d1151b4c (patch) | |
tree | 20e5dbc1399719e7e24ac6997adf2428a8cdce51 /src | |
parent | 82e4eda664d40ef60829e27d84b1610c2f4070cd (diff) | |
parent | 1e7a0e21c97ac1bbc743009e5ec8c12bc6200e19 (diff) |
Merge pull request #3394 from poettering/triple-tstamp
timestamping improvements and IPv6 RA revamp
Diffstat (limited to 'src')
31 files changed, 2187 insertions, 666 deletions
diff --git a/src/basic/exit-status.c b/src/basic/exit-status.c index 92fa5ace61..d488cfc59f 100644 --- a/src/basic/exit-status.c +++ b/src/basic/exit-status.c @@ -38,8 +38,7 @@ const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { return "FAILURE"; } - - if (level == EXIT_STATUS_SYSTEMD || level == EXIT_STATUS_LSB) { + if (IN_SET(level, EXIT_STATUS_SYSTEMD, EXIT_STATUS_LSB)) { switch ((int) status) { case EXIT_CHDIR: @@ -189,13 +188,9 @@ bool is_clean_exit(int code, int status, ExitStatusSet *success_status) { /* If a daemon does not implement handlers for some of the * signals that's not considered an unclean shutdown */ if (code == CLD_KILLED) - return - status == SIGHUP || - status == SIGINT || - status == SIGTERM || - status == SIGPIPE || + return IN_SET(status, SIGHUP, SIGINT, SIGTERM, SIGPIPE) || (success_status && - set_contains(success_status->signal, INT_TO_PTR(status))); + set_contains(success_status->signal, INT_TO_PTR(status))); return false; } @@ -207,15 +202,14 @@ bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status) { return code == CLD_EXITED && - (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED); + IN_SET(status, EXIT_NOTINSTALLED, EXIT_NOTCONFIGURED); } void exit_status_set_free(ExitStatusSet *x) { assert(x); - set_free(x->status); - set_free(x->signal); - x->status = x->signal = NULL; + x->status = set_free(x->status); + x->signal = set_free(x->signal); } bool exit_status_set_is_empty(ExitStatusSet *x) { diff --git a/src/basic/exit-status.h b/src/basic/exit-status.h index 1208c8feed..2309f68815 100644 --- a/src/basic/exit-status.h +++ b/src/basic/exit-status.h @@ -25,6 +25,12 @@ #include "macro.h" #include "set.h" +/* This defines pretty names for the LSB 'start' verb exit codes. Note that they shouldn't be confused with the LSB + * 'status' verb exit codes which are defined very differently. For details see: + * + * https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html + */ + typedef enum ExitStatus { /* EXIT_SUCCESS defined by libc */ /* EXIT_FAILURE defined by libc */ @@ -37,9 +43,7 @@ typedef enum ExitStatus { /* The LSB suggests that error codes >= 200 are "reserved". We * use them here under the assumption that they hence are - * unused by init scripts. - * - * http://refspecs.linuxfoundation.org/LSB_3.2.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */ + * unused by init scripts. */ EXIT_CHDIR = 200, EXIT_NICE, @@ -81,9 +85,9 @@ typedef enum ExitStatus { } ExitStatus; typedef enum ExitStatusLevel { - EXIT_STATUS_MINIMAL, - EXIT_STATUS_SYSTEMD, - EXIT_STATUS_LSB, + EXIT_STATUS_MINIMAL, /* only cover libc EXIT_STATUS/EXIT_FAILURE */ + EXIT_STATUS_SYSTEMD, /* cover libc and systemd's own exit codes */ + EXIT_STATUS_LSB, /* cover libc, systemd's own and LSB exit codes */ EXIT_STATUS_FULL = EXIT_STATUS_LSB } ExitStatusLevel; diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index 245107ebb8..1447fa84aa 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -28,18 +28,26 @@ #include "macro.h" #include "util.h" +bool in4_addr_is_null(const struct in_addr *a) { + return a->s_addr == 0; +} + +bool in6_addr_is_null(const struct in6_addr *a) { + return + a->s6_addr32[0] == 0 && + a->s6_addr32[1] == 0 && + a->s6_addr32[2] == 0 && + a->s6_addr32[3] == 0; +} + int in_addr_is_null(int family, const union in_addr_union *u) { assert(u); if (family == AF_INET) - return u->in.s_addr == 0; + return in4_addr_is_null(&u->in); if (family == AF_INET6) - return - u->in6.s6_addr32[0] == 0 && - u->in6.s6_addr32[1] == 0 && - u->in6.s6_addr32[2] == 0 && - u->in6.s6_addr32[3] == 0; + return in6_addr_is_null(&u->in6); return -EAFNOSUPPORT; } diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h index 17798ce816..62cc1e1aa4 100644 --- a/src/basic/in-addr-util.h +++ b/src/basic/in-addr-util.h @@ -36,6 +36,9 @@ struct in_addr_data { union in_addr_union address; }; +bool in4_addr_is_null(const struct in_addr *a); +bool in6_addr_is_null(const struct in6_addr *a); + int in_addr_is_null(int family, const union in_addr_union *u); int in_addr_is_link_local(int family, const union in_addr_union *u); int in_addr_is_localhost(int family, const union in_addr_union *u); diff --git a/src/basic/time-util.c b/src/basic/time-util.c index edd9179cb8..24e681bf85 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -87,6 +87,16 @@ dual_timestamp* dual_timestamp_get(dual_timestamp *ts) { return ts; } +triple_timestamp* triple_timestamp_get(triple_timestamp *ts) { + assert(ts); + + ts->realtime = now(CLOCK_REALTIME); + ts->monotonic = now(CLOCK_MONOTONIC); + ts->boottime = clock_boottime_supported() ? now(CLOCK_BOOTTIME) : USEC_INFINITY; + + return ts; +} + dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { int64_t delta; assert(ts); @@ -104,6 +114,24 @@ dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { return ts; } +triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) { + int64_t delta; + + assert(ts); + + if (u == USEC_INFINITY || u <= 0) { + ts->realtime = ts->monotonic = ts->boottime = u; + return ts; + } + + ts->realtime = u; + delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u; + ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta); + ts->boottime = clock_boottime_supported() ? usec_sub(now(CLOCK_BOOTTIME), delta) : USEC_INFINITY; + + return ts; +} + dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) { int64_t delta; assert(ts); @@ -136,6 +164,26 @@ dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, us return ts; } +usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) { + + switch (clock) { + + case CLOCK_REALTIME: + case CLOCK_REALTIME_ALARM: + return ts->realtime; + + case CLOCK_MONOTONIC: + return ts->monotonic; + + case CLOCK_BOOTTIME: + case CLOCK_BOOTTIME_ALARM: + return ts->boottime; + + default: + return USEC_INFINITY; + } +} + usec_t timespec_load(const struct timespec *ts) { assert(ts); @@ -1107,6 +1155,30 @@ clockid_t clock_boottime_or_monotonic(void) { return CLOCK_MONOTONIC; } +bool clock_supported(clockid_t clock) { + struct timespec ts; + + switch (clock) { + + case CLOCK_MONOTONIC: + case CLOCK_REALTIME: + return true; + + case CLOCK_BOOTTIME: + return clock_boottime_supported(); + + case CLOCK_BOOTTIME_ALARM: + if (!clock_boottime_supported()) + return false; + + /* fall through, after checking the cached value for CLOCK_BOOTTIME. */ + + default: + /* For everything else, check properly */ + return clock_gettime(clock, &ts) >= 0; + } +} + int get_timezone(char **tz) { _cleanup_free_ char *t = NULL; const char *e; diff --git a/src/basic/time-util.h b/src/basic/time-util.h index a5e3f567ec..1b058f0e49 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -39,6 +39,12 @@ typedef struct dual_timestamp { usec_t monotonic; } dual_timestamp; +typedef struct triple_timestamp { + usec_t realtime; + usec_t monotonic; + usec_t boottime; +} triple_timestamp; + #define USEC_INFINITY ((usec_t) -1) #define NSEC_INFINITY ((nsec_t) -1) @@ -69,7 +75,8 @@ typedef struct dual_timestamp { #define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1) -#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) { 0ULL, 0ULL }) +#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) {}) +#define TRIPLE_TIMESTAMP_NULL ((struct triple_timestamp) {}) usec_t now(clockid_t clock); nsec_t now_nsec(clockid_t clock); @@ -79,11 +86,28 @@ dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u); dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u); dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u); +triple_timestamp* triple_timestamp_get(triple_timestamp *ts); +triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u); + +#define DUAL_TIMESTAMP_HAS_CLOCK(clock) \ + IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC) + +#define TRIPLE_TIMESTAMP_HAS_CLOCK(clock) \ + IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) + static inline bool dual_timestamp_is_set(dual_timestamp *ts) { return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) || (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY)); } +static inline bool triple_timestamp_is_set(triple_timestamp *ts) { + return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) || + (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY) || + (ts->boottime > 0 && ts->boottime != USEC_INFINITY)); +} + +usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock); + usec_t timespec_load(const struct timespec *ts) _pure_; struct timespec *timespec_store(struct timespec *ts, usec_t u); @@ -113,6 +137,7 @@ int get_timezones(char ***l); bool timezone_is_valid(const char *name); bool clock_boottime_supported(void); +bool clock_supported(clockid_t clock); clockid_t clock_boottime_or_monotonic(void); #define xstrftime(buf, fmt, tm) \ diff --git a/src/basic/unaligned.h b/src/basic/unaligned.h index 79be645bed..7c847a3ccb 100644 --- a/src/basic/unaligned.h +++ b/src/basic/unaligned.h @@ -109,3 +109,21 @@ static inline void unaligned_write_le64(void *_u, uint64_t a) { unaligned_write_le32(u, (uint32_t) a); unaligned_write_le32(u + 4, (uint32_t) (a >> 32)); } + +#if __BYTE_ORDER == __BIG_ENDIAN +#define unaligned_read_ne16 unaligned_read_be16 +#define unaligned_read_ne32 unaligned_read_be32 +#define unaligned_read_ne64 unaligned_read_be64 + +#define unaligned_write_ne16 unaligned_write_be16 +#define unaligned_write_ne32 unaligned_write_be32 +#define unaligned_write_ne64 unaligned_write_be64 +#else +#define unaligned_read_ne16 unaligned_read_le16 +#define unaligned_read_ne32 unaligned_read_le32 +#define unaligned_read_ne64 unaligned_read_le64 + +#define unaligned_write_ne16 unaligned_write_le16 +#define unaligned_write_ne32 unaligned_write_le32 +#define unaligned_write_ne64 unaligned_write_le64 +#endif 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/lldp-internal.h b/src/libsystemd-network/lldp-internal.h index 7592bc4305..becc162fab 100644 --- a/src/libsystemd-network/lldp-internal.h +++ b/src/libsystemd-network/lldp-internal.h @@ -28,6 +28,8 @@ #include "prioq.h" struct sd_lldp { + unsigned n_ref; + int ifindex; int fd; diff --git a/src/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c index 6a716430e3..88f7e329b0 100644 --- a/src/libsystemd-network/lldp-neighbor.c +++ b/src/libsystemd-network/lldp-neighbor.c @@ -360,9 +360,16 @@ end_marker: void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) { assert(n); - if (n->ttl > 0) - n->until = usec_add(now(clock_boottime_or_monotonic()), n->ttl * USEC_PER_SEC); - else + if (n->ttl > 0) { + usec_t base; + + /* Use the packet's timestamp if there is one known */ + base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic()); + if (base <= 0 || base == USEC_INFINITY) + base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */ + + n->until = usec_add(base, n->ttl * USEC_PER_SEC); + } else n->until = 0; if (n->lldp) @@ -588,11 +595,11 @@ done: return 0; } -_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret) { +_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) { assert_return(n, -EINVAL); - assert_return(ret, -EINVAL); + assert_return(ret_sec, -EINVAL); - *ret = n->ttl; + *ret_sec = n->ttl; return 0; } @@ -651,7 +658,7 @@ _public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint return 0; } -int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) { +_public_ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) { _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; int r; @@ -668,7 +675,7 @@ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t ra return r; *ret = n; - n = 0; + n = NULL; return r; } @@ -679,7 +686,7 @@ _public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) { assert(n->raw_size >= sizeof(struct ether_header)); n->rindex = sizeof(struct ether_header); - return 0; + return n->rindex < n->raw_size; } _public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) { @@ -693,7 +700,7 @@ _public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) { if (n->rindex + 2 > n->raw_size) /* Truncated message */ return -EBADMSG; - length = LLDP_NEIGHBOR_LENGTH(n); + length = LLDP_NEIGHBOR_TLV_LENGTH(n); if (n->rindex + 2 + length > n->raw_size) return -EBADMSG; @@ -711,7 +718,7 @@ _public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) { if (n->rindex + 2 > n->raw_size) return -EBADMSG; - *type = LLDP_NEIGHBOR_TYPE(n); + *type = LLDP_NEIGHBOR_TLV_TYPE(n); return 0; } @@ -743,14 +750,14 @@ _public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], u if (r == 0) return -ENXIO; - length = LLDP_NEIGHBOR_LENGTH(n); + length = LLDP_NEIGHBOR_TLV_LENGTH(n); if (length < 4) return -EBADMSG; if (n->rindex + 2 + length > n->raw_size) return -EBADMSG; - d = LLDP_NEIGHBOR_DATA(n); + d = LLDP_NEIGHBOR_TLV_DATA(n); memcpy(oui, d, 3); *subtype = d[3]; @@ -782,8 +789,7 @@ _public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, if (n->rindex + 2 > n->raw_size) return -EBADMSG; - length = LLDP_NEIGHBOR_LENGTH(n); - + length = LLDP_NEIGHBOR_TLV_LENGTH(n); if (n->rindex + 2 + length > n->raw_size) return -EBADMSG; @@ -792,3 +798,16 @@ _public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, return 0; } + +_public_ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) { + assert_return(n, -EINVAL); + assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP); + assert_return(clock_supported(clock), -EOPNOTSUPP); + assert_return(ret, -EINVAL); + + if (!triple_timestamp_is_set(&n->timestamp)) + return -ENODATA; + + *ret = triple_timestamp_by_clock(&n->timestamp, clock); + return 0; +} diff --git a/src/libsystemd-network/lldp-neighbor.h b/src/libsystemd-network/lldp-neighbor.h index f203bfa604..c1a7606d06 100644 --- a/src/libsystemd-network/lldp-neighbor.h +++ b/src/libsystemd-network/lldp-neighbor.h @@ -43,6 +43,8 @@ struct sd_lldp_neighbor { sd_lldp *lldp; unsigned n_ref; + triple_timestamp timestamp; + usec_t until; unsigned prioq_idx; @@ -81,18 +83,18 @@ static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) { return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor)); } -static inline uint8_t LLDP_NEIGHBOR_TYPE(const sd_lldp_neighbor *n) { +static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) { return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1; } -static inline size_t LLDP_NEIGHBOR_LENGTH(const sd_lldp_neighbor *n) { +static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) { uint8_t *p; p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex; return p[1] + (((size_t) (p[0] & 1)) << 8); } -static inline void* LLDP_NEIGHBOR_DATA(const sd_lldp_neighbor *n) { +static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) { return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2; } 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/network-internal.c b/src/libsystemd-network/network-internal.c index bfaa75880b..ce30b7fc25 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -380,18 +380,21 @@ int deserialize_in_addrs(struct in_addr **ret, const char *string) { return size; } -void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, - size_t size) { +void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size) { unsigned i; assert(f); assert(addresses); assert(size); - for (i = 0; i < size; i++) - fprintf(f, SD_NDISC_ADDRESS_FORMAT_STR"%s", - SD_NDISC_ADDRESS_FORMAT_VAL(addresses[i]), - (i < (size - 1)) ? " ": ""); + for (i = 0; i < size; i++) { + char buffer[INET6_ADDRSTRLEN]; + + fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f); + + if (i < size - 1) + fputc(' ', f); + } } int deserialize_in6_addrs(struct in6_addr **ret, const char *string) { diff --git a/src/libsystemd-network/sd-lldp.c b/src/libsystemd-network/sd-lldp.c index 9d4587c80e..0bd1e66aa0 100644 --- a/src/libsystemd-network/sd-lldp.c +++ b/src/libsystemd-network/sd-lldp.c @@ -43,7 +43,6 @@ static void lldp_flush_neighbors(sd_lldp *lldp) { static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) { assert(lldp); - assert(n); log_lldp("Invoking callback for '%c'.", event); @@ -138,6 +137,7 @@ static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) { if (lldp_neighbor_equal(n, old)) { /* Is this equal, then restart the TTL counter, but don't do anyting else. */ + old->timestamp = n->timestamp; lldp_start_timer(lldp, old); lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old); return 0; @@ -171,7 +171,7 @@ static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) { finish: if (old) - lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n); + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old); return r; } @@ -202,6 +202,7 @@ static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, v _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; ssize_t space, length; sd_lldp *lldp = userdata; + struct timespec ts; assert(fd >= 0); assert(lldp); @@ -215,21 +216,41 @@ static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, v return -ENOMEM; length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT); - if (length < 0) + if (length < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + return log_lldp_errno(errno, "Failed to read LLDP datagram: %m"); + } if ((size_t) length != n->raw_size) { log_lldp("Packet size mismatch."); return -EINVAL; } + /* Try to get the timestamp of this packet if it is known */ + if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0) + triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts)); + else + triple_timestamp_get(&n->timestamp); + return lldp_handle_datagram(lldp, n); } +static void lldp_reset(sd_lldp *lldp) { + assert(lldp); + + lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source); + lldp->io_event_source = sd_event_source_unref(lldp->io_event_source); + lldp->fd = safe_close(lldp->fd); +} + _public_ int sd_lldp_start(sd_lldp *lldp) { int r; assert_return(lldp, -EINVAL); + assert_return(lldp->event, -EINVAL); + assert_return(lldp->ifindex > 0, -EINVAL); if (lldp->fd >= 0) return 0; @@ -240,24 +261,21 @@ _public_ int sd_lldp_start(sd_lldp *lldp) { if (lldp->fd < 0) return lldp->fd; - if (lldp->event) { - r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp); - if (r < 0) - goto fail; + r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp); + if (r < 0) + goto fail; - r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority); - if (r < 0) - goto fail; + r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority); + if (r < 0) + goto fail; - (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io"); - } + (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io"); + log_lldp("Started LLDP client"); return 1; fail: - lldp->io_event_source = sd_event_source_unref(lldp->io_event_source); - lldp->fd = safe_close(lldp->fd); - + lldp_reset(lldp); return r; } @@ -267,10 +285,9 @@ _public_ int sd_lldp_stop(sd_lldp *lldp) { if (lldp->fd < 0) return 0; - lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source); - lldp->io_event_source = sd_event_source_unref(lldp->io_event_source); - lldp->fd = safe_close(lldp->fd); + log_lldp("Stopping LLDP client"); + lldp_reset(lldp); lldp_flush_neighbors(lldp); return 1; @@ -305,6 +322,12 @@ _public_ int sd_lldp_detach_event(sd_lldp *lldp) { return 0; } +_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) { + assert_return(lldp, NULL); + + return lldp->event; +} + _public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) { assert_return(lldp, -EINVAL); @@ -314,39 +337,60 @@ _public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *us return 0; } +_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) { + assert_return(lldp, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); + + lldp->ifindex = ifindex; + return 0; +} + +_public_ sd_lldp* sd_lldp_ref(sd_lldp *lldp) { + + if (!lldp) + return NULL; + + assert(lldp->n_ref > 0); + lldp->n_ref++; + + return lldp; +} + _public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) { if (!lldp) return NULL; + assert(lldp->n_ref > 0); + lldp->n_ref --; + + if (lldp->n_ref > 0) + return NULL; + + lldp_reset(lldp); + sd_lldp_detach_event(lldp); lldp_flush_neighbors(lldp); hashmap_free(lldp->neighbor_by_id); prioq_free(lldp->neighbor_by_expiry); - - sd_event_source_unref(lldp->io_event_source); - sd_event_source_unref(lldp->timer_event_source); - sd_event_unref(lldp->event); - safe_close(lldp->fd); - free(lldp); return NULL; } -_public_ int sd_lldp_new(sd_lldp **ret, int ifindex) { +_public_ int sd_lldp_new(sd_lldp **ret) { _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL; int r; assert_return(ret, -EINVAL); - assert_return(ifindex > 0, -EINVAL); lldp = new0(sd_lldp, 1); if (!lldp) return -ENOMEM; + lldp->n_ref = 1; lldp->fd = -1; - lldp->ifindex = ifindex; lldp->neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX; lldp->capability_mask = (uint16_t) -1; @@ -486,11 +530,10 @@ _public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr * /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so * that our own can be filtered out here. */ - if (!addr) { + if (addr) + lldp->filter_address = *addr; + else zero(lldp->filter_address); - return 0; - } - lldp->filter_address = *addr; return 0; } 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-lldp.c b/src/libsystemd-network/test-lldp.c index 1aae2253c0..6bcd65de0a 100644 --- a/src/libsystemd-network/test-lldp.c +++ b/src/libsystemd-network/test-lldp.c @@ -54,11 +54,11 @@ static void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void *cb_data) { int r; - r = sd_lldp_new(lldp, 42); + r = sd_lldp_new(lldp); if (r < 0) return r; - r = sd_lldp_attach_event(*lldp, e, 0); + r = sd_lldp_set_ifindex(*lldp, 42); if (r < 0) return r; @@ -66,6 +66,10 @@ static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void * if (r < 0) return r; + r = sd_lldp_attach_event(*lldp, e, 0); + if (r < 0) + return r; + r = sd_lldp_start(*lldp); if (r < 0) return r; 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, <) >= 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, <) >= 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); diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 7ba6527f63..f364b54b50 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -216,8 +216,7 @@ struct sd_event { pid_t original_pid; unsigned iteration; - dual_timestamp timestamp; - usec_t timestamp_boottime; + triple_timestamp timestamp; int state; bool exit_requested:1; @@ -1072,16 +1071,16 @@ _public_ int sd_event_add_time( assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); assert_return(!event_pid_changed(e), -ECHILD); - if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) && - !clock_boottime_supported()) + if (!clock_supported(clock)) /* Checks whether the kernel supports the clock */ + return -EOPNOTSUPP; + + type = clock_to_event_source_type(clock); /* checks whether sd-event supports this clock */ + if (type < 0) return -EOPNOTSUPP; if (!callback) callback = time_exit_callback; - type = clock_to_event_source_type(clock); - assert_return(type >= 0, -EOPNOTSUPP); - d = event_get_clock_data(e, type); assert(d); @@ -2530,9 +2529,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) { goto finish; } - dual_timestamp_get(&e->timestamp); - if (clock_boottime_supported()) - e->timestamp_boottime = now(CLOCK_BOOTTIME); + triple_timestamp_get(&e->timestamp); for (i = 0; i < m; i++) { @@ -2573,7 +2570,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) { if (r < 0) goto finish; - r = process_timer(e, e->timestamp_boottime, &e->boottime); + r = process_timer(e, e->timestamp.boottime, &e->boottime); if (r < 0) goto finish; @@ -2585,7 +2582,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) { if (r < 0) goto finish; - r = process_timer(e, e->timestamp_boottime, &e->boottime_alarm); + r = process_timer(e, e->timestamp.boottime, &e->boottime_alarm); if (r < 0) goto finish; @@ -2759,43 +2756,24 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) { assert_return(e, -EINVAL); assert_return(usec, -EINVAL); assert_return(!event_pid_changed(e), -ECHILD); - assert_return(IN_SET(clock, - CLOCK_REALTIME, - CLOCK_REALTIME_ALARM, - CLOCK_MONOTONIC, - CLOCK_BOOTTIME, - CLOCK_BOOTTIME_ALARM), -EOPNOTSUPP); + if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock)) + return -EOPNOTSUPP; + + /* Generate a clean error in case CLOCK_BOOTTIME is not available. Note that don't use clock_supported() here, + * for a reason: there are systems where CLOCK_BOOTTIME is supported, but CLOCK_BOOTTIME_ALARM is not, but for + * the purpose of getting the time this doesn't matter. */ if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) && !clock_boottime_supported()) return -EOPNOTSUPP; - if (!dual_timestamp_is_set(&e->timestamp)) { + if (!triple_timestamp_is_set(&e->timestamp)) { /* Implicitly fall back to now() if we never ran * before and thus have no cached time. */ *usec = now(clock); return 1; } - switch (clock) { - - case CLOCK_REALTIME: - case CLOCK_REALTIME_ALARM: - *usec = e->timestamp.realtime; - break; - - case CLOCK_MONOTONIC: - *usec = e->timestamp.monotonic; - break; - - case CLOCK_BOOTTIME: - case CLOCK_BOOTTIME_ALARM: - *usec = e->timestamp_boottime; - break; - - default: - assert_not_reached("Unknown clock?"); - } - + *usec = triple_timestamp_by_clock(&e->timestamp, clock); return 0; } diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 50721b1c74..15acf56a5f 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -60,10 +60,15 @@ static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m, return 1; } -static int dhcp6_address_change(Link *link, struct in6_addr *ip6_addr, - uint32_t lifetime_preferred, uint32_t lifetime_valid) { - int r; +static int dhcp6_address_change( + Link *link, + struct in6_addr *ip6_addr, + uint32_t lifetime_preferred, + uint32_t lifetime_valid) { + _cleanup_address_free_ Address *addr = NULL; + char buffer[INET6_ADDRSTRLEN]; + int r; r = address_new(&addr); if (r < 0) @@ -79,8 +84,8 @@ static int dhcp6_address_change(Link *link, struct in6_addr *ip6_addr, addr->cinfo.ifa_valid = lifetime_valid; log_link_info(link, - "DHCPv6 address "SD_NDISC_ADDRESS_FORMAT_STR"/%d timeout preferred %d valid %d", - SD_NDISC_ADDRESS_FORMAT_VAL(addr->in_addr.in6), + "DHCPv6 address %s/%d timeout preferred %d valid %d", + inet_ntop(AF_INET6, &addr->in_addr.in6, buffer, sizeof(buffer)), addr->prefixlen, lifetime_preferred, lifetime_valid); r = address_configure(addr, link, dhcp6_address_handler, true); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index ee52b1ce1e..11628339b9 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -28,8 +28,9 @@ #include "fileio.h" #include "netlink-util.h" #include "network-internal.h" -#include "networkd.h" #include "networkd-lldp-tx.h" +#include "networkd-ndisc.h" +#include "networkd.h" #include "set.h" #include "socket-util.h" #include "stdio-util.h" @@ -504,7 +505,10 @@ static void link_free(Link *link) { sd_ipv4ll_unref(link->ipv4ll); sd_dhcp6_client_unref(link->dhcp6_client); - sd_ndisc_unref(link->ndisc_router_discovery); + sd_ndisc_unref(link->ndisc); + + set_free_free(link->ndisc_rdnss); + set_free_free(link->ndisc_dnssl); if (link->manager) hashmap_remove(link->manager->links, INT_TO_PTR(link->ifindex)); @@ -616,8 +620,8 @@ static int link_stop_clients(Link *link) { r = log_link_warning_errno(link, k, "Could not stop DHCPv6 client: %m"); } - if (link->ndisc_router_discovery) { - k = sd_ndisc_stop(link->ndisc_router_discovery); + if (link->ndisc) { + k = sd_ndisc_stop(link->ndisc); if (k < 0) r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m"); } @@ -1453,11 +1457,11 @@ static int link_acquire_ipv6_conf(Link *link) { } if (link_ipv6_accept_ra_enabled(link)) { - assert(link->ndisc_router_discovery); + assert(link->ndisc); log_link_debug(link, "Discovering IPv6 routers"); - r = sd_ndisc_router_discovery_start(link->ndisc_router_discovery); + r = sd_ndisc_start(link->ndisc); if (r < 0 && r != -EBUSY) return log_link_warning_errno(link, r, "Could not start IPv6 Router Discovery: %m"); } @@ -2364,7 +2368,11 @@ static int link_configure(Link *link) { } if (link_lldp_rx_enabled(link)) { - r = sd_lldp_new(&link->lldp, link->ifindex); + r = sd_lldp_new(&link->lldp); + if (r < 0) + return r; + + r = sd_lldp_set_ifindex(link->lldp, link->ifindex); if (r < 0) return r; @@ -3083,6 +3091,22 @@ int link_save(Link *link) { if (space) fputc(' ', f); serialize_in6_addrs(f, in6_addrs, r); + space = true; + } + } + + /* Make sure to flush out old entries before we use the NDISC data */ + ndisc_vacuum(link); + + if (link->network->dhcp_use_dns && link->ndisc_rdnss) { + NDiscRDNSS *dd; + + SET_FOREACH(dd, link->ndisc_rdnss, i) { + if (space) + fputc(' ', f); + + serialize_in6_addrs(f, &dd->address, 1); + space = true; } } @@ -3128,7 +3152,6 @@ int link_save(Link *link) { if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) { if (link->dhcp_lease) (void) sd_dhcp_lease_get_domainname(link->dhcp_lease, &dhcp_domainname); - if (dhcp6_lease) (void) sd_dhcp6_lease_get_domains(dhcp6_lease, &dhcp6_domains); } @@ -3136,22 +3159,34 @@ int link_save(Link *link) { fputs("DOMAINS=", f); fputstrv(f, link->network->search_domains, NULL, &space); - if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES && dhcp_domainname) - fputs_with_space(f, dhcp_domainname, NULL, &space); + if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) { + NDiscDNSSL *dd; + + if (dhcp_domainname) + fputs_with_space(f, dhcp_domainname, NULL, &space); + if (dhcp6_domains) + fputstrv(f, dhcp6_domains, NULL, &space); - if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES && dhcp6_domains) - fputstrv(f, dhcp6_domains, NULL, &space); + SET_FOREACH(dd, link->ndisc_dnssl, i) + fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space); + } fputc('\n', f); fputs("ROUTE_DOMAINS=", f); fputstrv(f, link->network->route_domains, NULL, NULL); - if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE && dhcp_domainname) - fputs_with_space(f, dhcp_domainname, NULL, &space); + if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE) { + NDiscDNSSL *dd; - if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE && dhcp6_domains) - fputstrv(f, dhcp6_domains, NULL, &space); + if (dhcp_domainname) + fputs_with_space(f, dhcp_domainname, NULL, &space); + if (dhcp6_domains) + fputstrv(f, dhcp6_domains, NULL, &space); + + SET_FOREACH(dd, link->ndisc_dnssl, i) + fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space); + } fputc('\n', f); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 5efefd27d6..7db94e79e8 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -98,6 +98,7 @@ typedef struct Link { unsigned dhcp4_messages; bool dhcp4_configured; bool dhcp6_configured; + unsigned ndisc_messages; bool ndisc_configured; @@ -111,7 +112,10 @@ typedef struct Link { sd_dhcp_server *dhcp_server; - sd_ndisc *ndisc_router_discovery; + sd_ndisc *ndisc; + Set *ndisc_rdnss; + Set *ndisc_dnssl; + sd_dhcp6_client *dhcp6_client; bool rtnl_extended_attrs; @@ -161,7 +165,6 @@ int ipv4ll_configure(Link *link); int dhcp4_configure(Link *link); int dhcp6_configure(Link *link); int dhcp6_request_address(Link *link, int ir); -int ndisc_configure(Link *link); const char* link_state_to_string(LinkState s) _const_; LinkState link_state_from_string(const char *s) _pure_; diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index a0d4fa77d8..2a1ba2bac7 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -17,14 +17,15 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <netinet/ether.h> #include <netinet/icmp6.h> -#include <netinet/in.h> -#include <linux/if.h> #include "sd-ndisc.h" #include "networkd.h" +#include "networkd-ndisc.h" + +#define NDISC_DNSSL_MAX 64U +#define NDISC_RDNSS_MAX 64U static int ndisc_netlink_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { _cleanup_link_unref_ Link *link = userdata; @@ -49,19 +50,92 @@ static int ndisc_netlink_handler(sd_netlink *rtnl, sd_netlink_message *m, void * return 1; } -static void ndisc_prefix_autonomous_handler(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen, - unsigned lifetime_preferred, unsigned lifetime_valid, void *userdata) { - _cleanup_address_free_ Address *address = NULL; - Link *link = userdata; +static void ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { + _cleanup_route_free_ Route *route = NULL; + struct in6_addr gateway; + uint16_t lifetime; + unsigned preference; usec_t time_now; int r; - assert(nd); assert(link); - assert(link->network); + assert(rt); - if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + 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; + } + + 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 = route_new(&route); + if (r < 0) { + log_link_error_errno(link, r, "Could not allocate route: %m"); + return; + } + + route->family = AF_INET6; + route->table = RT_TABLE_MAIN; + route->protocol = RTPROT_RA; + route->pref = preference; + route->gw.in6 = gateway; + route->lifetime = time_now + lifetime * USEC_PER_SEC; + + 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_free_ Address *address = NULL; + uint32_t lifetime_valid, lifetime_preferred; + unsigned prefixlen; + int r; + + assert(link); + assert(rt); + + 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; + } r = address_new(&address); if (r < 0) { @@ -69,10 +143,13 @@ static void ndisc_prefix_autonomous_handler(sd_ndisc *nd, const struct in6_addr return; } - assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0); - address->family = AF_INET6; - address->in_addr.in6 = *prefix; + 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 { @@ -102,17 +179,33 @@ static void ndisc_prefix_autonomous_handler(sd_ndisc *nd, const struct in6_addr link->ndisc_messages++; } -static void ndisc_prefix_onlink_handler(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen, unsigned lifetime, void *userdata) { +static void ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { _cleanup_route_free_ Route *route = NULL; - Link *link = userdata; usec_t time_now; + uint32_t lifetime; + unsigned prefixlen; int r; - assert(nd); assert(link); + assert(rt); - if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + 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) { @@ -120,16 +213,19 @@ static void ndisc_prefix_onlink_handler(sd_ndisc *nd, const struct in6_addr *pre return; } - assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0); - route->family = AF_INET6; route->table = RT_TABLE_MAIN; route->protocol = RTPROT_RA; route->flags = RTM_F_PREFIX; - route->dst.in6 = *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"); @@ -140,32 +236,47 @@ static void ndisc_prefix_onlink_handler(sd_ndisc *nd, const struct in6_addr *pre link->ndisc_messages++; } -static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata) { +static void ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { _cleanup_route_free_ Route *route = NULL; - Link *link = userdata; + struct in6_addr gateway; + uint32_t lifetime; + unsigned preference, prefixlen; usec_t time_now; int r; assert(link); - assert(link->network); - assert(link->manager); - assert(link->dhcp6_client); - assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0); - if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + 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; - 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 ? false : true); - 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"); + 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; } - if (!gateway) + 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) { @@ -173,18 +284,23 @@ static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_a return; } - assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0); - route->family = AF_INET6; route->table = RT_TABLE_MAIN; route->protocol = RTPROT_RA; - route->pref = pref; - route->gw.in6 = *gateway; + 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 default route: %m"); + log_link_warning_errno(link, r, "Could not set additional route: %m"); link_enter_failed(link); return; } @@ -192,7 +308,290 @@ static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_a link->ndisc_messages++; } -static void ndisc_handler(sd_ndisc *nd, int event, void *userdata) { +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) { + struct { + NDiscDNSSL header; + char domain[strlen(*i)]; + } s; + NDiscDNSSL *x; + + zero(s.header); + strcpy(s.domain, *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; + } + + x = malloc0(ALIGN(sizeof(NDiscDNSSL)) + strlen(*i) + 1); + if (!x) { + log_oom(); + return; + } + + strcpy(NDISC_DNSSL_DOMAIN(x), *i); + x->valid_until = time_now + lifetime * USEC_PER_SEC; + + r = set_put(link->ndisc_dnssl, x); + if (r < 0) { + free(x); + log_oom(); + return; + } + + 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: + ndisc_router_process_rdnss(link, rt); + break; + + case SD_NDISC_OPTION_DNSSL: + 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); @@ -201,13 +600,16 @@ static void ndisc_handler(sd_ndisc *nd, int event, void *userdata) { 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; - case SD_NDISC_EVENT_STOP: - break; default: log_link_warning(link, "IPv6 Neighbor Discovery unknown event: %d", event); } @@ -216,30 +618,52 @@ static void ndisc_handler(sd_ndisc *nd, int event, void *userdata) { int ndisc_configure(Link *link) { int r; - assert_return(link, -EINVAL); + assert(link); + + r = sd_ndisc_new(&link->ndisc); + if (r < 0) + return r; - r = sd_ndisc_new(&link->ndisc_router_discovery); + r = sd_ndisc_attach_event(link->ndisc, NULL, 0); if (r < 0) return r; - r = sd_ndisc_attach_event(link->ndisc_router_discovery, NULL, 0); + r = sd_ndisc_set_mac(link->ndisc, &link->mac); if (r < 0) return r; - r = sd_ndisc_set_mac(link->ndisc_router_discovery, &link->mac); + r = sd_ndisc_set_ifindex(link->ndisc, link->ifindex); if (r < 0) return r; - r = sd_ndisc_set_ifindex(link->ndisc_router_discovery, link->ifindex); + r = sd_ndisc_set_callback(link->ndisc, ndisc_handler, link); if (r < 0) return r; - r = sd_ndisc_set_callback(link->ndisc_router_discovery, - ndisc_router_handler, - ndisc_prefix_onlink_handler, - ndisc_prefix_autonomous_handler, - ndisc_handler, - link); + 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) { + (void) set_remove(link->ndisc_rdnss, r); + link_dirty(link); + } - return r; + SET_FOREACH(d, link->ndisc_dnssl, i) + if (d->valid_until < time_now) { + (void) set_remove(link->ndisc_dnssl, d); + link_dirty(link); + } } diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h new file mode 100644 index 0000000000..2002f55107 --- /dev/null +++ b/src/network/networkd-ndisc.h @@ -0,0 +1,39 @@ +#pragma once + +/*** + 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 "networkd-link.h" + +typedef struct NDiscRDNSS { + usec_t valid_until; + struct in6_addr address; +} NDiscRDNSS; + +typedef struct NDiscDNSSL { + usec_t valid_until; + /* The domain name follows immediately. */ +} NDiscDNSSL; + +static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) { + return ((char*) n) + ALIGN(sizeof(NDiscDNSSL)); +} + +int ndisc_configure(Link *link); +void ndisc_vacuum(Link *link); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 03e4e3b39f..c722db55c7 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -89,6 +89,8 @@ DHCP.DUIDRawData, config_parse_duid_rawdata, DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric) DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCP.IAID, config_parse_iaid, 0, offsetof(Network, iaid) +IPv6AcceptRouterAdvertisements.UseDNS config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns) +IPv6AcceptRouterAdvertisements.UseDomains config_parse_dhcp_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains) DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec) DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec) DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_dns) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index dd89b3770c..e961db60a7 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -51,8 +51,8 @@ static int network_load_one(Manager *manager, const char *filename) { if (!file) { if (errno == ENOENT) return 0; - else - return -errno; + + return -errno; } if (null_or_empty_fd(fileno(file))) { @@ -134,6 +134,7 @@ static int network_load_one(Manager *manager, const char *filename) { network->ipv6_hop_limit = -1; network->duid.type = _DUID_TYPE_INVALID; network->proxy_arp = -1; + network->ipv6_accept_ra_use_dns = true; r = config_parse(NULL, filename, file, "Match\0" diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 91099161ce..b54616b838 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -154,6 +154,9 @@ struct Network { int ipv6_hop_limit; int proxy_arp; + bool ipv6_accept_ra_use_dns; + DHCPUseDomains ipv6_accept_ra_use_domains; + union in_addr_union ipv6_token; IPv6PrivacyExtensions ipv6_privacy_extensions; diff --git a/src/systemd/sd-lldp.h b/src/systemd/sd-lldp.h index 5772d5794a..3f35eebea3 100644 --- a/src/systemd/sd-lldp.h +++ b/src/systemd/sd-lldp.h @@ -23,6 +23,7 @@ #include <inttypes.h> #include <net/ethernet.h> +#include <sys/types.h> #include "sd-event.h" @@ -30,9 +31,6 @@ _SD_BEGIN_DECLARATIONS; -typedef struct sd_lldp sd_lldp; -typedef struct sd_lldp_neighbor sd_lldp_neighbor; - /* IEEE 802.3AB Clause 9: TLV Types */ enum { SD_LLDP_TYPE_END = 0, @@ -111,6 +109,9 @@ enum { SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION = 7, }; +typedef struct sd_lldp sd_lldp; +typedef struct sd_lldp_neighbor sd_lldp_neighbor; + typedef enum sd_lldp_event { SD_LLDP_EVENT_ADDED = 'a', SD_LLDP_EVENT_REMOVED = 'r', @@ -120,7 +121,8 @@ typedef enum sd_lldp_event { typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata); -int sd_lldp_new(sd_lldp **ret, int ifindex); +int sd_lldp_new(sd_lldp **ret); +sd_lldp* sd_lldp_ref(sd_lldp *lldp); sd_lldp* sd_lldp_unref(sd_lldp *lldp); int sd_lldp_start(sd_lldp *lldp); @@ -128,8 +130,10 @@ int sd_lldp_stop(sd_lldp *lldp); int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority); int sd_lldp_detach_event(sd_lldp *lldp); +sd_event *sd_lldp_get_event(sd_lldp *lldp); int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata); +int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex); /* Controls how much and what to store in the neighbors database */ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t n); @@ -145,6 +149,7 @@ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n); /* Access to LLDP frame metadata */ int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address); int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address); +int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret); int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); /* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */ @@ -152,7 +157,7 @@ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const vo int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret); int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret); -int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret); +int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec); int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret); int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret); int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret); diff --git a/src/systemd/sd-ndisc.h b/src/systemd/sd-ndisc.h index 2b774233b8..9f7d4ef71a 100644 --- a/src/systemd/sd-ndisc.h +++ b/src/systemd/sd-ndisc.h @@ -22,6 +22,8 @@ #include <inttypes.h> #include <net/ethernet.h> +#include <netinet/in.h> +#include <sys/types.h> #include "sd-event.h" @@ -29,55 +31,99 @@ _SD_BEGIN_DECLARATIONS; +/* Neightbor Discovery Options, RFC 4861, Section 4.6 and + * https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-5 */ enum { - SD_NDISC_EVENT_STOP = 0, - SD_NDISC_EVENT_TIMEOUT = 1, + SD_NDISC_OPTION_SOURCE_LL_ADDRESS = 1, + SD_NDISC_OPTION_TARGET_LL_ADDRESS = 2, + SD_NDISC_OPTION_PREFIX_INFORMATION = 3, + SD_NDISC_OPTION_MTU = 5, + SD_NDISC_OPTION_ROUTE_INFORMATION = 24, + SD_NDISC_OPTION_RDNSS = 25, + SD_NDISC_OPTION_FLAGS_EXTENSION = 26, + SD_NDISC_OPTION_DNSSL = 31, + SD_NDISC_OPTION_CAPTIVE_PORTAL = 37, +}; + +/* Route preference, RFC 4191, Section 2.1 */ +enum { + SD_NDISC_PREFERENCE_LOW = 3U, + SD_NDISC_PREFERENCE_MEDIUM = 0U, + SD_NDISC_PREFERENCE_HIGH = 1U, }; typedef struct sd_ndisc sd_ndisc; +typedef struct sd_ndisc_router sd_ndisc_router; -typedef void(*sd_ndisc_router_callback_t)(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata); -typedef void(*sd_ndisc_prefix_onlink_callback_t)(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen, - unsigned lifetime, void *userdata); -typedef void(*sd_ndisc_prefix_autonomous_callback_t)(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen, - unsigned lifetime_prefered, unsigned lifetime_valid, void *userdata); -typedef void(*sd_ndisc_callback_t)(sd_ndisc *nd, int event, void *userdata); - -int sd_ndisc_set_callback(sd_ndisc *nd, - sd_ndisc_router_callback_t rcb, - sd_ndisc_prefix_onlink_callback_t plcb, - sd_ndisc_prefix_autonomous_callback_t pacb, - sd_ndisc_callback_t cb, - void *userdata); -int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index); -int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr); +typedef enum sd_ndisc_event { + SD_NDISC_EVENT_TIMEOUT = 't', + SD_NDISC_EVENT_ROUTER = 'r', +} sd_ndisc_event; -int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority); -int sd_ndisc_detach_event(sd_ndisc *nd); -sd_event *sd_ndisc_get_event(sd_ndisc *nd); +typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata); +int sd_ndisc_new(sd_ndisc **ret); sd_ndisc *sd_ndisc_ref(sd_ndisc *nd); sd_ndisc *sd_ndisc_unref(sd_ndisc *nd); -int sd_ndisc_new(sd_ndisc **ret); - -int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu); +int sd_ndisc_start(sd_ndisc *nd); int sd_ndisc_stop(sd_ndisc *nd); -int sd_ndisc_router_discovery_start(sd_ndisc *nd); -#define SD_NDISC_ADDRESS_FORMAT_STR "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x" +int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority); +int sd_ndisc_detach_event(sd_ndisc *nd); +sd_event *sd_ndisc_get_event(sd_ndisc *nd); + +int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t cb, void *userdata); +int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index); +int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr); -#define SD_NDISC_ADDRESS_FORMAT_VAL(address) \ - be16toh((address).s6_addr16[0]), \ - be16toh((address).s6_addr16[1]), \ - be16toh((address).s6_addr16[2]), \ - be16toh((address).s6_addr16[3]), \ - be16toh((address).s6_addr16[4]), \ - be16toh((address).s6_addr16[5]), \ - be16toh((address).s6_addr16[6]), \ - be16toh((address).s6_addr16[7]) +int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *ret); +int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret); + +int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size); +sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt); +sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt); + +int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); +int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size); + +int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags); +int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret); +int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime); +int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret); + +/* Generic option access */ +int sd_ndisc_router_option_rewind(sd_ndisc_router *rt); +int sd_ndisc_router_option_next(sd_ndisc_router *rt); +int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type); +int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size); + +/* Specific option access: SD_NDISC_OPTION_PREFIX_INFORMATION */ +int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen); + +/* Specific option access: SD_NDISC_OPTION_ROUTE_INFORMATION */ +int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen); +int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret); + +/* Specific option access: SD_NDISC_OPTION_RDNSS */ +int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret); +int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); + +/* Specific option access: SD_NDISC_OPTION_DNSSL */ +int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret); +int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref); _SD_END_DECLARATIONS; diff --git a/src/test/test-unaligned.c b/src/test/test-unaligned.c index b18b3fca0e..4f64398943 100644 --- a/src/test/test-unaligned.c +++ b/src/test/test-unaligned.c @@ -159,7 +159,31 @@ static void test_le(void) { assert_se(memcmp(&scratch[7], &data[7], sizeof(uint64_t)) == 0); } +static void test_ne(void) { + uint16_t x = 4711; + uint32_t y = 123456; + uint64_t z = 9876543210; + + /* Note that we don't bother actually testing alignment issues in this function, after all the _ne() functions + * are just aliases for the _le() or _be() implementations, which we test extensively above. Hence, in this + * function, just ensure that they map to the right version on the local architecture. */ + + assert_se(unaligned_read_ne16(&x) == 4711); + assert_se(unaligned_read_ne32(&y) == 123456); + assert_se(unaligned_read_ne64(&z) == 9876543210); + + unaligned_write_ne16(&x, 1); + unaligned_write_ne32(&y, 2); + unaligned_write_ne64(&z, 3); + + assert_se(x == 1); + assert_se(y == 2); + assert_se(z == 3); +} + int main(int argc, const char *argv[]) { test_be(); test_le(); + test_ne(); + return 0; } |