]>
git.ipfire.org Git - people/ms/dnsmasq.git/blob - src/rfc2131.c
1 /* dnsmasq is Copyright (c) 2000-2003 Simon Kelley
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.
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.
13 /* Author's email: simon@thekelleys.org.uk */
19 #define DHCP_COOKIE 0x63825363
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_REQUESTED_IP 50
29 #define OPTION_LEASE_TIME 51
30 #define OPTION_OVERLOAD 52
31 #define OPTION_MESSAGE_TYPE 53
32 #define OPTION_SERVER_IDENTIFIER 54
33 #define OPTION_REQUESTED_OPTIONS 55
34 #define OPTION_MESSAGE 56
35 #define OPTION_MAXMESSAGE 57
38 #define OPTION_CLIENT_ID 61
39 #define OPTION_END 255
41 #define DHCPDISCOVER 1
50 static unsigned char *option_put(unsigned char *p
, unsigned char *end
, int opt
, int len
, unsigned int val
);
51 static unsigned char *option_put_string(unsigned char *p
, unsigned char *end
, int opt
, char *string
);
52 static void bootp_option_put(struct dhcp_packet
*mess
, char *filename
, char *sname
);
53 static int option_len(unsigned char *opt
);
54 static void *option_ptr(unsigned char *opt
);
55 static struct in_addr
option_addr(unsigned char *opt
);
56 static unsigned int option_uint(unsigned char *opt
, int size
);
57 static void log_packet(char *type
, struct in_addr
*addr
, unsigned char *hwaddr
, char *interface
, char *string
);
58 static unsigned char *option_find(struct dhcp_packet
*mess
, int size
, int opt_type
);
59 static unsigned char *do_req_options(struct dhcp_context
*context
,
60 unsigned char *p
, unsigned char *end
,
61 unsigned char *req_options
,
62 struct dhcp_opt
*config_opts
,
63 char *domainname
, char *hostname
,
65 struct in_addr iface_addr
,
69 int dhcp_reply(struct dhcp_context
*context
,
70 struct in_addr iface_addr
,
73 struct udp_dhcp_packet
*rawpacket
,
74 unsigned int sz
, time_t now
, char *namebuff
,
75 struct dhcp_opt
*dhcp_opts
, struct dhcp_config
*dhcp_configs
,
76 char *domain_suffix
, char *dhcp_file
, char *dhcp_sname
,
77 struct in_addr dhcp_next_server
)
79 unsigned char *opt
, *clid
;
80 struct dhcp_lease
*lease
;
82 struct dhcp_packet
*mess
= &rawpacket
->data
;
83 unsigned char *p
= mess
->options
;
84 /* default max reply packet length, max be overridden */
85 unsigned char *end
= (unsigned char *)(rawpacket
+ 1);
86 char *hostname
= NULL
;
87 char *req_options
= NULL
;
89 unsigned int renewal_time
, expires_time
, def_time
;
90 struct dhcp_config
*config
;
92 if (mess
->op
!= BOOTREQUEST
||
93 mess
->htype
!= ARPHRD_ETHER
||
94 mess
->hlen
!= ETHER_ADDR_LEN
||
95 mess
->cookie
!= htonl(DHCP_COOKIE
))
100 if ((opt
= option_find(mess
, sz
, OPTION_MAXMESSAGE
)))
102 int maxsize
= (int)option_uint(opt
, 2);
103 if (maxsize
> DNSMASQ_PACKETSZ
)
104 maxsize
= DNSMASQ_PACKETSZ
;
105 if (maxsize
> iface_mtu
)
108 end
= ((unsigned char *)rawpacket
) + maxsize
;
111 /* If there is no client identifier option, use the hardware address */
112 if ((opt
= option_find(mess
, sz
, OPTION_CLIENT_ID
)))
114 clid
= option_ptr(opt
);
115 clid_len
= option_len(opt
);
123 /* do we have a lease in store? */
124 lease
= lease_find_by_client(clid
, clid_len
);
126 if ((opt
= option_find(mess
, sz
, OPTION_REQUESTED_OPTIONS
)))
128 int len
= option_len(opt
);
129 req_options
= namebuff
;
130 memcpy(req_options
, option_ptr(opt
), len
);
131 req_options
[len
] = OPTION_END
;
134 if ((config
= find_config(dhcp_configs
, context
, clid
, clid_len
, mess
->chaddr
, NULL
)) &&
136 hostname
= config
->hostname
;
137 else if ((opt
= option_find(mess
, sz
, OPTION_HOSTNAME
)))
139 int len
= option_len(opt
);
140 /* namebuff is 1K long, use half for requested options and half for hostname */
141 /* len < 256 by definition */
142 hostname
= namebuff
+ 500;
143 memcpy(hostname
, option_ptr(opt
), len
);
144 /* May not be zero terminated */
146 /* ensure there are no strange chars in there */
147 if (!canonicalise(hostname
))
151 char *dot
= strchr(hostname
, '.');
154 if (!domain_suffix
|| !hostname_isequal(dot
+1, domain_suffix
))
156 syslog(LOG_WARNING
, "Ignoring DHCP host name %s because it has an illegal domain part", hostname
);
160 *dot
= 0; /* truncate */
165 /* search again now we have a hostname */
166 config
= find_config(dhcp_configs
, context
, clid
, clid_len
, mess
->chaddr
, hostname
);
167 def_time
= config
&& config
->lease_time
? config
->lease_time
: context
->lease_time
;
169 if ((opt
= option_find(mess
, sz
, OPTION_LEASE_TIME
)))
171 unsigned int req_time
= option_uint(opt
, 4);
173 if (def_time
== 0xffffffff ||
174 (req_time
!= 0xffffffff && req_time
< def_time
))
175 expires_time
= renewal_time
= req_time
;
177 expires_time
= renewal_time
= def_time
;
181 renewal_time
= def_time
;
183 expires_time
= (unsigned int)difftime(lease
->expires
, now
);
185 expires_time
= def_time
;
188 if (!(opt
= option_find(mess
, sz
, OPTION_MESSAGE_TYPE
)))
194 if (!(opt
= option_find(mess
, sz
, OPTION_SERVER_IDENTIFIER
)) ||
195 (iface_addr
.s_addr
!= option_addr(opt
).s_addr
))
198 /* sanitise any message. Paranoid? Moi? */
199 if ((opt
= option_find(mess
, sz
, OPTION_MESSAGE
)))
201 char *p
= option_ptr(opt
), *q
= namebuff
;
204 for (i
= option_len(opt
); i
> 0; i
--)
210 *q
++ = 0; /* add terminator */
214 if (!(opt
= option_find(mess
, sz
, OPTION_REQUESTED_IP
)))
217 log_packet("DECLINE", option_ptr(opt
), mess
->chaddr
, iface_name
, message
);
219 if (lease
&& lease
->addr
.s_addr
== option_addr(opt
).s_addr
)
220 lease_prune(lease
, now
);
222 if (config
&& config
->addr
.s_addr
&&
223 config
->addr
.s_addr
== option_addr(opt
).s_addr
)
225 syslog(LOG_WARNING
, "disabling DHCP static address %s", inet_ntoa(config
->addr
));
226 config
->addr
.s_addr
= 0;
232 if (!(opt
= option_find(mess
, sz
, OPTION_SERVER_IDENTIFIER
)) ||
233 (iface_addr
.s_addr
!= option_addr(opt
).s_addr
))
236 log_packet("RELEASE", &mess
->ciaddr
, mess
->chaddr
, iface_name
, NULL
);
238 if (lease
&& lease
->addr
.s_addr
== mess
->ciaddr
.s_addr
)
239 lease_prune(lease
, now
);
244 if ((opt
= option_find(mess
, sz
, OPTION_REQUESTED_IP
)))
245 mess
->yiaddr
= option_addr(opt
);
247 log_packet("DISCOVER", opt
? &mess
->yiaddr
: NULL
, mess
->chaddr
, iface_name
, NULL
);
250 ((lease
->addr
.s_addr
& context
->netmask
.s_addr
) == (context
->start
.s_addr
& context
->netmask
.s_addr
)))
251 mess
->yiaddr
= lease
->addr
;
252 else if (config
&& config
->addr
.s_addr
&& !lease_find_by_addr(config
->addr
))
253 mess
->yiaddr
= config
->addr
;
254 else if ((!opt
|| !address_available(context
, mess
->yiaddr
)) &&
255 !address_allocate(context
, dhcp_configs
, &mess
->yiaddr
))
257 syslog(LOG_WARNING
, "address pool exhausted");
261 bootp_option_put(mess
, dhcp_file
, dhcp_sname
);
262 mess
->siaddr
= dhcp_next_server
;
263 p
= option_put(p
, end
, OPTION_MESSAGE_TYPE
, 1, DHCPOFFER
);
264 p
= option_put(p
, end
, OPTION_SERVER_IDENTIFIER
, INADDRSZ
, ntohl(iface_addr
.s_addr
));
265 p
= option_put(p
, end
, OPTION_LEASE_TIME
, 4, expires_time
);
266 p
= do_req_options(context
, p
, end
, req_options
, dhcp_opts
, domain_suffix
,
267 NULL
, mess
->giaddr
, iface_addr
, iface_mtu
);
268 p
= option_put(p
, end
, OPTION_END
, 0, 0);
270 log_packet("OFFER" , &mess
->yiaddr
, mess
->chaddr
, iface_name
, NULL
);
271 return p
- (unsigned char *)mess
;
275 if ((opt
= option_find(mess
, sz
, OPTION_REQUESTED_IP
)))
277 /* SELECTING or INIT_REBOOT */
278 mess
->yiaddr
= option_addr(opt
);
279 /* The RFC says that this is already zero, but there exist
280 real-world counter examples. */
281 mess
->ciaddr
.s_addr
= 0;
283 if ((opt
= option_find(mess
, sz
, OPTION_SERVER_IDENTIFIER
)) &&
284 (iface_addr
.s_addr
!= option_addr(opt
).s_addr
))
287 /* If a lease exists for this host and another address, squash it. */
288 if (lease
&& lease
->addr
.s_addr
!= mess
->yiaddr
.s_addr
)
290 lease_prune(lease
, now
);
294 /* accept addresses in the dynamic range or ones allocated statically to
295 particular hosts or an address which the host already has. */
298 if (!address_available(context
, mess
->yiaddr
) &&
299 (!config
|| config
->addr
.s_addr
== 0 || config
->addr
.s_addr
!= mess
->yiaddr
.s_addr
))
300 message
= "address unavailable";
301 else if (!(lease
= lease_allocate(clid
, clid_len
, mess
->yiaddr
)))
302 message
= "no leases left";
307 /* RENEWING or REBINDING */
308 /* Must exist a lease for this address */
309 if (!mess
->ciaddr
.s_addr
)
312 mess
->yiaddr
= mess
->ciaddr
;
313 if (!lease
|| mess
->ciaddr
.s_addr
!= lease
->addr
.s_addr
)
314 message
= "lease not found";
317 /* If a machine moves networks whilst it has a lease, we catch that here. */
318 if ((mess
->yiaddr
.s_addr
& context
->netmask
.s_addr
) != (context
->start
.s_addr
& context
->netmask
.s_addr
))
319 message
= "wrong network";
321 log_packet("REQUEST", &mess
->yiaddr
, mess
->chaddr
, iface_name
, NULL
);
325 log_packet("NAK", &mess
->yiaddr
, mess
->chaddr
, iface_name
, message
);
327 mess
->siaddr
.s_addr
= mess
->yiaddr
.s_addr
= mess
->ciaddr
.s_addr
= 0;
328 bootp_option_put(mess
, NULL
, NULL
);
329 p
= option_put(p
, end
, OPTION_MESSAGE_TYPE
, 1, DHCPNAK
);
330 p
= option_put_string(p
, end
, OPTION_MESSAGE
, message
);
331 p
= option_put(p
, end
, OPTION_END
, 0, 0);
332 mess
->flags
|= htons(0x8000); /* broadcast */
333 return p
- (unsigned char *)mess
;
336 log_packet("ACK", &mess
->yiaddr
, mess
->chaddr
, iface_name
, hostname
);
338 lease_set_hwaddr(lease
, mess
->chaddr
);
339 lease_set_hostname(lease
, hostname
, domain_suffix
);
340 lease_set_expires(lease
, renewal_time
== 0xffffffff ? 0 : now
+ (time_t)renewal_time
);
342 bootp_option_put(mess
, dhcp_file
, dhcp_sname
);
343 mess
->siaddr
= dhcp_next_server
;
344 p
= option_put(p
, end
, OPTION_MESSAGE_TYPE
, 1, DHCPACK
);
345 p
= option_put(p
, end
, OPTION_SERVER_IDENTIFIER
, INADDRSZ
, ntohl(iface_addr
.s_addr
));
346 p
= option_put(p
, end
, OPTION_LEASE_TIME
, 4, renewal_time
);
347 if (renewal_time
!= 0xffffffff)
349 unsigned short fuzz
= rand16();
350 while (fuzz
> (renewal_time
/16))
352 p
= option_put(p
, end
, OPTION_T1
, 4, (renewal_time
/2) - fuzz
);
353 p
= option_put(p
, end
, OPTION_T2
, 4, ((renewal_time
* 7)/8) - fuzz
);
355 p
= do_req_options(context
, p
, end
, req_options
, dhcp_opts
, domain_suffix
,
356 hostname
, mess
->giaddr
, iface_addr
, iface_mtu
);
357 p
= option_put(p
, end
, OPTION_END
, 0, 0);
358 return p
- (unsigned char *)mess
;
361 log_packet("INFORM", &mess
->ciaddr
, mess
->chaddr
, iface_name
, NULL
);
363 p
= option_put(p
, end
, OPTION_MESSAGE_TYPE
, 1, DHCPACK
);
364 p
= option_put(p
, end
, OPTION_SERVER_IDENTIFIER
, INADDRSZ
, ntohl(iface_addr
.s_addr
));
365 p
= do_req_options(context
, p
, end
, req_options
, dhcp_opts
, domain_suffix
,
366 hostname
, mess
->giaddr
, iface_addr
, iface_mtu
);
367 p
= option_put(p
, end
, OPTION_END
, 0, 0);
369 log_packet("ACK", &mess
->ciaddr
, mess
->chaddr
, iface_name
, hostname
);
370 return p
- (unsigned char *)mess
;
376 static void log_packet(char *type
, struct in_addr
*addr
, unsigned char *hwaddr
, char *interface
, char *string
)
378 syslog(LOG_INFO
, "DHCP%s(%s)%s%s %.2x:%.2x:%.2x:%.2x:%.2x:%.2x%s%s",
382 addr
? inet_ntoa(*addr
) : "",
383 hwaddr
[0], hwaddr
[1], hwaddr
[2], hwaddr
[3], hwaddr
[4], hwaddr
[5],
385 string
? string
: "");
388 static int option_len(unsigned char *opt
)
393 static void *option_ptr(unsigned char *opt
)
398 static struct in_addr
option_addr(unsigned char *opt
)
400 /* this worries about unaligned data in the option. */
401 /* struct in_addr is network byte order */
404 memcpy(&ret
, option_ptr(opt
), INADDRSZ
);
409 static unsigned int option_uint(unsigned char *opt
, int size
)
411 /* this worries about unaligned data and byte order */
412 unsigned int ret
= 0;
414 unsigned char *p
= option_ptr(opt
);
416 for (i
= 0; i
< size
; i
++)
417 ret
= (ret
<< 8) | *p
++;
422 static void bootp_option_put(struct dhcp_packet
*mess
, char *filename
, char *sname
)
424 memset(mess
->sname
, 0, sizeof(mess
->sname
));
425 memset(mess
->file
, 0, sizeof(mess
->file
));
427 strncpy(mess
->sname
, sname
, sizeof(mess
->sname
)-1);
429 strncpy(mess
->file
, filename
, sizeof(mess
->file
)-1);
432 static unsigned char *option_put(unsigned char *p
, unsigned char *end
, int opt
, int len
, unsigned int val
)
436 /* always keep one octet space for the END option. */
437 if ((opt
== OPTION_END
) || (p
+ len
+ 3 < end
))
440 if (opt
!= OPTION_END
)
444 for (i
= 0; i
< len
; i
++)
445 *(p
++) = val
>> (8 * (len
- (i
+ 1)));
451 static unsigned char *option_put_string(unsigned char *p
, unsigned char *end
, int opt
, char *string
)
453 if (p
+ strlen(string
) + 3 < end
)
456 *(p
++) = strlen(string
);
457 memcpy(p
, string
, strlen(string
));
463 static unsigned char *option_find1(unsigned char *p
, unsigned char *end
, int opt
, int *overload
)
468 while (*p
!= OPTION_END
)
470 if (end
&& (p
>= end
))
471 return 0; /* malformed packet */
472 else if (*p
== OPTION_PAD
)
474 else if (*p
== OPTION_OVERLOAD
)
476 if (end
&& (p
>= end
- 3))
477 return 0; /* malformed packet */
485 if (end
&& (p
>= end
- 2))
486 return 0; /* malformed packet */
487 opt_len
= option_len(p
);
488 if (end
&& (p
>= end
- (2 + opt_len
)))
489 return 0; /* malformed packet */
499 static unsigned char *option_find(struct dhcp_packet
*mess
, int size
, int opt_type
)
504 ret
= option_find1(&mess
->options
[0], ((unsigned char *)mess
) + size
, opt_type
, &overload
);
506 if (!ret
&& (overload
& 1))
507 ret
= option_find1(&mess
->file
[0], &mess
->file
[128], opt_type
, &overload
);
509 if (!ret
&& (overload
& 2))
510 ret
= option_find1(&mess
->sname
[0], &mess
->file
[64], opt_type
, &overload
);
515 static int in_list(unsigned char *list
, int opt
)
519 for (i
= 0; list
[i
] != OPTION_END
; i
++)
526 static struct dhcp_opt
*option_find2(struct dhcp_context
*context
, struct dhcp_opt
*opts
, int opt
)
528 for (; opts
; opts
= opts
->next
)
529 if (opts
->opt
== opt
&&
530 (!opts
->netid
|| (context
->netid
&& strcmp(opts
->netid
, context
->netid
) == 0)))
535 static unsigned char *do_req_options(struct dhcp_context
*context
,
536 unsigned char *p
, unsigned char *end
,
537 unsigned char *req_options
,
538 struct dhcp_opt
*config_opts
,
539 char *domainname
, char *hostname
,
540 struct in_addr relay
,
541 struct in_addr iface_addr
,
549 if (in_list(req_options
, OPTION_MAXMESSAGE
))
550 p
= option_put(p
, end
, OPTION_MAXMESSAGE
, 2,
551 DNSMASQ_PACKETSZ
> iface_mtu
?
552 iface_mtu
: DNSMASQ_PACKETSZ
);
554 if (in_list(req_options
, OPTION_NETMASK
) &&
555 !option_find2(context
, config_opts
, OPTION_NETMASK
))
556 p
= option_put(p
, end
, OPTION_NETMASK
, INADDRSZ
, ntohl(context
->netmask
.s_addr
));
558 if (in_list(req_options
, OPTION_BROADCAST
) &&
559 !option_find2(context
, config_opts
, OPTION_BROADCAST
))
560 p
= option_put(p
, end
, OPTION_BROADCAST
, INADDRSZ
, ntohl(context
->broadcast
.s_addr
));
562 if (in_list(req_options
, OPTION_ROUTER
) &&
563 !option_find2(context
, config_opts
, OPTION_ROUTER
))
564 p
= option_put(p
, end
, OPTION_ROUTER
, INADDRSZ
,
565 ntohl(relay
.s_addr
? relay
.s_addr
: iface_addr
.s_addr
));
567 if (in_list(req_options
, OPTION_DNSSERVER
) &&
568 !option_find2(context
, config_opts
, OPTION_DNSSERVER
))
569 p
= option_put(p
, end
, OPTION_DNSSERVER
, INADDRSZ
, ntohl(iface_addr
.s_addr
));
571 if (domainname
&& in_list(req_options
, OPTION_DOMAINNAME
) &&
572 !option_find2(context
, config_opts
, OPTION_DOMAINNAME
))
573 p
= option_put_string(p
, end
, OPTION_DOMAINNAME
, domainname
);
575 /* Note that we ignore attempts to set the hostname using
576 --dhcp-option=12,<name> */
577 if (hostname
&& in_list(req_options
, OPTION_HOSTNAME
))
578 p
= option_put_string(p
, end
, OPTION_HOSTNAME
, hostname
);
580 for (i
= 0; req_options
[i
] != OPTION_END
; i
++)
582 struct dhcp_opt
*opt
= option_find2(context
, config_opts
, req_options
[i
]);
583 if (req_options
[i
] != OPTION_HOSTNAME
&&
584 req_options
[i
] != OPTION_MAXMESSAGE
&&
585 opt
&& (p
+ opt
->len
+ 3 < end
))
594 struct in_addr
*a
= (struct in_addr
*)opt
->val
;
595 for (j
= 0; j
< opt
->len
; j
+=INADDRSZ
, a
++)
597 /* zero means "self" */
599 memcpy(p
, &iface_addr
, INADDRSZ
);
601 memcpy(p
, a
, INADDRSZ
);
607 memcpy(p
, opt
->val
, opt
->len
);