ubus: fix invalid ipv6-prefix json
[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%%%s", ipbuf, iface->ifname);
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         char ipbuf[INET6_ADDRSTRLEN];
288         uint8_t mac[6];
289
290         // Solicitation is for duplicate address detection
291         bool ns_is_dad = IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src);
292
293         // Don't process solicit messages on non relay interfaces
294         // Don't forward any non-DAD solicitation for external ifaces
295         // TODO: check if we should even forward DADs for them
296         if (iface->ndp != MODE_RELAY || (iface->external && !ns_is_dad))
297                 return;
298
299         if (len < sizeof(*ip6) + sizeof(*req))
300                 return; // Invalid reqicitation
301
302         if (IN6_IS_ADDR_LINKLOCAL(&req->nd_ns_target) ||
303                         IN6_IS_ADDR_LOOPBACK(&req->nd_ns_target) ||
304                         IN6_IS_ADDR_MULTICAST(&req->nd_ns_target))
305                 return; // Invalid target
306
307         inet_ntop(AF_INET6, &req->nd_ns_target, ipbuf, sizeof(ipbuf));
308         syslog(LOG_DEBUG, "Got a NS for %s%%%s", ipbuf, iface->ifname);
309
310         odhcpd_get_mac(iface, mac);
311         if (!memcmp(ll->sll_addr, mac, sizeof(mac)))
312                 return; // Looped back
313
314         struct interface *c;
315         list_for_each_entry(c, &interfaces, head)
316                 if (iface != c && c->ndp == MODE_RELAY &&
317                                 (ns_is_dad || !c->external))
318                         ping6(&req->nd_ns_target, c);
319 }
320
321 // Use rtnetlink to modify kernel routes
322 static void setup_route(struct in6_addr *addr, struct interface *iface, bool add)
323 {
324         char ipbuf[INET6_ADDRSTRLEN];
325
326         inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
327         syslog(LOG_NOTICE, "%s about %s%s%%%s",
328                         (add) ? "Learning" : "Forgetting",
329                         iface->learn_routes ? "proxy routing for " : "",
330                         ipbuf, iface->ifname);
331
332         if (iface->learn_routes)
333                 netlink_setup_route(addr, 128, iface->ifindex, NULL, 1024, add);
334 }
335
336 static void setup_addr_for_relaying(struct in6_addr *addr, struct interface *iface, bool add)
337 {
338         struct interface *c;
339         char ipbuf[INET6_ADDRSTRLEN];
340
341         inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
342
343         list_for_each_entry(c, &interfaces, head) {
344                 if (iface == c || (c->ndp != MODE_RELAY && !add))
345                         continue;
346
347                 bool neigh_add = (c->ndp == MODE_RELAY ? add : false);
348
349                 if (netlink_setup_proxy_neigh(addr, c->ifindex, neigh_add))
350                         syslog(LOG_DEBUG, "Failed to %s proxy neighbour entry %s%%%s",
351                                 neigh_add ? "add" : "delete", ipbuf, c->ifname);
352                 else
353                         syslog(LOG_DEBUG, "%s proxy neighbour entry %s%%%s",
354                                 neigh_add ? "Added" : "Deleted", ipbuf, c->ifname);
355         }
356 }