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 static int ping_socket
= -1;
44 // Filter ICMPv6 messages of type neighbor soliciation
45 static struct sock_filter bpf
[] = {
46 BPF_STMT(BPF_LD
| BPF_B
| BPF_ABS
, offsetof(struct ip6_hdr
, ip6_nxt
)),
47 BPF_JUMP(BPF_JMP
| BPF_JEQ
| BPF_K
, IPPROTO_ICMPV6
, 0, 3),
48 BPF_STMT(BPF_LD
| BPF_B
| BPF_ABS
, sizeof(struct ip6_hdr
) +
49 offsetof(struct icmp6_hdr
, icmp6_type
)),
50 BPF_JUMP(BPF_JMP
| BPF_JEQ
| BPF_K
, ND_NEIGHBOR_SOLICIT
, 0, 1),
51 BPF_STMT(BPF_RET
| BPF_K
, 0xffffffff),
52 BPF_STMT(BPF_RET
| BPF_K
, 0),
54 static const struct sock_fprog bpf_prog
= {sizeof(bpf
) / sizeof(*bpf
), bpf
};
55 static struct netevent_handler ndp_netevent_handler
= { .cb
= ndp_netevent_cb
, };
57 // Initialize NDP-proxy
63 ping_socket
= socket(AF_INET6
, SOCK_RAW
| SOCK_CLOEXEC
, IPPROTO_ICMPV6
);
64 if (ping_socket
< 0) {
65 syslog(LOG_ERR
, "Unable to open raw socket: %s", strerror(errno
));
69 setsockopt(ping_socket
, IPPROTO_RAW
, IPV6_CHECKSUM
, &val
, sizeof(val
));
71 // This is required by RFC 4861
73 setsockopt(ping_socket
, IPPROTO_IPV6
, IPV6_MULTICAST_HOPS
, &val
, sizeof(val
));
74 setsockopt(ping_socket
, IPPROTO_IPV6
, IPV6_UNICAST_HOPS
, &val
, sizeof(val
));
76 // Filter all packages, we only want to send
77 struct icmp6_filter filt
;
78 ICMP6_FILTER_SETBLOCKALL(&filt
);
79 setsockopt(ping_socket
, IPPROTO_ICMPV6
, ICMP6_FILTER
, &filt
, sizeof(filt
));
81 netlink_add_netevent_handler(&ndp_netevent_handler
);
86 int ndp_setup_interface(struct interface
*iface
, bool enable
)
89 bool dump_neigh
= false;
92 snprintf(procbuf
, sizeof(procbuf
), "/proc/sys/net/ipv6/conf/%s/proxy_ndp", iface
->ifname
);
93 procfd
= open(procbuf
, O_WRONLY
);
100 if (iface
->ndp_event
.uloop
.fd
> 0) {
101 uloop_fd_delete(&iface
->ndp_event
.uloop
);
102 close(iface
->ndp_event
.uloop
.fd
);
103 iface
->ndp_event
.uloop
.fd
= -1;
105 if (!enable
|| iface
->ndp
!= MODE_RELAY
)
106 if (write(procfd
, "0\n", 2) < 0) {}
111 if (enable
&& iface
->ndp
== MODE_RELAY
) {
112 if (write(procfd
, "1\n", 2) < 0) {}
114 int sock
= socket(AF_PACKET
, SOCK_DGRAM
| SOCK_CLOEXEC
, htons(ETH_P_IPV6
));
116 syslog(LOG_ERR
, "Unable to open packet socket: %s",
122 #ifdef PACKET_RECV_TYPE
123 int pktt
= 1 << PACKET_MULTICAST
;
124 setsockopt(sock
, SOL_PACKET
, PACKET_RECV_TYPE
, &pktt
, sizeof(pktt
));
127 if (setsockopt(sock
, SOL_SOCKET
, SO_ATTACH_FILTER
,
128 &bpf_prog
, sizeof(bpf_prog
))) {
129 syslog(LOG_ERR
, "Failed to set BPF: %s", strerror(errno
));
134 struct sockaddr_ll ll
= {
135 .sll_family
= AF_PACKET
,
136 .sll_ifindex
= iface
->ifindex
,
137 .sll_protocol
= htons(ETH_P_IPV6
),
143 bind(sock
, (struct sockaddr
*)&ll
, sizeof(ll
));
145 struct packet_mreq mreq
= {iface
->ifindex
, PACKET_MR_ALLMULTI
, ETH_ALEN
, {0}};
146 setsockopt(sock
, SOL_PACKET
, PACKET_ADD_MEMBERSHIP
, &mreq
, sizeof(mreq
));
148 iface
->ndp_event
.uloop
.fd
= sock
;
149 iface
->ndp_event
.handle_dgram
= handle_solicit
;
150 odhcpd_register(&iface
->ndp_event
);
152 // If we already were enabled dump is unnecessary, if not do dump
154 netlink_dump_neigh_table(false);
160 netlink_dump_neigh_table(true);
169 static void ndp_netevent_cb(unsigned long event
, struct netevent_handler_info
*info
)
171 struct interface
*iface
= info
->iface
;
174 if (!iface
|| iface
->ndp
== MODE_DISABLED
)
178 case NETEV_ADDR6_DEL
:
180 netlink_dump_neigh_table(false);
181 case NETEV_ADDR6_ADD
:
182 setup_addr_for_relaying(&info
->addr
.in6
, iface
, add
);
184 case NETEV_NEIGH6_DEL
:
186 case NETEV_NEIGH6_ADD
:
187 if (info
->neigh
.flags
& NTF_PROXY
) {
189 netlink_setup_proxy_neigh(&info
->neigh
.dst
.in6
, iface
->ifindex
, false);
190 setup_route(&info
->neigh
.dst
.in6
, iface
, false);
191 netlink_dump_neigh_table(false);
197 !(info
->neigh
.state
&
198 (NUD_REACHABLE
|NUD_STALE
|NUD_DELAY
|NUD_PROBE
|NUD_PERMANENT
|NUD_NOARP
)))
201 setup_addr_for_relaying(&info
->neigh
.dst
.in6
, iface
, add
);
202 setup_route(&info
->neigh
.dst
.in6
, iface
, add
);
205 netlink_dump_neigh_table(false);
212 // Send an ICMP-ECHO. This is less for actually pinging but for the
213 // neighbor cache to be kept up-to-date.
214 static void ping6(struct in6_addr
*addr
,
215 const struct interface
*iface
)
217 struct sockaddr_in6 dest
= { .sin6_family
= AF_INET6
, .sin6_addr
= *addr
, .sin6_scope_id
= iface
->ifindex
, };
218 struct icmp6_hdr echo
= { .icmp6_type
= ICMP6_ECHO_REQUEST
};
219 struct iovec iov
= { .iov_base
= &echo
, .iov_len
= sizeof(echo
) };
220 char ipbuf
[INET6_ADDRSTRLEN
];
222 inet_ntop(AF_INET6
, addr
, ipbuf
, sizeof(ipbuf
));
223 syslog(LOG_NOTICE
, "Pinging for %s%%%s", ipbuf
, iface
->ifname
);
225 netlink_setup_route(addr
, 128, iface
->ifindex
, NULL
, 128, true);
226 odhcpd_send(ping_socket
, &dest
, &iov
, 1, iface
);
227 netlink_setup_route(addr
, 128, iface
->ifindex
, NULL
, 128, false);
230 // Handle solicitations
231 static void handle_solicit(void *addr
, void *data
, size_t len
,
232 struct interface
*iface
, _unused
void *dest
)
234 struct ip6_hdr
*ip6
= data
;
235 struct nd_neighbor_solicit
*req
= (struct nd_neighbor_solicit
*)&ip6
[1];
236 struct sockaddr_ll
*ll
= addr
;
237 char ipbuf
[INET6_ADDRSTRLEN
];
240 // Solicitation is for duplicate address detection
241 bool ns_is_dad
= IN6_IS_ADDR_UNSPECIFIED(&ip6
->ip6_src
);
243 // Don't process solicit messages on non relay interfaces
244 // Don't forward any non-DAD solicitation for external ifaces
245 // TODO: check if we should even forward DADs for them
246 if (iface
->ndp
!= MODE_RELAY
|| (iface
->external
&& !ns_is_dad
))
249 if (len
< sizeof(*ip6
) + sizeof(*req
))
250 return; // Invalid reqicitation
252 if (IN6_IS_ADDR_LINKLOCAL(&req
->nd_ns_target
) ||
253 IN6_IS_ADDR_LOOPBACK(&req
->nd_ns_target
) ||
254 IN6_IS_ADDR_MULTICAST(&req
->nd_ns_target
))
255 return; // Invalid target
257 inet_ntop(AF_INET6
, &req
->nd_ns_target
, ipbuf
, sizeof(ipbuf
));
258 syslog(LOG_DEBUG
, "Got a NS for %s%%%s", ipbuf
, iface
->ifname
);
260 odhcpd_get_mac(iface
, mac
);
261 if (!memcmp(ll
->sll_addr
, mac
, sizeof(mac
)))
262 return; // Looped back
265 list_for_each_entry(c
, &interfaces
, head
)
266 if (iface
!= c
&& c
->ndp
== MODE_RELAY
&&
267 (ns_is_dad
|| !c
->external
))
268 ping6(&req
->nd_ns_target
, c
);
271 // Use rtnetlink to modify kernel routes
272 static void setup_route(struct in6_addr
*addr
, struct interface
*iface
, bool add
)
274 char ipbuf
[INET6_ADDRSTRLEN
];
276 inet_ntop(AF_INET6
, addr
, ipbuf
, sizeof(ipbuf
));
277 syslog(LOG_NOTICE
, "%s about %s%s%%%s",
278 (add
) ? "Learning" : "Forgetting",
279 iface
->learn_routes
? "proxy routing for " : "",
280 ipbuf
, iface
->ifname
);
282 if (iface
->learn_routes
)
283 netlink_setup_route(addr
, 128, iface
->ifindex
, NULL
, 1024, add
);
286 static void setup_addr_for_relaying(struct in6_addr
*addr
, struct interface
*iface
, bool add
)
289 char ipbuf
[INET6_ADDRSTRLEN
];
291 inet_ntop(AF_INET6
, addr
, ipbuf
, sizeof(ipbuf
));
293 list_for_each_entry(c
, &interfaces
, head
) {
294 if (iface
== c
|| (c
->ndp
!= MODE_RELAY
&& !add
))
297 bool neigh_add
= (c
->ndp
== MODE_RELAY
? add
: false);
299 if (netlink_setup_proxy_neigh(addr
, c
->ifindex
, neigh_add
))
300 syslog(LOG_DEBUG
, "Failed to %s proxy neighbour entry %s%%%s",
301 neigh_add
? "add" : "delete", ipbuf
, c
->ifname
);
303 syslog(LOG_DEBUG
, "%s proxy neighbour entry %s%%%s",
304 neigh_add
? "Added" : "Deleted", ipbuf
, c
->ifname
);