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/filter.h>
30 #include <linux/neighbour.h>
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
);
42 /* Filter ICMPv6 messages of type neighbor soliciation */
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),
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
, };
55 /* Initialize NDP-proxy */
60 if (netlink_add_netevent_handler(&ndp_netevent_handler
) < 0) {
61 syslog(LOG_ERR
, "Failed to add ndp netevent handler");
68 int ndp_setup_interface(struct interface
*iface
, bool enable
)
71 bool dump_neigh
= false;
74 enable
= enable
&& (iface
->ndp
== MODE_RELAY
);
76 snprintf(procbuf
, sizeof(procbuf
), "/proc/sys/net/ipv6/conf/%s/proxy_ndp", iface
->ifname
);
77 procfd
= open(procbuf
, O_WRONLY
);
84 if (iface
->ndp_ping_fd
>= 0) {
85 close(iface
->ndp_ping_fd
);
86 iface
->ndp_ping_fd
= -1;
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;
95 if (write(procfd
, "0\n", 2) < 0) {}
101 struct sockaddr_ll ll
;
102 struct packet_mreq mreq
;
103 struct icmp6_filter filt
;
106 if (write(procfd
, "1\n", 2) < 0) {}
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");
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");
123 if (setsockopt(iface
->ndp_ping_fd
, IPPROTO_RAW
, IPV6_CHECKSUM
,
124 &val
, sizeof(val
)) < 0) {
125 syslog(LOG_ERR
, "setsockopt(IPV6_CHECKSUM): %m");
130 /* This is required by RFC 4861 */
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");
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");
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");
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");
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");
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");
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
);
185 if (bind(iface
->ndp_event
.uloop
.fd
, (struct sockaddr
*)&ll
, sizeof(ll
)) < 0) {
186 syslog(LOG_ERR
, "bind(): %m");
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
;
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");
203 iface
->ndp_event
.handle_dgram
= handle_solicit
;
204 odhcpd_register(&iface
->ndp_event
);
206 /* If we already were enabled dump is unnecessary, if not do dump */
208 netlink_dump_neigh_table(false);
214 netlink_dump_neigh_table(true);
218 if (iface
->ndp_event
.uloop
.fd
>= 0) {
219 close(iface
->ndp_event
.uloop
.fd
);
220 iface
->ndp_event
.uloop
.fd
= -1;
223 if (iface
->ndp_ping_fd
>= 0) {
224 close(iface
->ndp_ping_fd
);
225 iface
->ndp_ping_fd
= -1;
235 static void ndp_netevent_cb(unsigned long event
, struct netevent_handler_info
*info
)
237 struct interface
*iface
= info
->iface
;
240 if (!iface
|| iface
->ndp
== MODE_DISABLED
)
244 case NETEV_ADDR6_DEL
:
246 netlink_dump_neigh_table(false);
248 case NETEV_ADDR6_ADD
:
249 setup_addr_for_relaying(&info
->addr
.in6
, iface
, add
);
251 case NETEV_NEIGH6_DEL
:
254 case NETEV_NEIGH6_ADD
:
255 if (info
->neigh
.flags
& NTF_PROXY
) {
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);
265 !(info
->neigh
.state
&
266 (NUD_REACHABLE
|NUD_STALE
|NUD_DELAY
|NUD_PROBE
|NUD_PERMANENT
|NUD_NOARP
)))
269 setup_addr_for_relaying(&info
->neigh
.dst
.in6
, iface
, add
);
270 setup_route(&info
->neigh
.dst
.in6
, iface
, add
);
273 netlink_dump_neigh_table(false);
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
)
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
];
290 inet_ntop(AF_INET6
, addr
, ipbuf
, sizeof(ipbuf
));
291 syslog(LOG_DEBUG
, "Pinging for %s on %s", ipbuf
, iface
->name
);
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);
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
,
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
];
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
}
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);
319 inet_ntop(AF_INET6
, to_addr
, ipbuf
, sizeof(ipbuf
));
320 syslog(LOG_DEBUG
, "Answering NS to %s on %s", ipbuf
, iface
->ifname
);
322 odhcpd_send(iface
->ndp_ping_fd
, &dest
, &iov
, 1, iface
);
325 /* Handle solicitations */
326 static void handle_solicit(void *addr
, void *data
, size_t len
,
327 struct interface
*iface
, _unused
void *dest
)
329 struct ip6_hdr
*ip6
= data
;
330 struct nd_neighbor_solicit
*req
= (struct nd_neighbor_solicit
*)&ip6
[1];
331 struct sockaddr_ll
*ll
= addr
;
333 char ipbuf
[INET6_ADDRSTRLEN
];
336 /* Solicitation is for duplicate address detection */
337 bool ns_is_dad
= IN6_IS_ADDR_UNSPECIFIED(&ip6
->ip6_src
);
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
))
345 if (len
< sizeof(*ip6
) + sizeof(*req
))
346 return; // Invalid reqicitation
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 */
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
);
356 odhcpd_get_mac(iface
, mac
);
357 if (!memcmp(ll
->sll_addr
, mac
, sizeof(mac
)))
358 return; /* Looped back */
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
);
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;
374 send_na(&ip6
->ip6_src
, iface
, &req
->nd_ns_target
, mac
);
378 /* Use rtnetlink to modify kernel routes */
379 static void setup_route(struct in6_addr
*addr
, struct interface
*iface
, bool add
)
381 char ipbuf
[INET6_ADDRSTRLEN
];
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 " : "",
389 if (iface
->learn_routes
)
390 netlink_setup_route(addr
, 128, iface
->ifindex
, NULL
, 1024, add
);
393 static void setup_addr_for_relaying(struct in6_addr
*addr
, struct interface
*iface
, bool add
)
396 char ipbuf
[INET6_ADDRSTRLEN
];
398 inet_ntop(AF_INET6
, addr
, ipbuf
, sizeof(ipbuf
));
400 avl_for_each_element(&interfaces
, c
, avl
) {
401 if (iface
== c
|| c
->ndp
!= MODE_RELAY
)
404 if (netlink_setup_proxy_neigh(addr
, c
->ifindex
, add
)) {
406 syslog(LOG_ERR
, "Failed to add proxy neighbour entry %s on %s",
409 syslog(LOG_DEBUG
, "%s proxy neighbour entry %s on %s",
410 add
? "Added" : "Deleted", ipbuf
, c
->name
);