1 /* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
4 * We are making the OpenFlow specification and associated documentation
5 * (Software) available for public use and benefit with the expectation
6 * that others will use, modify and enhance the Software and contribute
7 * those enhancements back to the community. However, since we would
8 * like to make the Software available for broadest use, with as few
9 * restrictions as possible permission is hereby granted, free of
10 * charge, to any person obtaining a copy of this Software to deal in
11 * the Software under the copyrights without restriction, including
12 * without limitation the rights to use, copy, modify, merge, publish,
13 * distribute, sublicense, and/or sell copies of the Software, and to
14 * permit persons to whom the Software is furnished to do so, subject to
15 * the following conditions:
17 * The above copyright notice and this permission notice shall be
18 * included in all copies or substantial portions of the Software.
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
24 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
25 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 * The name and trademarks of copyright holder(s) may NOT be used in
30 * advertising or publicity pertaining to the Software or any
31 * derivatives without specific, written prior permission.
36 #include <arpa/inet.h>
43 #include "dynamic-string.h"
45 #define THIS_MODULE VLM_dhcp
48 /* Information about a DHCP argument type. */
50 const char *name; /* Name. */
51 size_t size; /* Number of bytes per argument. */
54 static struct arg_type types[] = {
55 #define DHCP_ARG(NAME, SIZE) [DHCP_ARG_##NAME] = {#NAME, SIZE},
60 /* Information about a DHCP option. */
62 const char *name; /* Name. */
63 enum dhcp_arg_type type; /* Argument type. */
64 size_t min_args; /* Minimum number of arguments. */
65 size_t max_args; /* Maximum number of arguments. */
68 static struct option_class classes[DHCP_N_OPTIONS] = {
69 [0 ... 255] = {NULL, DHCP_ARG_UINT8, 0, SIZE_MAX},
70 #define DHCP_OPT(NAME, CODE, TYPE, MIN, MAX) \
71 [CODE] = {#NAME, DHCP_ARG_##TYPE, MIN, MAX},
76 static void copy_data(struct dhcp_msg *);
79 dhcp_type_name(enum dhcp_msg_type type)
82 #define DHCP_MSG(NAME, VALUE) case NAME: return #NAME;
86 return "<<unknown DHCP message type>>";
89 /* Initializes 'msg' as a DHCP message. The message should be freed with
90 * dhcp_msg_uninit() when it is no longer needed. */
92 dhcp_msg_init(struct dhcp_msg *msg)
94 memset(msg, 0, sizeof *msg);
97 /* Frees the contents of 'msg'. The caller is responsible for freeing 'msg',
100 dhcp_msg_uninit(struct dhcp_msg *msg)
107 /* Initializes 'dst' as a copy of 'src'. 'dst' (and 'src') should be freed
108 * with dhcp_msg_uninit() when it is no longer needed. */
110 dhcp_msg_copy(struct dhcp_msg *dst, const struct dhcp_msg *src)
113 dst->data_allocated = src->data_used;
115 dst->data = xmalloc(dst->data_allocated);
120 prealloc_data(struct dhcp_msg *msg, size_t n)
122 size_t needed = msg->data_used + n;
123 if (needed > msg->data_allocated) {
124 uint8_t *old_data = msg->data;
125 msg->data_allocated = MAX(needed * 2, 64);
126 msg->data = xmalloc(msg->data_allocated);
135 append_data(struct dhcp_msg *msg, const void *data, size_t n)
137 uint8_t *p = &msg->data[msg->data_used];
144 copy_data(struct dhcp_msg *msg)
149 for (code = 0; code < DHCP_N_OPTIONS; code++) {
150 struct dhcp_option *opt = &msg->options[code];
152 assert(msg->data_used + opt->n <= msg->data_allocated);
153 opt->data = append_data(msg, opt->data, opt->n);
158 /* Appends the 'n' bytes in 'data' to the DHCP option in 'msg' represented by
159 * 'code' (which must be in the range 0...DHCP_N_OPTIONS). */
161 dhcp_msg_put(struct dhcp_msg *msg, int code,
162 const void *data, size_t n)
164 struct dhcp_option *opt;
165 if (code == DHCP_CODE_PAD || code == DHCP_CODE_END) {
169 opt = &msg->options[code];
170 prealloc_data(msg, n + opt->n);
172 if (&msg->data[msg->data_used - opt->n] != opt->data) {
173 opt->data = append_data(msg, opt->data, opt->n);
175 append_data(msg, data, n);
177 opt->data = append_data(msg, data, n);
182 /* Appends the boolean value 'b', as a octet with value 0 (false) or 1 (true),
183 * to the DHCP option in 'msg' represented by 'code' (which must be in the
184 * range 0...DHCP_N_OPTIONS). */
186 dhcp_msg_put_bool(struct dhcp_msg *msg, int code, bool b_)
189 dhcp_msg_put(msg, code, &b, 1);
192 /* Appends the number of seconds 'secs', as a 32-bit number in network byte
193 * order, to the DHCP option in 'msg' represented by 'code' (which must be in
194 * the range 0...DHCP_N_OPTIONS). */
196 dhcp_msg_put_secs(struct dhcp_msg *msg, int code, uint32_t secs_)
198 uint32_t secs = htonl(secs_);
199 dhcp_msg_put(msg, code, &secs, sizeof secs);
202 /* Appends the IP address 'ip', as a 32-bit number in network byte order, to
203 * the DHCP option in 'msg' represented by 'code' (which must be in the range
204 * 0...DHCP_N_OPTIONS). */
206 dhcp_msg_put_ip(struct dhcp_msg *msg, int code, uint32_t ip)
208 dhcp_msg_put(msg, code, &ip, sizeof ip);
211 /* Appends the ASCII string 'string', to the DHCP option in 'msg' represented
212 * by 'code' (which must be in the range 0...DHCP_N_OPTIONS). */
214 dhcp_msg_put_string(struct dhcp_msg *msg, int code, const char *string)
216 dhcp_msg_put(msg, code, string, strlen(string));
219 /* Appends octet 'x' to DHCP option in 'msg' represented by 'code' (which must
220 * be in the range 0...DHCP_N_OPTIONS). */
222 dhcp_msg_put_uint8(struct dhcp_msg *msg, int code, uint8_t x)
224 dhcp_msg_put(msg, code, &x, sizeof x);
227 /* Appends the 'n' octets in 'data' to DHCP option in 'msg' represented by
228 * 'code' (which must be in the range 0...DHCP_N_OPTIONS). */
229 void dhcp_msg_put_uint8_array(struct dhcp_msg *msg, int code,
230 const uint8_t data[], size_t n)
232 dhcp_msg_put(msg, code, data, n);
235 /* Appends the 16-bit value in 'x', in network byte order, to DHCP option in
236 * 'msg' represented by 'code' (which must be in the range
237 * 0...DHCP_N_OPTIONS). */
239 dhcp_msg_put_uint16(struct dhcp_msg *msg, int code, uint16_t x_)
241 uint16_t x = htons(x_);
242 dhcp_msg_put(msg, code, &x, sizeof x);
246 /* Appends the 'n' 16-bit values in 'data', in network byte order, to DHCP
247 * option in 'msg' represented by 'code' (which must be in the range
248 * 0...DHCP_N_OPTIONS). */
250 dhcp_msg_put_uint16_array(struct dhcp_msg *msg, int code,
251 const uint16_t data[], size_t n)
255 for (i = 0; i < n; i++) {
256 dhcp_msg_put_uint16(msg, code, data[i]);
260 /* Returns a pointer to the 'size' bytes starting at byte offset 'offset' in
261 * the DHCP option in 'msg' represented by 'code' (which must be in the range
262 * 0...DHCP_N_OPTIONS). If the option has fewer than 'offset + size' bytes,
263 * returns a null pointer. */
265 dhcp_msg_get(const struct dhcp_msg *msg, int code,
266 size_t offset, size_t size)
268 const struct dhcp_option *opt = &msg->options[code];
269 return offset + size <= opt->n ? (const char *) opt->data + offset : NULL;
272 /* Stores in '*out' the boolean value at byte offset 'offset' in the DHCP
273 * option in 'msg' represented by 'code' (which must be in the range
274 * 0...DHCP_N_OPTIONS). Returns true if successful, false if the option has
275 * fewer than 'offset + 1' bytes. */
277 dhcp_msg_get_bool(const struct dhcp_msg *msg, int code, size_t offset,
280 const uint8_t *uint8 = dhcp_msg_get(msg, code, offset, sizeof *uint8);
289 /* Stores in '*out' the 32-bit count of seconds at offset 'offset' (in
290 * 4-byte increments) in the DHCP option in 'msg' represented by 'code'
291 * (which must be in the range 0...DHCP_N_OPTIONS). The value is converted to
292 * native byte order. Returns true if successful, false if the option has
293 * fewer than '4 * (offset + 1)' bytes. */
295 dhcp_msg_get_secs(const struct dhcp_msg *msg, int code, size_t offset,
298 const uint32_t *uint32 = dhcp_msg_get(msg, code, offset * sizeof *uint32,
301 *out = ntohl(*uint32);
308 /* Stores in '*out' the IP address at offset 'offset' (in 4-byte increments) in
309 * the DHCP option in 'msg' represented by 'code' (which must be in the range
310 * 0...DHCP_N_OPTIONS). The IP address is stored in network byte order.
311 * Returns true if successful, false if the option has fewer than '4 * (offset
314 dhcp_msg_get_ip(const struct dhcp_msg *msg, int code,
315 size_t offset, uint32_t *out)
317 const uint32_t *uint32 = dhcp_msg_get(msg, code, offset * sizeof *uint32,
327 /* Returns the string in the DHCP option in 'msg' represented by 'code' (which
328 * must be in the range 0...DHCP_N_OPTIONS). The caller is responsible for
329 * freeing the string with free().
331 * If 'msg' has no option represented by 'code', returns a null pointer. (If
332 * the option was specified but had no content, then an empty string is
333 * returned, not a null pointer.) */
335 dhcp_msg_get_string(const struct dhcp_msg *msg, int code)
337 const struct dhcp_option *opt = &msg->options[code];
338 return opt->data ? xmemdup0(opt->data, opt->n) : NULL;
341 /* Stores in '*out' the octet at byte offset 'offset' in the DHCP option in
342 * 'msg' represented by 'code' (which must be in the range 0...DHCP_N_OPTIONS).
343 * Returns true if successful, false if the option has fewer than 'offset + 1'
346 dhcp_msg_get_uint8(const struct dhcp_msg *msg, int code,
347 size_t offset, uint8_t *out)
349 const uint8_t *uint8 = dhcp_msg_get(msg, code, offset, sizeof *uint8);
358 /* Stores in '*out' the 16-bit value at offset 'offset' (in 2-byte units) in
359 * the DHCP option in 'msg' represented by 'code' (which must be in the range
360 * 0...DHCP_N_OPTIONS). The value is converted to native byte order. Returns
361 * true if successful, false if the option has fewer than '2 * (offset + 1)'
364 dhcp_msg_get_uint16(const struct dhcp_msg *msg, int code,
365 size_t offset, uint16_t *out)
367 const uint16_t *uint16 = dhcp_msg_get(msg, code, offset * sizeof *uint16,
370 *out = ntohs(*uint16);
377 /* Appends a string representing 'duration' seconds to 'ds'. */
379 put_duration(struct ds *ds, unsigned int duration)
382 if (duration >= 86400) {
383 ds_put_format(ds, "%ud", duration / 86400);
386 if (duration >= 3600) {
387 ds_put_format(ds, "%uh", duration / 3600);
390 if (duration >= 60) {
391 ds_put_format(ds, "%umin", duration / 60);
395 ds_put_format(ds, "%us", duration);
398 ds_put_cstr(ds, "0s");
402 /* Appends a string representation of 'opt', which has the given 'code', to
405 dhcp_option_to_string(const struct dhcp_option *opt, int code, struct ds *ds)
407 struct option_class *class = &classes[code];
408 const struct arg_type *type = &types[class->type];
413 for (cp = class->name; *cp; cp++) {
414 unsigned char c = *cp;
415 ds_put_char(ds, c == '_' ? '-' : tolower(c));
418 ds_put_format(ds, "option-%d", code);
420 ds_put_char(ds, '=');
422 if (!opt->data || !opt->n) {
423 ds_put_cstr(ds, opt->data ? "empty" : "null");
427 if (class->type == DHCP_ARG_STRING) {
428 ds_put_char(ds, '"');
429 ds_put_printable(ds, opt->data, opt->n);
430 ds_put_char(ds, '"');
433 for (offset = 0; offset + type->size <= opt->n; offset += type->size) {
434 const void *p = (const char *) opt->data + offset;
435 const uint8_t *uint8 = p;
436 const uint32_t *uint32 = p;
437 const uint16_t *uint16 = p;
439 if (offset && class->type != DHCP_ARG_STRING) {
440 ds_put_cstr(ds, class->type == DHCP_ARG_UINT8 ? ":" : ", ");
442 switch (class->type) {
446 ds_put_format(ds, IP_FMT, IP_ARGS(uint32));
449 ds_put_format(ds, "%02"PRIx8, *uint8);
451 case DHCP_ARG_UINT16:
452 ds_put_format(ds, "%"PRIu16, ntohs(*uint16));
454 case DHCP_ARG_UINT32:
455 ds_put_format(ds, "%"PRIu32, ntohl(*uint32));
458 put_duration(ds, ntohl(*uint32));
460 case DHCP_ARG_STRING:
462 case DHCP_ARG_BOOLEAN:
464 ds_put_cstr(ds, "false");
465 } else if (*uint8 == 1) {
466 ds_put_cstr(ds, "true");
468 ds_put_format(ds, "**%"PRIu8"**", *uint8);
473 if (offset != opt->n) {
475 ds_put_cstr(ds, ", ");
477 ds_put_cstr(ds, "**leftovers:");
478 for (; offset < opt->n; offset++) {
479 const void *p = (const char *) opt->data + offset;
480 const uint8_t *uint8 = p;
481 ds_put_format(ds, " %"PRIu8, *uint8);
483 ds_put_cstr(ds, "**");
488 /* Returns true if 'a' and 'b' have the same content, false otherwise. */
490 dhcp_option_equals(const struct dhcp_option *a, const struct dhcp_option *b)
492 return ((a->data != NULL) == (b->data != NULL)
494 && !memcmp(a->data, b->data, a->n));
497 /* Replaces 'ds' by a string representation of 'msg'. If 'multiline' is
498 * false, 'ds' receives a single-line representation of 'msg', otherwise a
499 * multiline representation. */
501 dhcp_msg_to_string(const struct dhcp_msg *msg, bool multiline, struct ds *ds)
503 char separator = multiline ? '\n' : ' ';
507 ds_put_format(ds, "op=%s",
508 (msg->op == DHCP_BOOTREQUEST ? "request"
509 : msg->op == DHCP_BOOTREPLY ? "reply"
511 ds_put_format(ds, "%ctype=%s", separator, dhcp_type_name(msg->type));
512 ds_put_format(ds, "%cxid=0x%08"PRIx32, separator, msg->xid);
513 ds_put_format(ds, "%csecs=", separator);
514 put_duration(ds, msg->secs);
516 ds_put_format(ds, "%cflags=", separator);
517 if (msg->flags & DHCP_FLAGS_BROADCAST) {
518 ds_put_cstr(ds, "[BROADCAST]");
520 if (msg->flags & DHCP_FLAGS_MBZ) {
521 ds_put_format(ds, "[0x%04"PRIx16"]", msg->flags & DHCP_FLAGS_MBZ);
525 ds_put_format(ds, "%cciaddr="IP_FMT, separator, IP_ARGS(&msg->ciaddr));
528 ds_put_format(ds, "%cyiaddr="IP_FMT, separator, IP_ARGS(&msg->yiaddr));
531 ds_put_format(ds, "%csiaddr="IP_FMT, separator, IP_ARGS(&msg->siaddr));
534 ds_put_format(ds, "%cgiaddr="IP_FMT, separator, IP_ARGS(&msg->giaddr));
536 ds_put_format(ds, "%cchaddr="ETH_ADDR_FMT,
537 separator, ETH_ADDR_ARGS(msg->chaddr));
539 for (code = 0; code < DHCP_N_OPTIONS; code++) {
540 const struct dhcp_option *opt = &msg->options[code];
542 ds_put_char(ds, separator);
543 dhcp_option_to_string(opt, code, ds);
547 ds_put_char(ds, separator);
553 parse_options(struct dhcp_msg *msg, const char *name, void *data, size_t size,
564 code = buffer_try_pull(&b, 1);
565 if (!code || *code == DHCP_CODE_END) {
567 } else if (*code == DHCP_CODE_PAD) {
571 len = buffer_try_pull(&b, 1);
573 VLOG_DBG("reached end of %s expecting length byte", name);
577 payload = buffer_try_pull(&b, *len);
579 VLOG_DBG("expected %"PRIu8" bytes of option-%"PRIu8" payload "
580 "with only %zu bytes of %s left",
581 *len, *code, b.size, name);
584 dhcp_msg_put(msg, *code + option_offset, payload, *len);
589 validate_options(struct dhcp_msg *msg)
593 for (code = 0; code < DHCP_N_OPTIONS; code++) {
594 struct dhcp_option *opt = &msg->options[code];
595 struct option_class *class = &classes[code];
596 struct arg_type *type = &types[class->type];
598 size_t n_elems = opt->n / type->size;
599 size_t remainder = opt->n % type->size;
602 VLOG_DBG("%s option has %zu %zu-byte %s arguments with "
603 "%zu bytes left over",
604 class->name, n_elems, type->size,
605 type->name, remainder);
608 if (n_elems < class->min_args || n_elems > class->max_args) {
609 VLOG_DBG("%s option has %zu %zu-byte %s arguments but "
610 "between %zu and %zu are required",
611 class->name, n_elems, type->size, type->name,
612 class->min_args, class->max_args);
616 struct ds ds = DS_EMPTY_INITIALIZER;
617 VLOG_DBG("%s option contains: %s",
618 class->name, dhcp_option_to_string(opt, code, &ds));
628 /* Attempts to parse 'b' as a DHCP message. If successful, initializes '*msg'
629 * to the parsed message and returns 0. Otherwise, returns a positive errno
630 * value and '*msg' is indeterminate. */
632 dhcp_parse(struct dhcp_msg *msg, const struct buffer *b_)
634 struct buffer b = *b_;
635 struct dhcp_header *dhcp;
640 dhcp = buffer_try_pull(&b, sizeof *dhcp);
642 VLOG_DBG("buffer too small for DHCP header (%zu bytes)", b.size);
646 if (dhcp->op != DHCP_BOOTREPLY && dhcp->op != DHCP_BOOTREQUEST) {
647 VLOG_DBG("invalid DHCP op (%"PRIu8")", dhcp->op);
650 if (dhcp->htype != ARP_HRD_ETHERNET) {
651 VLOG_DBG("invalid DHCP htype (%"PRIu8")", dhcp->htype);
654 if (dhcp->hlen != ETH_ADDR_LEN) {
655 VLOG_DBG("invalid DHCP hlen (%"PRIu8")", dhcp->hlen);
661 msg->xid = ntohl(dhcp->xid);
662 msg->secs = ntohs(dhcp->secs);
663 msg->flags = ntohs(dhcp->flags);
664 msg->ciaddr = dhcp->ciaddr;
665 msg->yiaddr = dhcp->yiaddr;
666 msg->siaddr = dhcp->siaddr;
667 msg->giaddr = dhcp->giaddr;
668 memcpy(msg->chaddr, dhcp->chaddr, ETH_ADDR_LEN);
670 cookie = buffer_try_pull(&b, sizeof cookie);
672 if (ntohl(*cookie) == DHCP_OPTS_COOKIE) {
675 parse_options(msg, "options", b.data, b.size, 0);
676 if (dhcp_msg_get_uint8(msg, DHCP_CODE_OPTION_OVERLOAD,
679 parse_options(msg, "file", dhcp->file, sizeof dhcp->file,
683 parse_options(msg, "sname",
684 dhcp->sname, sizeof dhcp->sname, 0);
688 VLOG_DBG("bad DHCP options cookie: %08"PRIx32, ntohl(*cookie));
691 VLOG_DBG("DHCP packet has no options");
694 vendor_class = dhcp_msg_get_string(msg, DHCP_CODE_VENDOR_CLASS);
695 if (vendor_class && !strcmp(vendor_class, "OpenFlow")) {
696 parse_options(msg, "vendor-specific",
697 msg->options[DHCP_CODE_VENDOR_SPECIFIC].data,
698 msg->options[DHCP_CODE_VENDOR_SPECIFIC].n,
703 validate_options(msg);
704 if (!dhcp_msg_get_uint8(msg, DHCP_CODE_DHCP_MSG_TYPE, 0, &type)) {
705 VLOG_DBG("missing DHCP message type");
706 dhcp_msg_uninit(msg);
713 if (VLOG_IS_DBG_ENABLED()) {
717 ds_put_hex_dump(&ds, b_->data, b_->size, 0, true);
718 VLOG_DBG("invalid DHCP message dump:\n%s", ds_cstr(&ds));
721 dhcp_msg_to_string(msg, false, &ds);
722 VLOG_DBG("partially dissected DHCP message: %s", ds_cstr(&ds));
730 put_option_chunk(struct buffer *b, uint8_t code, void *data, size_t n)
737 buffer_put(b, header, sizeof header);
738 buffer_put(b, data, n);
742 put_option(struct buffer *b, uint8_t code, void *data, size_t n)
746 /* Divide the data into chunks of 255 bytes or less. Make
747 * intermediate chunks multiples of 8 bytes in case the
748 * recipient validates a chunk at a time instead of the
749 * concatenated value. */
752 size_t chunk = n > 255 ? 248 : n;
753 put_option_chunk(b, code, p, chunk);
758 /* Option should be present but carry no data. */
759 put_option_chunk(b, code, NULL, 0);
764 /* Appends to 'b' the DHCP message represented by 'msg'. */
766 dhcp_assemble(const struct dhcp_msg *msg, struct buffer *b)
768 const uint8_t end = DHCP_CODE_END;
769 uint32_t cookie = htonl(DHCP_OPTS_COOKIE);
770 struct buffer vnd_data;
771 struct dhcp_header dhcp;
774 memset(&dhcp, 0, sizeof dhcp);
776 dhcp.htype = ARP_HRD_ETHERNET;
777 dhcp.hlen = ETH_ADDR_LEN;
779 dhcp.xid = htonl(msg->xid);
780 dhcp.secs = htons(msg->secs);
781 dhcp.flags = htons(msg->flags);
782 dhcp.ciaddr = msg->ciaddr;
783 dhcp.yiaddr = msg->yiaddr;
784 dhcp.siaddr = msg->siaddr;
785 dhcp.giaddr = msg->giaddr;
786 memcpy(dhcp.chaddr, msg->chaddr, ETH_ADDR_LEN);
787 buffer_put(b, &dhcp, sizeof dhcp);
788 buffer_put(b, &cookie, sizeof cookie);
790 /* Put DHCP message type first. (The ordering is not required but it
793 uint8_t type = msg->type;
794 put_option(b, DHCP_CODE_DHCP_MSG_TYPE, &type, 1);
797 /* Put the standard options. */
798 for (i = 0; i < DHCP_VENDOR_OFS; i++) {
799 const struct dhcp_option *option = &msg->options[i];
800 put_option(b, i, option->data, option->n);
803 /* Assemble vendor specific option and put it. */
804 buffer_init(&vnd_data, 0);
805 for (i = DHCP_VENDOR_OFS; i < DHCP_N_OPTIONS; i++) {
806 const struct dhcp_option *option = &msg->options[i];
807 put_option(&vnd_data, i - DHCP_VENDOR_OFS, option->data, option->n);
810 put_option(b, DHCP_CODE_VENDOR_SPECIFIC, vnd_data.data, vnd_data.size);
812 buffer_uninit(&vnd_data);
814 /* Put end-of-options option. */
815 buffer_put(b, &end, sizeof end);