f2bf19c1f7eb776deec5457909482c25ded0267d
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.
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>
29 #include <linux/rtnetlink.h>
30 #include <linux/filter.h>
37 static void handle_solicit(void *addr
, void *data
, size_t len
,
38 struct interface
*iface
, void *dest
);
39 static void handle_rtnetlink(void *addr
, void *data
, size_t len
,
40 struct interface
*iface
, void *dest
);
41 static void catch_rtnetlink(int error
);
43 static uint32_t rtnl_seqid
= 0;
44 static int ping_socket
= -1;
45 static struct odhcpd_event rtnl_event
= {{.fd
= -1}, handle_rtnetlink
, catch_rtnetlink
};
48 // Filter ICMPv6 messages of type neighbor soliciation
49 static struct sock_filter bpf
[] = {
50 BPF_STMT(BPF_LD
| BPF_B
| BPF_ABS
, offsetof(struct ip6_hdr
, ip6_nxt
)),
51 BPF_JUMP(BPF_JMP
| BPF_JEQ
| BPF_K
, IPPROTO_ICMPV6
, 0, 3),
52 BPF_STMT(BPF_LD
| BPF_B
| BPF_ABS
, sizeof(struct ip6_hdr
) +
53 offsetof(struct icmp6_hdr
, icmp6_type
)),
54 BPF_JUMP(BPF_JMP
| BPF_JEQ
| BPF_K
, ND_NEIGHBOR_SOLICIT
, 0, 1),
55 BPF_STMT(BPF_RET
| BPF_K
, 0xffffffff),
56 BPF_STMT(BPF_RET
| BPF_K
, 0),
58 static const struct sock_fprog bpf_prog
= {sizeof(bpf
) / sizeof(*bpf
), bpf
};
61 // Initialize NDP-proxy
66 // Setup netlink socket
67 if ((rtnl_event
.uloop
.fd
= socket(AF_NETLINK
, SOCK_RAW
| SOCK_CLOEXEC
, NETLINK_ROUTE
)) < 0)
70 struct sockaddr_nl nl
= {.nl_family
= AF_NETLINK
};
71 if (connect(rtnl_event
.uloop
.fd
, (struct sockaddr
*)&nl
, sizeof(nl
)) < 0)
74 if (setsockopt(rtnl_event
.uloop
.fd
, SOL_SOCKET
, SO_RCVBUF
, &val
, sizeof(val
)))
75 setsockopt(rtnl_event
.uloop
.fd
, SOL_SOCKET
, SO_RCVBUFFORCE
, &val
, sizeof(val
));
77 // Receive netlink neighbor and ip-address events
78 uint32_t group
= RTNLGRP_IPV6_IFADDR
;
79 setsockopt(rtnl_event
.uloop
.fd
, SOL_NETLINK
,
80 NETLINK_ADD_MEMBERSHIP
, &group
, sizeof(group
));
81 group
= RTNLGRP_IPV6_ROUTE
;
82 setsockopt(rtnl_event
.uloop
.fd
, SOL_NETLINK
,
83 NETLINK_ADD_MEMBERSHIP
, &group
, sizeof(group
));
85 odhcpd_register(&rtnl_event
);
88 ping_socket
= socket(AF_INET6
, SOCK_RAW
| SOCK_CLOEXEC
, IPPROTO_ICMPV6
);
89 if (ping_socket
< 0) {
90 syslog(LOG_ERR
, "Unable to open raw socket: %s", strerror(errno
));
95 setsockopt(ping_socket
, IPPROTO_RAW
, IPV6_CHECKSUM
, &val
, sizeof(val
));
97 // This is required by RFC 4861
99 setsockopt(ping_socket
, IPPROTO_IPV6
, IPV6_MULTICAST_HOPS
, &val
, sizeof(val
));
100 setsockopt(ping_socket
, IPPROTO_IPV6
, IPV6_UNICAST_HOPS
, &val
, sizeof(val
));
102 // Filter all packages, we only want to send
103 struct icmp6_filter filt
;
104 ICMP6_FILTER_SETBLOCKALL(&filt
);
105 setsockopt(ping_socket
, IPPROTO_ICMPV6
, ICMP6_FILTER
, &filt
, sizeof(filt
));
108 // Netlink socket, continued...
109 group
= RTNLGRP_NEIGH
;
110 setsockopt(rtnl_event
.uloop
.fd
, SOL_NETLINK
, NETLINK_ADD_MEMBERSHIP
, &group
, sizeof(group
));
116 static void dump_neigh_table(bool proxy
)
122 {sizeof(req
), RTM_GETNEIGH
, NLM_F_REQUEST
| NLM_F_DUMP
,
124 {.ndm_family
= AF_INET6
, .ndm_flags
= (proxy
) ? NTF_PROXY
: 0}
126 send(rtnl_event
.uloop
.fd
, &req
, sizeof(req
), MSG_DONTWAIT
);
127 odhcpd_process(&rtnl_event
);
131 int setup_ndp_interface(struct interface
*iface
, bool enable
)
134 snprintf(procbuf
, sizeof(procbuf
), "/proc/sys/net/ipv6/conf/%s/proxy_ndp", iface
->ifname
);
135 int procfd
= open(procbuf
, O_WRONLY
);
136 bool dump_neigh
= false;
138 if (iface
->ndp_event
.uloop
.fd
> 0) {
139 uloop_fd_delete(&iface
->ndp_event
.uloop
);
140 close(iface
->ndp_event
.uloop
.fd
);
141 iface
->ndp_event
.uloop
.fd
= -1;
143 if (!enable
|| iface
->ndp
!= RELAYD_RELAY
)
144 if (write(procfd
, "0\n", 2) < 0) {}
149 if (enable
&& (iface
->ra
== RELAYD_SERVER
||
150 iface
->dhcpv6
== RELAYD_SERVER
|| iface
->ndp
== RELAYD_RELAY
)) {
151 // Synthesize initial address events
154 struct ifaddrmsg ifa
;
156 {sizeof(req2
), RTM_GETADDR
, NLM_F_REQUEST
| NLM_F_DUMP
,
158 {.ifa_family
= AF_INET6
, .ifa_index
= iface
->ifindex
}
160 send(rtnl_event
.uloop
.fd
, &req2
, sizeof(req2
), MSG_DONTWAIT
);
163 if (enable
&& iface
->ndp
== RELAYD_RELAY
) {
164 if (write(procfd
, "1\n", 2) < 0) {}
167 int sock
= socket(AF_PACKET
, SOCK_DGRAM
| SOCK_CLOEXEC
, htons(ETH_P_IPV6
));
169 syslog(LOG_ERR
, "Unable to open packet socket: %s",
174 #ifdef PACKET_RECV_TYPE
175 int pktt
= 1 << PACKET_MULTICAST
;
176 setsockopt(sock
, SOL_PACKET
, PACKET_RECV_TYPE
, &pktt
, sizeof(pktt
));
179 if (setsockopt(sock
, SOL_SOCKET
, SO_ATTACH_FILTER
,
180 &bpf_prog
, sizeof(bpf_prog
))) {
181 syslog(LOG_ERR
, "Failed to set BPF: %s", strerror(errno
));
185 struct sockaddr_ll ll
= {
186 .sll_family
= AF_PACKET
,
187 .sll_ifindex
= iface
->ifindex
,
188 .sll_protocol
= htons(ETH_P_IPV6
),
194 bind(sock
, (struct sockaddr
*)&ll
, sizeof(ll
));
196 struct packet_mreq mreq
= {iface
->ifindex
, PACKET_MR_ALLMULTI
, ETH_ALEN
, {0}};
197 setsockopt(sock
, SOL_PACKET
, PACKET_ADD_MEMBERSHIP
, &mreq
, sizeof(mreq
));
199 iface
->ndp_event
.uloop
.fd
= sock
;
200 iface
->ndp_event
.handle_dgram
= handle_solicit
;
201 odhcpd_register(&iface
->ndp_event
);
203 // If we already were enabled dump is unnecessary, if not do dump
205 dump_neigh_table(false);
213 dump_neigh_table(true);
219 // Send an ICMP-ECHO. This is less for actually pinging but for the
220 // neighbor cache to be kept up-to-date.
221 static void ping6(struct in6_addr
*addr
,
222 const struct interface
*iface
)
224 struct sockaddr_in6 dest
= {AF_INET6
, 0, 0, *addr
, iface
->ifindex
};
225 struct icmp6_hdr echo
= {.icmp6_type
= ICMP6_ECHO_REQUEST
};
226 struct iovec iov
= {&echo
, sizeof(echo
)};
228 odhcpd_setup_route(addr
, 128, iface
, NULL
, 128, true);
229 odhcpd_send(ping_socket
, &dest
, &iov
, 1, iface
);
230 odhcpd_setup_route(addr
, 128, iface
, NULL
, 128, false);
234 // Handle solicitations
235 static void handle_solicit(void *addr
, void *data
, size_t len
,
236 struct interface
*iface
, _unused
void *dest
)
238 struct ip6_hdr
*ip6
= data
;
239 struct nd_neighbor_solicit
*req
= (struct nd_neighbor_solicit
*)&ip6
[1];
240 struct sockaddr_ll
*ll
= addr
;
242 // Solicitation is for duplicate address detection
243 bool ns_is_dad
= IN6_IS_ADDR_UNSPECIFIED(&ip6
->ip6_src
);
245 // Don't forward any non-DAD solicitation for external ifaces
246 // TODO: check if we should even forward DADs for them
247 if (iface
->external
&& !ns_is_dad
)
250 if (len
< sizeof(*ip6
) + sizeof(*req
))
251 return; // Invalid reqicitation
253 if (IN6_IS_ADDR_LINKLOCAL(&req
->nd_ns_target
) ||
254 IN6_IS_ADDR_LOOPBACK(&req
->nd_ns_target
) ||
255 IN6_IS_ADDR_MULTICAST(&req
->nd_ns_target
))
256 return; // Invalid target
258 char ipbuf
[INET6_ADDRSTRLEN
];
259 inet_ntop(AF_INET6
, &req
->nd_ns_target
, ipbuf
, sizeof(ipbuf
));
260 syslog(LOG_DEBUG
, "Got a NS for %s", ipbuf
);
263 odhcpd_get_mac(iface
, mac
);
264 if (!memcmp(ll
->sll_addr
, mac
, sizeof(mac
)))
265 return; // Looped back
268 list_for_each_entry(c
, &interfaces
, head
)
269 if (iface
->ndp
== RELAYD_RELAY
&& iface
!= c
&&
270 (ns_is_dad
|| !c
->external
))
271 ping6(&req
->nd_ns_target
, c
);
274 // Use rtnetlink to modify kernel routes
275 static void setup_route(struct in6_addr
*addr
, struct interface
*iface
, bool add
)
277 char namebuf
[INET6_ADDRSTRLEN
];
278 inet_ntop(AF_INET6
, addr
, namebuf
, sizeof(namebuf
));
279 syslog(LOG_NOTICE
, "%s about %s on %s",
280 (add
) ? "Learned" : "Forgot", namebuf
, iface
->ifname
);
282 if (iface
->learn_routes
)
283 odhcpd_setup_route(addr
, 128, iface
, NULL
, 1024, add
);
287 static int prefixcmp(const void *va
, const void *vb
)
289 const struct odhcpd_ipaddr
*a
= va
, *b
= vb
;
290 uint32_t a_pref
= ((a
->addr
.s6_addr
[0] & 0xfe) != 0xfc) ? a
->preferred
: 1;
291 uint32_t b_pref
= ((b
->addr
.s6_addr
[0] & 0xfe) != 0xfc) ? b
->preferred
: 1;
292 return (a_pref
< b_pref
) ? 1 : (a_pref
> b_pref
) ? -1 : 0;
295 // Check address update
296 static void check_updates(struct interface
*iface
)
298 struct odhcpd_ipaddr addr
[RELAYD_MAX_ADDRS
] = {{IN6ADDR_ANY_INIT
, 0, 0, 0, 0}};
299 time_t now
= odhcpd_time();
300 ssize_t len
= odhcpd_get_interface_addresses(iface
->ifindex
, addr
, ARRAY_SIZE(addr
));
305 qsort(addr
, len
, sizeof(*addr
), prefixcmp
);
307 for (int i
= 0; i
< len
; ++i
) {
308 addr
[i
].addr
.s6_addr32
[3] = 0;
310 if (addr
[i
].preferred
< UINT32_MAX
- now
)
311 addr
[i
].preferred
+= now
;
313 if (addr
[i
].valid
< UINT32_MAX
- now
)
314 addr
[i
].valid
+= now
;
317 bool change
= len
!= (ssize_t
)iface
->ia_addr_len
;
318 for (ssize_t i
= 0; !change
&& i
< len
; ++i
)
319 if (!IN6_ARE_ADDR_EQUAL(&addr
[i
].addr
, &iface
->ia_addr
[i
].addr
) ||
320 (addr
[i
].preferred
> 0) != (iface
->ia_addr
[i
].preferred
> 0) ||
321 addr
[i
].valid
< iface
->ia_addr
[i
].valid
||
322 addr
[i
].preferred
< iface
->ia_addr
[i
].preferred
)
326 dhcpv6_ia_preupdate(iface
);
328 memcpy(iface
->ia_addr
, addr
, len
* sizeof(*addr
));
329 iface
->ia_addr_len
= len
;
332 dhcpv6_ia_postupdate(iface
, now
);
335 syslog(LOG_INFO
, "Raising SIGUSR1 due to address change on %s", iface
->ifname
);
341 // Handler for neighbor cache entries from the kernel. This is our source
342 // to learn and unlearn hosts on interfaces.
343 static void handle_rtnetlink(_unused
void *addr
, void *data
, size_t len
,
344 _unused
struct interface
*iface
, _unused
void *dest
)
346 bool dump_neigh
= false;
347 struct in6_addr last_solicited
= IN6ADDR_ANY_INIT
;
349 for (struct nlmsghdr
*nh
= data
; NLMSG_OK(nh
, len
);
350 nh
= NLMSG_NEXT(nh
, len
)) {
351 struct ndmsg
*ndm
= NLMSG_DATA(nh
);
352 struct rtmsg
*rtm
= NLMSG_DATA(nh
);
354 bool is_addr
= (nh
->nlmsg_type
== RTM_NEWADDR
355 || nh
->nlmsg_type
== RTM_DELADDR
);
356 bool is_route
= (nh
->nlmsg_type
== RTM_NEWROUTE
357 || nh
->nlmsg_type
== RTM_DELROUTE
);
358 bool is_neigh
= (nh
->nlmsg_type
== RTM_NEWNEIGH
359 || nh
->nlmsg_type
== RTM_DELNEIGH
);
361 // Family and ifindex are on the same offset for NEIGH and ADDR
362 if ((!is_addr
&& !is_route
&& !is_neigh
)
363 || NLMSG_PAYLOAD(nh
, 0) < sizeof(*ndm
)
364 || ndm
->ndm_family
!= AF_INET6
)
368 // Inform about a change in default route
369 if (rtm
->rtm_dst_len
== 0) {
370 syslog(LOG_INFO
, "Raising SIGUSR1 due to default route change");
378 size_t rta_offset
= (is_addr
) ? sizeof(struct ifaddrmsg
) : sizeof(*ndm
);
379 uint16_t atype
= (is_addr
) ? IFA_ADDRESS
: NDA_DST
;
380 ssize_t alen
= NLMSG_PAYLOAD(nh
, rta_offset
);
381 struct in6_addr
*addr
= NULL
;
383 for (struct rtattr
*rta
= (void*)(((uint8_t*)ndm
) + rta_offset
);
384 RTA_OK(rta
, alen
); rta
= RTA_NEXT(rta
, alen
)) {
385 if (rta
->rta_type
== atype
&&
386 RTA_PAYLOAD(rta
) >= sizeof(*addr
)) {
387 addr
= RTA_DATA(rta
);
392 struct interface
*iface
= odhcpd_get_interface_by_index(ndm
->ndm_ifindex
);
396 // Address not specified or unrelated
397 if (!addr
|| IN6_IS_ADDR_LINKLOCAL(addr
) ||
398 IN6_IS_ADDR_MULTICAST(addr
))
404 add
= (nh
->nlmsg_type
== RTM_NEWADDR
);
406 add
= (nh
->nlmsg_type
== RTM_NEWNEIGH
&& (ndm
->ndm_state
&
407 (NUD_REACHABLE
| NUD_STALE
| NUD_DELAY
| NUD_PROBE
408 | NUD_PERMANENT
| NUD_NOARP
)));
410 if (iface
->ndp
== RELAYD_RELAY
) {
411 // Replay change to all neighbor cache
415 struct nlattr nla_dst
;
418 {sizeof(req
), RTM_DELNEIGH
, NLM_F_REQUEST
,
420 {.ndm_family
= AF_INET6
, .ndm_flags
= NTF_PROXY
},
421 {sizeof(struct nlattr
) + sizeof(struct in6_addr
), NDA_DST
},
425 if (ndm
->ndm_flags
& NTF_PROXY
) {
426 // Dump & flush proxy entries
427 if (nh
->nlmsg_type
== RTM_NEWNEIGH
) {
428 req
.ndm
.ndm_ifindex
= iface
->ifindex
;
429 send(rtnl_event
.uloop
.fd
, &req
, sizeof(req
), MSG_DONTWAIT
);
430 setup_route(addr
, iface
, false);
435 list_for_each_entry(c
, &interfaces
, head
) {
439 if (c
->ndp
== RELAYD_RELAY
) {
440 req
.nh
.nlmsg_type
= RTM_NEWNEIGH
;
441 req
.nh
.nlmsg_flags
|= NLM_F_CREATE
| NLM_F_REPLACE
;
443 req
.ndm
.ndm_ifindex
= c
->ifindex
;
444 send(rtnl_event
.uloop
.fd
, &req
, sizeof(req
), MSG_DONTWAIT
);
445 } else { // Delete NDP cache from interfaces without relay
446 req
.nh
.nlmsg_type
= RTM_DELNEIGH
;
447 req
.nh
.nlmsg_flags
&= ~(NLM_F_CREATE
| NLM_F_REPLACE
);
449 req
.ndm
.ndm_ifindex
= c
->ifindex
;
450 send(rtnl_event
.uloop
.fd
, &req
, sizeof(req
), MSG_DONTWAIT
);
454 setup_route(addr
, iface
, true);
456 if (nh
->nlmsg_type
== RTM_NEWNEIGH
) {
457 // might be locally originating
458 if (!IN6_ARE_ADDR_EQUAL(&last_solicited
, addr
)) {
459 last_solicited
= *addr
;
462 list_for_each_entry(c
, &interfaces
, head
)
463 if (iface
->ndp
== RELAYD_RELAY
&& iface
!= c
&&
464 !c
->external
== false)
469 list_for_each_entry(c
, &interfaces
, head
) {
470 if (c
->ndp
== RELAYD_RELAY
&& iface
!= c
) {
471 req
.ndm
.ndm_ifindex
= c
->ifindex
;
472 send(rtnl_event
.uloop
.fd
, &req
, sizeof(req
), MSG_DONTWAIT
);
475 setup_route(addr
, iface
, false);
477 // also: dump to add proxies back in case it moved elsewhere
484 check_updates(iface
);
486 if (iface
->ndp
== RELAYD_RELAY
&& iface
->master
) {
487 // Replay address changes on all slave interfaces
488 nh
->nlmsg_flags
= NLM_F_REQUEST
;
490 if (nh
->nlmsg_type
== RTM_NEWADDR
)
491 nh
->nlmsg_flags
|= NLM_F_CREATE
| NLM_F_REPLACE
;
494 list_for_each_entry(c
, &interfaces
, head
) {
495 if (c
->ndp
== RELAYD_RELAY
&& !c
->master
) {
496 ndm
->ndm_ifindex
= c
->ifindex
;
497 send(rtnl_event
.uloop
.fd
, nh
, nh
->nlmsg_len
, MSG_DONTWAIT
);
505 dump_neigh_table(false);
508 static void catch_rtnetlink(int error
)
510 if (error
== ENOBUFS
) {
513 struct ifaddrmsg ifa
;
515 {sizeof(req2
), RTM_GETADDR
, NLM_F_REQUEST
| NLM_F_DUMP
,
517 {.ifa_family
= AF_INET6
}
519 send(rtnl_event
.uloop
.fd
, &req2
, sizeof(req2
), MSG_DONTWAIT
);