diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c index f1cb0bc8a0d..7fbebd6f275 100644 --- a/src/libsystemd-network/icmp6-util.c +++ b/src/libsystemd-network/icmp6-util.c @@ -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; +} diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h index 13c237f5fb2..16b8be8298d 100644 --- a/src/libsystemd-network/icmp6-util.h +++ b/src/libsystemd-network/icmp6-util.h @@ -21,6 +21,8 @@ #include +#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); diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h index 536923d7dfb..b21d4e54cbd 100644 --- a/src/libsystemd-network/radv-internal.h +++ b/src/libsystemd-network/radv-internal.h @@ -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; diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 83e57d43f73..0437e0b0b76 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -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; + 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; - len = recvmsg(fd, &msg, MSG_DONTWAIT); - if (len < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; + case -EMULTIHOP: + log_ndisc("Received RA with invalid hop limit. Ignoring."); + break; - 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; + case -EPFNOSUPPORT: + log_ndisc("Received invalid source address from ICMPv6 socket."); + break; } - 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; + return 0; } - /* 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; - } - } - - 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))); - } - - 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); diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index 1b60af0dd76..f23275a80cd 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -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, ×tamp); + 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; diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index e212b8a9670..b4a65395999 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -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,