diff options
Diffstat (limited to 'src/libsystemd-network/icmp6-util.c')
-rw-r--r-- | src/libsystemd-network/icmp6-util.c | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c new file mode 100644 index 0000000..e535b12 --- /dev/null +++ b/src/libsystemd-network/icmp6-util.c @@ -0,0 +1,217 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/*** + Copyright © 2014 Intel Corporation. All rights reserved. +***/ + +#include <errno.h> +#include <netinet/icmp6.h> +#include <netinet/in.h> +#include <netinet/ip6.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> +#include <net/if.h> +#include <linux/if_packet.h> + +#include "fd-util.h" +#include "icmp6-util.h" +#include "in-addr-util.h" +#include "io-util.h" +#include "socket-util.h" + +#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } } + +#define IN6ADDR_ALL_NODES_MULTICAST_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } + +static int icmp6_bind_router_message(const struct icmp6_filter *filter, + const struct ipv6_mreq *mreq) { + int index = mreq->ipv6mr_interface; + _cleanup_close_ int s = -1; + char ifname[IF_NAMESIZE] = ""; + int r; + + s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6); + if (s < 0) + return -errno; + + r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, filter, sizeof(*filter)); + if (r < 0) + return -errno; + + r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, mreq, sizeof(*mreq)); + if (r < 0) + return -errno; + + /* RFC 3315, section 6.7, bullet point 2 may indicate that an + IPV6_PKTINFO socket option also applies for ICMPv6 multicast. + Empirical experiments indicates otherwise and therefore an + IPV6_MULTICAST_IF socket option is used here instead */ + r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, index); + if (r < 0) + return r; + + r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, false); + if (r < 0) + return r; + + r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 255); + if (r < 0) + return r; + + r = setsockopt_int(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, 255); + if (r < 0) + return r; + + r = setsockopt_int(s, SOL_IPV6, IPV6_RECVHOPLIMIT, true); + if (r < 0) + return r; + + r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true); + if (r < 0) + return r; + + if (if_indextoname(index, ifname) == 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)); + if (r < 0) + return -errno; + + return TAKE_FD(s); +} + +int icmp6_bind_router_solicitation(int index) { + struct icmp6_filter filter = {}; + struct ipv6_mreq mreq = { + .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT, + .ipv6mr_interface = index, + }; + + ICMP6_FILTER_SETBLOCKALL(&filter); + ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); + + return icmp6_bind_router_message(&filter, &mreq); +} + +int icmp6_bind_router_advertisement(int index) { + struct icmp6_filter filter = {}; + struct ipv6_mreq mreq = { + .ipv6mr_multiaddr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT, + .ipv6mr_interface = index, + }; + + ICMP6_FILTER_SETBLOCKALL(&filter); + ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); + + return icmp6_bind_router_message(&filter, &mreq); +} + +int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { + struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT, + }; + struct { + struct nd_router_solicit rs; + struct nd_opt_hdr rs_opt; + struct ether_addr rs_opt_mac; + } _packed_ rs = { + .rs.nd_rs_type = ND_ROUTER_SOLICIT, + .rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR, + .rs_opt.nd_opt_len = 1, + }; + struct iovec iov = { + .iov_base = &rs, + .iov_len = sizeof(rs), + }; + struct msghdr msg = { + .msg_name = &dst, + .msg_namelen = sizeof(dst), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + int r; + + assert(s >= 0); + assert(ether_addr); + + rs.rs_opt_mac = *ether_addr; + + r = sendmsg(s, &msg, 0); + if (r < 0) + return -errno; + + 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 = IOVEC_MAKE(buffer, size); + + len = recvmsg(fd, &msg, MSG_DONTWAIT); + if (len < 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; +} |