]>
Commit | Line | Data |
---|---|---|
59546085 | 1 | /* dnsmasq is Copyright (c) 2000-2012 Simon Kelley |
c72daea8 SK |
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 | int ind; | |
24 | }; | |
25 | ||
4cb1b320 SK |
26 | struct listen_param { |
27 | int fd_or_iface; | |
28 | struct listen_param *next; | |
29 | }; | |
30 | ||
c72daea8 | 31 | static int join_multicast(struct in6_addr *local, int prefix, |
52b92f4d | 32 | int scope, int if_index, int dad, void *vparam); |
c72daea8 SK |
33 | |
34 | static int complete_context6(struct in6_addr *local, int prefix, | |
52b92f4d | 35 | int scope, int if_index, int dad, void *vparam); |
c72daea8 | 36 | |
4cb1b320 SK |
37 | static int make_duid1(unsigned int type, unsigned int flags, char *mac, |
38 | size_t maclen, void *parm); | |
39 | ||
c72daea8 SK |
40 | void dhcp6_init(void) |
41 | { | |
42 | int fd; | |
43 | struct sockaddr_in6 saddr; | |
4cb1b320 SK |
44 | struct listen_param *listenp, listen; |
45 | #if defined(IP_TOS) && defined(IPTOS_CLASS_CS6) | |
c72daea8 | 46 | int class = IPTOS_CLASS_CS6; |
4cb1b320 | 47 | #endif |
c72daea8 SK |
48 | |
49 | if ((fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1 || | |
4cb1b320 | 50 | #if defined(IP_TOS) && defined(IPTOS_CLASS_CS6) |
c72daea8 | 51 | setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &class, sizeof(class)) == -1 || |
4cb1b320 | 52 | #endif |
c72daea8 SK |
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 | |
4cb1b320 | 59 | saddr.sin6_len = sizeof(struct sockaddr_in6); |
c72daea8 SK |
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 */ | |
4cb1b320 SK |
69 | listen.fd_or_iface = fd; |
70 | listen.next = NULL; | |
71 | if (!iface_enumerate(AF_INET6, &listen, join_multicast)) | |
c72daea8 | 72 | die(_("failed to join DHCPv6 multicast group: %s"), NULL, EC_BADNET); |
4cb1b320 SK |
73 | for (listenp = listen.next; listenp; ) |
74 | { | |
75 | struct listen_param *tmp = listenp->next; | |
76 | free(listenp); | |
77 | listenp = tmp; | |
78 | } | |
c72daea8 SK |
79 | |
80 | daemon->dhcp6fd = fd; | |
c72daea8 SK |
81 | } |
82 | ||
83 | static int join_multicast(struct in6_addr *local, int prefix, | |
52b92f4d | 84 | int scope, int if_index, int dad, void *vparam) |
c72daea8 SK |
85 | { |
86 | char ifrn_name[IFNAMSIZ]; | |
87 | struct ipv6_mreq mreq; | |
4cb1b320 SK |
88 | struct listen_param *listenp, *param = vparam; |
89 | int fd = param->fd_or_iface; | |
c72daea8 SK |
90 | struct dhcp_context *context; |
91 | struct iname *tmp; | |
92 | ||
93 | (void)prefix; | |
4cb1b320 SK |
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; | |
52b92f4d | 103 | |
c72daea8 SK |
104 | if (!indextoname(fd, if_index, ifrn_name)) |
105 | return 0; | |
4cb1b320 | 106 | |
c72daea8 SK |
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 */ | |
52b92f4d | 116 | for (context = daemon->dhcp6; context; context = context->next) |
c72daea8 SK |
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; | |
52b92f4d | 124 | inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &mreq.ipv6mr_multiaddr); |
c72daea8 | 125 | |
52b92f4d | 126 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) |
c72daea8 SK |
127 | return 0; |
128 | ||
52b92f4d | 129 | inet_pton(AF_INET6, ALL_SERVERS, &mreq.ipv6mr_multiaddr); |
c72daea8 | 130 | |
52b92f4d | 131 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) |
c72daea8 SK |
132 | return 0; |
133 | ||
4cb1b320 SK |
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 | ||
c72daea8 | 139 | return 1; |
c72daea8 SK |
140 | } |
141 | ||
142 | ||
143 | ||
144 | ||
145 | void dhcp6_packet(time_t now) | |
146 | { | |
147 | struct dhcp_context *context; | |
148 | struct iface_param parm; | |
149 | struct cmsghdr *cmptr; | |
150 | struct msghdr msg; | |
151 | int if_index = 0; | |
152 | union { | |
153 | struct cmsghdr align; /* this ensures alignment */ | |
154 | char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; | |
155 | } control_u; | |
156 | union mysockaddr from; | |
157 | struct all_addr dest; | |
158 | ssize_t sz; | |
159 | struct ifreq ifr; | |
160 | struct iname *tmp; | |
161 | ||
162 | msg.msg_control = control_u.control6; | |
163 | msg.msg_controllen = sizeof(control_u); | |
164 | msg.msg_flags = 0; | |
165 | msg.msg_name = &from; | |
166 | msg.msg_namelen = sizeof(from); | |
167 | msg.msg_iov = &daemon->dhcp_packet; | |
168 | msg.msg_iovlen = 1; | |
169 | ||
52b92f4d | 170 | if ((sz = recv_dhcp_packet(daemon->dhcp6fd, &msg)) == -1 || sz <= 4) |
c72daea8 SK |
171 | return; |
172 | ||
173 | for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) | |
174 | if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) | |
175 | { | |
176 | union { | |
177 | unsigned char *c; | |
178 | struct in6_pktinfo *p; | |
179 | } p; | |
180 | p.c = CMSG_DATA(cmptr); | |
181 | ||
182 | if_index = p.p->ipi6_ifindex; | |
183 | dest.addr.addr6 = p.p->ipi6_addr; | |
184 | } | |
185 | ||
186 | if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name)) | |
187 | return; | |
52b92f4d | 188 | |
c72daea8 SK |
189 | if (!iface_check(AF_INET6, (struct all_addr *)&dest, ifr.ifr_name)) |
190 | return; | |
191 | ||
192 | for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) | |
193 | if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0)) | |
194 | return; | |
195 | ||
196 | /* weird libvirt-inspired access control */ | |
52b92f4d | 197 | for (context = daemon->dhcp6; context; context = context->next) |
c72daea8 SK |
198 | if (!context->interface || strcmp(context->interface, ifr.ifr_name) == 0) |
199 | break; | |
200 | ||
201 | if (!context) | |
202 | return; | |
4cb1b320 | 203 | |
c72daea8 | 204 | /* unlinked contexts are marked by context->current == context */ |
52b92f4d | 205 | for (context = daemon->dhcp6; context; context = context->next) |
c72daea8 SK |
206 | context->current = context; |
207 | ||
208 | parm.current = NULL; | |
209 | parm.ind = if_index; | |
210 | ||
211 | if (!iface_enumerate(AF_INET6, &parm, complete_context6)) | |
212 | return; | |
213 | ||
214 | lease_prune(NULL, now); /* lose any expired leases */ | |
215 | ||
216 | msg.msg_iov = &daemon->dhcp_packet; | |
ceae00dd | 217 | sz = dhcp6_reply(parm.current, if_index, ifr.ifr_name, sz, IN6_IS_ADDR_MULTICAST(&from), now); |
c72daea8 SK |
218 | /* ifr.ifr_name, if_index, (size_t)sz, |
219 | now, unicast_dest, &is_inform, pxe_fd, iface_addr); */ | |
220 | lease_update_file(now); | |
221 | lease_update_dns(); | |
222 | ||
223 | if (sz != 0) | |
4cb1b320 SK |
224 | while (sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, sz, 0, (struct sockaddr *)&from, sizeof(from)) && |
225 | retry_send()); | |
c72daea8 SK |
226 | } |
227 | ||
228 | static int complete_context6(struct in6_addr *local, int prefix, | |
52b92f4d | 229 | int scope, int if_index, int dad, void *vparam) |
c72daea8 SK |
230 | { |
231 | struct dhcp_context *context; | |
232 | struct iface_param *param = vparam; | |
4cb1b320 | 233 | |
52b92f4d | 234 | (void)scope; /* warning */ |
4cb1b320 | 235 | (void)dad; |
52b92f4d | 236 | |
c72daea8 SK |
237 | for (context = daemon->dhcp6; context; context = context->next) |
238 | { | |
52b92f4d | 239 | if (prefix == context->prefix && |
4cb1b320 SK |
240 | !IN6_IS_ADDR_LOOPBACK(local) && |
241 | !IN6_IS_ADDR_LINKLOCAL(local) && | |
242 | !IN6_IS_ADDR_MULTICAST(local) && | |
c72daea8 SK |
243 | is_same_net6(local, &context->start6, prefix) && |
244 | is_same_net6(local, &context->end6, prefix)) | |
245 | { | |
246 | /* link it onto the current chain if we've not seen it before */ | |
247 | if (if_index == param->ind && context->current == context) | |
248 | { | |
249 | context->current = param->current; | |
250 | param->current = context; | |
4cb1b320 | 251 | context->local6 = *local; |
c72daea8 SK |
252 | } |
253 | } | |
254 | } | |
255 | return 1; | |
256 | } | |
52b92f4d SK |
257 | |
258 | struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr) | |
259 | { | |
260 | struct dhcp_config *config; | |
261 | ||
262 | for (config = configs; config; config = config->next) | |
263 | if ((config->flags & CONFIG_ADDR6) && | |
264 | is_same_net6(&config->addr6, net, prefix) && | |
265 | (prefix == 128 || addr6part(&config->addr6) == addr)) | |
266 | return config; | |
267 | ||
268 | return NULL; | |
269 | } | |
270 | ||
271 | int address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, | |
4cb1b320 | 272 | int serial, struct dhcp_netid *netids, struct in6_addr *ans) |
52b92f4d SK |
273 | { |
274 | /* Find a free address: exclude anything in use and anything allocated to | |
275 | a particular hwaddr/clientid/hostname in our configuration. | |
276 | Try to return from contexts which match netids first. | |
277 | ||
278 | Note that we assume the address prefix lengths are 64 or greater, so we can | |
279 | get by with 64 bit arithmetic. | |
280 | */ | |
281 | ||
282 | u64 start, addr; | |
283 | struct dhcp_context *c, *d; | |
284 | int i, pass; | |
285 | u64 j; | |
286 | ||
287 | /* hash hwaddr: use the SDBM hashing algorithm. This works | |
288 | for MAC addresses, let's see how it manages with client-ids! */ | |
289 | for (j = 0, i = 0; i < clid_len; i++) | |
290 | j += clid[i] + (j << 6) + (j << 16) - j; | |
291 | ||
292 | for (pass = 0; pass <= 1; pass++) | |
293 | for (c = context; c; c = c->current) | |
294 | if (c->flags & (CONTEXT_STATIC | CONTEXT_PROXY)) | |
295 | continue; | |
296 | else if (!match_netid(c->filter, netids, pass)) | |
297 | continue; | |
298 | else | |
299 | { | |
4cb1b320 | 300 | start = addr6part(&c->start6) + ((j + c->addr_epoch + serial) % (1 + addr6part(&c->end6) - addr6part(&c->start6))); |
52b92f4d SK |
301 | |
302 | /* iterate until we find a free address. */ | |
303 | addr = start; | |
304 | ||
305 | do { | |
306 | /* eliminate addresses in use by the server. */ | |
307 | for (d = context; d; d = d->current) | |
308 | if (addr == addr6part(&d->router6)) | |
309 | break; | |
310 | ||
311 | if (!d && | |
312 | !lease6_find_by_addr(&c->start6, c->prefix, addr) && | |
313 | !config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, addr)) | |
314 | { | |
315 | *ans = c->start6; | |
316 | setaddr6part (ans, addr); | |
317 | return 1; | |
318 | } | |
319 | ||
320 | addr++; | |
321 | ||
322 | if (addr == addr6part(&c->end6) + 1) | |
323 | addr = addr6part(&c->start6); | |
324 | ||
325 | } while (addr != start); | |
326 | } | |
327 | ||
328 | return 0; | |
329 | } | |
330 | ||
331 | struct dhcp_context *address6_available(struct dhcp_context *context, | |
332 | struct in6_addr *taddr, | |
333 | struct dhcp_netid *netids) | |
334 | { | |
335 | u64 start, end, addr = addr6part(taddr); | |
336 | struct dhcp_context *tmp; | |
337 | ||
338 | for (tmp = context; tmp; tmp = tmp->current) | |
339 | { | |
340 | start = addr6part(&tmp->start6); | |
341 | end = addr6part(&tmp->end6); | |
342 | ||
343 | if (!(tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY)) && | |
344 | is_same_net6(&context->start6, taddr, context->prefix) && | |
345 | is_same_net6(&context->end6, taddr, context->prefix) && | |
346 | addr >= start && | |
347 | addr <= end && | |
348 | match_netid(tmp->filter, netids, 1)) | |
349 | return tmp; | |
350 | } | |
351 | ||
352 | return NULL; | |
353 | } | |
354 | ||
355 | struct dhcp_context *narrow_context6(struct dhcp_context *context, | |
356 | struct in6_addr *taddr, | |
357 | struct dhcp_netid *netids) | |
358 | { | |
359 | /* We start of with a set of possible contexts, all on the current physical interface. | |
360 | These are chained on ->current. | |
361 | Here we have an address, and return the actual context correponding to that | |
362 | address. Note that none may fit, if the address came a dhcp-host and is outside | |
363 | any dhcp-range. In that case we return a static range if possible, or failing that, | |
364 | any context on the correct subnet. (If there's more than one, this is a dodgy | |
365 | configuration: maybe there should be a warning.) */ | |
366 | ||
367 | struct dhcp_context *tmp; | |
368 | ||
369 | if (!(tmp = address6_available(context, taddr, netids))) | |
370 | { | |
371 | for (tmp = context; tmp; tmp = tmp->current) | |
372 | if (match_netid(tmp->filter, netids, 1) && | |
373 | is_same_net6(taddr, &tmp->start6, tmp->prefix) && | |
374 | (tmp->flags & CONTEXT_STATIC)) | |
375 | break; | |
376 | ||
377 | if (!tmp) | |
378 | for (tmp = context; tmp; tmp = tmp->current) | |
379 | if (match_netid(tmp->filter, netids, 1) && | |
380 | is_same_net6(taddr, &tmp->start6, tmp->prefix) && | |
381 | !(tmp->flags & CONTEXT_PROXY)) | |
382 | break; | |
383 | } | |
384 | ||
385 | /* Only one context allowed now */ | |
386 | if (tmp) | |
387 | tmp->current = NULL; | |
388 | ||
389 | return tmp; | |
390 | } | |
391 | ||
4cb1b320 SK |
392 | static int is_addr_in_context6(struct dhcp_context *context, struct dhcp_config *config) |
393 | { | |
394 | if (!context) /* called via find_config() from lease_update_from_configs() */ | |
395 | return 1; | |
396 | if (!(config->flags & CONFIG_ADDR6)) | |
397 | return 1; | |
398 | for (; context; context = context->current) | |
399 | if (is_same_net6(&config->addr6, &context->start6, context->prefix)) | |
400 | return 1; | |
401 | ||
402 | return 0; | |
403 | } | |
404 | ||
405 | ||
406 | struct dhcp_config *find_config6(struct dhcp_config *configs, | |
407 | struct dhcp_context *context, | |
408 | unsigned char *duid, int duid_len, | |
409 | char *hostname) | |
410 | { | |
411 | int count, new; | |
412 | struct dhcp_config *config; | |
413 | struct hwaddr_config *conf_addr; | |
414 | unsigned char *hwaddr = NULL; | |
415 | int duid_type, hw_len = 0, hw_type = 0; | |
416 | ||
417 | if (duid) | |
418 | { | |
419 | for (config = configs; config; config = config->next) | |
420 | if (config->flags & CONFIG_CLID) | |
421 | { | |
422 | if (config->clid_len == duid_len && | |
423 | memcmp(config->clid, duid, duid_len) == 0 && | |
424 | is_addr_in_context6(context, config)) | |
425 | return config; | |
426 | } | |
427 | ||
428 | /* DHCPv6 doesn't deal in MAC addresses per-se, but some DUIDs do include | |
429 | MAC addresses, so we try and parse them out here. Not that there is only one | |
430 | DUID per host and it's created using any one of the MACs, so this is no | |
431 | good no good for multihomed hosts. */ | |
432 | hwaddr = duid; | |
433 | GETSHORT(duid_type, hwaddr); | |
434 | if (duid_type == 1 || duid_type == 3) | |
435 | { | |
436 | GETSHORT(hw_type, hwaddr); | |
437 | if (duid_type == 1) | |
438 | hwaddr += 4; /* skip time */ | |
439 | hw_len = duid_len - 8; | |
440 | } | |
441 | ||
442 | if (hwaddr) | |
443 | for (config = configs; config; config = config->next) | |
444 | if (config_has_mac(config, hwaddr, hw_len, hw_type) && | |
445 | is_addr_in_context6(context, config)) | |
446 | return config; | |
447 | } | |
448 | ||
449 | if (hostname && context) | |
450 | for (config = configs; config; config = config->next) | |
451 | if ((config->flags & CONFIG_NAME) && | |
452 | hostname_isequal(config->hostname, hostname) && | |
453 | is_addr_in_context6(context, config)) | |
454 | return config; | |
455 | ||
456 | /* use match with fewest wildcard octets */ | |
457 | if (hwaddr) | |
458 | { | |
459 | struct dhcp_config *candidate; | |
460 | ||
461 | for (candidate = NULL, count = 0, config = configs; config; config = config->next) | |
462 | if (is_addr_in_context6(context, config)) | |
463 | for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next) | |
464 | if (conf_addr->wildcard_mask != 0 && | |
465 | conf_addr->hwaddr_len == hw_len && | |
466 | (conf_addr->hwaddr_type == hw_type || conf_addr->hwaddr_type == 0) && | |
467 | (new = memcmp_masked(conf_addr->hwaddr, hwaddr, hw_len, conf_addr->wildcard_mask)) > count) | |
468 | { | |
469 | count = new; | |
470 | candidate = config; | |
471 | } | |
472 | ||
473 | return candidate; | |
474 | } | |
475 | ||
476 | return NULL; | |
477 | } | |
478 | ||
479 | void make_duid(time_t now) | |
480 | { | |
481 | /* rebase epoch to 1/1/2000 */ | |
482 | time_t newnow = now - 946684800; | |
483 | iface_enumerate(AF_LOCAL, &newnow, make_duid1); | |
484 | ||
485 | if (!daemon->duid) | |
486 | die("Cannot create DHCPv6 server DUID", NULL, EC_MISC); | |
487 | } | |
488 | ||
489 | static int make_duid1(unsigned int type, unsigned int flags, char *mac, | |
490 | size_t maclen, void *parm) | |
491 | { | |
492 | /* create DUID as specified in RFC3315. We use the MAC of the | |
493 | first interface we find that isn't loopback or P-to-P */ | |
494 | ||
495 | unsigned char *p; | |
496 | ||
497 | if (flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) | |
498 | return 1; | |
499 | ||
500 | daemon->duid = p = safe_malloc(maclen + 8); | |
501 | daemon->duid_len = maclen + 8; | |
502 | ||
503 | #ifdef HAVE_BROKEN_RTC | |
504 | PUTSHORT(3, p); /* DUID_LL */ | |
505 | #else | |
506 | PUTSHORT(1, p); /* DUID_LLT */ | |
507 | #endif | |
508 | ||
509 | PUTSHORT(type, p); /* address type */ | |
510 | ||
511 | #ifndef HAVE_BROKEN_RTC | |
512 | PUTLONG(*((time_t *)parm), p); /* time */ | |
513 | #endif | |
514 | ||
515 | memcpy(p, mac, maclen); | |
516 | ||
517 | return 0; | |
518 | } | |
c72daea8 SK |
519 | #endif |
520 | ||
521 |