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