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