]> git.ipfire.org Git - people/ms/dnsmasq.git/blame - src/radv.c
Fix FTBFS when HAVE_BROKEN_RTC defined.
[people/ms/dnsmasq.git] / src / radv.c
CommitLineData
c5ad4e79
SK
1/* dnsmasq is Copyright (c) 2000-2012 Simon Kelley
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; version 2 dated June, 1991, or
6 (at your option) version 3 dated 29 June, 2007.
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 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17
843c96b4 18/* NB. This code may be called during a DHCPv4 or transaction which is in ping-wait
c5ad4e79 19 It therefore cannot use any DHCP buffer resources except outpacket, which is
843c96b4
SK
20 not used by DHCPv4 code. This code may also be called when DHCP 4 or 6 isn't
21 active, so we ensure that outpacket is allocated here too */
c5ad4e79
SK
22
23#include "dnsmasq.h"
c5ad4e79
SK
24
25#ifdef HAVE_DHCP6
26
22d904db
SK
27#include <netinet/icmp6.h>
28
c5ad4e79
SK
29struct ra_param {
30 int ind, managed, found_context, first;
31 char *if_name;
32 struct in6_addr link_local;
33};
34
35struct search_param {
36 time_t now; int iface;
37};
38
39static void send_ra(int iface, char *iface_name, struct in6_addr *dest);
40static int add_prefixes(struct in6_addr *local, int prefix,
41 int scope, int if_index, int dad, void *vparam);
42static int iface_search(struct in6_addr *local, int prefix,
43 int scope, int if_index, int dad, void *vparam);
44static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm);
45
c5379c1a 46static int hop_limit;
c5ad4e79
SK
47static time_t ra_short_period_start;
48
49void ra_init(time_t now)
50{
c5ad4e79
SK
51 struct icmp6_filter filter;
52 int fd;
53#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6)
54 int class = IPTOS_CLASS_CS6;
55#endif
56 int val = 255; /* radvd uses this value */
c5379c1a 57 size_t len = sizeof(int);
c5ad4e79 58
843c96b4
SK
59 /* ensure this is around even if we're not doing DHCPv6 */
60 expand_buf(&daemon->outpacket, sizeof(struct dhcp_packet));
61
c5ad4e79
SK
62 ICMP6_FILTER_SETBLOCKALL(&filter);
63 ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
64 ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
65
66 if ((fd = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) == -1 ||
c5379c1a 67 getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hop_limit, &len) ||
c5ad4e79
SK
68#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6)
69 setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &class, sizeof(class)) == -1 ||
70#endif
71 !fix_fd(fd) ||
72 !set_ipv6pktinfo(fd) ||
73 setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val)) ||
74 setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)) ||
75 setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) == -1)
76 die (_("cannot create ICMPv6 socket: %s"), NULL, EC_BADNET);
77
78 daemon->icmp6fd = fd;
79
c5ad4e79
SK
80 ra_start_unsolicted(now);
81}
82
83void ra_start_unsolicted(time_t now)
84{
85 struct dhcp_context *context;
86
87 /* init timers so that we do ra's for all soon. some ra_times will end up zeroed
88 if it's not appropriate to advertise those contexts.
89 This gets re-called on a netlink route-change to re-do the advertisement
90 and pick up new interfaces */
91
92 /* range 0 - 5 */
93 for (context = daemon->ra_contexts; context; context = context->next)
94 context->ra_time = now + (rand16()/13000);
95
22d904db 96 /* re-do frequently for a minute or so, in case the first gets lost. */
c5ad4e79
SK
97 ra_short_period_start = now;
98}
99
100void icmp6_packet(void)
101{
102 char interface[IF_NAMESIZE+1];
103 ssize_t sz;
104 int if_index = 0;
105 struct cmsghdr *cmptr;
106 struct msghdr msg;
107 union {
108 struct cmsghdr align; /* this ensures alignment */
109 char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
110 } control_u;
111 struct sockaddr_in6 from;
112 unsigned char *p;
113 char *mac = "";
114 struct iname *tmp;
115 struct dhcp_context *context;
116
117 /* Note: use outpacket for input buffer */
118 msg.msg_control = control_u.control6;
119 msg.msg_controllen = sizeof(control_u);
120 msg.msg_flags = 0;
121 msg.msg_name = &from;
122 msg.msg_namelen = sizeof(from);
123 msg.msg_iov = &daemon->outpacket;
124 msg.msg_iovlen = 1;
125
126 if ((sz = recv_dhcp_packet(daemon->icmp6fd, &msg)) == -1 || sz < 8)
127 return;
128
129 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
130 if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
131 {
132 union {
133 unsigned char *c;
134 struct in6_pktinfo *p;
135 } p;
136 p.c = CMSG_DATA(cmptr);
137
138 if_index = p.p->ipi6_ifindex;
139 }
140
141 if (!indextoname(daemon->icmp6fd, if_index, interface))
142 return;
143
144 if (!iface_check(AF_LOCAL, NULL, interface))
145 return;
146
147 for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
148 if (tmp->name && (strcmp(tmp->name, interface) == 0))
149 return;
150
151 /* weird libvirt-inspired access control */
152 for (context = daemon->dhcp6; context; context = context->next)
153 if (!context->interface || strcmp(context->interface, interface) == 0)
154 break;
155
156 if (!context)
157 return;
158
159 p = (unsigned char *)daemon->outpacket.iov_base;
160
161 if (p[0] != ICMP6_ROUTER_SOLICIT || p[1] != 0)
162 return;
163
164 /* look for link-layer address option for logging */
165 if (sz >= 16 && p[8] == ICMP6_OPT_SOURCE_MAC && (p[9] * 8) + 8 <= sz)
166 {
167 print_mac(daemon->namebuff, &p[10], (p[9] * 8) - 2);
168 mac = daemon->namebuff;
169 }
170
171 my_syslog(MS_DHCP | LOG_INFO, "RTR-SOLICIT(%s) %s", interface, mac);
172
173 send_ra(if_index, interface, &from.sin6_addr);
174}
175
176static void send_ra(int iface, char *iface_name, struct in6_addr *dest)
177{
178 struct ra_packet *ra;
179 struct ra_param parm;
180 struct ifreq ifr;
181 struct sockaddr_in6 addr;
182 struct dhcp_context *context;
183
184 save_counter(0);
185 ra = expand(sizeof(struct ra_packet));
186
187 ra->type = ICMP6_ROUTER_ADVERT;
188 ra->code = 0;
c5379c1a 189 ra->hop_limit = hop_limit;
c5ad4e79
SK
190 ra->flags = 0;
191 ra->lifetime = htons(1800); /* AdvDefaultLifetime*/
192 ra->reachable_time = 0;
193 ra->retrans_time = 0;
194
195 parm.ind = iface;
196 parm.managed = 0;
197 parm.found_context = 0;
198 parm.if_name = iface_name;
199 parm.first = 1;
200
201 for (context = daemon->ra_contexts; context; context = context->next)
202 context->flags &= ~CONTEXT_RA_DONE;
203
204 if (!iface_enumerate(AF_INET6, &parm, add_prefixes) ||
205 !parm.found_context)
206 return;
207
208 strncpy(ifr.ifr_name, iface_name, IF_NAMESIZE);
209
210 if (ioctl(daemon->icmp6fd, SIOCGIFMTU, &ifr) != -1)
211 {
212 put_opt6_char(ICMP6_OPT_MTU);
213 put_opt6_char(1);
214 put_opt6_short(0);
215 put_opt6_long(ifr.ifr_mtu);
216 }
217
218 iface_enumerate(AF_LOCAL, &iface, add_lla);
219
220 /* RDNSS, RFC 6106 */
221 put_opt6_char(ICMP6_OPT_RDNSS);
222 put_opt6_char(3);
223 put_opt6_short(0);
224 put_opt6_long(1800); /* lifetime - twice RA retransmit */
225 put_opt6(&parm.link_local, IN6ADDRSZ);
226
227
228 /* set managed bits unless we're providing only RA on this link */
229 if (parm.managed)
230 ra->flags = 0xc0;
231
232 /* decide where we're sending */
233 memset(&addr, 0, sizeof(addr));
22d904db
SK
234#ifdef HAVE_SOCKADDR_SA_LEN
235 addr.sin6_len = sizeof(struct sockaddr_in6);
236#endif
c5ad4e79
SK
237 addr.sin6_family = AF_INET6;
238 addr.sin6_port = htons(IPPROTO_ICMPV6);
239 if (dest)
240 {
241 memcpy(&addr.sin6_addr, dest, sizeof(struct in6_addr));
242 if (IN6_IS_ADDR_LINKLOCAL(dest) ||
243 IN6_IS_ADDR_MC_LINKLOCAL(dest))
244 addr.sin6_scope_id = iface;
245 }
246 else
22d904db
SK
247 inet_pton(AF_INET6, ALL_HOSTS, &addr.sin6_addr);
248
c5ad4e79
SK
249 send_from(daemon->icmp6fd, 0, daemon->outpacket.iov_base, save_counter(0),
250 (union mysockaddr *)&addr, (struct all_addr *)&parm.link_local, iface);
251
252}
253
254static int add_prefixes(struct in6_addr *local, int prefix,
255 int scope, int if_index, int dad, void *vparam)
256{
257 struct dhcp_context *context, *tmp;
258 struct ra_param *param = vparam;
259 struct prefix_opt *opt;
260
261 (void)scope; /* warning */
262 (void)dad;
263
264 if (if_index == param->ind)
265 {
266 if (IN6_IS_ADDR_LINKLOCAL(local))
267 param->link_local = *local;
268 else if (!IN6_IS_ADDR_LOOPBACK(local) &&
269 !IN6_IS_ADDR_LINKLOCAL(local) &&
270 !IN6_IS_ADDR_MULTICAST(local))
271 {
272 for (context = daemon->ra_contexts; context; context = context->next)
273 if (prefix == context->prefix &&
274 is_same_net6(local, &context->start6, prefix) &&
275 is_same_net6(local, &context->end6, prefix))
276 {
277 if (!(context->flags & CONTEXT_RA_ONLY))
0010b474
SK
278 {
279 /* don't do RA for non-ra-only unless --enable-ra is set */
280 if (!option_bool(OPT_RA))
281 continue;
282 param->managed = 1;
283 }
284
c5ad4e79
SK
285 if (context->flags & CONTEXT_RA_DONE)
286 continue;
287
288 /* subsequent prefixes on the same interface don't need timers */
289 if (!param->first)
290 context->ra_time = 0;
291 param->first = 0;
292 param->found_context = 1;
293 context->flags |= CONTEXT_RA_DONE;
294
295 /* mark this subnet and duplicates: as done. */
296 for (tmp = context->next; tmp; tmp = tmp->next)
297 if (tmp->prefix == prefix &&
298 is_same_net6(local, &tmp->start6, prefix) &&
299 is_same_net6(local, &tmp->end6, prefix))
300 {
301 tmp->flags |= CONTEXT_RA_DONE;
302 context->ra_time = 0;
303 }
304
305 if ((opt = expand(sizeof(struct prefix_opt))))
306 {
307 u64 addrpart = addr6part(&context->start6);
308 u64 mask = (prefix == 64) ? (u64)-1LL : (1LLU << (128 - prefix)) - 1LLU;
309 unsigned int time = context->lease_time;
310
311 /* lifetimes must be min 2 hrs, by RFC 2462 */
312 if (time < 7200)
313 time = 7200;
314
315 opt->type = ICMP6_OPT_PREFIX;
316 opt->len = 4;
317 opt->prefix_len = prefix;
318 /* autonomous only is we're not doing dhcp */
319 opt->flags = (context->flags & CONTEXT_RA_ONLY) ? 0xc0 : 0x00;
320 opt->valid_lifetime = opt->preferred_lifetime = htonl(time);
321 opt->reserved = 0;
322
323 opt->prefix = context->start6;
324 setaddr6part(&opt->prefix, addrpart & ~mask);
325
326 inet_ntop(AF_INET6, &opt->prefix, daemon->addrbuff, ADDRSTRLEN);
327 my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s", param->if_name, daemon->addrbuff);
328 }
329 }
330 }
331 }
332 return 1;
333}
334
335static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm)
336{
337 (void)type;
338
339 if (index == *((int *)parm))
340 {
341 /* size is in units of 8 octets and includes type and length (2 bytes)
342 add 7 to round up */
343 int len = (maclen + 9) >> 3;
344 unsigned char *p = expand(len << 3);
345 memset(p, 0, len << 3);
346 *p++ = ICMP6_OPT_SOURCE_MAC;
347 *p++ = len;
348 memcpy(p, mac, maclen);
349
350 return 0;
351 }
352
353 return 1;
354}
355
356time_t periodic_ra(time_t now)
357{
358 struct search_param param;
359 struct dhcp_context *context;
360 time_t next_event;
361 char interface[IF_NAMESIZE+1];
362
363 param.now = now;
364
365 while (1)
366 {
367 /* find overdue events, and time of first future event */
368 for (next_event = 0, context = daemon->ra_contexts; context; context = context->next)
369 if (context->ra_time != 0)
370 {
371 if (difftime(context->ra_time, now) < 0.0)
372 break; /* overdue */
373
374 if (next_event == 0 || difftime(next_event, context->ra_time + 2) > 0.0)
375 next_event = context->ra_time + 2;
376 }
377
378 /* none overdue */
379 if (!context)
380 break;
381
382 /* There's a context overdue, but we can't find an interface
383 associated with it, because it's for a subnet we dont
384 have an interface on. Probably we're doing DHCP on
385 a remote subnet via a relay. Zero the timer, since we won't
386 ever be able to send ra's and satistfy it. */
387 if (iface_enumerate(AF_INET6, &param, iface_search))
388 context->ra_time = 0;
389 else if (indextoname(daemon->icmp6fd, param.iface, interface))
390 send_ra(param.iface, interface, NULL);
391 }
392
393 return next_event;
394}
395
396static int iface_search(struct in6_addr *local, int prefix,
397 int scope, int if_index, int dad, void *vparam)
398{
399 struct search_param *param = vparam;
741c2952 400 struct dhcp_context *context;
c5ad4e79
SK
401
402 (void)scope;
403 (void)dad;
404
405 for (context = daemon->ra_contexts; context; context = context->next)
406 if (prefix == context->prefix &&
407 is_same_net6(local, &context->start6, prefix) &&
408 is_same_net6(local, &context->end6, prefix))
409 if (context->ra_time != 0 && difftime(context->ra_time, param->now) < 0.0)
410 {
411 /* found an interface that's overdue for RA determine new
412 timeout value and zap other contexts on the same interface
413 so they don't timeout independently .*/
414 param->iface = if_index;
415
416 if (difftime(param->now, ra_short_period_start) < 60.0)
417 /* range 5 - 20 */
418 context->ra_time = param->now + 5 + (rand16()/4400);
419 else
420 /* range 450 - 600 */
421 context->ra_time = param->now + 450 + (rand16()/440);
422
423 return 0; /* found, abort */
424 }
425
426 return 1; /* keep searching */
427}
428
429#endif