blob: 67dc2be4b3d025dfdcf57ddae3f9b67787060c67 [file] [log] [blame]
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001/* dnsmasq is Copyright (c) 2000-2003 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.
6
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
11*/
12
13/* Author's email: simon@thekelleys.org.uk */
14
15#include "dnsmasq.h"
16
17#define BOOTREQUEST 1
18#define BOOTREPLY 2
19#define DHCP_COOKIE 0x63825363
20
21#define OPTION_PAD 0
22#define OPTION_NETMASK 1
23#define OPTION_ROUTER 3
24#define OPTION_DNSSERVER 6
25#define OPTION_HOSTNAME 12
26#define OPTION_DOMAINNAME 15
27#define OPTION_BROADCAST 28
28#define OPTION_CLIENT_ID 61
29#define OPTION_REQUESTED_IP 50
30#define OPTION_LEASE_TIME 51
31#define OPTION_OVERLOAD 52
32#define OPTION_MESSAGE_TYPE 53
33#define OPTION_SERVER_IDENTIFIER 54
34#define OPTION_REQUESTED_OPTIONS 55
35#define OPTION_MAXMESSAGE 57
36#define OPTION_END 255
37
38#define DHCPDISCOVER 1
39#define DHCPOFFER 2
40#define DHCPREQUEST 3
41#define DHCPDECLINE 4
42#define DHCPACK 5
43#define DHCPNAK 6
44#define DHCPRELEASE 7
45#define DHCPINFORM 8
46
47static unsigned char *option_put(unsigned char *p, unsigned char *end, int opt, int len, unsigned int val);
48static void bootp_option_put(struct dhcp_packet *mess, char *filename, char *sname);
49static int option_len(unsigned char *opt);
50static void *option_ptr(unsigned char *opt);
51static struct in_addr option_addr(unsigned char *opt);
52static unsigned int option_uint(unsigned char *opt);
53static void log_packet(char *type, struct in_addr *addr, unsigned char *hwaddr, char *interface);
54static unsigned char *option_find(struct dhcp_packet *mess, int size, int opt_type);
55static unsigned char *do_req_options(struct dhcp_context *context,
56 unsigned char *p, unsigned char *end,
57 unsigned char *req_options,
58 struct dhcp_opt *config_opts,
59 char *domainname, char *hostname);
60
61
62int dhcp_reply(struct dhcp_context *context, struct dhcp_packet *mess,
63 unsigned int sz, time_t now, char *namebuff,
64 struct dhcp_opt *dhcp_opts, struct dhcp_config *dhcp_configs,
65 char *domain_suffix, char *dhcp_file, char *dhcp_sname,
66 struct in_addr dhcp_next_server)
67{
68 unsigned char *opt, *clid;
69 struct dhcp_lease *lease;
70 int clid_len;
71 unsigned char *p = mess->options;
72 char *hostname = NULL;
73 char *req_options = NULL;
74 unsigned int renewal_time, expires_time, def_time;
75 struct dhcp_config *config;
76
77 if (mess->op != BOOTREQUEST ||
78 mess->htype != ARPHRD_ETHER ||
79 mess->hlen != ETHER_ADDR_LEN ||
80 mess->cookie != htonl(DHCP_COOKIE))
81 return 0;
82
83 mess->op = BOOTREPLY;
84
85 /* If there is no client identifier option, use the hardware address */
86 if ((opt = option_find(mess, sz, OPTION_CLIENT_ID)))
87 {
88 clid = option_ptr(opt);
89 clid_len = option_len(opt);
90 }
91 else
92 {
93 clid = mess->chaddr;
94 clid_len = 0;
95 }
96
97 /* do we have a lease in store? */
98 lease = lease_find_by_client(clid, clid_len);
99
100 if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS)))
101 {
102 int len = option_len(opt);
103 req_options = namebuff;
104 memcpy(req_options, option_ptr(opt), len);
105 req_options[len] = OPTION_END;
106 }
107
108 if ((config = find_config(dhcp_configs, context, clid, clid_len, mess->chaddr, NULL)) &&
109 config->hostname)
110 hostname = config->hostname;
111 else if ((opt = option_find(mess, sz, OPTION_HOSTNAME)))
112 {
113 int len = option_len(opt);
114 /* namebuff is 1K long, use half for requested options and half for hostname */
115 /* len < 256 by definition */
116 hostname = namebuff + 500;
117 memcpy(hostname, option_ptr(opt), len);
118 /* May not be zero terminated */
119 hostname[len] = 0;
120 /* ensure there are no strange chars in there */
121 if (!canonicalise(hostname))
122 hostname = NULL;
123 }
124
125 if (hostname)
126 {
127 char *dot = strchr(hostname, '.');
128 if (dot)
129 {
130 if (!domain_suffix || !hostname_isequal(dot+1, domain_suffix))
131 {
132 syslog(LOG_WARNING, "Ignoring DHCP host name %s because it has an illegal domain part", hostname);
133 hostname = NULL;
134 }
135 else
136 *dot = 0; /* truncate */
137 }
138 }
139
140 /* search again now we have a hostname */
141 config = find_config(dhcp_configs, context, clid, clid_len, mess->chaddr, hostname);
Simon Kelley1ab84e22004-01-29 16:48:35 +0000142 def_time = config && config->lease_time ? config->lease_time : context->lease_time;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000143
144 if ((opt = option_find(mess, sz, OPTION_LEASE_TIME)))
145 {
146 unsigned int req_time = option_uint(opt);
147
148 if (def_time == 0xffffffff ||
149 (req_time != 0xffffffff && req_time < def_time))
150 expires_time = renewal_time = req_time;
151 else
152 expires_time = renewal_time = def_time;
153 }
154 else
155 {
156 renewal_time = def_time;
157 if (lease)
158 expires_time = (unsigned int)difftime(lease->expires, now);
159 else
160 expires_time = def_time;
161 }
162
163 if (!(opt = option_find(mess, sz, OPTION_MESSAGE_TYPE)))
164 return 0;
165
166 switch (opt[2])
167 {
168 case DHCPRELEASE:
169 if (lease)
170 {
171 log_packet("RELEASE", &lease->addr, mess->chaddr, context->iface);
172 lease_prune(lease, now);
173 }
174 return 0;
175
176 case DHCPDISCOVER:
177
178 if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP)))
179 mess->yiaddr = option_addr(opt);
180
181 log_packet("DISCOVER", opt ? &mess->yiaddr : NULL, mess->chaddr, context->iface);
182
183 if (lease)
184 mess->yiaddr = lease->addr;
185 else if (config && config->addr.s_addr && !lease_find_by_addr(config->addr))
186 mess->yiaddr = config->addr;
187 else if ((!opt || !address_available(context, mess->yiaddr)) &&
188 !address_allocate(context, dhcp_configs, &mess->yiaddr))
189 {
190 syslog(LOG_WARNING, "address pool exhausted");
191 return 0;
192 }
193
194 bootp_option_put(mess, dhcp_file, dhcp_sname);
195 mess->siaddr = dhcp_next_server;
196 p = option_put(p, &mess->options[308], OPTION_MESSAGE_TYPE, 1, DHCPOFFER);
197 p = option_put(p, &mess->options[308], OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(context->serv_addr.s_addr));
198 p = option_put(p, &mess->options[308], OPTION_LEASE_TIME, 4, expires_time);
199 p = do_req_options(context, p, &mess->options[308], req_options, dhcp_opts, domain_suffix, NULL);
200 p = option_put(p, &mess->options[308], OPTION_END, 0, 0);
201
202 log_packet("OFFER" , &mess->yiaddr, mess->chaddr, context->iface);
203 return p - (unsigned char *)mess;
204
205
206 case DHCPREQUEST:
207 if (mess->ciaddr.s_addr)
208 {
209 /* RENEWING or REBINDING */
210 /* Must exist a lease for this address */
211 log_packet("REQUEST", &mess->ciaddr, mess->chaddr, context->iface);
212
213 if (!lease || mess->ciaddr.s_addr != lease->addr.s_addr)
214 {
215 log_packet("NAK", &mess->ciaddr, mess->chaddr, context->iface);
216
217 mess->siaddr.s_addr = mess->yiaddr.s_addr = mess->ciaddr.s_addr = 0;
218 bootp_option_put(mess, NULL, NULL);
219 p = option_put(p, &mess->options[308], OPTION_MESSAGE_TYPE, 1, DHCPNAK);
220 p = option_put(p, &mess->options[308], OPTION_END, 0, 0);
221
222 return (unsigned char *)mess - p; /* -ve to force bcast */
223 }
224
225 mess->yiaddr = mess->ciaddr;
226 }
227 else
228 {
229 /* SELECTING or INIT_REBOOT */
230 if ((opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER)) &&
231 (context->serv_addr.s_addr != option_addr(opt).s_addr))
232 return 0;
233
234 if (!(opt = option_find(mess, sz, OPTION_REQUESTED_IP)))
235 return 0;
236
237 mess->yiaddr = option_addr(opt);
238 log_packet("REQUEST", &mess->yiaddr, mess->chaddr, context->iface);
239
240 /* If a lease exists for this host and another address, squash it. */
241 if (lease && lease->addr.s_addr != mess->yiaddr.s_addr)
242 {
243 lease_prune(lease, now);
244 lease = NULL;
245 }
246
247 /* accept addresses in the dynamic range or ones allocated statically to
248 particular hosts or an address which the host already has. */
249 if (!lease &&
250 !address_available(context, mess->yiaddr) &&
251 (!config || config->addr.s_addr == 0 || config->addr.s_addr != mess->yiaddr.s_addr))
252 {
253 log_packet("NAK", &mess->yiaddr, mess->chaddr, context->iface);
254
255 mess->siaddr.s_addr = mess->yiaddr.s_addr = mess->ciaddr.s_addr = 0;
256 bootp_option_put(mess, NULL, NULL);
257 p = option_put(p, &mess->options[308], OPTION_MESSAGE_TYPE, 1, DHCPNAK);
258 p = option_put(p, &mess->options[308], OPTION_END, 0, 0);
259
260 return (unsigned char *)mess - p; /* -ve to force bcast */
261 }
262
263 if (!lease &&
264 !(lease = lease_allocate(clid, clid_len, mess->yiaddr)))
265 return 0;
266 }
267
268 lease_set_hwaddr(lease, mess->chaddr);
269 lease_set_hostname(lease, hostname, domain_suffix);
270 lease_set_expires(lease, renewal_time == 0xffffffff ? 0 : now + (time_t)renewal_time);
271
272 bootp_option_put(mess, dhcp_file, dhcp_sname);
273 mess->siaddr = dhcp_next_server;
274 p = option_put(p, &mess->options[308], OPTION_MESSAGE_TYPE, 1, DHCPACK);
275 p = option_put(p, &mess->options[308], OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(context->serv_addr.s_addr));
276 p = option_put(p, &mess->options[308], OPTION_LEASE_TIME, 4, renewal_time);
277 p = do_req_options(context, p, &mess->options[308], req_options, dhcp_opts, domain_suffix, hostname);
278 p = option_put(p, &mess->options[308], OPTION_END, 0, 0);
279
280 log_packet("ACK", &mess->yiaddr, mess->chaddr, context->iface);
281 return p - (unsigned char *)mess;
282
283 case DHCPINFORM:
284 log_packet("INFORM", &mess->ciaddr, mess->chaddr, context->iface);
285
286 p = option_put(p, &mess->options[308], OPTION_MESSAGE_TYPE, 1, DHCPACK);
287 p = option_put(p, &mess->options[308], OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(context->serv_addr.s_addr));
288 p = do_req_options(context, p, &mess->options[308], req_options, dhcp_opts, domain_suffix, hostname);
289 p = option_put(p, &mess->options[308], OPTION_END, 0, 0);
290
291 log_packet("ACK", &mess->ciaddr, mess->chaddr, context->iface);
292 return p - (unsigned char *)mess;
293 }
294
295 return 0;
296}
297
298static void log_packet(char *type, struct in_addr *addr, unsigned char *hwaddr, char *interface)
299{
300 syslog(LOG_INFO, "DHCP%s(%s)%s%s hwaddr=%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
301 type,
302 interface,
303 addr ? " " : "",
304 addr ? inet_ntoa(*addr) : "",
305 hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]);
306}
307
308static int option_len(unsigned char *opt)
309{
310 return opt[1];
311}
312
313static void *option_ptr(unsigned char *opt)
314{
315 return &opt[2];
316}
317
318static struct in_addr option_addr(unsigned char *opt)
319{
320 /* this worries about unaligned data in the option. */
321 /* struct in_addr is network byte order */
322 struct in_addr ret;
323
324 memcpy(&ret, option_ptr(opt), INADDRSZ);
325
326 return ret;
327}
328
329static unsigned int option_uint(unsigned char *opt)
330{
331 /* this worries about unaligned data and byte order */
332 unsigned int ret;
333
334 memcpy(&ret, option_ptr(opt), sizeof(unsigned int));
335
336 return ntohl(ret);
337}
338
339static void bootp_option_put(struct dhcp_packet *mess, char *filename, char *sname)
340{
341 memset(mess->sname, 0, sizeof(mess->sname));
342 memset(mess->file, 0, sizeof(mess->file));
343 if (sname)
344 strncpy(mess->sname, sname, sizeof(mess->sname)-1);
345 if (filename)
346 strncpy(mess->file, filename, sizeof(mess->file)-1);
347}
348
349static unsigned char *option_put(unsigned char *p, unsigned char *end, int opt, int len, unsigned int val)
350{
351 int i;
352
353 if (p + len + 2 < end)
354 {
355 *(p++) = opt;
356 *(p++) = len;
357
358 for (i = 0; i < len; i++)
359 *(p++) = val >> (8 * (len - (i + 1)));
360 }
361 return p;
362}
363
364static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt, int *overload)
365{
366
367 if (!p)
368 return NULL;
369
370 while (*p != OPTION_END)
371 {
372 if (end && (p >= end))
373 return 0; /* malformed packet */
374 else if (*p == OPTION_PAD)
375 p++;
376 else if (*p == OPTION_OVERLOAD)
377 {
378 if (end && (p >= end - 3))
379 return 0; /* malformed packet */
380 if (overload)
381 *overload = *(p+2);
382 p += 3;
383 }
384 else
385 {
386 int opt_len;;
387 if (end && (p >= end - 2))
388 return 0; /* malformed packet */
389 opt_len = option_len(p);
390 if (end && (p >= end - (2 + opt_len)))
391 return 0; /* malformed packet */
392 if (*p == opt)
393 return p;
394 p += opt_len + 2;
395 }
396 }
397
398 return NULL;
399}
400
401static unsigned char *option_find(struct dhcp_packet *mess, int size, int opt_type)
402{
403 int overload = 0;
404 unsigned char *ret;
405
406 ret = option_find1(&mess->options[0], ((unsigned char *)mess) + size, opt_type, &overload);
407
408 if (!ret && (overload & 1))
409 ret = option_find1(&mess->file[0], &mess->file[128], opt_type, &overload);
410
411 if (!ret && (overload & 2))
412 ret = option_find1(&mess->sname[0], &mess->file[64], opt_type, &overload);
413
414 return ret;
415}
416
417static int in_list(unsigned char *list, int opt)
418{
419 int i;
420
421 for (i = 0; list[i] != OPTION_END; i++)
422 if (opt == list[i])
423 return 1;
424
425 return 0;
426}
427
428static struct dhcp_opt *option_find2(struct dhcp_opt *opts, int opt)
429{
430 for (; opts; opts = opts->next)
431 if (opts->opt == opt)
432 return opts;
433 return NULL;
434}
435
436static unsigned char *do_req_options(struct dhcp_context *context,
437 unsigned char *p, unsigned char *end,
438 unsigned char *req_options,
439 struct dhcp_opt *config_opts,
440 char *domainname, char *hostname)
441{
442 int i;
443
444 if (!req_options)
445 return p;
446
Simon Kelley1ab84e22004-01-29 16:48:35 +0000447 if (in_list(req_options, OPTION_MAXMESSAGE))
448 p = option_put(p, end, OPTION_MAXMESSAGE, 2, sizeof(struct udp_dhcp_packet));
449
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000450 if (in_list(req_options, OPTION_NETMASK) &&
451 !option_find2(config_opts, OPTION_NETMASK))
452 p = option_put(p, end, OPTION_NETMASK, INADDRSZ, ntohl(context->netmask.s_addr));
453
454 if (in_list(req_options, OPTION_BROADCAST) &&
455 !option_find2(config_opts, OPTION_BROADCAST))
456 p = option_put(p, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr));
457
458 if (in_list(req_options, OPTION_ROUTER) &&
459 !option_find2(config_opts, OPTION_ROUTER))
460 p = option_put(p, end, OPTION_ROUTER, INADDRSZ, ntohl(context->serv_addr.s_addr));
461
462 if (in_list(req_options, OPTION_DNSSERVER) &&
463 !option_find2(config_opts, OPTION_DNSSERVER))
464 p = option_put(p, end, OPTION_DNSSERVER, INADDRSZ, ntohl(context->serv_addr.s_addr));
465
466 if (in_list(req_options, OPTION_DOMAINNAME) &&
467 !option_find2(config_opts, OPTION_DOMAINNAME) &&
468 domainname && (p + strlen(domainname) + 2 < end))
469 {
470 *(p++) = OPTION_DOMAINNAME;
471 *(p++) = strlen(domainname);
472 memcpy(p, domainname, strlen(domainname));
473 p += strlen(domainname);
474 }
475
476 /* Note that we ignore attempts to set the hostname using
477 --dhcp-option=12,<name> */
478 if (in_list(req_options, OPTION_HOSTNAME) &&
479 hostname && (p + strlen(hostname) + 2 < end))
480 {
481 *(p++) = OPTION_HOSTNAME;
482 *(p++) = strlen(hostname);
483 memcpy(p, hostname, strlen(hostname));
484 p += strlen(hostname);
485 }
486
487 for (i = 0; req_options[i] != OPTION_END; i++)
488 {
489 struct dhcp_opt *opt = option_find2(config_opts, req_options[i]);
Simon Kelley1ab84e22004-01-29 16:48:35 +0000490 if (req_options[i] != OPTION_HOSTNAME &&
491 req_options[i] != OPTION_MAXMESSAGE &&
492 opt && (p + opt->len + 2 < end))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000493 {
494 *(p++) = opt->opt;
495 *(p++) = opt->len;
Simon Kelley1ab84e22004-01-29 16:48:35 +0000496 if (opt->len != 0)
497 {
498 if (opt->is_addr)
499 {
500 int j;
501 struct in_addr *a = (struct in_addr *)opt->val;
502 for (j = 0; j < opt->len; j+=INADDRSZ, a++)
503 {
504 /* zero means "self" */
505 if (a->s_addr == 0)
506 memcpy(p, &context->serv_addr, INADDRSZ);
507 else
508 memcpy(p, a, INADDRSZ);
509 p += INADDRSZ;
510 }
511 }
512 else
513 {
514 memcpy(p, opt->val, opt->len);
515 p += opt->len;
516 }
517 }
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000518 }
519 }
520
521 return p;
522}
523
524