X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=src%2Flibsystemd-network%2Fsd-ndisc.c;h=b5c6d6e84d47994e9d3f54750d6078f3662d61d2;hb=53e1b683907c2f12330f00feb9630150196f064d;hp=bae6a49fe6f342a8c565d87c41706fb072454a87;hpb=12343facf32d5d4c7945f9768db09fd2a8526cd2;p=thirdparty%2Fsystemd.git diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index bae6a49fe6f..b5c6d6e84d4 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ /*** This file is part of systemd. @@ -19,157 +20,71 @@ #include #include -#include -#include -#include -#include #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 "random-util.h" #include "socket-util.h" #include "string-util.h" +#include "util.h" -#define NDISC_ROUTER_SOLICITATION_INTERVAL 4 * USEC_PER_SEC -#define NDISC_MAX_ROUTER_SOLICITATIONS 3 - -enum NDiscState { - NDISC_STATE_IDLE, - NDISC_STATE_SOLICITATION_SENT, - NDISC_STATE_ADVERTISMENT_LISTEN, - _NDISC_STATE_MAX, - _NDISC_STATE_INVALID = -1, -}; - -#define IP6_MIN_MTU (unsigned)1280 -#define ICMP6_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr)) -#define NDISC_OPT_LEN_UNITS 8 - -#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 - -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; - sd_event *event; - int event_priority; - int index; - struct ether_addr mac_addr; - uint32_t mtu; - LIST_HEAD(NDiscPrefix, prefixes); - int fd; - sd_event_source *recv; - sd_event_source *timeout; - int 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(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "NDisc CLIENT: " fmt, ##__VA_ARGS__) - -static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) { - - if (!prefix) - return NULL; - - assert(prefix->n_ref > 0); - prefix->n_ref--; +#define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS) - if (prefix->n_ref > 0) - return NULL; +static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) { + assert(ndisc); - if (prefix->nd) - LIST_REMOVE(prefixes, prefix->nd->prefixes, prefix); + log_ndisc("Invoking callback for '%c'.", event); - free(prefix); + if (!ndisc->callback) + return; - return NULL; + ndisc->callback(ndisc, event, rt, ndisc->userdata); } -static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) { - NDiscPrefix *prefix; - - assert(ret); - - prefix = new0(NDiscPrefix, 1); - if (!prefix) - return -ENOMEM; +_public_ int sd_ndisc_set_callback( + sd_ndisc *nd, + sd_ndisc_callback_t callback, + void *userdata) { - prefix->n_ref = 1; - LIST_INIT(prefixes, prefix); - prefix->nd = nd; - - *ret = prefix; - return 0; -} - -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(nd); + 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_index(sd_ndisc *nd, int interface_index) { - assert(nd); - assert(interface_index >= -1); - - nd->index = interface_index; +_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) { - assert(nd); +_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, int 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) @@ -185,21 +100,22 @@ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int 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) { - assert(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; @@ -210,18 +126,19 @@ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) { return nd; } -static int ndisc_init(sd_ndisc *nd) { +static int ndisc_reset(sd_ndisc *nd) { assert(nd); - nd->recv = sd_event_source_unref(nd->recv); - nd->fd = asynchronous_close(nd->fd); - nd->timeout = sd_event_source_unref(nd->timeout); + nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); + nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra); + nd->retransmit_time = 0; + 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; @@ -232,482 +149,273 @@ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) { if (nd->n_ref > 0) return NULL; - ndisc_init(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; + return mfree(nd); } -int sd_ndisc_new(sd_ndisc **ret) { +_public_ int sd_ndisc_new(sd_ndisc **ret) { _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; - assert(ret); + assert_return(ret, -EINVAL); nd = new0(sd_ndisc, 1); if (!nd) return -ENOMEM; nd->n_ref = 1; - - nd->index = -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_return(prefix, -EINVAL); - assert_return(addr, -EINVAL); - - 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; +static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) { int r; assert(nd); + assert(rt); - r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now); + r = ndisc_router_parse(rt); + if (r == -EBADMSG) /* Bad packet */ + return 0; if (r < 0) - return r; + return 0; - LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) { - if (prefix->valid_until < time_now) { - prefix = ndisc_prefix_unref(prefix); - continue; - } + /* 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 (prefix_match(&prefix->addr, prefix->len, addr, addr_len) >= 0) { - *result = prefix; - return 0; - } - } + 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); - return -EADDRNOTAVAIL; + ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt); + return 0; } -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_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; + ssize_t buflen; int r; + _cleanup_free_ char *addr = NULL; + assert(s); assert(nd); - assert(prefix_opt); - - if (len < prefix_opt->nd_opt_pi_len) - return -ENOMSG; - - if (!(prefix_opt->nd_opt_pi_flags_reserved & (ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO))) - return 0; - - if (in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &prefix_opt->nd_opt_pi_prefix) > 0) - return 0; + assert(nd->event); - lifetime_valid = be32toh(prefix_opt->nd_opt_pi_valid_time); - lifetime_preferred = be32toh(prefix_opt->nd_opt_pi_preferred_time); + buflen = next_datagram_size_fd(fd); + if (buflen < 0) + return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m"); - if (lifetime_valid < lifetime_preferred) - return 0; + rt = ndisc_router_new(buflen); + if (!rt) + return -ENOMEM; - r = ndisc_prefix_match(nd, &prefix_opt->nd_opt_pi_prefix, - prefix_opt->nd_opt_pi_prefix_len, &prefix); + r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, + &rt->timestamp); if (r < 0) { - if (r != -EADDRNOTAVAIL) - return r; - - /* if router advertisment 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, ssize_t len) { - void *opt; - struct nd_opt_hdr *opt_hdr; - - assert_return(nd, -EINVAL); - assert_return(ra, -EINVAL); - - len -= sizeof(*ra); - if (len < NDISC_OPT_LEN_UNITS) { - log_ndisc(nd, "Router Advertisement below minimum length"); - - return -ENOMSG; - } - - 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; - uint32_t mtu; - struct nd_opt_prefix_info *opt_prefix; - - if (opt_hdr->nd_opt_len == 0) - return -ENOMSG; - - 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); - } - + switch (r) { + case -EADDRNOTAVAIL: + (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &rt->address, &addr); + log_ndisc("Received RA from non-link-local address %s. Ignoring", addr); break; - case ND_OPT_PREFIX_INFORMATION: - opt_prefix = opt; - - ndisc_prefix_update(nd, len, opt_prefix); + case -EMULTIHOP: + log_ndisc("Received RA with invalid hop limit. Ignoring."); + break; + case -EPFNOSUPPORT: + log_ndisc("Received invalid source address from ICMPv6 socket."); break; } - len -= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS; - opt = (void *)((char *)opt + - opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS); - opt_hdr = opt; + return 0; } - if (len > 0) - log_ndisc(nd, "Router Advertisement contains %zd bytes of trailing garbage", len); + nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); - return 0; + return ndisc_handle_datagram(nd, rt); } -static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_free_ struct nd_router_advert *ra = NULL; +static usec_t ndisc_timeout_compute_random(usec_t val) { + /* compute a time that is random within ±10% of the given value */ + return val - val / 10 + + (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC; +} + +static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { sd_ndisc *nd = userdata; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_LEN(sizeof(int))]; - } control = {}; - struct iovec iov = {}; - union sockaddr_union sa = {}; - struct msghdr msg = { - .msg_name = &sa.sa, - .msg_namelen = sizeof(sa), - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - struct cmsghdr *cmsg; - struct in6_addr *gw; - unsigned lifetime; - ssize_t len, buflen; - int r, pref, stateful; + usec_t time_now; + int r; + char time_string[FORMAT_TIMESPAN_MAX]; assert(s); assert(nd); assert(nd->event); - buflen = next_datagram_size_fd(fd); - if (buflen < 0) - return buflen; - - iov.iov_len = buflen; - - ra = malloc(iov.iov_len); - if (!ra) - return -ENOMEM; - - iov.iov_base = ra; - - len = recvmsg(fd, &msg, 0); - if (len < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - log_ndisc(nd, "Could not receive message from ICMPv6 socket: %m"); - return -errno; - } else if ((size_t)len < sizeof(struct nd_router_advert)) { - return 0; - } else 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; + assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0); - assert(!(msg.msg_flags & MSG_CTRUNC)); - assert(!(msg.msg_flags & MSG_TRUNC)); + nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); - CMSG_FOREACH(cmsg, &msg) { - if (cmsg->cmsg_level == SOL_IPV6 && - cmsg->cmsg_type == IPV6_HOPLIMIT && - cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { - int hops = *(int*)CMSG_DATA(cmsg); - - if (hops != 255) { - log_ndisc(nd, "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 (!nd->retransmit_time) + nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL); + else { + if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2) + nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL); + else + nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time); } - if (ra->nd_ra_type != ND_ROUTER_ADVERT) - return 0; - - if (ra->nd_ra_code != 0) - return 0; - - nd->timeout = sd_event_source_unref(nd->timeout); + r = sd_event_add_time(nd->event, &nd->timeout_event_source, + clock_boottime_or_monotonic(), + time_now + nd->retransmit_time, + 10 * USEC_PER_MSEC, ndisc_timeout, nd); + if (r < 0) + goto fail; - nd->state = NDISC_STATE_ADVERTISMENT_LISTEN; + r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority); + if (r < 0) + goto fail; - 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; + (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout-no-ra"); - switch (pref) { - case ND_RA_FLAG_PREF_LOW: - case ND_RA_FLAG_PREF_HIGH: - break; - default: - pref = ND_RA_FLAG_PREF_MEDIUM; - break; + 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; } - 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, len); + r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); if (r < 0) { - log_ndisc(nd, "Could not parse Router Advertisement: %s", strerror(-r)); - return 0; + log_ndisc_errno(r, "Error sending Router Solicitation: %m"); + goto fail; } - if (nd->router_callback) - nd->router_callback(nd, stateful, gw, lifetime, pref, nd->userdata); + log_ndisc("Sent Router Solicitation, next solicitation in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, + nd->retransmit_time, USEC_PER_SEC)); return 0; + +fail: + sd_ndisc_stop(nd); + return 0; } -static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) { +static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) { sd_ndisc *nd = userdata; - uint64_t time_now, next_timeout; - int r; assert(s); assert(nd); - assert(nd->event); - nd->timeout = sd_event_source_unref(nd->timeout); + log_ndisc("No RA received before link confirmation timeout"); - if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) { - if (nd->callback) - nd->callback(nd, SD_NDISC_EVENT_TIMEOUT, nd->userdata); - nd->state = NDISC_STATE_ADVERTISMENT_LISTEN; - } else { - r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); - if (r < 0) - log_ndisc(nd, "Error sending Router Solicitation"); - else { - nd->state = NDISC_STATE_SOLICITATION_SENT; - log_ndisc(nd, "Sent Router Solicitation"); - } - - nd->nd_sent++; - - 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_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(), - next_timeout, 0, - ndisc_router_solicitation_timeout, nd); - if (r < 0) { - /* we cannot continue if we are unable to rearm the timer */ - sd_ndisc_stop(nd); - return 0; - } - - r = sd_event_source_set_priority(nd->timeout, nd->event_priority); - if (r < 0) - return 0; - - r = sd_event_source_set_description(nd->timeout, "ndisc-timeout"); - if (r < 0) - return 0; - } + nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra); + ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL); return 0; } -int sd_ndisc_stop(sd_ndisc *nd) { +_public_ int sd_ndisc_stop(sd_ndisc *nd) { assert_return(nd, -EINVAL); - assert_return(nd->event, -EINVAL); - - log_ndisc(client, "Stop NDisc"); - ndisc_init(nd); - - nd->state = NDISC_STATE_IDLE; + if (nd->fd < 0) + return 0; - if (nd->callback) - nd->callback(nd, SD_NDISC_EVENT_STOP, nd->userdata); + log_ndisc("Stopping IPv6 Router Solicitation client"); - return 0; + ndisc_reset(nd); + return 1; } -int sd_ndisc_router_discovery_start(sd_ndisc *nd) { +_public_ int sd_ndisc_start(sd_ndisc *nd) { int r; + usec_t time_now; - assert(nd); - assert(nd->event); + assert_return(nd, -EINVAL); + assert_return(nd->event, -EINVAL); + assert_return(nd->ifindex > 0, -EINVAL); - if (nd->state != NDISC_STATE_IDLE) - return -EBUSY; + if (nd->fd >= 0) + return 0; - if (nd->index < 1) - return -EINVAL; + assert(!nd->recv_event_source); + assert(!nd->timeout_event_source); - r = icmp6_bind_router_solicitation(nd->index); + r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now); if (r < 0) - return r; + goto fail; - nd->fd = r; + nd->fd = icmp6_bind_router_solicitation(nd->ifindex); + if (nd->fd < 0) + return nd->fd; - r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN, - ndisc_router_advertisment_recv, nd); + r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd); if (r < 0) - goto error; + goto fail; - r = sd_event_source_set_priority(nd->recv, nd->event_priority); + r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority); if (r < 0) - goto error; + goto fail; - r = sd_event_source_set_description(nd->recv, "ndisc-receive-message"); + (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_timeout, nd); if (r < 0) - goto error; + goto fail; - r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(), - 0, 0, ndisc_router_solicitation_timeout, nd); + r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority); if (r < 0) - goto error; + goto fail; + + (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout"); - r = sd_event_source_set_priority(nd->timeout, nd->event_priority); + r = sd_event_add_time(nd->event, &nd->timeout_no_ra, + clock_boottime_or_monotonic(), + time_now + NDISC_TIMEOUT_NO_RA_USEC, + 10 * USEC_PER_MSEC, ndisc_timeout_no_ra, nd); if (r < 0) - goto error; + goto fail; - r = sd_event_source_set_description(nd->timeout, "ndisc-timeout"); -error: + r = sd_event_source_set_priority(nd->timeout_no_ra, nd->event_priority); if (r < 0) - ndisc_init(nd); - else - log_ndisc(client, "Start Router Solicitation"); + goto fail; + + (void) sd_event_source_set_description(nd->timeout_no_ra, "ndisc-timeout-no-ra"); + + log_ndisc("Started IPv6 Router Solicitation client"); + return 1; +fail: + ndisc_reset(nd); return r; }