]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/icmp6-util.c
icmp6-util: merge icmp6_bind_router_{solicitation,advertisement}() into icmp6_bind()
[thirdparty/systemd.git] / src / libsystemd-network / icmp6-util.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
940367a0 2/***
810adae9 3 Copyright © 2014 Intel Corporation. All rights reserved.
940367a0
TG
4***/
5
6#include <errno.h>
940367a0
TG
7#include <netinet/icmp6.h>
8#include <netinet/in.h>
3ffd4af2
LP
9#include <netinet/ip6.h>
10#include <stdio.h>
11#include <string.h>
3ffd4af2
LP
12#include <sys/types.h>
13#include <unistd.h>
15fec93b 14#include <net/if.h>
3ffd4af2 15#include <linux/if_packet.h>
940367a0 16
3ffd4af2 17#include "fd-util.h"
940367a0 18#include "icmp6-util.h"
88d5a3db 19#include "in-addr-util.h"
bd1ae178 20#include "iovec-util.h"
dd59d5e5 21#include "network-common.h"
5cfa2c3d 22#include "socket-util.h"
940367a0 23
37c011e7
YW
24int icmp6_bind(int ifindex, bool is_router) {
25 struct icmp6_filter filter = {};
26 struct ipv6_mreq mreq;
5bb1d7fb 27 _cleanup_close_ int s = -EBADF;
1e7a0e21 28 int r;
940367a0 29
37c011e7
YW
30 assert(ifindex > 0);
31
32 ICMP6_FILTER_SETBLOCKALL(&filter);
33 if (is_router) {
34 mreq = (struct ipv6_mreq) {
35 .ipv6mr_multiaddr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
36 .ipv6mr_interface = ifindex,
37 };
38 ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
39 } else {
40 mreq = (struct ipv6_mreq) {
41 .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT,
42 .ipv6mr_interface = ifindex,
43 };
44 ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
45 }
113e124f 46
cddf4d81 47 s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6);
940367a0
TG
48 if (s < 0)
49 return -errno;
50
37c011e7 51 if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) < 0)
6142bb37
PF
52 return -errno;
53
37c011e7 54 if (setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
940367a0
TG
55 return -errno;
56
37c011e7
YW
57 /* RFC 3315, section 6.7, bullet point 2 may indicate that an IPV6_PKTINFO socket option also applies
58 * for ICMPv6 multicast. Empirical experiments indicates otherwise and therefore an IPV6_MULTICAST_IF
59 * socket option is used here instead. */
953a02d1 60 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, ifindex);
940367a0 61 if (r < 0)
9e5b6496 62 return r;
940367a0 63
2ff48e98 64 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, false);
940367a0 65 if (r < 0)
2ff48e98 66 return r;
940367a0 67
9e5b6496 68 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 255);
940367a0 69 if (r < 0)
9e5b6496 70 return r;
940367a0 71
9e5b6496 72 r = setsockopt_int(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, 255);
cddf4d81 73 if (r < 0)
9e5b6496 74 return r;
cddf4d81 75
2ff48e98 76 r = setsockopt_int(s, SOL_IPV6, IPV6_RECVHOPLIMIT, true);
940367a0 77 if (r < 0)
2ff48e98 78 return r;
940367a0 79
2ff48e98 80 r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true);
1e7a0e21 81 if (r < 0)
2ff48e98 82 return r;
1e7a0e21 83
953a02d1 84 r = socket_bind_to_ifindex(s, ifindex);
15fec93b 85 if (r < 0)
953a02d1 86 return r;
15fec93b 87
c10d6bdb 88 return TAKE_FD(s);
940367a0
TG
89}
90
91int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
92 struct sockaddr_in6 dst = {
93 .sin6_family = AF_INET6,
94 .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
95 };
96 struct {
97 struct nd_router_solicit rs;
98 struct nd_opt_hdr rs_opt;
99 struct ether_addr rs_opt_mac;
100 } _packed_ rs = {
101 .rs.nd_rs_type = ND_ROUTER_SOLICIT,
6d06ac1f
TG
102 .rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR,
103 .rs_opt.nd_opt_len = 1,
940367a0 104 };
6d06ac1f
TG
105 struct iovec iov = {
106 .iov_base = &rs,
107 .iov_len = sizeof(rs),
940367a0
TG
108 };
109 struct msghdr msg = {
110 .msg_name = &dst,
111 .msg_namelen = sizeof(dst),
6d06ac1f 112 .msg_iov = &iov,
940367a0
TG
113 .msg_iovlen = 1,
114 };
940367a0 115
6d06ac1f
TG
116 assert(s >= 0);
117 assert(ether_addr);
118
119 rs.rs_opt_mac = *ether_addr;
940367a0 120
113e124f 121 if (sendmsg(s, &msg, 0) < 0)
940367a0
TG
122 return -errno;
123
124 return 0;
125}
88d5a3db 126
51211638
YW
127int icmp6_receive(
128 int fd,
129 void *buffer,
130 size_t size,
131 struct in6_addr *ret_sender,
132 triple_timestamp *ret_timestamp) {
fb29cdbe 133
f782eee6 134 /* This needs to be initialized with zero. See #20741. */
fb29cdbe 135 CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int)) + /* ttl */
f782eee6 136 CMSG_SPACE_TIMEVAL) control = {};
88d5a3db
PF
137 struct iovec iov = {};
138 union sockaddr_union sa = {};
139 struct msghdr msg = {
140 .msg_name = &sa.sa,
141 .msg_namelen = sizeof(sa),
142 .msg_iov = &iov,
143 .msg_iovlen = 1,
144 .msg_control = &control,
145 .msg_controllen = sizeof(control),
146 };
94876904 147 struct in6_addr addr = {};
88d5a3db
PF
148 ssize_t len;
149
5cfa2c3d 150 iov = IOVEC_MAKE(buffer, size);
88d5a3db 151
2adfd1bd 152 len = recvmsg_safe(fd, &msg, MSG_DONTWAIT);
14f37112 153 if (len < 0)
2adfd1bd 154 return (int) len;
88d5a3db
PF
155
156 if ((size_t) len != size)
157 return -EINVAL;
158
159 if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
160 sa.in6.sin6_family == AF_INET6) {
161
94876904 162 addr = sa.in6.sin6_addr;
4961f566 163 if (!in6_addr_is_link_local(&addr) && !in6_addr_is_null(&addr))
88d5a3db
PF
164 return -EADDRNOTAVAIL;
165
166 } else if (msg.msg_namelen > 0)
167 return -EPFNOSUPPORT;
168
169 /* namelen == 0 only happens when running the test-suite over a socketpair */
170
88d5a3db
PF
171 assert(!(msg.msg_flags & MSG_TRUNC));
172
dd59d5e5
YW
173 int *hops = CMSG_FIND_DATA(&msg, SOL_IPV6, IPV6_HOPLIMIT, int);
174 if (hops && *hops != 255)
175 return -EMULTIHOP;
88d5a3db 176
dd59d5e5
YW
177 if (ret_timestamp)
178 triple_timestamp_from_cmsg(ret_timestamp, &msg);
51211638
YW
179 if (ret_sender)
180 *ret_sender = addr;
88d5a3db
PF
181 return 0;
182}