]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/libsystemd-network/sd-ndisc.c
tree-wide: use mfree more
[thirdparty/systemd.git] / src / libsystemd-network / sd-ndisc.c
index bae6a49fe6f342a8c565d87c41706fb072454a87..1d3be9b86239ca61227068a8b62e932175142d78 100644 (file)
 
 #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      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_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
+#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
 
-        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 +99,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 +125,17 @@ 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->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,251 +146,85 @@ 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;
-        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);
-
-        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;
+        assert(rt);
 
-        if (in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &prefix_opt->nd_opt_pi_prefix) > 0)
+        r = ndisc_router_parse(rt);
+        if (r == -EBADMSG) /* Bad packet */
                 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 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);
-                        }
-
-                        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 *)((char *)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_advertisment_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 = {};
@@ -489,10 +237,7 @@ static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t r
                 .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);
@@ -500,32 +245,47 @@ static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t r
 
         buflen = next_datagram_size_fd(fd);
         if (buflen < 0)
-                return buflen;
+                return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m");
 
-        iov.iov_len = buflen;
-
-        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;
 
-                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;
+                return log_ndisc_errno(errno, "Could not receive message from ICMPv6 socket: %m");
+        }
+
+        if ((size_t) len != rt->raw_size) {
+                log_ndisc("Packet size mismatch.");
+                return -EINVAL;
+        }
+
+        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));
@@ -534,180 +294,127 @@ static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t r
                 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 (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);
-
-        nd->state = NDISC_STATE_ADVERTISMENT_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;
 
-        switch (pref) {
-        case ND_RA_FLAG_PREF_LOW:
-        case ND_RA_FLAG_PREF_HIGH:
-                break;
-        default:
-                pref = ND_RA_FLAG_PREF_MEDIUM;
-                break;
+                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((struct timeval*) CMSG_DATA(cmsg)));
         }
 
-        lifetime = be16toh(ra->nd_ra_router_lifetime);
+        if (!triple_timestamp_is_set(&rt->timestamp))
+                triple_timestamp_get(&rt->timestamp);
 
-        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);
+        nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
 
-        r = ndisc_ra_parse(nd, ra, len);
-        if (r < 0) {
-                log_ndisc(nd, "Could not parse Router Advertisement: %s", strerror(-r));
-                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;
-        uint64_t time_now, next_timeout;
+        usec_t time_now, next_timeout;
         int r;
 
         assert(s);
         assert(nd);
         assert(nd->event);
 
-        nd->timeout = sd_event_source_unref(nd->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++;
+                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, 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;
-                }
+        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, nd->event_priority);
-                if (r < 0)
-                        return 0;
+        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;
+        }
 
-                r = sd_event_source_set_description(nd->timeout, "ndisc-timeout");
-                if (r < 0)
-                        return 0;
+        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;
+
+fail:
+        sd_ndisc_stop(nd);
+        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;
 
-        assert(nd);
-        assert(nd->event);
-
-        if (nd->state != NDISC_STATE_IDLE)
-                return -EBUSY;
+        assert_return(nd, -EINVAL);
+        assert_return(nd->event, -EINVAL);
+        assert_return(nd->ifindex > 0, -EINVAL);
 
-        if (nd->index < 1)
-                return -EINVAL;
+        if (nd->fd >= 0)
+                return 0;
 
-        r = icmp6_bind_router_solicitation(nd->index);
-        if (r < 0)
-                return r;
+        assert(!nd->recv_event_source);
+        assert(!nd->timeout_event_source);
 
-        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");
-        if (r < 0)
-                goto error;
+        (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
 
-        r = sd_event_add_time(nd->event, &nd->timeout, 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 error;
+                goto fail;
 
-        r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
+        r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
         if (r < 0)
-                goto error;
+                goto fail;
 
-        r = sd_event_source_set_description(nd->timeout, "ndisc-timeout");
-error:
-        if (r < 0)
-                ndisc_init(nd);
-        else
-                log_ndisc(client, "Start Router Solicitation");
+        (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
+
+        log_ndisc("Started IPv6 Router Solicitation client");
+        return 1;
 
+fail:
+        ndisc_reset(nd);
         return r;
 }