2 * Copyright (c) 2002-2007 Apple Inc. All rights reserved.
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
30 * - routines to parse and access dhcp options
31 * and create new dhcp option areas
32 * - handles overloaded areas as well as vendor-specific options
33 * that are encoded using the RFC 2132 encoding
37 * Modification History
39 * March 15, 2002 Dieter Siegmund (dieter@apple)
40 * - imported from bootp project
44 #include <sys/types.h>
45 #include <sys/param.h>
46 #include <netinet/in.h>
47 #include <sys/malloc.h>
48 #include <libkern/libkern.h>
50 #include <netinet/dhcp.h>
51 #include <netinet/dhcp_options.h>
54 #define dprintf(x) printf x;
55 #else /* !DHCP_DEBUG */
57 #endif /* DHCP_DEBUG */
59 static __inline__
void
65 static __inline__
void *
69 MALLOC(data
, void *, size
, M_TEMP
, M_WAITOK
);
73 static __inline__
void *
74 my_realloc(void * oldptr
, int oldsize
, int newsize
)
78 MALLOC(data
, void *, newsize
, M_TEMP
, M_WAITOK
);
79 bcopy(oldptr
, data
, oldsize
);
85 * Functions: ptrlist_*
87 * A dynamically growable array of pointers.
90 #define PTRLIST_NUMBER 16
93 ptrlist_init(ptrlist_t
* list
)
95 bzero(list
, sizeof(*list
));
100 ptrlist_free(ptrlist_t
* list
)
103 my_free(list
->array
);
109 ptrlist_count(ptrlist_t
* list
)
111 if (list
== NULL
|| list
->array
== NULL
)
114 return (list
->count
);
118 ptrlist_element(ptrlist_t
* list
, int i
)
120 if (list
->array
== NULL
)
123 return (list
->array
[i
]);
129 ptrlist_grow(ptrlist_t
* list
)
131 if (list
->array
== NULL
) {
133 list
->size
= PTRLIST_NUMBER
;
135 list
->array
= my_malloc(sizeof(*list
->array
) * list
->size
);
137 else if (list
->size
== list
->count
) {
138 dprintf(("doubling %d to %d\n", list
->size
, list
->size
* 2));
139 list
->array
= my_realloc(list
->array
,
140 sizeof(*list
->array
) * list
->size
,
141 sizeof(*list
->array
) * list
->size
* 2);
144 if (list
->array
== NULL
)
150 ptrlist_add(ptrlist_t
* list
, const void * element
)
152 if (ptrlist_grow(list
) == FALSE
)
155 list
->array
[list
->count
++] = element
;
159 /* concatenates extra onto list */
161 ptrlist_concat(ptrlist_t
* list
, ptrlist_t
* extra
)
163 if (extra
->count
== 0)
166 if ((extra
->count
+ list
->count
) > list
->size
) {
167 int old_size
= list
->size
;
169 list
->size
= extra
->count
+ list
->count
;
170 if (list
->array
== NULL
)
171 list
->array
= my_malloc(sizeof(*list
->array
) * list
->size
);
173 list
->array
= my_realloc(list
->array
, old_size
,
174 sizeof(*list
->array
) * list
->size
);
176 if (list
->array
== NULL
)
178 bcopy(extra
->array
, list
->array
+ list
->count
,
179 extra
->count
* sizeof(*list
->array
));
180 list
->count
+= extra
->count
;
186 * Functions: dhcpol_*
189 * Routines to parse/access existing options buffers.
192 dhcpol_add(dhcpol_t
* list
, const void * element
)
194 return (ptrlist_add((ptrlist_t
*)list
, element
));
198 dhcpol_count(dhcpol_t
* list
)
200 return (ptrlist_count((ptrlist_t
*)list
));
204 dhcpol_element(dhcpol_t
* list
, int i
)
206 return (ptrlist_element((ptrlist_t
*)list
, i
));
210 dhcpol_init(dhcpol_t
* list
)
212 ptrlist_init((ptrlist_t
*)list
);
216 dhcpol_free(dhcpol_t
* list
)
218 ptrlist_free((ptrlist_t
*)list
);
222 dhcpol_concat(dhcpol_t
* list
, dhcpol_t
* extra
)
224 return (ptrlist_concat((ptrlist_t
*)list
, (ptrlist_t
*)extra
));
228 * Function: dhcpol_parse_buffer
231 * Parse the given buffer into DHCP options, returning the
232 * list of option pointers in the given dhcpol_t.
233 * Parsing continues until we hit the end of the buffer or
237 dhcpol_parse_buffer(dhcpol_t
* list
, const void * buffer
, int length
)
240 const uint8_t * scan
;
247 for (scan
= (const uint8_t *)buffer
; tag
!= dhcptag_end_e
&& len
> 0; ) {
249 tag
= scan
[DHCP_TAG_OFFSET
];
253 /* remember that it was terminated */
254 dhcpol_add(list
, scan
);
258 case dhcptag_pad_e
: /* ignore pad */
263 uint8_t option_len
= scan
[DHCP_LEN_OFFSET
];
265 dhcpol_add(list
, scan
);
266 len
-= (option_len
+ 2);
267 scan
+= (option_len
+ 2);
273 /* ran off the end */
274 dprintf(("dhcp_options: parse failed near tag %d", tag
));
282 * Function: dhcpol_find
285 * Finds the first occurence of the given option, and returns its
286 * length and the option data pointer.
288 * The optional start parameter allows this function to
289 * return the next start point so that successive
290 * calls will retrieve the next occurence of the option.
291 * Before the first call, *start should be set to 0.
294 dhcpol_find(dhcpol_t
* list
, int tag
, int * len_p
, int * start
)
298 if (tag
== dhcptag_end_e
|| tag
== dhcptag_pad_e
)
304 for (; i
< dhcpol_count(list
); i
++) {
305 const uint8_t * option
= dhcpol_element(list
, i
);
307 if (option
[DHCP_TAG_OFFSET
] == tag
) {
309 *len_p
= option
[DHCP_LEN_OFFSET
];
312 return (option
+ DHCP_OPTION_OFFSET
);
320 * Function: dhcpol_get
323 * Accumulate all occurences of the given option into a
324 * malloc'd buffer, and return its length. Used to get
325 * all occurrences of a particular option in a single
328 * Use _FREE(val, M_TEMP) to free the returned data area.
331 dhcpol_get(dhcpol_t
* list
, int tag
, int * len_p
)
337 if (tag
== dhcptag_end_e
|| tag
== dhcptag_pad_e
)
340 for (i
= 0; i
< dhcpol_count(list
); i
++) {
341 const uint8_t * option
= dhcpol_element(list
, i
);
343 if (option
[DHCP_TAG_OFFSET
] == tag
) {
344 int len
= option
[DHCP_LEN_OFFSET
];
347 data
= my_malloc(len
);
350 data
= my_realloc(data
, data_len
, data_len
+ len
);
352 bcopy(option
+ DHCP_OPTION_OFFSET
, data
+ data_len
, len
);
362 * Function: dhcpol_parse_packet
365 * Parse the option areas in the DHCP packet.
366 * Verifies that the packet has the right magic number,
367 * then parses and accumulates the option areas.
368 * First the pkt->dp_options is parsed. If that contains
369 * the overload option, it parses pkt->dp_file if specified,
370 * then parses pkt->dp_sname if specified.
373 dhcpol_parse_packet(dhcpol_t
* options
, const struct dhcp
* pkt
, int len
)
375 char rfc_magic
[4] = RFC_OPTIONS_MAGIC
;
377 dhcpol_init(options
); /* make sure it's empty */
379 if (len
< (sizeof(*pkt
) + RFC_MAGIC_SIZE
)) {
380 dprintf(("dhcp_options: packet is too short: %d < %d\n",
381 len
, (int)sizeof(*pkt
) + RFC_MAGIC_SIZE
));
384 if (bcmp(pkt
->dp_options
, rfc_magic
, RFC_MAGIC_SIZE
)) {
385 dprintf(("dhcp_options: missing magic number\n"));
388 if (dhcpol_parse_buffer(options
, pkt
->dp_options
+ RFC_MAGIC_SIZE
,
389 len
- sizeof(*pkt
) - RFC_MAGIC_SIZE
) == FALSE
)
391 { /* get overloaded options */
392 const uint8_t * overload
;
395 overload
= dhcpol_find(options
, dhcptag_option_overload_e
,
396 &overload_len
, NULL
);
397 if (overload
&& overload_len
== 1) { /* has overloaded options */
401 if (*overload
== DHCP_OVERLOAD_FILE
402 || *overload
== DHCP_OVERLOAD_BOTH
) {
403 if (dhcpol_parse_buffer(&extra
, pkt
->dp_file
,
404 sizeof(pkt
->dp_file
))) {
405 dhcpol_concat(options
, &extra
);
409 if (*overload
== DHCP_OVERLOAD_SNAME
410 || *overload
== DHCP_OVERLOAD_BOTH
) {
411 if (dhcpol_parse_buffer(&extra
, pkt
->dp_sname
,
412 sizeof(pkt
->dp_sname
))) {
413 dhcpol_concat(options
, &extra
);
426 * Types and functions to create new dhcp option areas.
430 * Function: dhcpoa_{init_common, init_no_end, init}
433 * Initialize an option area structure so that it can be used
434 * in calling the dhcpoa_* routines.
437 dhcpoa_init_common(dhcpoa_t
* oa_p
, void * buffer
, int size
, int reserve
)
439 bzero(oa_p
, sizeof(*oa_p
));
440 oa_p
->oa_buffer
= buffer
;
441 oa_p
->oa_size
= size
;
442 oa_p
->oa_reserve
= reserve
;
446 dhcpoa_init_no_end(dhcpoa_t
* oa_p
, void * buffer
, int size
)
448 dhcpoa_init_common(oa_p
, buffer
, size
, 0);
453 dhcpoa_size(dhcpoa_t
* oa_p
)
455 return (oa_p
->oa_size
);
459 dhcpoa_init(dhcpoa_t
* oa_p
, void * buffer
, int size
)
461 /* initialize the area, reserve space for the end tag */
462 dhcpoa_init_common(oa_p
, buffer
, size
, 1);
466 * Function: dhcpoa_add
469 * Add an option to the option area.
472 dhcpoa_add(dhcpoa_t
* oa_p
, dhcptag_t tag
, int len
, const void * option
)
474 if (len
> DHCP_OPTION_SIZE_MAX
) {
475 dprintf(("tag %d option %d > %d\n", tag
, len
, DHCP_OPTION_SIZE_MAX
));
476 return (dhcpoa_failed_e
);
479 if (oa_p
->oa_end_tag
) {
480 dprintf(("attempt to add data after end tag\n"));
481 return (dhcpoa_failed_e
);
486 if ((oa_p
->oa_offset
+ 1) > oa_p
->oa_size
) {
487 /* this can't happen since we're careful to leave space */
488 dprintf(("can't add end tag %d > %d\n",
489 oa_p
->oa_offset
+ oa_p
->oa_reserve
, oa_p
->oa_size
));
490 return (dhcpoa_failed_e
);
492 ((uint8_t *)oa_p
->oa_buffer
)[oa_p
->oa_offset
+ DHCP_TAG_OFFSET
] = tag
;
494 oa_p
->oa_end_tag
= 1;
499 if ((oa_p
->oa_offset
+ oa_p
->oa_reserve
+ 1) > oa_p
->oa_size
) {
500 dprintf(("can't add pad tag %d > %d\n",
501 oa_p
->oa_offset
+ oa_p
->oa_reserve
+ 1, oa_p
->oa_size
));
502 return (dhcpoa_full_e
);
504 ((uint8_t *)oa_p
->oa_buffer
)[oa_p
->oa_offset
+ DHCP_TAG_OFFSET
] = tag
;
510 if ((oa_p
->oa_offset
+ len
+ 2 + oa_p
->oa_reserve
) > oa_p
->oa_size
) {
511 dprintf(("can't add tag %d (%d > %d)\n", tag
,
512 oa_p
->oa_offset
+ len
+ 2 + oa_p
->oa_reserve
,
514 return (dhcpoa_full_e
);
516 ((uint8_t *)oa_p
->oa_buffer
)[oa_p
->oa_offset
+ DHCP_TAG_OFFSET
] = tag
;
517 ((uint8_t *)oa_p
->oa_buffer
)[oa_p
->oa_offset
+ DHCP_LEN_OFFSET
] = (uint8_t)len
;
519 memcpy(oa_p
->oa_buffer
+ (DHCP_OPTION_OFFSET
+ oa_p
->oa_offset
),
522 oa_p
->oa_offset
+= len
+ DHCP_OPTION_OFFSET
;
525 oa_p
->oa_option_count
++;
526 return (dhcpoa_success_e
);
530 * Function: dhcpoa_add_dhcpmsg
533 * Add a dhcp message option to the option area.
536 dhcpoa_add_dhcpmsg(dhcpoa_t
* oa_p
, dhcp_msgtype_t msgtype
)
538 return (dhcpoa_add(oa_p
, dhcptag_dhcp_message_type_e
,
539 sizeof(msgtype
), &msgtype
));
543 dhcpoa_used(dhcpoa_t
* oa_p
)
545 return (oa_p
->oa_offset
);
549 dhcpoa_freespace(dhcpoa_t
* oa_p
)
553 freespace
= oa_p
->oa_size
- oa_p
->oa_offset
- oa_p
->oa_reserve
;
561 dhcpoa_count(dhcpoa_t
* oa_p
)
563 return (oa_p
->oa_option_count
);
567 dhcpoa_buffer(dhcpoa_t
* oa_p
)
569 return (oa_p
->oa_buffer
);
573 #ifdef TEST_DHCP_OPTIONS
574 char test_empty
[] = {
579 char test_simple
[] = {
581 1, 4, 255, 255, 252, 0,
582 3, 4, 17, 202, 40, 1,
586 char test_vendor
[] = {
588 1, 4, 255, 255, 252, 0,
589 3, 4, 17, 202, 40, 1,
590 43, 6, 1, 4, 1, 2, 3, 4,
591 43, 6, 1, 4, 1, 2, 3, 4,
595 char test_no_end
[] = {
596 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36,
597 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x33, 0x04, 0x80,
598 0x00, 0x80, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff,
599 0x00, 0x03, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x06,
600 0x0c, 0x18, 0x1a, 0xa3, 0x21, 0x18, 0x1a, 0xa3,
601 0x20, 0x18, 0x5e, 0xa3, 0x21, 0x00, 0x00, 0x00,
602 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
603 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
606 char test_too_short
[] = {
616 struct test tests
[] = {
617 { "empty", test_empty
, sizeof(test_empty
), TRUE
},
618 { "simple", test_simple
, sizeof(test_simple
), TRUE
},
619 { "vendor", test_vendor
, sizeof(test_vendor
), TRUE
},
620 { "no_end", test_no_end
, sizeof(test_no_end
), TRUE
},
621 { "too_short", test_too_short
, sizeof(test_too_short
), FALSE
},
622 { NULL
, NULL
, 0, FALSE
},
626 static char buf
[2048];
633 struct dhcp
* pkt
= (struct dhcp
*)buf
;
635 dhcpol_init(&options
);
637 for (i
= 0; tests
[i
].name
; i
++) {
638 printf("\nTest %d: ", i
);
639 bcopy(tests
[i
].data
, pkt
->dp_options
, tests
[i
].len
);
640 if (dhcpol_parse_packet(&options
, pkt
,
641 sizeof(*pkt
) + tests
[i
].len
)
642 != tests
[i
].result
) {
643 printf("test '%s' FAILED\n", tests
[i
].name
);
646 printf("test '%s' PASSED\n", tests
[i
].name
);
648 dhcpol_free(&options
);
652 #endif /* TEST_DHCP_OPTIONS */