a9dbd9a635ce30214ad7697147254ce565874a4a
[project/odhcpd.git] / src / ndp.c
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 static int ping_socket = -1;
43
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),
53 };
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, };
56
57 // Initialize NDP-proxy
58 int ndp_init(void)
59 {
60         int val = 2;
61
62         // Open ICMPv6 socket
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));
66                         return -1;
67         }
68
69         setsockopt(ping_socket, IPPROTO_RAW, IPV6_CHECKSUM, &val, sizeof(val));
70
71         // This is required by RFC 4861
72         val = 255;
73         setsockopt(ping_socket, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val));
74         setsockopt(ping_socket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val));
75
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));
80
81         netlink_add_netevent_handler(&ndp_netevent_handler);
82
83         return 0;
84 }
85
86 int ndp_setup_interface(struct interface *iface, bool enable)
87 {
88         int ret = 0, procfd;
89         bool dump_neigh = false;
90         char procbuf[64];
91
92         snprintf(procbuf, sizeof(procbuf), "/proc/sys/net/ipv6/conf/%s/proxy_ndp", iface->ifname);
93         procfd = open(procbuf, O_WRONLY);
94
95         if (procfd < 0) {
96                 ret = -1;
97                 goto out;
98         }
99
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;
104
105                 if (!enable || iface->ndp != MODE_RELAY)
106                         if (write(procfd, "0\n", 2) < 0) {}
107
108                 dump_neigh = true;
109         }
110
111         if (enable && iface->ndp == MODE_RELAY) {
112                 if (write(procfd, "1\n", 2) < 0) {}
113
114                 int sock = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6));
115                 if (sock < 0) {
116                         syslog(LOG_ERR, "Unable to open packet socket: %s",
117                                         strerror(errno));
118                         ret = -1;
119                         goto out;
120                 }
121
122 #ifdef PACKET_RECV_TYPE
123                 int pktt = 1 << PACKET_MULTICAST;
124                 setsockopt(sock, SOL_PACKET, PACKET_RECV_TYPE, &pktt, sizeof(pktt));
125 #endif
126
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));
130                         ret = -1;
131                         goto out;
132                 }
133
134                 struct sockaddr_ll ll = {
135                         .sll_family = AF_PACKET,
136                         .sll_ifindex = iface->ifindex,
137                         .sll_protocol = htons(ETH_P_IPV6),
138                         .sll_hatype = 0,
139                         .sll_pkttype = 0,
140                         .sll_halen = 0,
141                         .sll_addr = {0},
142                 };
143                 bind(sock, (struct sockaddr*)&ll, sizeof(ll));
144
145                 struct packet_mreq mreq = {iface->ifindex, PACKET_MR_ALLMULTI, ETH_ALEN, {0}};
146                 setsockopt(sock, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
147
148                 iface->ndp_event.uloop.fd = sock;
149                 iface->ndp_event.handle_dgram = handle_solicit;
150                 odhcpd_register(&iface->ndp_event);
151
152                 // If we already were enabled dump is unnecessary, if not do dump
153                 if (!dump_neigh)
154                         netlink_dump_neigh_table(false);
155                 else
156                         dump_neigh = false;
157         }
158
159         if (dump_neigh)
160                 netlink_dump_neigh_table(true);
161
162 out:
163         if (procfd >= 0)
164                 close(procfd);
165
166         return ret;
167 }
168
169 static void ndp_netevent_cb(unsigned long event, struct netevent_handler_info *info)
170 {
171         struct interface *iface = info->iface;
172         bool add = true;
173
174         if (!iface || iface->ndp == MODE_DISABLED)
175                 return;
176
177         switch (event) {
178         case NETEV_ADDR6_DEL:
179                 add = false;
180                 netlink_dump_neigh_table(false);
181         case NETEV_ADDR6_ADD:
182                 setup_addr_for_relaying(&info->addr.in6, iface, add);
183                 break;
184         case NETEV_NEIGH6_DEL:
185                 add = false;
186         case NETEV_NEIGH6_ADD:
187                 if (info->neigh.flags & NTF_PROXY) {
188                         if (add) {
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);
192                         }
193                         break;
194                 }
195
196                 if (add &&
197                     !(info->neigh.state &
198                       (NUD_REACHABLE|NUD_STALE|NUD_DELAY|NUD_PROBE|NUD_PERMANENT|NUD_NOARP)))
199                         break;
200
201                 setup_addr_for_relaying(&info->neigh.dst.in6, iface, add);
202                 setup_route(&info->neigh.dst.in6, iface, add);
203
204                 if (!add)
205                         netlink_dump_neigh_table(false);
206                 break;
207         default:
208                 break;
209         }
210 }
211
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)
216 {
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];
221
222         inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
223         syslog(LOG_NOTICE, "Pinging for %s%%%s", ipbuf, iface->ifname);
224
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);
228 }
229
230 // Handle solicitations
231 static void handle_solicit(void *addr, void *data, size_t len,
232                 struct interface *iface, _unused void *dest)
233 {
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];
238         uint8_t mac[6];
239
240         // Solicitation is for duplicate address detection
241         bool ns_is_dad = IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src);
242
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))
247                 return;
248
249         if (len < sizeof(*ip6) + sizeof(*req))
250                 return; // Invalid reqicitation
251
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
256
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);
259
260         odhcpd_get_mac(iface, mac);
261         if (!memcmp(ll->sll_addr, mac, sizeof(mac)))
262                 return; // Looped back
263
264         struct interface *c;
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);
269 }
270
271 // Use rtnetlink to modify kernel routes
272 static void setup_route(struct in6_addr *addr, struct interface *iface, bool add)
273 {
274         char ipbuf[INET6_ADDRSTRLEN];
275
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);
281
282         if (iface->learn_routes)
283                 netlink_setup_route(addr, 128, iface->ifindex, NULL, 1024, add);
284 }
285
286 static void setup_addr_for_relaying(struct in6_addr *addr, struct interface *iface, bool add)
287 {
288         struct interface *c;
289         char ipbuf[INET6_ADDRSTRLEN];
290
291         inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
292
293         list_for_each_entry(c, &interfaces, head) {
294                 if (iface == c || (c->ndp != MODE_RELAY && !add))
295                         continue;
296
297                 bool neigh_add = (c->ndp == MODE_RELAY ? add : false);
298
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);
302                 else
303                         syslog(LOG_DEBUG, "%s proxy neighbour entry %s%%%s",
304                                 neigh_add ? "Added" : "Deleted", ipbuf, c->ifname);
305         }
306 }