• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/odhcpd/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 struct netevent_handler ndp_netevent_handler = { .cb = ndp_netevent_cb, };
 43 
 44 /* Initialize NDP-proxy */
 45 int ndp_init(void)
 46 {
 47         int ret = 0;
 48 
 49         if (netlink_add_netevent_handler(&ndp_netevent_handler) < 0) {
 50                 error("Failed to add ndp netevent handler");
 51                 ret = -1;
 52         }
 53 
 54         return ret;
 55 }
 56 
 57 int ndp_setup_interface(struct interface *iface, bool enable)
 58 {
 59         /* Drop everything */
 60         static const struct sock_filter bpf_drop_filter[] = {
 61                 BPF_STMT(BPF_RET | BPF_K, 0),
 62         };
 63         static const struct sock_fprog bpf_drop = {
 64                 .len = ARRAY_SIZE(bpf_drop_filter),
 65                 .filter = (struct sock_filter *)bpf_drop_filter,
 66         };
 67 
 68         /* Filter ICMPv6 messages of type neighbor solicitation */
 69         static const struct sock_filter bpf[] = {
 70                 BPF_STMT(BPF_LD | BPF_B | BPF_ABS, offsetof(struct ip6_hdr, ip6_nxt)),
 71                 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
 72                 BPF_STMT(BPF_LD | BPF_B | BPF_ABS, sizeof(struct ip6_hdr) +
 73                          offsetof(struct icmp6_hdr, icmp6_type)),
 74                 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 0, 1),
 75                 BPF_STMT(BPF_RET | BPF_K, 0xffffffff),
 76                 BPF_STMT(BPF_RET | BPF_K, 0),
 77         };
 78         static const struct sock_fprog bpf_prog = {
 79                 .len = ARRAY_SIZE(bpf),
 80                 .filter = (struct sock_filter *)bpf,
 81         };
 82 
 83         int ret = 0, procfd;
 84         bool dump_neigh = false;
 85         char procbuf[64];
 86 
 87         enable = enable && (iface->ndp == MODE_RELAY);
 88 
 89         snprintf(procbuf, sizeof(procbuf), "/proc/sys/net/ipv6/conf/%s/proxy_ndp", iface->ifname);
 90         procfd = open(procbuf, O_WRONLY);
 91 
 92         if (procfd < 0) {
 93                 ret = -1;
 94                 goto out;
 95         }
 96 
 97         if (iface->ndp_ping_fd >= 0) {
 98                 close(iface->ndp_ping_fd);
 99                 iface->ndp_ping_fd = -1;
100         }
101 
102         if (iface->ndp_event.uloop.fd >= 0) {
103                 uloop_fd_delete(&iface->ndp_event.uloop);
104                 close(iface->ndp_event.uloop.fd);
105                 iface->ndp_event.uloop.fd = -1;
106 
107                 if (!enable)
108                         if (write(procfd, "\n", 2) < 0) {}
109 
110                 dump_neigh = true;
111         }
112 
113         if (enable) {
114                 struct sockaddr_ll ll;
115                 struct packet_mreq mreq;
116                 struct icmp6_filter filt;
117                 int val = 2;
118 
119                 if (write(procfd, "1\n", 2) < 0) {}
120 
121                 /* Open ICMPv6 socket */
122                 iface->ndp_ping_fd = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6);
123                 if (iface->ndp_ping_fd < 0) {
124                         error("socket(AF_INET6): %m");
125                         ret = -1;
126                         goto out;
127                 }
128 
129                 if (setsockopt(iface->ndp_ping_fd, SOL_SOCKET, SO_BINDTODEVICE,
130                                iface->ifname, strlen(iface->ifname)) < 0) {
131                         error("setsockopt(SO_BINDTODEVICE): %m");
132                         ret = -1;
133                         goto out;
134                 }
135 
136                 if (setsockopt(iface->ndp_ping_fd, IPPROTO_RAW, IPV6_CHECKSUM,
137                                &val, sizeof(val)) < 0) {
138                         error("setsockopt(IPV6_CHECKSUM): %m");
139                         ret = -1;
140                         goto out;
141                 }
142 
143                 /* This is required by RFC 4861 */
144                 val = 255;
145                 if (setsockopt(iface->ndp_ping_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
146                                &val, sizeof(val)) < 0) {
147                         error("setsockopt(IPV6_MULTICAST_HOPS): %m");
148                         ret = -1;
149                         goto out;
150                 }
151 
152                 if (setsockopt(iface->ndp_ping_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
153                                &val, sizeof(val)) < 0) {
154                         error("setsockopt(IPV6_UNICAST_HOPS): %m");
155                         ret = -1;
156                         goto out;
157                 }
158 
159                 /* Filter all packages, we only want to send */
160                 ICMP6_FILTER_SETBLOCKALL(&filt);
161                 if (setsockopt(iface->ndp_ping_fd, IPPROTO_ICMPV6, ICMP6_FILTER,
162                                &filt, sizeof(filt)) < 0) {
163                         error("setsockopt(ICMP6_FILTER): %m");
164                         ret = -1;
165                         goto out;
166                 }
167 
168 
169                 iface->ndp_event.uloop.fd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6));
170                 if (iface->ndp_event.uloop.fd < 0) {
171                         error("socket(AF_PACKET): %m");
172                         ret = -1;
173                         goto out;
174                 }
175 
176 #ifdef PACKET_RECV_TYPE
177                 int pktt = 1 << PACKET_MULTICAST;
178                 if (setsockopt(iface->ndp_event.uloop.fd, SOL_PACKET, PACKET_RECV_TYPE,
179                                 &pktt, sizeof(pktt)) < 0) {
180                         error("setsockopt(PACKET_RECV_TYPE): %m");
181                         ret = -1;
182                         goto out;
183                 }
184 #endif
185 
186                 /*
187                  * AF_PACKET sockets can receive packets as soon as they are
188                  * created, so make sure we don't accept anything...
189                  */
190                 if (setsockopt(iface->ndp_event.uloop.fd, SOL_SOCKET, SO_ATTACH_FILTER,
191                                &bpf_drop, sizeof(bpf_drop))) {
192                         error("setsockopt(SO_ATTACH_FILTER): %m");
193                         ret = -1;
194                         goto out;
195                 }
196 
197                 /* ...and remove stray packets... */
198                 while (true) {
199                         char null[1];
200                         if (recv(iface->ndp_event.uloop.fd, null, sizeof(null), MSG_DONTWAIT | MSG_TRUNC) < 0)
201                                 break;
202                 }
203 
204                 /* ...until the real filter is installed */
205                 if (setsockopt(iface->ndp_event.uloop.fd, SOL_SOCKET, SO_ATTACH_FILTER,
206                                &bpf_prog, sizeof(bpf_prog))) {
207                         error("setsockopt(SO_ATTACH_FILTER): %m");
208                         ret = -1;
209                         goto out;
210                 }
211 
212                 memset(&ll, 0, sizeof(ll));
213                 ll.sll_family = AF_PACKET;
214                 ll.sll_ifindex = iface->ifindex;
215                 ll.sll_protocol = htons(ETH_P_IPV6);
216 
217                 if (bind(iface->ndp_event.uloop.fd, (struct sockaddr*)&ll, sizeof(ll)) < 0) {
218                         error("bind(): %m");
219                         ret = -1;
220                         goto out;
221                 }
222 
223                 memset(&mreq, 0, sizeof(mreq));
224                 mreq.mr_ifindex = iface->ifindex;
225                 mreq.mr_type = PACKET_MR_ALLMULTI;
226                 mreq.mr_alen = ETH_ALEN;
227 
228                 if (setsockopt(iface->ndp_event.uloop.fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP,
229                                 &mreq, sizeof(mreq)) < 0) {
230                         error("setsockopt(PACKET_ADD_MEMBERSHIP): %m");
231                         ret = -1;
232                         goto out;
233                 }
234 
235                 iface->ndp_event.handle_dgram = handle_solicit;
236                 odhcpd_register(&iface->ndp_event);
237 
238                 /* If we already were enabled dump is unnecessary, if not do dump */
239                 if (!dump_neigh)
240                         netlink_dump_neigh_table(false);
241                 else
242                         dump_neigh = false;
243         }
244 
245         if (dump_neigh)
246                 netlink_dump_neigh_table(true);
247 
248  out:
249         if (ret < 0) {
250                 if (iface->ndp_event.uloop.fd >= 0) {
251                         close(iface->ndp_event.uloop.fd);
252                         iface->ndp_event.uloop.fd = -1;
253                 }
254 
255                 if (iface->ndp_ping_fd >= 0) {
256                         close(iface->ndp_ping_fd);
257                         iface->ndp_ping_fd = -1;
258                 }
259         }
260 
261         if (procfd >= 0)
262                 close(procfd);
263 
264         return ret;
265 }
266 
267 static void ndp_netevent_cb(unsigned long event, struct netevent_handler_info *info)
268 {
269         struct interface *iface = info->iface;
270         bool add = true;
271 
272         if (!iface || iface->ndp == MODE_DISABLED)
273                 return;
274 
275         switch (event) {
276         case NETEV_ADDR6_DEL:
277                 add = false;
278                 netlink_dump_neigh_table(false);
279                 _o_fallthrough;
280         case NETEV_ADDR6_ADD:
281                 setup_addr_for_relaying(&info->addr.in6, iface, add);
282                 break;
283         case NETEV_NEIGH6_DEL:
284                 add = false;
285                 _o_fallthrough;
286         case NETEV_NEIGH6_ADD:
287                 if (info->neigh.flags & NTF_PROXY) {
288                         if (add) {
289                                 netlink_setup_proxy_neigh(&info->neigh.dst.in6, iface->ifindex, false);
290                                 setup_route(&info->neigh.dst.in6, iface, false);
291                                 netlink_dump_neigh_table(false);
292                         }
293                         break;
294                 }
295 
296                 if (add &&
297                     !(info->neigh.state &
298                       (NUD_REACHABLE|NUD_STALE|NUD_DELAY|NUD_PROBE|NUD_PERMANENT|NUD_NOARP)))
299                         break;
300 
301                 setup_addr_for_relaying(&info->neigh.dst.in6, iface, add);
302                 setup_route(&info->neigh.dst.in6, iface, add);
303 
304                 if (!add)
305                         netlink_dump_neigh_table(false);
306                 break;
307         default:
308                 break;
309         }
310 }
311 
312 /* Send an ICMP-ECHO. This is less for actually pinging but for the
313  * neighbor cache to be kept up-to-date. */
314 static void ping6(struct in6_addr *addr,
315                 struct interface *iface)
316 {
317         struct sockaddr_in6 dest = { .sin6_family = AF_INET6, .sin6_addr = *addr , };
318         struct icmp6_hdr echo = { .icmp6_type = ICMP6_ECHO_REQUEST };
319         struct iovec iov = { .iov_base = &echo, .iov_len = sizeof(echo) };
320         char ipbuf[INET6_ADDRSTRLEN];
321 
322         inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
323         debug("Pinging for %s on %s", ipbuf, iface->name);
324 
325         netlink_setup_route(addr, 128, iface->ifindex, NULL, 128, true);
326 
327         /* Use link-local address as source for RFC 4861 compliance and macOS compatibility */
328         odhcpd_try_send_with_src(iface->ndp_ping_fd, &dest, &iov, 1, iface);
329 
330         netlink_setup_route(addr, 128, iface->ifindex, NULL, 128, false);
331 }
332 
333 /* Send a Neighbor Advertisement. */
334 static void send_na(struct in6_addr *to_addr,
335                 struct interface *iface, struct in6_addr *for_addr,
336                 const uint8_t *mac)
337 {
338         struct sockaddr_in6 dest = { .sin6_family = AF_INET6, .sin6_addr = *to_addr };
339         char pbuf[sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr) + 6];
340         struct nd_neighbor_advert *adv = (struct nd_neighbor_advert*)pbuf;
341         struct nd_opt_hdr *opt = (struct nd_opt_hdr*) &pbuf[sizeof(struct nd_neighbor_advert)];
342         struct iovec iov = { .iov_base = &pbuf, .iov_len = sizeof(pbuf) };
343         char ipbuf[INET6_ADDRSTRLEN];
344 
345         memset(pbuf, 0, sizeof(pbuf));
346         adv->nd_na_hdr = (struct icmp6_hdr) {
347                 .icmp6_type = ND_NEIGHBOR_ADVERT,
348                 .icmp6_dataun.icmp6_un_data32 = { ND_NA_FLAG_SOLICITED }
349         };
350         adv->nd_na_target = *for_addr;
351         *opt = (struct nd_opt_hdr) { .nd_opt_type = ND_OPT_TARGET_LINKADDR, .nd_opt_len = 1 };
352         memcpy(&pbuf[sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr)], mac, 6);
353 
354         inet_ntop(AF_INET6, to_addr, ipbuf, sizeof(ipbuf));
355         debug("Answering NS to %s on %s", ipbuf, iface->ifname);
356 
357         /* Use link-local address as source for RFC 4861 compliance and macOS compatibility */
358         odhcpd_try_send_with_src(iface->ndp_ping_fd, &dest, &iov, 1, iface);
359 }
360 
361 /* Handle solicitations */
362 static void handle_solicit(void *addr, void *data, size_t len,
363                 struct interface *iface, _o_unused void *dest)
364 {
365         struct ip6_hdr *ip6 = data;
366         struct nd_neighbor_solicit *req = (struct nd_neighbor_solicit*)&ip6[1];
367         struct sockaddr_ll *ll = addr;
368         struct interface *c;
369         char ipbuf[INET6_ADDRSTRLEN];
370         uint8_t mac[6];
371         bool is_self_sent;
372 
373         /* Solicitation is for duplicate address detection */
374         bool ns_is_dad = IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src);
375 
376         /* Don't process solicit messages on non relay interfaces
377          * Don't forward any non-DAD solicitation for external ifaces
378          * TODO: check if we should even forward DADs for them */
379         if (iface->ndp != MODE_RELAY || (iface->external && !ns_is_dad))
380                 return;
381 
382         if (len < sizeof(*ip6) + sizeof(*req))
383                 return; // Invalid total length
384 
385         if (IN6_IS_ADDR_LINKLOCAL(&req->nd_ns_target) ||
386                         IN6_IS_ADDR_LOOPBACK(&req->nd_ns_target) ||
387                         IN6_IS_ADDR_MULTICAST(&req->nd_ns_target))
388                 return; /* Invalid target */
389 
390         inet_ntop(AF_INET6, &req->nd_ns_target, ipbuf, sizeof(ipbuf));
391         debug("Got a NS for %s on %s", ipbuf, iface->name);
392 
393         odhcpd_get_mac(iface, mac);
394         is_self_sent = !memcmp(ll->sll_addr, mac, sizeof(mac));
395         if (is_self_sent && !iface->master)
396                 return; /* Looped back */
397 
398         avl_for_each_element(&interfaces, c, avl) {
399                 if (iface != c && c->ndp == MODE_RELAY &&
400                                 (ns_is_dad || !c->external))
401                         ping6(&req->nd_ns_target, c);
402         }
403 
404         /* Catch global-addressed NS and answer them manually.
405          * The kernel won't answer these and cannot route them either. */
406         if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) &&
407                         IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src) && !is_self_sent) {
408                 bool is_proxy_neigh = netlink_get_interface_proxy_neigh(iface->ifindex,
409                                 &req->nd_ns_target) == 1;
410 
411                 if (is_proxy_neigh)
412                         send_na(&ip6->ip6_src, iface, &req->nd_ns_target, mac);
413         }
414 }
415 
416 /* Use rtnetlink to modify kernel routes */
417 static void setup_route(struct in6_addr *addr, struct interface *iface, bool add)
418 {
419         char ipbuf[INET6_ADDRSTRLEN];
420 
421         inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
422         debug("%s about %s%s on %s",
423               (add) ? "Learning" : "Forgetting",
424               iface->learn_routes ? "proxy routing for " : "",
425               ipbuf, iface->name);
426 
427         if (iface->learn_routes)
428                 netlink_setup_route(addr, 128, iface->ifindex, NULL, 1024, add);
429 }
430 
431 static void setup_addr_for_relaying(struct in6_addr *addr, struct interface *iface, bool add)
432 {
433         struct interface *c;
434         char ipbuf[INET6_ADDRSTRLEN];
435 
436         inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
437 
438         avl_for_each_element(&interfaces, c, avl) {
439                 if (iface == c || c->ndp != MODE_RELAY)
440                         continue;
441 
442                 if (netlink_setup_proxy_neigh(addr, c->ifindex, add)) {
443                         if (add)
444                                 error("Failed to add proxy neighbour entry %s on %s",
445                                       ipbuf, c->name);
446                 } else
447                         debug("%s proxy neighbour entry %s on %s",
448                               add ? "Added" : "Deleted", ipbuf, c->name);
449         }
450 }
451 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt