treewide: use avl tree to store interfaces
[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 static int ping_socket = -1;
43
44 /* Filter ICMPv6 messages of type neighbor soliciation */
45 static struct sock_filter bpf[] = {
46 BPF_STMT(BPF_LD | BPF_B | BPF_ABS, offsetof(struct ip6_hdr, ip6_nxt)),
47 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
48 BPF_STMT(BPF_LD | BPF_B | BPF_ABS, sizeof(struct ip6_hdr) +
49 offsetof(struct icmp6_hdr, icmp6_type)),
50 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 0, 1),
51 BPF_STMT(BPF_RET | BPF_K, 0xffffffff),
52 BPF_STMT(BPF_RET | BPF_K, 0),
53 };
54 static const struct sock_fprog bpf_prog = {sizeof(bpf) / sizeof(*bpf), bpf};
55 static struct netevent_handler ndp_netevent_handler = { .cb = ndp_netevent_cb, };
56
57 /* Initialize NDP-proxy */
58 int ndp_init(void)
59 {
60 struct icmp6_filter filt;
61 int val = 2, ret = 0;
62
63 /* Open ICMPv6 socket */
64 ping_socket = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6);
65 if (ping_socket < 0) {
66 syslog(LOG_ERR, "socket(AF_INET6): %m");
67 ret = -1;
68 goto out;
69 }
70
71 if (setsockopt(ping_socket, IPPROTO_RAW, IPV6_CHECKSUM,
72 &val, sizeof(val)) < 0) {
73 syslog(LOG_ERR, "setsockopt(IPV6_CHECKSUM): %m");
74 ret = -1;
75 goto out;
76 }
77
78 /* This is required by RFC 4861 */
79 val = 255;
80 if (setsockopt(ping_socket, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
81 &val, sizeof(val)) < 0) {
82 syslog(LOG_ERR, "setsockopt(IPV6_MULTICAST_HOPS): %m");
83 ret = -1;
84 goto out;
85 }
86
87 if (setsockopt(ping_socket, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
88 &val, sizeof(val)) < 0) {
89 syslog(LOG_ERR, "setsockopt(IPV6_UNICAST_HOPS): %m");
90 ret = -1;
91 goto out;
92 }
93
94 /* Filter all packages, we only want to send */
95 ICMP6_FILTER_SETBLOCKALL(&filt);
96 if (setsockopt(ping_socket, IPPROTO_ICMPV6, ICMP6_FILTER,
97 &filt, sizeof(filt)) < 0) {
98 syslog(LOG_ERR, "setsockopt(ICMP6_FILTER): %m");
99 ret = -1;
100 goto out;
101 }
102
103 netlink_add_netevent_handler(&ndp_netevent_handler);
104
105 out:
106 if (ret < 0 && ping_socket > 0) {
107 close(ping_socket);
108 ping_socket = -1;
109 }
110
111 return ret;
112 }
113
114 int ndp_setup_interface(struct interface *iface, bool enable)
115 {
116 int ret = 0, procfd;
117 bool dump_neigh = false;
118 char procbuf[64];
119
120 snprintf(procbuf, sizeof(procbuf), "/proc/sys/net/ipv6/conf/%s/proxy_ndp", iface->ifname);
121 procfd = open(procbuf, O_WRONLY);
122
123 if (procfd < 0) {
124 ret = -1;
125 goto out;
126 }
127
128 if (iface->ndp_event.uloop.fd > 0) {
129 uloop_fd_delete(&iface->ndp_event.uloop);
130 close(iface->ndp_event.uloop.fd);
131 iface->ndp_event.uloop.fd = -1;
132
133 if (!enable || iface->ndp != MODE_RELAY)
134 if (write(procfd, "0\n", 2) < 0) {}
135
136 dump_neigh = true;
137 }
138
139 if (enable && iface->ndp == MODE_RELAY) {
140 struct sockaddr_ll ll;
141 struct packet_mreq mreq;
142
143 if (write(procfd, "1\n", 2) < 0) {}
144
145 iface->ndp_event.uloop.fd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6));
146 if (iface->ndp_event.uloop.fd < 0) {
147 syslog(LOG_ERR, "socket(AF_PACKET): %m");
148 ret = -1;
149 goto out;
150 }
151
152 #ifdef PACKET_RECV_TYPE
153 int pktt = 1 << PACKET_MULTICAST;
154 if (setsockopt(iface->ndp_event.uloop.fd, SOL_PACKET, PACKET_RECV_TYPE,
155 &pktt, sizeof(pktt)) < 0) {
156 syslog(LOG_ERR, "setsockopt(PACKET_RECV_TYPE): %m");
157 ret = -1;
158 goto out;
159 }
160 #endif
161
162 if (setsockopt(iface->ndp_event.uloop.fd, SOL_SOCKET, SO_ATTACH_FILTER,
163 &bpf_prog, sizeof(bpf_prog))) {
164 syslog(LOG_ERR, "setsockopt(SO_ATTACH_FILTER): %m");
165 ret = -1;
166 goto out;
167 }
168
169 memset(&ll, 0, sizeof(ll));
170 ll.sll_family = AF_PACKET;
171 ll.sll_ifindex = iface->ifindex;
172 ll.sll_protocol = htons(ETH_P_IPV6);
173
174 if (bind(iface->ndp_event.uloop.fd, (struct sockaddr*)&ll, sizeof(ll)) < 0) {
175 syslog(LOG_ERR, "bind(): %m");
176 ret = -1;
177 goto out;
178 }
179
180 memset(&mreq, 0, sizeof(mreq));
181 mreq.mr_ifindex = iface->ifindex;
182 mreq.mr_type = PACKET_MR_ALLMULTI;
183 mreq.mr_alen = ETH_ALEN;
184
185 if (setsockopt(iface->ndp_event.uloop.fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP,
186 &mreq, sizeof(mreq)) < 0) {
187 syslog(LOG_ERR, "setsockopt(PACKET_ADD_MEMBERSHIP): %m");
188 ret = -1;
189 goto out;
190 }
191
192 iface->ndp_event.handle_dgram = handle_solicit;
193 odhcpd_register(&iface->ndp_event);
194
195 /* If we already were enabled dump is unnecessary, if not do dump */
196 if (!dump_neigh)
197 netlink_dump_neigh_table(false);
198 else
199 dump_neigh = false;
200 }
201
202 if (dump_neigh)
203 netlink_dump_neigh_table(true);
204
205 out:
206 if (ret < 0 && iface->ndp_event.uloop.fd > 0) {
207 close(iface->ndp_event.uloop.fd);
208 iface->ndp_event.uloop.fd = -1;
209 }
210
211 if (procfd >= 0)
212 close(procfd);
213
214 return ret;
215 }
216
217 static void ndp_netevent_cb(unsigned long event, struct netevent_handler_info *info)
218 {
219 struct interface *iface = info->iface;
220 bool add = true;
221
222 if (!iface || iface->ndp == MODE_DISABLED)
223 return;
224
225 switch (event) {
226 case NETEV_ADDR6_DEL:
227 add = false;
228 netlink_dump_neigh_table(false);
229 /* fall through */
230 case NETEV_ADDR6_ADD:
231 setup_addr_for_relaying(&info->addr.in6, iface, add);
232 break;
233 case NETEV_NEIGH6_DEL:
234 add = false;
235 /* fall through */
236 case NETEV_NEIGH6_ADD:
237 if (info->neigh.flags & NTF_PROXY) {
238 if (add) {
239 netlink_setup_proxy_neigh(&info->neigh.dst.in6, iface->ifindex, false);
240 setup_route(&info->neigh.dst.in6, iface, false);
241 netlink_dump_neigh_table(false);
242 }
243 break;
244 }
245
246 if (add &&
247 !(info->neigh.state &
248 (NUD_REACHABLE|NUD_STALE|NUD_DELAY|NUD_PROBE|NUD_PERMANENT|NUD_NOARP)))
249 break;
250
251 setup_addr_for_relaying(&info->neigh.dst.in6, iface, add);
252 setup_route(&info->neigh.dst.in6, iface, add);
253
254 if (!add)
255 netlink_dump_neigh_table(false);
256 break;
257 default:
258 break;
259 }
260 }
261
262 /* Send an ICMP-ECHO. This is less for actually pinging but for the
263 * neighbor cache to be kept up-to-date. */
264 static void ping6(struct in6_addr *addr,
265 const struct interface *iface)
266 {
267 struct sockaddr_in6 dest = { .sin6_family = AF_INET6, .sin6_addr = *addr, .sin6_scope_id = iface->ifindex, };
268 struct icmp6_hdr echo = { .icmp6_type = ICMP6_ECHO_REQUEST };
269 struct iovec iov = { .iov_base = &echo, .iov_len = sizeof(echo) };
270 char ipbuf[INET6_ADDRSTRLEN];
271
272 inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
273 syslog(LOG_NOTICE, "Pinging for %s on %s", ipbuf, iface->name);
274
275 netlink_setup_route(addr, 128, iface->ifindex, NULL, 128, true);
276 odhcpd_send(ping_socket, &dest, &iov, 1, iface);
277 netlink_setup_route(addr, 128, iface->ifindex, NULL, 128, false);
278 }
279
280 /* Handle solicitations */
281 static void handle_solicit(void *addr, void *data, size_t len,
282 struct interface *iface, _unused void *dest)
283 {
284 struct ip6_hdr *ip6 = data;
285 struct nd_neighbor_solicit *req = (struct nd_neighbor_solicit*)&ip6[1];
286 struct sockaddr_ll *ll = addr;
287 struct interface *c;
288 char ipbuf[INET6_ADDRSTRLEN];
289 uint8_t mac[6];
290
291 /* Solicitation is for duplicate address detection */
292 bool ns_is_dad = IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src);
293
294 /* Don't process solicit messages on non relay interfaces
295 * Don't forward any non-DAD solicitation for external ifaces
296 * TODO: check if we should even forward DADs for them */
297 if (iface->ndp != MODE_RELAY || (iface->external && !ns_is_dad))
298 return;
299
300 if (len < sizeof(*ip6) + sizeof(*req))
301 return; // Invalid reqicitation
302
303 if (IN6_IS_ADDR_LINKLOCAL(&req->nd_ns_target) ||
304 IN6_IS_ADDR_LOOPBACK(&req->nd_ns_target) ||
305 IN6_IS_ADDR_MULTICAST(&req->nd_ns_target))
306 return; /* Invalid target */
307
308 inet_ntop(AF_INET6, &req->nd_ns_target, ipbuf, sizeof(ipbuf));
309 syslog(LOG_DEBUG, "Got a NS for %s on %s", ipbuf, iface->name);
310
311 odhcpd_get_mac(iface, mac);
312 if (!memcmp(ll->sll_addr, mac, sizeof(mac)))
313 return; /* Looped back */
314
315 avl_for_each_element(&interfaces, c, avl) {
316 if (iface != c && c->ndp == MODE_RELAY &&
317 (ns_is_dad || !c->external))
318 ping6(&req->nd_ns_target, c);
319 }
320 }
321
322 /* Use rtnetlink to modify kernel routes */
323 static void setup_route(struct in6_addr *addr, struct interface *iface, bool add)
324 {
325 char ipbuf[INET6_ADDRSTRLEN];
326
327 inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
328 syslog(LOG_NOTICE, "%s about %s%s on %s",
329 (add) ? "Learning" : "Forgetting",
330 iface->learn_routes ? "proxy routing for " : "",
331 ipbuf, iface->name);
332
333 if (iface->learn_routes)
334 netlink_setup_route(addr, 128, iface->ifindex, NULL, 1024, add);
335 }
336
337 static void setup_addr_for_relaying(struct in6_addr *addr, struct interface *iface, bool add)
338 {
339 struct interface *c;
340 char ipbuf[INET6_ADDRSTRLEN];
341
342 inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
343
344 avl_for_each_element(&interfaces, c, avl) {
345 if (iface == c || (c->ndp != MODE_RELAY && !add))
346 continue;
347
348 bool neigh_add = (c->ndp == MODE_RELAY ? add : false);
349
350 if (netlink_setup_proxy_neigh(addr, c->ifindex, neigh_add))
351 syslog(LOG_DEBUG, "Failed to %s proxy neighbour entry %s on %s",
352 neigh_add ? "add" : "delete", ipbuf, c->name);
353 else
354 syslog(LOG_DEBUG, "%s proxy neighbour entry %s on %s",
355 neigh_add ? "Added" : "Deleted", ipbuf, c->name);
356 }
357 }