2 * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
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.
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.
20 #include <arpa/inet.h>
21 #include <sys/socket.h>
22 #include <net/ethernet.h>
23 #include <netinet/ip6.h>
24 #include <netinet/icmp6.h>
25 #include <netpacket/packet.h>
27 #include <linux/rtnetlink.h>
28 #include <linux/filter.h>
34 static void handle_solicit(void *addr
, void *data
, size_t len
,
35 struct interface
*iface
);
36 static void handle_rtnetlink(void *addr
, void *data
, size_t len
,
37 struct interface
*iface
);
38 static struct ndp_neighbor
* find_neighbor(struct in6_addr
*addr
, bool strict
);
39 static void modify_neighbor(struct in6_addr
*addr
, struct interface
*iface
,
41 static ssize_t
ping6(struct in6_addr
*addr
,
42 const struct interface
*iface
);
44 static struct list_head neighbors
= LIST_HEAD_INIT(neighbors
);
45 static size_t neighbor_count
= 0;
46 static uint32_t rtnl_seqid
= 0;
48 static int ping_socket
= -1;
49 static struct odhcpd_event ndp_event
= {{.fd
= -1}, handle_solicit
};
50 static struct odhcpd_event rtnl_event
= {{.fd
= -1}, handle_rtnetlink
};
53 // Filter ICMPv6 messages of type neighbor soliciation
54 static struct sock_filter bpf
[] = {
55 BPF_STMT(BPF_LD
| BPF_B
| BPF_ABS
, offsetof(struct ip6_hdr
, ip6_nxt
)),
56 BPF_JUMP(BPF_JMP
| BPF_JEQ
| BPF_K
, IPPROTO_ICMPV6
, 0, 3),
57 BPF_STMT(BPF_LD
| BPF_B
| BPF_ABS
, sizeof(struct ip6_hdr
) +
58 offsetof(struct icmp6_hdr
, icmp6_type
)),
59 BPF_JUMP(BPF_JMP
| BPF_JEQ
| BPF_K
, ND_NEIGHBOR_SOLICIT
, 0, 1),
60 BPF_STMT(BPF_RET
| BPF_K
, 0xffffffff),
61 BPF_STMT(BPF_RET
| BPF_K
, 0),
63 static const struct sock_fprog bpf_prog
= {sizeof(bpf
) / sizeof(*bpf
), bpf
};
66 // Initialize NDP-proxy
69 // Setup netlink socket
70 if ((rtnl_event
.uloop
.fd
= odhcpd_open_rtnl()) < 0)
73 // Receive netlink neighbor and ip-address events
74 uint32_t group
= RTNLGRP_IPV6_IFADDR
;
75 setsockopt(rtnl_event
.uloop
.fd
, SOL_NETLINK
,
76 NETLINK_ADD_MEMBERSHIP
, &group
, sizeof(group
));
77 group
= RTNLGRP_IPV6_ROUTE
;
78 setsockopt(rtnl_event
.uloop
.fd
, SOL_NETLINK
,
79 NETLINK_ADD_MEMBERSHIP
, &group
, sizeof(group
));
81 // Synthesize initial address events
86 {sizeof(req2
), RTM_GETADDR
, NLM_F_REQUEST
| NLM_F_DUMP
,
88 {.ifa_family
= AF_INET6
}
90 send(rtnl_event
.uloop
.fd
, &req2
, sizeof(req2
), MSG_DONTWAIT
);
91 odhcpd_register(&rtnl_event
);
94 // Create socket for intercepting NDP
95 int sock
= socket(AF_PACKET
, SOCK_DGRAM
| SOCK_CLOEXEC
| SOCK_NONBLOCK
,
96 htons(ETH_P_ALL
)); // ETH_P_ALL for ingress + egress
98 syslog(LOG_ERR
, "Unable to open packet socket: %s",
103 if (setsockopt(sock
, SOL_SOCKET
, SO_ATTACH_FILTER
,
104 &bpf_prog
, sizeof(bpf_prog
))) {
105 syslog(LOG_ERR
, "Failed to set BPF: %s", strerror(errno
));
109 ndp_event
.uloop
.fd
= sock
;
110 odhcpd_register(&ndp_event
);
112 // Open ICMPv6 socket
113 ping_socket
= socket(AF_INET6
, SOCK_RAW
| SOCK_CLOEXEC
, IPPROTO_ICMPV6
);
116 setsockopt(ping_socket
, IPPROTO_RAW
, IPV6_CHECKSUM
, &val
, sizeof(val
));
118 // This is required by RFC 4861
120 setsockopt(ping_socket
, IPPROTO_IPV6
, IPV6_MULTICAST_HOPS
, &val
, sizeof(val
));
121 setsockopt(ping_socket
, IPPROTO_IPV6
, IPV6_UNICAST_HOPS
, &val
, sizeof(val
));
123 // Filter all packages, we only want to send
124 struct icmp6_filter filt
;
125 ICMP6_FILTER_SETBLOCKALL(&filt
);
126 setsockopt(ping_socket
, IPPROTO_ICMPV6
, ICMP6_FILTER
, &filt
, sizeof(filt
));
129 // Netlink socket, continued...
130 group
= RTNLGRP_NEIGH
;
131 setsockopt(rtnl_event
.uloop
.fd
, SOL_NETLINK
, NETLINK_ADD_MEMBERSHIP
, &group
, sizeof(group
));
133 // Synthesize initial neighbor events
138 {sizeof(req
), RTM_GETNEIGH
, NLM_F_REQUEST
| NLM_F_DUMP
,
140 {.ndm_family
= AF_INET6
}
142 send(rtnl_event
.uloop
.fd
, &req
, sizeof(req
), MSG_DONTWAIT
);
148 int setup_ndp_interface(struct interface
*iface
, bool enable
)
150 struct packet_mreq mreq
= {iface
->ifindex
, PACKET_MR_ALLMULTI
, ETH_ALEN
, {0}};
151 setsockopt(ndp_event
.uloop
.fd
, SOL_PACKET
, PACKET_DROP_MEMBERSHIP
, &mreq
, sizeof(mreq
));
153 struct ndp_neighbor
*c
, *n
;
154 list_for_each_entry_safe(c
, n
, &neighbors
, head
)
155 if (c
->iface
== iface
&& (c
->timeout
== 0 || iface
->ndp
!= RELAYD_RELAY
|| !enable
))
156 modify_neighbor(&c
->addr
, c
->iface
, false);
158 if (enable
&& iface
->ndp
== RELAYD_RELAY
) {
159 setsockopt(ndp_event
.uloop
.fd
, SOL_PACKET
, PACKET_ADD_MEMBERSHIP
, &mreq
, sizeof(mreq
));
161 if (iface
->static_ndp_len
) {
162 char *entry
= alloca(iface
->static_ndp_len
), *saveptr
;
164 syslog(LOG_ERR
, "Alloca failed for static NDP list");
167 memcpy(entry
, iface
->static_ndp
, iface
->static_ndp_len
);
169 for (entry
= strtok_r(entry
, " ", &saveptr
); entry
; entry
= strtok_r(NULL
, " ", &saveptr
)) {
170 struct ndp_neighbor
*n
= malloc(sizeof(*n
));
172 syslog(LOG_ERR
, "Malloc failed for static NDP-prefix %s", entry
);
179 char ipbuf
[INET6_ADDRSTRLEN
];
180 if (sscanf(entry
, "%45s/%hhu", ipbuf
, &n
->len
) < 2
181 || n
->len
> 128 || inet_pton(AF_INET6
, ipbuf
, &n
->addr
) != 1) {
182 syslog(LOG_ERR
, "Invalid static NDP-prefix %s", entry
);
186 list_add(&n
->head
, &neighbors
);
195 // Send an ICMP-ECHO. This is less for actually pinging but for the
196 // neighbor cache to be kept up-to-date.
197 static ssize_t
ping6(struct in6_addr
*addr
,
198 const struct interface
*iface
)
200 struct sockaddr_in6 dest
= {AF_INET6
, 0, 0, *addr
, 0};
201 struct icmp6_hdr echo
= {.icmp6_type
= ICMP6_ECHO_REQUEST
};
202 struct iovec iov
= {&echo
, sizeof(echo
)};
204 // Linux seems to not honor IPV6_PKTINFO on raw-sockets, so work around
205 setsockopt(ping_socket
, SOL_SOCKET
, SO_BINDTODEVICE
,
206 iface
->ifname
, sizeof(iface
->ifname
));
207 return odhcpd_send(ping_socket
, &dest
, &iov
, 1, iface
);
211 // Handle solicitations
212 static void handle_solicit(void *addr
, void *data
, size_t len
,
213 struct interface
*iface
)
215 struct ip6_hdr
*ip6
= data
;
216 struct nd_neighbor_solicit
*req
= (struct nd_neighbor_solicit
*)&ip6
[1];
217 struct sockaddr_ll
*ll
= addr
;
219 // Solicitation is for duplicate address detection
220 bool ns_is_dad
= IN6_IS_ADDR_UNSPECIFIED(&ip6
->ip6_src
);
222 // Don't forward any non-DAD solicitation for external ifaces
223 // TODO: check if we should even forward DADs for them
224 if (iface
->external
&& !ns_is_dad
)
227 if (len
< sizeof(*ip6
) + sizeof(*req
))
228 return; // Invalid reqicitation
230 if (IN6_IS_ADDR_LINKLOCAL(&req
->nd_ns_target
) ||
231 IN6_IS_ADDR_LOOPBACK(&req
->nd_ns_target
) ||
232 IN6_IS_ADDR_MULTICAST(&req
->nd_ns_target
))
233 return; // Invalid target
235 char ipbuf
[INET6_ADDRSTRLEN
];
236 inet_ntop(AF_INET6
, &req
->nd_ns_target
, ipbuf
, sizeof(ipbuf
));
237 syslog(LOG_NOTICE
, "Got a NS for %s", ipbuf
);
240 odhcpd_get_mac(iface
, mac
);
241 if (!memcmp(ll
->sll_addr
, mac
, sizeof(mac
)) &&
242 ll
->sll_pkttype
!= PACKET_OUTGOING
)
243 return; // Looped back
245 time_t now
= time(NULL
);
247 struct ndp_neighbor
*n
= find_neighbor(&req
->nd_ns_target
, false);
248 if (n
&& (n
->iface
|| abs(n
->timeout
- now
) < 5)) {
249 syslog(LOG_NOTICE
, "%s is on %s", ipbuf
,
250 (n
->iface
) ? n
->iface
->ifname
: "<pending>");
251 if (!n
->iface
|| n
->iface
== iface
)
254 // Found on other interface, answer with advertisement
256 struct nd_neighbor_advert body
;
257 struct nd_opt_hdr opt_ll_hdr
;
261 .nd_na_hdr
= {ND_NEIGHBOR_ADVERT
,
263 .nd_na_target
= req
->nd_ns_target
,
265 .opt_ll_hdr
= {ND_OPT_TARGET_LINKADDR
, 1},
268 memcpy(advert
.mac
, mac
, sizeof(advert
.mac
));
269 advert
.body
.nd_na_flags_reserved
= ND_NA_FLAG_ROUTER
|
270 ND_NA_FLAG_SOLICITED
;
272 struct sockaddr_in6 dest
= {AF_INET6
, 0, 0, ALL_IPV6_NODES
, 0};
273 if (!ns_is_dad
) // If not DAD, then unicast to source
274 dest
.sin6_addr
= ip6
->ip6_src
;
276 // Linux seems to not honor IPV6_PKTINFO on raw-sockets, so work around
277 setsockopt(ping_socket
, SOL_SOCKET
, SO_BINDTODEVICE
,
278 iface
->ifname
, sizeof(iface
->ifname
));
279 struct iovec iov
= {&advert
, sizeof(advert
)};
280 odhcpd_send(ping_socket
, &dest
, &iov
, 1, iface
);
282 // Send echo to all other interfaces to see where target is on
283 // This will trigger neighbor discovery which is what we want.
284 // We will observe the neighbor cache to see results.
288 list_for_each_entry(c
, &interfaces
, head
)
289 if (iface
->ndp
== RELAYD_RELAY
&& iface
!= c
&&
290 (!ns_is_dad
|| !c
->external
== false))
291 sent
+= ping6(&req
->nd_ns_target
, c
);
293 if (sent
> 0) // Sent a ping, add pending neighbor entry
294 modify_neighbor(&req
->nd_ns_target
, NULL
, true);
299 void odhcpd_setup_route(const struct in6_addr
*addr
, int prefixlen
,
300 const struct interface
*iface
, const struct in6_addr
*gw
, bool add
)
305 struct rtattr rta_dst
;
306 struct in6_addr dst_addr
;
307 struct rtattr rta_oif
;
309 struct rtattr rta_table
;
311 struct rtattr rta_gw
;
314 {sizeof(req
), 0, NLM_F_REQUEST
, ++rtnl_seqid
, 0},
315 {AF_INET6
, prefixlen
, 0, 0, 0, 0, 0, 0, 0},
316 {sizeof(struct rtattr
) + sizeof(struct in6_addr
), RTA_DST
},
318 {sizeof(struct rtattr
) + sizeof(uint32_t), RTA_OIF
},
320 {sizeof(struct rtattr
) + sizeof(uint32_t), RTA_TABLE
},
322 {sizeof(struct rtattr
) + sizeof(struct in6_addr
), RTA_GATEWAY
},
330 req
.nh
.nlmsg_type
= RTM_NEWROUTE
;
331 req
.nh
.nlmsg_flags
|= (NLM_F_CREATE
| NLM_F_REPLACE
);
332 req
.rtm
.rtm_protocol
= RTPROT_BOOT
;
333 req
.rtm
.rtm_scope
= (gw
) ? RT_SCOPE_UNIVERSE
: RT_SCOPE_LINK
;
334 req
.rtm
.rtm_type
= RTN_UNICAST
;
336 req
.nh
.nlmsg_type
= RTM_DELROUTE
;
337 req
.rtm
.rtm_scope
= RT_SCOPE_NOWHERE
;
340 size_t reqlen
= (gw
) ? sizeof(req
) : offsetof(struct req
, rta_gw
);
341 send(rtnl_event
.uloop
.fd
, &req
, reqlen
, MSG_DONTWAIT
);
344 // Use rtnetlink to modify kernel routes
345 static void setup_route(struct in6_addr
*addr
, struct interface
*iface
,
348 char namebuf
[INET6_ADDRSTRLEN
];
349 inet_ntop(AF_INET6
, addr
, namebuf
, sizeof(namebuf
));
350 syslog(LOG_NOTICE
, "%s about %s on %s", (add
) ? "Learned" : "Forgot",
351 namebuf
, (iface
) ? iface
->ifname
: "<pending>");
353 if (!iface
|| !iface
->learn_routes
)
356 odhcpd_setup_route(addr
, 128, iface
, NULL
, add
);
359 static void free_neighbor(struct ndp_neighbor
*n
)
361 setup_route(&n
->addr
, n
->iface
, false);
368 static bool match_neighbor(struct ndp_neighbor
*n
, struct in6_addr
*addr
)
371 return ntohl(n
->addr
.s6_addr32
[0]) >> (32 - n
->len
) ==
372 ntohl(addr
->s6_addr32
[0]) >> (32 - n
->len
);
374 if (n
->addr
.s6_addr32
[0] != addr
->s6_addr32
[0])
378 return ntohl(n
->addr
.s6_addr32
[1]) >> (64 - n
->len
) ==
379 ntohl(addr
->s6_addr32
[1]) >> (64 - n
->len
);
381 if (n
->addr
.s6_addr32
[1] != addr
->s6_addr32
[1])
385 return ntohl(n
->addr
.s6_addr32
[2]) >> (96 - n
->len
) ==
386 ntohl(addr
->s6_addr32
[2]) >> (96 - n
->len
);
388 if (n
->addr
.s6_addr32
[2] != addr
->s6_addr32
[2])
391 return ntohl(n
->addr
.s6_addr32
[3]) >> (128 - n
->len
) ==
392 ntohl(addr
->s6_addr32
[3]) >> (128 - n
->len
);
396 static struct ndp_neighbor
* find_neighbor(struct in6_addr
*addr
, bool strict
)
398 time_t now
= time(NULL
);
399 struct ndp_neighbor
*n
, *e
;
400 list_for_each_entry_safe(n
, e
, &neighbors
, head
) {
401 if ((!strict
&& match_neighbor(n
, addr
)) ||
402 (n
->len
== 128 && IN6_ARE_ADDR_EQUAL(&n
->addr
, addr
)))
405 if (!n
->iface
&& abs(n
->timeout
- now
) >= 5)
412 // Modified our own neighbor-entries
413 static void modify_neighbor(struct in6_addr
*addr
,
414 struct interface
*iface
, bool add
)
416 if (!addr
|| (void*)addr
== (void*)iface
)
419 struct ndp_neighbor
*n
= find_neighbor(addr
, true);
420 if (!add
) { // Delete action
421 if (n
&& (!n
->iface
|| n
->iface
== iface
))
423 } else if (!n
) { // No entry yet, add one if possible
424 if (neighbor_count
>= NDP_MAX_NEIGHBORS
||
425 !(n
= malloc(sizeof(*n
))))
433 list_add(&n
->head
, &neighbors
);
435 setup_route(addr
, n
->iface
, add
);
436 } else if (n
->iface
== iface
) {
439 } else if (iface
&& (!n
->iface
||
440 (!iface
->external
&& n
->iface
->external
))) {
441 setup_route(addr
, n
->iface
, false);
443 setup_route(addr
, n
->iface
, add
);
445 // TODO: In case a host switches interfaces we might want
446 // to set its old neighbor entry to NUD_STALE and ping it
447 // on the old interface to confirm if the MACs match.
451 // Handler for neighbor cache entries from the kernel. This is our source
452 // to learn and unlearn hosts on interfaces.
453 static void handle_rtnetlink(_unused
void *addr
, void *data
, size_t len
,
454 _unused
struct interface
*iface
)
456 for (struct nlmsghdr
*nh
= data
; NLMSG_OK(nh
, len
);
457 nh
= NLMSG_NEXT(nh
, len
)) {
458 struct rtmsg
*rtm
= NLMSG_DATA(nh
);
459 if ((nh
->nlmsg_type
== RTM_NEWROUTE
||
460 nh
->nlmsg_type
== RTM_DELROUTE
) &&
461 rtm
->rtm_dst_len
== 0)
462 raise(SIGUSR1
); // Inform about a change in default route
464 struct ndmsg
*ndm
= NLMSG_DATA(nh
);
465 struct ifaddrmsg
*ifa
= NLMSG_DATA(nh
);
466 if (nh
->nlmsg_type
!= RTM_NEWNEIGH
467 && nh
->nlmsg_type
!= RTM_DELNEIGH
468 && nh
->nlmsg_type
!= RTM_NEWADDR
469 && nh
->nlmsg_type
!= RTM_DELADDR
)
470 continue; // Unrelated message type
471 bool is_addr
= (nh
->nlmsg_type
== RTM_NEWADDR
472 || nh
->nlmsg_type
== RTM_DELADDR
);
474 // Family and ifindex are on the same offset for NEIGH and ADDR
475 if (NLMSG_PAYLOAD(nh
, 0) < sizeof(*ndm
)
476 || ndm
->ndm_family
!= AF_INET6
)
480 struct interface
*iface
;
481 if (!(iface
= odhcpd_get_interface_by_index(ndm
->ndm_ifindex
)))
485 size_t rta_offset
= (is_addr
) ? sizeof(*ifa
) : sizeof(*ndm
);
486 uint16_t atype
= (is_addr
) ? IFA_ADDRESS
: NDA_DST
;
487 ssize_t alen
= NLMSG_PAYLOAD(nh
, rta_offset
);
488 struct in6_addr
*addr
= NULL
;
490 for (struct rtattr
*rta
= (void*)(((uint8_t*)ndm
) + rta_offset
);
491 RTA_OK(rta
, alen
); rta
= RTA_NEXT(rta
, alen
))
492 if (rta
->rta_type
== atype
&&
493 RTA_PAYLOAD(rta
) >= sizeof(*addr
))
494 addr
= RTA_DATA(rta
);
496 // Address not specified or unrelated
497 if (!addr
|| IN6_IS_ADDR_LINKLOCAL(addr
) ||
498 IN6_IS_ADDR_MULTICAST(addr
))
504 add
= (nh
->nlmsg_type
== RTM_NEWADDR
);
506 add
= (nh
->nlmsg_type
== RTM_NEWNEIGH
&& (ndm
->ndm_state
&
507 (NUD_REACHABLE
| NUD_STALE
| NUD_DELAY
| NUD_PROBE
508 | NUD_PERMANENT
| NUD_NOARP
)));
510 if (iface
->ndp
== RELAYD_RELAY
)
511 modify_neighbor(addr
, iface
, add
);
513 if (is_addr
&& iface
->ra
== RELAYD_SERVER
)
514 raise(SIGUSR1
); // Inform about a change in addresses
516 if (is_addr
&& iface
->dhcpv6
== RELAYD_SERVER
)
517 iface
->ia_reconf
= true;
519 if (iface
->ndp
== RELAYD_RELAY
&& is_addr
&& iface
->master
) {
520 // Replay address changes on all slave interfaces
521 nh
->nlmsg_flags
= NLM_F_REQUEST
;
523 if (nh
->nlmsg_type
== RTM_NEWADDR
)
524 nh
->nlmsg_flags
|= NLM_F_CREATE
| NLM_F_REPLACE
;
527 list_for_each_entry(c
, &interfaces
, head
) {
528 if (c
->ndp
== RELAYD_RELAY
&& !c
->master
) {
529 ifa
->ifa_index
= c
->ifindex
;
530 send(rtnl_event
.uloop
.fd
, nh
, nh
->nlmsg_len
, MSG_DONTWAIT
);
535 /* TODO: See if this is required for optimal operation
536 // Keep neighbor entries alive so we don't loose routes
538 if (add
&& (ndm
->ndm_state
& NUD_STALE
))