Systemd/src/libsystemd-network/icmp6-util.c
Lennart Poettering fb29cdbef2 tree-wide: make sure our control buffers are properly aligned
We always need to make them unions with a "struct cmsghdr" in them, so
that things properly aligned. Otherwise we might end up at an unaligned
address and the counting goes all wrong, possibly making the kernel
refuse our buffers.

Also, let's make sure we initialize the control buffers to zero when
sending, but leave them uninitialized when reading.

Both the alignment and the initialization thing is mentioned in the
cmsg(3) man page.
2020-05-07 14:39:44 +02:00

211 lines
6.5 KiB
C

/* 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/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 ifindex = mreq->ipv6mr_interface;
_cleanup_close_ int s = -1;
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, ifindex);
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;
r = socket_bind_to_ifindex(s, ifindex);
if (r < 0)
return r;
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) {
CMSG_BUFFER_TYPE(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_safe(fd, &msg, MSG_DONTWAIT);
if (len < 0)
return (int) len;
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;
}