ndp: fix endian issue
[project/odhcpd.git] / src / ndp.c
1 /**
2 * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License v2 as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 */
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <signal.h>
18 #include <errno.h>
19
20 #include <fcntl.h>
21 #include <unistd.h>
22 #include <arpa/inet.h>
23 #include <sys/socket.h>
24 #include <net/ethernet.h>
25 #include <netinet/ip6.h>
26 #include <netinet/icmp6.h>
27 #include <netpacket/packet.h>
28
29 #include <linux/filter.h>
30 #include <linux/neighbour.h>
31
32 #include "dhcpv6.h"
33 #include "odhcpd.h"
34
35
36 static void ndp_netevent_cb(unsigned long event, struct netevent_handler_info *info);
37 static void setup_route(struct in6_addr *addr, struct interface *iface, bool add);
38 static void setup_addr_for_relaying(struct in6_addr *addr, struct interface *iface, bool add);
39 static void handle_solicit(void *addr, void *data, size_t len,
40 struct interface *iface, void *dest);
41
42 /* Filter ICMPv6 messages of type neighbor soliciation */
43 static struct sock_filter bpf[] = {
44 BPF_STMT(BPF_LD | BPF_B | BPF_ABS, offsetof(struct ip6_hdr, ip6_nxt)),
45 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
46 BPF_STMT(BPF_LD | BPF_B | BPF_ABS, sizeof(struct ip6_hdr) +
47 offsetof(struct icmp6_hdr, icmp6_type)),
48 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 0, 1),
49 BPF_STMT(BPF_RET | BPF_K, 0xffffffff),
50 BPF_STMT(BPF_RET | BPF_K, 0),
51 };
52 static const struct sock_fprog bpf_prog = {sizeof(bpf) / sizeof(*bpf), bpf};
53 static struct netevent_handler ndp_netevent_handler = { .cb = ndp_netevent_cb, };
54
55 /* Initialize NDP-proxy */
56 int ndp_init(void)
57 {
58 int ret = 0;
59
60 if (netlink_add_netevent_handler(&ndp_netevent_handler) < 0) {
61 syslog(LOG_ERR, "Failed to add ndp netevent handler");
62 ret = -1;
63 }
64
65 return ret;
66 }
67
68 int ndp_setup_interface(struct interface *iface, bool enable)
69 {
70 int ret = 0, procfd;
71 bool dump_neigh = false;
72 char procbuf[64];
73
74 enable = enable && (iface->ndp == MODE_RELAY);
75
76 snprintf(procbuf, sizeof(procbuf), "/proc/sys/net/ipv6/conf/%s/proxy_ndp", iface->ifname);
77 procfd = open(procbuf, O_WRONLY);
78
79 if (procfd < 0) {
80 ret = -1;
81 goto out;
82 }
83
84 if (iface->ndp_ping_fd >= 0) {
85 close(iface->ndp_ping_fd);
86 iface->ndp_ping_fd = -1;
87 }
88
89 if (iface->ndp_event.uloop.fd >= 0) {
90 uloop_fd_delete(&iface->ndp_event.uloop);
91 close(iface->ndp_event.uloop.fd);
92 iface->ndp_event.uloop.fd = -1;
93
94 if (!enable)
95 if (write(procfd, "0\n", 2) < 0) {}
96
97 dump_neigh = true;
98 }
99
100 if (enable) {
101 struct sockaddr_ll ll;
102 struct packet_mreq mreq;
103 struct icmp6_filter filt;
104 int val = 2;
105
106 if (write(procfd, "1\n", 2) < 0) {}
107
108 /* Open ICMPv6 socket */
109 iface->ndp_ping_fd = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6);
110 if (iface->ndp_ping_fd < 0) {
111 syslog(LOG_ERR, "socket(AF_INET6): %m");
112 ret = -1;
113 goto out;
114 }
115
116 if (setsockopt(iface->ndp_ping_fd, SOL_SOCKET, SO_BINDTODEVICE,
117 iface->ifname, strlen(iface->ifname)) < 0) {
118 syslog(LOG_ERR, "setsockopt(SO_BINDTODEVICE): %m");
119 ret = -1;
120 goto out;
121 }
122
123 if (setsockopt(iface->ndp_ping_fd, IPPROTO_RAW, IPV6_CHECKSUM,
124 &val, sizeof(val)) < 0) {
125 syslog(LOG_ERR, "setsockopt(IPV6_CHECKSUM): %m");
126 ret = -1;
127 goto out;
128 }
129
130 /* This is required by RFC 4861 */
131 val = 255;
132 if (setsockopt(iface->ndp_ping_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
133 &val, sizeof(val)) < 0) {
134 syslog(LOG_ERR, "setsockopt(IPV6_MULTICAST_HOPS): %m");
135 ret = -1;
136 goto out;
137 }
138
139 if (setsockopt(iface->ndp_ping_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
140 &val, sizeof(val)) < 0) {
141 syslog(LOG_ERR, "setsockopt(IPV6_UNICAST_HOPS): %m");
142 ret = -1;
143 goto out;
144 }
145
146 /* Filter all packages, we only want to send */
147 ICMP6_FILTER_SETBLOCKALL(&filt);
148 if (setsockopt(iface->ndp_ping_fd, IPPROTO_ICMPV6, ICMP6_FILTER,
149 &filt, sizeof(filt)) < 0) {
150 syslog(LOG_ERR, "setsockopt(ICMP6_FILTER): %m");
151 ret = -1;
152 goto out;
153 }
154
155
156 iface->ndp_event.uloop.fd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6));
157 if (iface->ndp_event.uloop.fd < 0) {
158 syslog(LOG_ERR, "socket(AF_PACKET): %m");
159 ret = -1;
160 goto out;
161 }
162
163 #ifdef PACKET_RECV_TYPE
164 int pktt = 1 << PACKET_MULTICAST;
165 if (setsockopt(iface->ndp_event.uloop.fd, SOL_PACKET, PACKET_RECV_TYPE,
166 &pktt, sizeof(pktt)) < 0) {
167 syslog(LOG_ERR, "setsockopt(PACKET_RECV_TYPE): %m");
168 ret = -1;
169 goto out;
170 }
171 #endif
172
173 if (setsockopt(iface->ndp_event.uloop.fd, SOL_SOCKET, SO_ATTACH_FILTER,
174 &bpf_prog, sizeof(bpf_prog))) {
175 syslog(LOG_ERR, "setsockopt(SO_ATTACH_FILTER): %m");
176 ret = -1;
177 goto out;
178 }
179
180 memset(&ll, 0, sizeof(ll));
181 ll.sll_family = AF_PACKET;
182 ll.sll_ifindex = iface->ifindex;
183 ll.sll_protocol = htons(ETH_P_IPV6);
184
185 if (bind(iface->ndp_event.uloop.fd, (struct sockaddr*)&ll, sizeof(ll)) < 0) {
186 syslog(LOG_ERR, "bind(): %m");
187 ret = -1;
188 goto out;
189 }
190
191 memset(&mreq, 0, sizeof(mreq));
192 mreq.mr_ifindex = iface->ifindex;
193 mreq.mr_type = PACKET_MR_ALLMULTI;
194 mreq.mr_alen = ETH_ALEN;
195
196 if (setsockopt(iface->ndp_event.uloop.fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP,
197 &mreq, sizeof(mreq)) < 0) {
198 syslog(LOG_ERR, "setsockopt(PACKET_ADD_MEMBERSHIP): %m");
199 ret = -1;
200 goto out;
201 }
202
203 iface->ndp_event.handle_dgram = handle_solicit;
204 odhcpd_register(&iface->ndp_event);
205
206 /* If we already were enabled dump is unnecessary, if not do dump */
207 if (!dump_neigh)
208 netlink_dump_neigh_table(false);
209 else
210 dump_neigh = false;
211 }
212
213 if (dump_neigh)
214 netlink_dump_neigh_table(true);
215
216 out:
217 if (ret < 0) {
218 if (iface->ndp_event.uloop.fd >= 0) {
219 close(iface->ndp_event.uloop.fd);
220 iface->ndp_event.uloop.fd = -1;
221 }
222
223 if (iface->ndp_ping_fd >= 0) {
224 close(iface->ndp_ping_fd);
225 iface->ndp_ping_fd = -1;
226 }
227 }
228
229 if (procfd >= 0)
230 close(procfd);
231
232 return ret;
233 }
234
235 static void ndp_netevent_cb(unsigned long event, struct netevent_handler_info *info)
236 {
237 struct interface *iface = info->iface;
238 bool add = true;
239
240 if (!iface || iface->ndp == MODE_DISABLED)
241 return;
242
243 switch (event) {
244 case NETEV_ADDR6_DEL:
245 add = false;
246 netlink_dump_neigh_table(false);
247 /* fall through */
248 case NETEV_ADDR6_ADD:
249 setup_addr_for_relaying(&info->addr.in6, iface, add);
250 break;
251 case NETEV_NEIGH6_DEL:
252 add = false;
253 /* fall through */
254 case NETEV_NEIGH6_ADD:
255 if (info->neigh.flags & NTF_PROXY) {
256 if (add) {
257 netlink_setup_proxy_neigh(&info->neigh.dst.in6, iface->ifindex, false);
258 setup_route(&info->neigh.dst.in6, iface, false);
259 netlink_dump_neigh_table(false);
260 }
261 break;
262 }
263
264 if (add &&
265 !(info->neigh.state &
266 (NUD_REACHABLE|NUD_STALE|NUD_DELAY|NUD_PROBE|NUD_PERMANENT|NUD_NOARP)))
267 break;
268
269 setup_addr_for_relaying(&info->neigh.dst.in6, iface, add);
270 setup_route(&info->neigh.dst.in6, iface, add);
271
272 if (!add)
273 netlink_dump_neigh_table(false);
274 break;
275 default:
276 break;
277 }
278 }
279
280 /* Send an ICMP-ECHO. This is less for actually pinging but for the
281 * neighbor cache to be kept up-to-date. */
282 static void ping6(struct in6_addr *addr,
283 const struct interface *iface)
284 {
285 struct sockaddr_in6 dest = { .sin6_family = AF_INET6, .sin6_addr = *addr , };
286 struct icmp6_hdr echo = { .icmp6_type = ICMP6_ECHO_REQUEST };
287 struct iovec iov = { .iov_base = &echo, .iov_len = sizeof(echo) };
288 char ipbuf[INET6_ADDRSTRLEN];
289
290 inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
291 syslog(LOG_NOTICE, "Pinging for %s on %s", ipbuf, iface->name);
292
293 netlink_setup_route(addr, 128, iface->ifindex, NULL, 128, true);
294 odhcpd_send(iface->ndp_ping_fd, &dest, &iov, 1, iface);
295 netlink_setup_route(addr, 128, iface->ifindex, NULL, 128, false);
296 }
297
298 /* Send a Neighbor Advertisement. */
299 static void send_na(struct in6_addr *to_addr,
300 const struct interface *iface, struct in6_addr *for_addr,
301 const uint8_t *mac)
302 {
303 struct sockaddr_in6 dest = { .sin6_family = AF_INET6, .sin6_addr = *to_addr };
304 char pbuf[sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr) + 6];
305 struct nd_neighbor_advert *adv = (struct nd_neighbor_advert*)pbuf;
306 struct nd_opt_hdr *opt = (struct nd_opt_hdr*) &pbuf[sizeof(struct nd_neighbor_advert)];
307 struct iovec iov = { .iov_base = &pbuf, .iov_len = sizeof(pbuf) };
308 char ipbuf[INET6_ADDRSTRLEN];
309
310 memset(pbuf, 0, sizeof(pbuf));
311 adv->nd_na_hdr = (struct icmp6_hdr) {
312 .icmp6_type = ND_NEIGHBOR_ADVERT,
313 .icmp6_dataun.icmp6_un_data32 = { ND_NA_FLAG_SOLICITED }
314 };
315 adv->nd_na_target = *for_addr;
316 *opt = (struct nd_opt_hdr) { .nd_opt_type = ND_OPT_TARGET_LINKADDR, .nd_opt_len = 1 };
317 memcpy(&pbuf[sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr)], mac, 6);
318
319 inet_ntop(AF_INET6, to_addr, ipbuf, sizeof(ipbuf));
320 syslog(LOG_DEBUG, "Answering NS to %s on %s", ipbuf, iface->ifname);
321
322 odhcpd_send(iface->ndp_ping_fd, &dest, &iov, 1, iface);
323 }
324
325 /* Handle solicitations */
326 static void handle_solicit(void *addr, void *data, size_t len,
327 struct interface *iface, _unused void *dest)
328 {
329 struct ip6_hdr *ip6 = data;
330 struct nd_neighbor_solicit *req = (struct nd_neighbor_solicit*)&ip6[1];
331 struct sockaddr_ll *ll = addr;
332 struct interface *c;
333 char ipbuf[INET6_ADDRSTRLEN];
334 uint8_t mac[6];
335
336 /* Solicitation is for duplicate address detection */
337 bool ns_is_dad = IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src);
338
339 /* Don't process solicit messages on non relay interfaces
340 * Don't forward any non-DAD solicitation for external ifaces
341 * TODO: check if we should even forward DADs for them */
342 if (iface->ndp != MODE_RELAY || (iface->external && !ns_is_dad))
343 return;
344
345 if (len < sizeof(*ip6) + sizeof(*req))
346 return; // Invalid reqicitation
347
348 if (IN6_IS_ADDR_LINKLOCAL(&req->nd_ns_target) ||
349 IN6_IS_ADDR_LOOPBACK(&req->nd_ns_target) ||
350 IN6_IS_ADDR_MULTICAST(&req->nd_ns_target))
351 return; /* Invalid target */
352
353 inet_ntop(AF_INET6, &req->nd_ns_target, ipbuf, sizeof(ipbuf));
354 syslog(LOG_DEBUG, "Got a NS for %s on %s", ipbuf, iface->name);
355
356 odhcpd_get_mac(iface, mac);
357 if (!memcmp(ll->sll_addr, mac, sizeof(mac)))
358 return; /* Looped back */
359
360 avl_for_each_element(&interfaces, c, avl) {
361 if (iface != c && c->ndp == MODE_RELAY &&
362 (ns_is_dad || !c->external))
363 ping6(&req->nd_ns_target, c);
364 }
365
366 /* Catch global-addressed NS and answer them manually.
367 * The kernel won't answer these and cannot route them either. */
368 if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) &&
369 IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src)) {
370 bool is_proxy_neigh = netlink_get_interface_proxy_neigh(iface->ifindex,
371 &req->nd_ns_target) == 1;
372
373 if (is_proxy_neigh)
374 send_na(&ip6->ip6_src, iface, &req->nd_ns_target, mac);
375 }
376 }
377
378 /* Use rtnetlink to modify kernel routes */
379 static void setup_route(struct in6_addr *addr, struct interface *iface, bool add)
380 {
381 char ipbuf[INET6_ADDRSTRLEN];
382
383 inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
384 syslog(LOG_NOTICE, "%s about %s%s on %s",
385 (add) ? "Learning" : "Forgetting",
386 iface->learn_routes ? "proxy routing for " : "",
387 ipbuf, iface->name);
388
389 if (iface->learn_routes)
390 netlink_setup_route(addr, 128, iface->ifindex, NULL, 1024, add);
391 }
392
393 static void setup_addr_for_relaying(struct in6_addr *addr, struct interface *iface, bool add)
394 {
395 struct interface *c;
396 char ipbuf[INET6_ADDRSTRLEN];
397
398 inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
399
400 avl_for_each_element(&interfaces, c, avl) {
401 if (iface == c || c->ndp != MODE_RELAY)
402 continue;
403
404 if (netlink_setup_proxy_neigh(addr, c->ifindex, add)) {
405 if (add)
406 syslog(LOG_ERR, "Failed to add proxy neighbour entry %s on %s",
407 ipbuf, c->name);
408 } else
409 syslog(LOG_DEBUG, "%s proxy neighbour entry %s on %s",
410 add ? "Added" : "Deleted", ipbuf, c->name);
411 }
412 }