blob: 747e1569b3c8a481691796e6cf1b6d2e483fd708 [file] [log] [blame]
Simon Kelley59546082012-01-06 20:02:04 +00001/* dnsmasq is Copyright (c) 2000-2012 Simon Kelley
Simon Kelleyc72daea2012-01-05 21:33:27 +00002
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
21struct iface_param {
22 struct dhcp_context *current;
Simon Kelleye44ddca2012-02-18 17:08:50 +000023 struct in6_addr fallback;
Simon Kelleyc72daea2012-01-05 21:33:27 +000024 int ind;
25};
26
Simon Kelleyc72daea2012-01-05 21:33:27 +000027static int complete_context6(struct in6_addr *local, int prefix,
Simon Kelley52b92f42012-01-22 16:05:15 +000028 int scope, int if_index, int dad, void *vparam);
Simon Kelleyc72daea2012-01-05 21:33:27 +000029
Simon Kelleyc5ad4e72012-02-24 16:06:20 +000030static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm);
Simon Kelley4cb1b322012-02-06 14:30:41 +000031
Simon Kelleyc72daea2012-01-05 21:33:27 +000032void dhcp6_init(void)
33{
34 int fd;
35 struct sockaddr_in6 saddr;
Simon Kelley4cb1b322012-02-06 14:30:41 +000036#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6)
Simon Kelleyc72daea2012-01-05 21:33:27 +000037 int class = IPTOS_CLASS_CS6;
Simon Kelley4cb1b322012-02-06 14:30:41 +000038#endif
Simon Kelleyc72daea2012-01-05 21:33:27 +000039
40 if ((fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1 ||
Simon Kelley4cb1b322012-02-06 14:30:41 +000041#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6)
Simon Kelleyc72daea2012-01-05 21:33:27 +000042 setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &class, sizeof(class)) == -1 ||
Simon Kelley4cb1b322012-02-06 14:30:41 +000043#endif
Simon Kelleyc72daea2012-01-05 21:33:27 +000044 !fix_fd(fd) ||
45 !set_ipv6pktinfo(fd))
46 die (_("cannot create DHCPv6 socket: %s"), NULL, EC_BADNET);
47
48 memset(&saddr, 0, sizeof(saddr));
49#ifdef HAVE_SOCKADDR_SA_LEN
Simon Kelley4cb1b322012-02-06 14:30:41 +000050 saddr.sin6_len = sizeof(struct sockaddr_in6);
Simon Kelleyc72daea2012-01-05 21:33:27 +000051#endif
52 saddr.sin6_family = AF_INET6;
53 saddr.sin6_addr = in6addr_any;
54 saddr.sin6_port = htons(DHCPV6_SERVER_PORT);
55
56 if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in6)))
57 die(_("failed to bind DHCPv6 server socket: %s"), NULL, EC_BADNET);
58
Simon Kelleyc72daea2012-01-05 21:33:27 +000059 daemon->dhcp6fd = fd;
Simon Kelleyc72daea2012-01-05 21:33:27 +000060}
61
Simon Kelleyc72daea2012-01-05 21:33:27 +000062void dhcp6_packet(time_t now)
63{
64 struct dhcp_context *context;
65 struct iface_param parm;
66 struct cmsghdr *cmptr;
67 struct msghdr msg;
68 int if_index = 0;
69 union {
70 struct cmsghdr align; /* this ensures alignment */
71 char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
72 } control_u;
Simon Kelleyc5ad4e72012-02-24 16:06:20 +000073 struct sockaddr_in6 from;
Simon Kelleyc72daea2012-01-05 21:33:27 +000074 struct all_addr dest;
75 ssize_t sz;
76 struct ifreq ifr;
77 struct iname *tmp;
78
79 msg.msg_control = control_u.control6;
80 msg.msg_controllen = sizeof(control_u);
81 msg.msg_flags = 0;
82 msg.msg_name = &from;
83 msg.msg_namelen = sizeof(from);
84 msg.msg_iov = &daemon->dhcp_packet;
85 msg.msg_iovlen = 1;
86
Simon Kelley52b92f42012-01-22 16:05:15 +000087 if ((sz = recv_dhcp_packet(daemon->dhcp6fd, &msg)) == -1 || sz <= 4)
Simon Kelleyc72daea2012-01-05 21:33:27 +000088 return;
89
90 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
91 if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
92 {
93 union {
94 unsigned char *c;
95 struct in6_pktinfo *p;
96 } p;
97 p.c = CMSG_DATA(cmptr);
98
99 if_index = p.p->ipi6_ifindex;
100 dest.addr.addr6 = p.p->ipi6_addr;
101 }
102
103 if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name))
104 return;
Simon Kelley52b92f42012-01-22 16:05:15 +0000105
Simon Kelleyc72daea2012-01-05 21:33:27 +0000106 if (!iface_check(AF_INET6, (struct all_addr *)&dest, ifr.ifr_name))
107 return;
108
109 for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
110 if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0))
111 return;
112
113 /* weird libvirt-inspired access control */
Simon Kelley52b92f42012-01-22 16:05:15 +0000114 for (context = daemon->dhcp6; context; context = context->next)
Simon Kelleyc72daea2012-01-05 21:33:27 +0000115 if (!context->interface || strcmp(context->interface, ifr.ifr_name) == 0)
116 break;
117
118 if (!context)
119 return;
Simon Kelley4cb1b322012-02-06 14:30:41 +0000120
Simon Kelleyc72daea2012-01-05 21:33:27 +0000121 /* unlinked contexts are marked by context->current == context */
Simon Kelley52b92f42012-01-22 16:05:15 +0000122 for (context = daemon->dhcp6; context; context = context->next)
Simon Kelleye44ddca2012-02-18 17:08:50 +0000123 {
124 context->current = context;
125 memset(&context->local6, 0, IN6ADDRSZ);
126 }
127
Simon Kelleyc72daea2012-01-05 21:33:27 +0000128 parm.current = NULL;
129 parm.ind = if_index;
Simon Kelleye44ddca2012-02-18 17:08:50 +0000130 memset(&parm.fallback, 0, IN6ADDRSZ);
Simon Kelleyc72daea2012-01-05 21:33:27 +0000131
132 if (!iface_enumerate(AF_INET6, &parm, complete_context6))
133 return;
134
135 lease_prune(NULL, now); /* lose any expired leases */
136
Simon Kelleyc5ad4e72012-02-24 16:06:20 +0000137 sz = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback,
138 sz, IN6_IS_ADDR_MULTICAST(&from.sin6_addr), now);
Simon Kelley62779782012-02-10 21:19:25 +0000139
Simon Kelleyc72daea2012-01-05 21:33:27 +0000140 lease_update_file(now);
141 lease_update_dns();
142
143 if (sz != 0)
Simon Kelley87b8ecb2012-02-18 21:20:43 +0000144 while (sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, sz, 0, (struct sockaddr *)&from, sizeof(from)) == -1 &&
Simon Kelley4cb1b322012-02-06 14:30:41 +0000145 retry_send());
Simon Kelleyc72daea2012-01-05 21:33:27 +0000146}
147
148static int complete_context6(struct in6_addr *local, int prefix,
Simon Kelley52b92f42012-01-22 16:05:15 +0000149 int scope, int if_index, int dad, void *vparam)
Simon Kelleyc72daea2012-01-05 21:33:27 +0000150{
151 struct dhcp_context *context;
152 struct iface_param *param = vparam;
Simon Kelley4cb1b322012-02-06 14:30:41 +0000153
Simon Kelley52b92f42012-01-22 16:05:15 +0000154 (void)scope; /* warning */
Simon Kelley4cb1b322012-02-06 14:30:41 +0000155 (void)dad;
Simon Kelleye44ddca2012-02-18 17:08:50 +0000156
157 if (if_index == param->ind &&
158 !IN6_IS_ADDR_LOOPBACK(local) &&
159 !IN6_IS_ADDR_LINKLOCAL(local) &&
160 !IN6_IS_ADDR_MULTICAST(local))
Simon Kelleyc72daea2012-01-05 21:33:27 +0000161 {
Simon Kelleye44ddca2012-02-18 17:08:50 +0000162 /* Determine a globally address on the arrival interface, even
163 if we have no matching dhcp-context, because we're only
164 allocating on remote subnets via relays. This
165 is used as a default for the DNS server option. */
Simon Kelley270dc2e2012-02-19 20:53:20 +0000166 param->fallback = *local;
Simon Kelleye44ddca2012-02-18 17:08:50 +0000167
168 for (context = daemon->dhcp6; context; context = context->next)
169 {
170 if (prefix == context->prefix &&
171 is_same_net6(local, &context->start6, prefix) &&
172 is_same_net6(local, &context->end6, prefix))
173 {
174 /* link it onto the current chain if we've not seen it before */
175 if (context->current == context)
176 {
177 context->current = param->current;
178 param->current = context;
179 context->local6 = *local;
180 }
181 }
Simon Kelleyc72daea2012-01-05 21:33:27 +0000182 }
183 }
184 return 1;
185}
Simon Kelley52b92f42012-01-22 16:05:15 +0000186
187struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr)
188{
189 struct dhcp_config *config;
190
191 for (config = configs; config; config = config->next)
192 if ((config->flags & CONFIG_ADDR6) &&
193 is_same_net6(&config->addr6, net, prefix) &&
194 (prefix == 128 || addr6part(&config->addr6) == addr))
195 return config;
196
197 return NULL;
198}
199
200int address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len,
Simon Kelley4cb1b322012-02-06 14:30:41 +0000201 int serial, struct dhcp_netid *netids, struct in6_addr *ans)
Simon Kelley52b92f42012-01-22 16:05:15 +0000202{
203 /* Find a free address: exclude anything in use and anything allocated to
204 a particular hwaddr/clientid/hostname in our configuration.
205 Try to return from contexts which match netids first.
206
207 Note that we assume the address prefix lengths are 64 or greater, so we can
208 get by with 64 bit arithmetic.
209*/
210
211 u64 start, addr;
212 struct dhcp_context *c, *d;
213 int i, pass;
214 u64 j;
215
216 /* hash hwaddr: use the SDBM hashing algorithm. This works
217 for MAC addresses, let's see how it manages with client-ids! */
218 for (j = 0, i = 0; i < clid_len; i++)
219 j += clid[i] + (j << 6) + (j << 16) - j;
220
221 for (pass = 0; pass <= 1; pass++)
222 for (c = context; c; c = c->current)
223 if (c->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
224 continue;
225 else if (!match_netid(c->filter, netids, pass))
226 continue;
227 else
Simon Kelley07933802012-02-14 20:55:25 +0000228 {
229 if (option_bool(OPT_CONSEC_ADDR))
230 /* seed is largest extant lease addr in this context */
231 start = lease_find_max_addr6(c) + serial;
232 else
233 start = addr6part(&c->start6) + ((j + c->addr_epoch + serial) % (1 + addr6part(&c->end6) - addr6part(&c->start6)));
Simon Kelley52b92f42012-01-22 16:05:15 +0000234
235 /* iterate until we find a free address. */
236 addr = start;
237
238 do {
239 /* eliminate addresses in use by the server. */
240 for (d = context; d; d = d->current)
Simon Kelleye44ddca2012-02-18 17:08:50 +0000241 if (addr == addr6part(&d->local6))
Simon Kelley52b92f42012-01-22 16:05:15 +0000242 break;
243
244 if (!d &&
245 !lease6_find_by_addr(&c->start6, c->prefix, addr) &&
246 !config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, addr))
247 {
248 *ans = c->start6;
249 setaddr6part (ans, addr);
250 return 1;
251 }
252
253 addr++;
254
255 if (addr == addr6part(&c->end6) + 1)
256 addr = addr6part(&c->start6);
257
258 } while (addr != start);
259 }
260
261 return 0;
262}
263
264struct dhcp_context *address6_available(struct dhcp_context *context,
265 struct in6_addr *taddr,
266 struct dhcp_netid *netids)
267{
268 u64 start, end, addr = addr6part(taddr);
269 struct dhcp_context *tmp;
270
271 for (tmp = context; tmp; tmp = tmp->current)
272 {
273 start = addr6part(&tmp->start6);
274 end = addr6part(&tmp->end6);
275
276 if (!(tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY)) &&
277 is_same_net6(&context->start6, taddr, context->prefix) &&
278 is_same_net6(&context->end6, taddr, context->prefix) &&
279 addr >= start &&
280 addr <= end &&
281 match_netid(tmp->filter, netids, 1))
282 return tmp;
283 }
284
285 return NULL;
286}
287
288struct dhcp_context *narrow_context6(struct dhcp_context *context,
289 struct in6_addr *taddr,
290 struct dhcp_netid *netids)
291{
292 /* We start of with a set of possible contexts, all on the current physical interface.
293 These are chained on ->current.
294 Here we have an address, and return the actual context correponding to that
295 address. Note that none may fit, if the address came a dhcp-host and is outside
296 any dhcp-range. In that case we return a static range if possible, or failing that,
297 any context on the correct subnet. (If there's more than one, this is a dodgy
298 configuration: maybe there should be a warning.) */
299
300 struct dhcp_context *tmp;
301
302 if (!(tmp = address6_available(context, taddr, netids)))
303 {
304 for (tmp = context; tmp; tmp = tmp->current)
305 if (match_netid(tmp->filter, netids, 1) &&
306 is_same_net6(taddr, &tmp->start6, tmp->prefix) &&
307 (tmp->flags & CONTEXT_STATIC))
308 break;
309
310 if (!tmp)
311 for (tmp = context; tmp; tmp = tmp->current)
312 if (match_netid(tmp->filter, netids, 1) &&
313 is_same_net6(taddr, &tmp->start6, tmp->prefix) &&
314 !(tmp->flags & CONTEXT_PROXY))
315 break;
316 }
317
318 /* Only one context allowed now */
319 if (tmp)
320 tmp->current = NULL;
321
322 return tmp;
323}
324
Simon Kelley4cb1b322012-02-06 14:30:41 +0000325static int is_addr_in_context6(struct dhcp_context *context, struct dhcp_config *config)
326{
327 if (!context) /* called via find_config() from lease_update_from_configs() */
328 return 1;
329 if (!(config->flags & CONFIG_ADDR6))
330 return 1;
331 for (; context; context = context->current)
332 if (is_same_net6(&config->addr6, &context->start6, context->prefix))
333 return 1;
334
335 return 0;
336}
337
338
339struct dhcp_config *find_config6(struct dhcp_config *configs,
340 struct dhcp_context *context,
341 unsigned char *duid, int duid_len,
342 char *hostname)
343{
Simon Kelley4cb1b322012-02-06 14:30:41 +0000344 struct dhcp_config *config;
Simon Kelley4cb1b322012-02-06 14:30:41 +0000345
Simon Kelley60ac5af2012-02-15 10:41:03 +0000346 if (duid)
347 for (config = configs; config; config = config->next)
348 if (config->flags & CONFIG_CLID)
Simon Kelley4cb1b322012-02-06 14:30:41 +0000349 {
Simon Kelley60ac5af2012-02-15 10:41:03 +0000350 if (config->clid_len == duid_len &&
351 memcmp(config->clid, duid, duid_len) == 0 &&
Simon Kelley4cb1b322012-02-06 14:30:41 +0000352 is_addr_in_context6(context, config))
353 return config;
Simon Kelley60ac5af2012-02-15 10:41:03 +0000354 }
355
Simon Kelley4cb1b322012-02-06 14:30:41 +0000356 if (hostname && context)
357 for (config = configs; config; config = config->next)
358 if ((config->flags & CONFIG_NAME) &&
359 hostname_isequal(config->hostname, hostname) &&
360 is_addr_in_context6(context, config))
361 return config;
362
Simon Kelley4cb1b322012-02-06 14:30:41 +0000363 return NULL;
364}
365
366void make_duid(time_t now)
367{
368 /* rebase epoch to 1/1/2000 */
369 time_t newnow = now - 946684800;
Simon Kelley6aef6002012-02-11 22:01:50 +0000370
Simon Kelley4cb1b322012-02-06 14:30:41 +0000371 iface_enumerate(AF_LOCAL, &newnow, make_duid1);
Simon Kelley6aef6002012-02-11 22:01:50 +0000372
373 if(!daemon->duid)
374 die("Cannot create DHCPv6 server DUID: %s", NULL, EC_MISC);
Simon Kelley4cb1b322012-02-06 14:30:41 +0000375}
376
Simon Kelleyc5ad4e72012-02-24 16:06:20 +0000377static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm)
Simon Kelley4cb1b322012-02-06 14:30:41 +0000378{
379 /* create DUID as specified in RFC3315. We use the MAC of the
380 first interface we find that isn't loopback or P-to-P */
381
382 unsigned char *p;
383
Simon Kelleyc5ad4e72012-02-24 16:06:20 +0000384 (void)index;
385
Simon Kelleye3e86342012-03-01 10:35:34 +0000386#ifdef HAVE_BROKEN_RTC
387 daemon->duid = p = safe_malloc(maclen + 4);
388 daemon->duid_len = maclen + 4;
389 PUTSHORT(3, p); /* DUID_LL */
390 PUTSHORT(type, p); /* address type */
391#else
Simon Kelley4cb1b322012-02-06 14:30:41 +0000392 daemon->duid = p = safe_malloc(maclen + 8);
393 daemon->duid_len = maclen + 8;
Simon Kelley4cb1b322012-02-06 14:30:41 +0000394 PUTSHORT(1, p); /* DUID_LLT */
Simon Kelley4cb1b322012-02-06 14:30:41 +0000395 PUTSHORT(type, p); /* address type */
Simon Kelley4cb1b322012-02-06 14:30:41 +0000396 PUTLONG(*((time_t *)parm), p); /* time */
397#endif
398
399 memcpy(p, mac, maclen);
400
401 return 0;
402}
Simon Kelleyc72daea2012-01-05 21:33:27 +0000403#endif
404
405