]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-radv: Receive Router Solicitations
authorPatrik Flykt <patrik.flykt@linux.intel.com>
Fri, 12 May 2017 13:48:38 +0000 (16:48 +0300)
committerPatrik Flykt <patrik.flykt@linux.intel.com>
Mon, 15 May 2017 11:49:50 +0000 (14:49 +0300)
Receive Router Solicitations and send a unicast Router Advertisment
in response. Refactor ICMPv6 packet handling code so that the common
ICMPv6 validation parts are reused between the existing router
discovery and the new functionality adding reception of Router
Solicitation messages.

src/libsystemd-network/icmp6-util.c
src/libsystemd-network/icmp6-util.h
src/libsystemd-network/radv-internal.h
src/libsystemd-network/sd-ndisc.c
src/libsystemd-network/sd-radv.c
src/libsystemd-network/test-ndisc-rs.c

index f1cb0bc8a0d34af7afbf8612a4a378f098d54b02..7fbebd6f2757385f044ae2581f0843eefb1932af 100644 (file)
@@ -32,6 +32,7 @@
 #include "fd-util.h"
 #include "icmp6-util.h"
 #include "socket-util.h"
+#include "in-addr-util.h"
 
 #define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \
         { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
@@ -164,3 +165,74 @@ int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
 
         return 0;
 }
+
+int icmp6_receive(int fd, void *buffer, size_t size, struct in6_addr *dst,
+                  triple_timestamp *timestamp) {
+        union {
+                struct cmsghdr cmsghdr;
+                uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */
+                            CMSG_SPACE(sizeof(struct timeval))];
+        } 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;
+        ssize_t len;
+
+        iov.iov_base = buffer;
+        iov.iov_len = size;
+
+        len = recvmsg(fd, &msg, MSG_DONTWAIT);
+        if (len < 0) {
+                if (errno == EAGAIN || errno == EINTR)
+                        return 0;
+
+                return -errno;
+        }
+
+        if ((size_t) len != size)
+                return -EINVAL;
+
+        if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
+            sa.in6.sin6_family == AF_INET6)  {
+
+                *dst = sa.in6.sin6_addr;
+                if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) dst) <= 0)
+                        return -EADDRNOTAVAIL;
+
+        } else if (msg.msg_namelen > 0)
+                return -EPFNOSUPPORT;
+
+        /* namelen == 0 only happens when running the test-suite over a socketpair */
+
+        assert(!(msg.msg_flags & MSG_CTRUNC));
+        assert(!(msg.msg_flags & MSG_TRUNC));
+
+        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)
+                                return -EMULTIHOP;
+                }
+
+                if (cmsg->cmsg_level == SOL_SOCKET &&
+                    cmsg->cmsg_type == SO_TIMESTAMP &&
+                    cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
+                        triple_timestamp_from_realtime(timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
+        }
+
+        if (!triple_timestamp_is_set(timestamp))
+                triple_timestamp_get(timestamp);
+
+        return 0;
+}
index 13c237f5fb22eb4c28fc75da5ebe286e5e83105d..16b8be8298d655fd28fde9caba8cf088874dbdcb 100644 (file)
@@ -21,6 +21,8 @@
 
 #include <net/ethernet.h>
 
+#include "time-util.h"
+
 #define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \
         { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } }
@@ -32,3 +34,5 @@
 int icmp6_bind_router_solicitation(int index);
 int icmp6_bind_router_advertisement(int index);
 int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr);
+int icmp6_receive(int fd, void *buffer, size_t size, struct in6_addr *dst,
+                  triple_timestamp *timestamp);
index 536923d7dfb9ccd540aed82400ae64a615b34f39..b21d4e54cbd798ebb76eeb31219d6276936a342e 100644 (file)
@@ -58,6 +58,7 @@ struct sd_radv {
 
         int fd;
         unsigned ra_sent;
+        sd_event_source *recv_event_source;
         sd_event_source *timeout_event_source;
 
         unsigned n_prefixes;
index 83e57d43f734b390aee1d54863452985916901f6..0437e0b0b7644f0a67ebb35a2af2248f0ec4bd11 100644 (file)
@@ -222,23 +222,9 @@ static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
 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_SPACE(sizeof(int)) + /* ttl */
-                            CMSG_SPACE(sizeof(struct timeval))];
-        } 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;
-        ssize_t len, buflen;
+        ssize_t buflen;
+        int r;
+        _cleanup_free_ char *addr = NULL;
 
         assert(s);
         assert(nd);
@@ -252,66 +238,27 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda
         if (!rt)
                 return -ENOMEM;
 
-        iov.iov_base = NDISC_ROUTER_RAW(rt);
-        iov.iov_len = rt->raw_size;
-
-        len = recvmsg(fd, &msg, MSG_DONTWAIT);
-        if (len < 0) {
-                if (errno == EAGAIN || errno == EINTR)
-                        return 0;
-
-                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));
-
-        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("Received RA with invalid hop limit %d. Ignoring.", hops);
-                                return 0;
-                        }
+        r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address,
+                     &rt->timestamp);
+        if (r < 0) {
+                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 -EMULTIHOP:
+                        log_ndisc("Received RA with invalid hop limit. Ignoring.");
+                        break;
+
+                case -EPFNOSUPPORT:
+                        log_ndisc("Received invalid source address from ICMPv6 socket.");
+                        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)));
+                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);
 
         return ndisc_handle_datagram(nd, rt);
index 1b60af0dd7607fc2ae1862699d77e20c0cf895a4..f23275a80cdd5353d93017e844f7b97bb23f7bb7 100644 (file)
@@ -93,6 +93,9 @@ static void radv_reset(sd_radv *ra) {
         ra->timeout_event_source =
                 sd_event_source_unref(ra->timeout_event_source);
 
+        ra->recv_event_source =
+                sd_event_source_unref(ra->recv_event_source);
+
         ra->ra_sent = 0;
 }
 
@@ -160,8 +163,9 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst,
                 .msg_iov = iov,
         };
 
-        if (dst)
+        if (dst && !in_addr_is_null(AF_INET6, (union in_addr_union*) dst))
                 dst_addr.sin6_addr = *dst;
+
         adv.nd_ra_type = ND_ROUTER_ADVERT;
         adv.nd_ra_curhoplimit = ra->hop_limit;
         adv.nd_ra_flags_reserved = ra->flags;
@@ -198,6 +202,63 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst,
         return 0;
 }
 
+static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        sd_radv *ra = userdata;
+        _cleanup_free_ char *addr = NULL;
+        struct in6_addr src;
+        triple_timestamp timestamp;
+        int r;
+        ssize_t buflen;
+        _cleanup_free_ char *buf = NULL;
+
+        assert(s);
+        assert(ra);
+        assert(ra->event);
+
+        buflen = next_datagram_size_fd(fd);
+
+        if ((unsigned) buflen < sizeof(struct nd_router_solicit))
+                return log_radv("Too short packet received");
+
+        buf = new0(char, buflen);
+        if (!buf)
+                return 0;
+
+        r = icmp6_receive(fd, buf, buflen, &src, &timestamp);
+        if (r < 0) {
+                switch (r) {
+                case -EADDRNOTAVAIL:
+                        (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &src, &addr);
+                        log_radv("Received RS from non-link-local address %s. Ignoring", addr);
+                        break;
+
+                case -EMULTIHOP:
+                        log_radv("Received RS with invalid hop limit. Ignoring.");
+                        break;
+
+                case -EPFNOSUPPORT:
+                        log_radv("Received invalid source address from ICMPv6 socket. Ignoring.");
+                        break;
+
+                default:
+                        log_radv_warning_errno(r, "Error receiving from ICMPv6 socket: %m");
+                        break;
+                }
+
+                return 0;
+        }
+
+        (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &src, &addr);
+
+        r = radv_send(ra, &src, ra->lifetime);
+        if (r < 0)
+                log_radv_warning_errno(r, "Unable to send solicited Router Advertisment to %s: %m", addr);
+        else
+                log_radv("Sent solicited Router Advertisement to %s", addr);
+
+        return 0;
+}
+
 static usec_t radv_compute_timeout(usec_t min, usec_t max) {
         assert_return(min <= max, SD_RADV_DEFAULT_MIN_TIMEOUT_USEC);
 
@@ -313,7 +374,16 @@ _public_ int sd_radv_start(sd_radv *ra) {
                 goto fail;
 
         ra->fd = r;
-        r = 0;
+
+        r = sd_event_add_io(ra->event, &ra->recv_event_source, ra->fd, EPOLLIN, radv_recv, ra);
+        if (r < 0)
+                goto fail;
+
+        r = sd_event_source_set_priority(ra->recv_event_source, ra->event_priority);
+        if (r < 0)
+                goto fail;
+
+        (void) sd_event_source_set_description(ra->recv_event_source, "radv-receive-message");
 
         ra->state = SD_RADV_STATE_ADVERTISING;
 
index e212b8a9670dd8b6fc2b19cc6c4b83cdb3fc57e6..b4a65395999319a8af573459518c50ca34630f68 100644 (file)
@@ -198,6 +198,16 @@ int icmp6_bind_router_advertisement(int index) {
         return -ENOSYS;
 }
 
+int icmp6_receive(int fd, void *iov_base, size_t iov_len,
+                  struct in6_addr *dst, triple_timestamp *timestamp) {
+        assert (read (fd, iov_base, iov_len) == (ssize_t)iov_len);
+
+        if (timestamp)
+                triple_timestamp_get(timestamp);
+
+        return 0;
+}
+
 static int send_ra(uint8_t flags) {
         uint8_t advertisement[] = {
                 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4,