]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/icmp6-util.c
d4b7ae58558b5b545984c9fa9582733cca7eefc8
[thirdparty/systemd.git] / src / libsystemd-network / icmp6-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /***
3 Copyright © 2014 Intel Corporation. All rights reserved.
4 ***/
5
6 /* Make sure the net/if.h header is included before any linux/ one */
7 #include <net/if.h>
8 #include <errno.h>
9 #include <linux/if_packet.h>
10 #include <netinet/icmp6.h>
11 #include <netinet/in.h>
12 #include <netinet/ip6.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <sys/types.h>
16 #include <unistd.h>
17
18 #include "fd-util.h"
19 #include "icmp6-util.h"
20 #include "in-addr-util.h"
21 #include "iovec-util.h"
22 #include "network-common.h"
23 #include "socket-util.h"
24
25 int icmp6_bind(int ifindex, bool is_router) {
26 struct icmp6_filter filter = {};
27 struct ipv6_mreq mreq;
28 _cleanup_close_ int s = -EBADF;
29 int r;
30
31 assert(ifindex > 0);
32
33 ICMP6_FILTER_SETBLOCKALL(&filter);
34 if (is_router) {
35 mreq = (struct ipv6_mreq) {
36 .ipv6mr_multiaddr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
37 .ipv6mr_interface = ifindex,
38 };
39 ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
40 } else {
41 mreq = (struct ipv6_mreq) {
42 .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT,
43 .ipv6mr_interface = ifindex,
44 };
45 ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
46 ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter);
47 ICMP6_FILTER_SETPASS(ND_REDIRECT, &filter);
48 }
49
50 s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6);
51 if (s < 0)
52 return -errno;
53
54 if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) < 0)
55 return -errno;
56
57 if (setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
58 return -errno;
59
60 /* RFC 3315, section 6.7, bullet point 2 may indicate that an IPV6_PKTINFO socket option also applies
61 * for ICMPv6 multicast. Empirical experiments indicates otherwise and therefore an IPV6_MULTICAST_IF
62 * socket option is used here instead. */
63 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, ifindex);
64 if (r < 0)
65 return r;
66
67 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, false);
68 if (r < 0)
69 return r;
70
71 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 255);
72 if (r < 0)
73 return r;
74
75 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, 255);
76 if (r < 0)
77 return r;
78
79 r = setsockopt_int(s, SOL_IPV6, IPV6_RECVHOPLIMIT, true);
80 if (r < 0)
81 return r;
82
83 r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true);
84 if (r < 0)
85 return r;
86
87 r = socket_bind_to_ifindex(s, ifindex);
88 if (r < 0)
89 return r;
90
91 return TAKE_FD(s);
92 }
93
94 int icmp6_send(int fd, const struct sockaddr_in6 *dst, const struct iovec *iov, size_t n_iov) {
95 struct msghdr msg = {
96 .msg_name = (struct sockaddr_in6*) dst,
97 .msg_namelen = sizeof(struct sockaddr_in6),
98 .msg_iov = (struct iovec*) iov,
99 .msg_iovlen = n_iov,
100 };
101
102 if (sendmsg(fd, &msg, 0) < 0)
103 return -errno;
104
105 return 0;
106 }
107
108 int icmp6_receive(
109 int fd,
110 void *buffer,
111 size_t size,
112 struct in6_addr *ret_sender,
113 triple_timestamp *ret_timestamp) {
114
115 /* This needs to be initialized with zero. See #20741. */
116 CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int)) + /* ttl */
117 CMSG_SPACE_TIMEVAL) control = {};
118 struct iovec iov = {};
119 union sockaddr_union sa = {};
120 struct msghdr msg = {
121 .msg_name = &sa.sa,
122 .msg_namelen = sizeof(sa),
123 .msg_iov = &iov,
124 .msg_iovlen = 1,
125 .msg_control = &control,
126 .msg_controllen = sizeof(control),
127 };
128 ssize_t len;
129
130 iov = IOVEC_MAKE(buffer, size);
131
132 len = recvmsg_safe(fd, &msg, MSG_DONTWAIT);
133 if (len < 0)
134 return (int) len;
135
136 if ((size_t) len != size)
137 return -EINVAL;
138
139 if (msg.msg_namelen != sizeof(struct sockaddr_in6) || sa.in6.sin6_family != AF_INET6)
140 return -EPFNOSUPPORT;
141
142 if (!in6_addr_is_link_local(&sa.in6.sin6_addr) && !in6_addr_is_null(&sa.in6.sin6_addr))
143 return -EADDRNOTAVAIL;
144
145 assert(!(msg.msg_flags & MSG_TRUNC));
146
147 int *hops = CMSG_FIND_DATA(&msg, SOL_IPV6, IPV6_HOPLIMIT, int);
148 if (hops && *hops != 255)
149 return -EMULTIHOP;
150
151 if (ret_timestamp)
152 triple_timestamp_from_cmsg(ret_timestamp, &msg);
153 if (ret_sender)
154 *ret_sender = sa.in6.sin6_addr;
155 return 0;
156 }