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