]> git.ipfire.org Git - people/ms/dnsmasq.git/blob - src/dhcp6.c
6c20a0fd09c0dd1e7b15063ee315f68be9443fdf
[people/ms/dnsmasq.git] / src / dhcp6.c
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 #include "dnsmasq.h"
18
19 #ifdef HAVE_DHCP6
20
21 struct iface_param {
22 struct dhcp_context *current;
23 struct in6_addr fallback;
24 int ind;
25 };
26
27 struct listen_param {
28 int fd_or_iface;
29 struct listen_param *next;
30 };
31
32 static int join_multicast(struct in6_addr *local, int prefix,
33 int scope, int if_index, int dad, void *vparam);
34
35 static int complete_context6(struct in6_addr *local, int prefix,
36 int scope, int if_index, int dad, void *vparam);
37
38 static int make_duid1(unsigned int type, char *mac, size_t maclen, void *parm);
39
40 void dhcp6_init(void)
41 {
42 int fd;
43 struct sockaddr_in6 saddr;
44 struct listen_param *listenp, listen;
45 #if defined(IP_TOS) && defined(IPTOS_CLASS_CS6)
46 int class = IPTOS_CLASS_CS6;
47 #endif
48
49 if ((fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1 ||
50 #if defined(IP_TOS) && defined(IPTOS_CLASS_CS6)
51 setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &class, sizeof(class)) == -1 ||
52 #endif
53 !fix_fd(fd) ||
54 !set_ipv6pktinfo(fd))
55 die (_("cannot create DHCPv6 socket: %s"), NULL, EC_BADNET);
56
57 memset(&saddr, 0, sizeof(saddr));
58 #ifdef HAVE_SOCKADDR_SA_LEN
59 saddr.sin6_len = sizeof(struct sockaddr_in6);
60 #endif
61 saddr.sin6_family = AF_INET6;
62 saddr.sin6_addr = in6addr_any;
63 saddr.sin6_port = htons(DHCPV6_SERVER_PORT);
64
65 if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in6)))
66 die(_("failed to bind DHCPv6 server socket: %s"), NULL, EC_BADNET);
67
68 /* join multicast groups on each interface we're interested in */
69 listen.fd_or_iface = fd;
70 listen.next = NULL;
71 if (!iface_enumerate(AF_INET6, &listen, join_multicast))
72 die(_("failed to join DHCPv6 multicast group: %s"), NULL, EC_BADNET);
73 for (listenp = listen.next; listenp; )
74 {
75 struct listen_param *tmp = listenp->next;
76 free(listenp);
77 listenp = tmp;
78 }
79
80 daemon->dhcp6fd = fd;
81 }
82
83 static int join_multicast(struct in6_addr *local, int prefix,
84 int scope, int if_index, int dad, void *vparam)
85 {
86 char ifrn_name[IFNAMSIZ];
87 struct ipv6_mreq mreq;
88 struct listen_param *listenp, *param = vparam;
89 int fd = param->fd_or_iface;
90 struct dhcp_context *context;
91 struct iname *tmp;
92
93 (void)prefix;
94 (void)scope;
95 (void)dad;
96
97 /* record which interfaces we join on, so
98 that we do it at most one per interface, even when they
99 have multiple addresses */
100 for (listenp = param->next; listenp; listenp = listenp->next)
101 if (if_index == listenp->fd_or_iface)
102 return 1;
103
104 if (!indextoname(fd, if_index, ifrn_name))
105 return 0;
106
107 /* Are we doing DHCP on this interface? */
108 if (!iface_check(AF_INET6, (struct all_addr *)local, ifrn_name))
109 return 1;
110
111 for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
112 if (tmp->name && (strcmp(tmp->name, ifrn_name) == 0))
113 return 1;
114
115 /* weird libvirt-inspired access control */
116 for (context = daemon->dhcp6; context; context = context->next)
117 if (!context->interface || strcmp(context->interface, ifrn_name) == 0)
118 break;
119
120 if (!context)
121 return 1;
122
123 mreq.ipv6mr_interface = if_index;
124 inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &mreq.ipv6mr_multiaddr);
125
126 if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
127 return 0;
128
129 inet_pton(AF_INET6, ALL_SERVERS, &mreq.ipv6mr_multiaddr);
130
131 if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
132 return 0;
133
134 listenp = whine_malloc(sizeof(struct listen_param));
135 listenp->fd_or_iface = if_index;
136 listenp->next = param->next;
137 param->next = listenp;
138
139 return 1;
140 }
141
142
143 void dhcp6_packet(time_t now)
144 {
145 struct dhcp_context *context;
146 struct iface_param parm;
147 struct cmsghdr *cmptr;
148 struct msghdr msg;
149 int if_index = 0;
150 union {
151 struct cmsghdr align; /* this ensures alignment */
152 char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
153 } control_u;
154 union mysockaddr from;
155 struct all_addr dest;
156 ssize_t sz;
157 struct ifreq ifr;
158 struct iname *tmp;
159
160 msg.msg_control = control_u.control6;
161 msg.msg_controllen = sizeof(control_u);
162 msg.msg_flags = 0;
163 msg.msg_name = &from;
164 msg.msg_namelen = sizeof(from);
165 msg.msg_iov = &daemon->dhcp_packet;
166 msg.msg_iovlen = 1;
167
168 if ((sz = recv_dhcp_packet(daemon->dhcp6fd, &msg)) == -1 || sz <= 4)
169 return;
170
171 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
172 if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
173 {
174 union {
175 unsigned char *c;
176 struct in6_pktinfo *p;
177 } p;
178 p.c = CMSG_DATA(cmptr);
179
180 if_index = p.p->ipi6_ifindex;
181 dest.addr.addr6 = p.p->ipi6_addr;
182 }
183
184 if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name))
185 return;
186
187 if (!iface_check(AF_INET6, (struct all_addr *)&dest, ifr.ifr_name))
188 return;
189
190 for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
191 if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0))
192 return;
193
194 /* weird libvirt-inspired access control */
195 for (context = daemon->dhcp6; context; context = context->next)
196 if (!context->interface || strcmp(context->interface, ifr.ifr_name) == 0)
197 break;
198
199 if (!context)
200 return;
201
202 /* unlinked contexts are marked by context->current == context */
203 for (context = daemon->dhcp6; context; context = context->next)
204 {
205 context->current = context;
206 memset(&context->local6, 0, IN6ADDRSZ);
207 }
208
209 parm.current = NULL;
210 parm.ind = if_index;
211 memset(&parm.fallback, 0, IN6ADDRSZ);
212
213 if (!iface_enumerate(AF_INET6, &parm, complete_context6))
214 return;
215
216 lease_prune(NULL, now); /* lose any expired leases */
217
218 msg.msg_iov = &daemon->dhcp_packet;
219 sz = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback, sz, IN6_IS_ADDR_MULTICAST(&from.in6.sin6_addr), now);
220
221 lease_update_file(now);
222 lease_update_dns();
223
224 if (sz != 0)
225 while (sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, sz, 0, (struct sockaddr *)&from, sizeof(from)) == -1 &&
226 retry_send());
227 }
228
229 static int complete_context6(struct in6_addr *local, int prefix,
230 int scope, int if_index, int dad, void *vparam)
231 {
232 struct dhcp_context *context;
233 struct iface_param *param = vparam;
234
235 (void)scope; /* warning */
236 (void)dad;
237
238 if (if_index == param->ind &&
239 !IN6_IS_ADDR_LOOPBACK(local) &&
240 !IN6_IS_ADDR_LINKLOCAL(local) &&
241 !IN6_IS_ADDR_MULTICAST(local))
242 {
243 /* Determine a globally address on the arrival interface, even
244 if we have no matching dhcp-context, because we're only
245 allocating on remote subnets via relays. This
246 is used as a default for the DNS server option. */
247 param->fallback = *local;
248
249 for (context = daemon->dhcp6; context; context = context->next)
250 {
251 if (prefix == context->prefix &&
252 is_same_net6(local, &context->start6, prefix) &&
253 is_same_net6(local, &context->end6, prefix))
254 {
255 /* link it onto the current chain if we've not seen it before */
256 if (context->current == context)
257 {
258 context->current = param->current;
259 param->current = context;
260 context->local6 = *local;
261 }
262 }
263 }
264 }
265 return 1;
266 }
267
268 struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr)
269 {
270 struct dhcp_config *config;
271
272 for (config = configs; config; config = config->next)
273 if ((config->flags & CONFIG_ADDR6) &&
274 is_same_net6(&config->addr6, net, prefix) &&
275 (prefix == 128 || addr6part(&config->addr6) == addr))
276 return config;
277
278 return NULL;
279 }
280
281 int address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len,
282 int serial, struct dhcp_netid *netids, struct in6_addr *ans)
283 {
284 /* Find a free address: exclude anything in use and anything allocated to
285 a particular hwaddr/clientid/hostname in our configuration.
286 Try to return from contexts which match netids first.
287
288 Note that we assume the address prefix lengths are 64 or greater, so we can
289 get by with 64 bit arithmetic.
290 */
291
292 u64 start, addr;
293 struct dhcp_context *c, *d;
294 int i, pass;
295 u64 j;
296
297 /* hash hwaddr: use the SDBM hashing algorithm. This works
298 for MAC addresses, let's see how it manages with client-ids! */
299 for (j = 0, i = 0; i < clid_len; i++)
300 j += clid[i] + (j << 6) + (j << 16) - j;
301
302 for (pass = 0; pass <= 1; pass++)
303 for (c = context; c; c = c->current)
304 if (c->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
305 continue;
306 else if (!match_netid(c->filter, netids, pass))
307 continue;
308 else
309 {
310 if (option_bool(OPT_CONSEC_ADDR))
311 /* seed is largest extant lease addr in this context */
312 start = lease_find_max_addr6(c) + serial;
313 else
314 start = addr6part(&c->start6) + ((j + c->addr_epoch + serial) % (1 + addr6part(&c->end6) - addr6part(&c->start6)));
315
316 /* iterate until we find a free address. */
317 addr = start;
318
319 do {
320 /* eliminate addresses in use by the server. */
321 for (d = context; d; d = d->current)
322 if (addr == addr6part(&d->local6))
323 break;
324
325 if (!d &&
326 !lease6_find_by_addr(&c->start6, c->prefix, addr) &&
327 !config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, addr))
328 {
329 *ans = c->start6;
330 setaddr6part (ans, addr);
331 return 1;
332 }
333
334 addr++;
335
336 if (addr == addr6part(&c->end6) + 1)
337 addr = addr6part(&c->start6);
338
339 } while (addr != start);
340 }
341
342 return 0;
343 }
344
345 struct dhcp_context *address6_available(struct dhcp_context *context,
346 struct in6_addr *taddr,
347 struct dhcp_netid *netids)
348 {
349 u64 start, end, addr = addr6part(taddr);
350 struct dhcp_context *tmp;
351
352 for (tmp = context; tmp; tmp = tmp->current)
353 {
354 start = addr6part(&tmp->start6);
355 end = addr6part(&tmp->end6);
356
357 if (!(tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY)) &&
358 is_same_net6(&context->start6, taddr, context->prefix) &&
359 is_same_net6(&context->end6, taddr, context->prefix) &&
360 addr >= start &&
361 addr <= end &&
362 match_netid(tmp->filter, netids, 1))
363 return tmp;
364 }
365
366 return NULL;
367 }
368
369 struct dhcp_context *narrow_context6(struct dhcp_context *context,
370 struct in6_addr *taddr,
371 struct dhcp_netid *netids)
372 {
373 /* We start of with a set of possible contexts, all on the current physical interface.
374 These are chained on ->current.
375 Here we have an address, and return the actual context correponding to that
376 address. Note that none may fit, if the address came a dhcp-host and is outside
377 any dhcp-range. In that case we return a static range if possible, or failing that,
378 any context on the correct subnet. (If there's more than one, this is a dodgy
379 configuration: maybe there should be a warning.) */
380
381 struct dhcp_context *tmp;
382
383 if (!(tmp = address6_available(context, taddr, netids)))
384 {
385 for (tmp = context; tmp; tmp = tmp->current)
386 if (match_netid(tmp->filter, netids, 1) &&
387 is_same_net6(taddr, &tmp->start6, tmp->prefix) &&
388 (tmp->flags & CONTEXT_STATIC))
389 break;
390
391 if (!tmp)
392 for (tmp = context; tmp; tmp = tmp->current)
393 if (match_netid(tmp->filter, netids, 1) &&
394 is_same_net6(taddr, &tmp->start6, tmp->prefix) &&
395 !(tmp->flags & CONTEXT_PROXY))
396 break;
397 }
398
399 /* Only one context allowed now */
400 if (tmp)
401 tmp->current = NULL;
402
403 return tmp;
404 }
405
406 static int is_addr_in_context6(struct dhcp_context *context, struct dhcp_config *config)
407 {
408 if (!context) /* called via find_config() from lease_update_from_configs() */
409 return 1;
410 if (!(config->flags & CONFIG_ADDR6))
411 return 1;
412 for (; context; context = context->current)
413 if (is_same_net6(&config->addr6, &context->start6, context->prefix))
414 return 1;
415
416 return 0;
417 }
418
419
420 struct dhcp_config *find_config6(struct dhcp_config *configs,
421 struct dhcp_context *context,
422 unsigned char *duid, int duid_len,
423 char *hostname)
424 {
425 struct dhcp_config *config;
426
427 if (duid)
428 for (config = configs; config; config = config->next)
429 if (config->flags & CONFIG_CLID)
430 {
431 if (config->clid_len == duid_len &&
432 memcmp(config->clid, duid, duid_len) == 0 &&
433 is_addr_in_context6(context, config))
434 return config;
435 }
436
437 if (hostname && context)
438 for (config = configs; config; config = config->next)
439 if ((config->flags & CONFIG_NAME) &&
440 hostname_isequal(config->hostname, hostname) &&
441 is_addr_in_context6(context, config))
442 return config;
443
444 return NULL;
445 }
446
447 void make_duid(time_t now)
448 {
449 /* rebase epoch to 1/1/2000 */
450 time_t newnow = now - 946684800;
451
452 iface_enumerate(AF_LOCAL, &newnow, make_duid1);
453
454 if(!daemon->duid)
455 die("Cannot create DHCPv6 server DUID: %s", NULL, EC_MISC);
456 }
457
458 static int make_duid1(unsigned int type, char *mac, size_t maclen, void *parm)
459 {
460 /* create DUID as specified in RFC3315. We use the MAC of the
461 first interface we find that isn't loopback or P-to-P */
462
463 unsigned char *p;
464
465 daemon->duid = p = safe_malloc(maclen + 8);
466 daemon->duid_len = maclen + 8;
467
468 #ifdef HAVE_BROKEN_RTC
469 PUTSHORT(3, p); /* DUID_LL */
470 #else
471 PUTSHORT(1, p); /* DUID_LLT */
472 #endif
473
474 PUTSHORT(type, p); /* address type */
475
476 #ifndef HAVE_BROKEN_RTC
477 PUTLONG(*((time_t *)parm), p); /* time */
478 #endif
479
480 memcpy(p, mac, maclen);
481
482 return 0;
483 }
484 #endif
485
486