• 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 /* Filter ICMPv6 messages of type neighbor solicitation */
 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, "\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_DEBUG, "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 total length
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_DEBUG, "%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 }
413 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt