3 * - routines to parse and access dhcp options
4 * and create new dhcp option areas
5 * - handles overloaded areas as well as vendor-specific options
6 * that are encoded using the RFC 2132 encoding
10 * Modification History
12 * March 15, 2002 Dieter Siegmund (dieter@apple)
13 * - imported from bootp project
16 #include <sys/types.h>
17 #include <sys/param.h>
18 #include <netinet/in.h>
19 #include <sys/malloc.h>
21 #include <netinet/dhcp.h>
22 #include <netinet/dhcp_options.h>
24 static __inline__
void
30 static __inline__
void *
34 MALLOC(data
, void *, size
, M_TEMP
, M_WAITOK
);
38 static __inline__
void *
39 my_realloc(void * oldptr
, int oldsize
, int newsize
)
43 MALLOC(data
, void *, newsize
, M_TEMP
, M_WAITOK
);
44 bcopy(oldptr
, data
, oldsize
);
50 * Functions: ptrlist_*
52 * A dynamically growable array of pointers.
55 #define PTRLIST_NUMBER 16
58 ptrlist_init(ptrlist_t
* list
)
60 bzero(list
, sizeof(*list
));
65 ptrlist_free(ptrlist_t
* list
)
74 ptrlist_count(ptrlist_t
* list
)
76 if (list
== NULL
|| list
->array
== NULL
)
83 ptrlist_element(ptrlist_t
* list
, int i
)
85 if (list
->array
== NULL
)
88 return (list
->array
[i
]);
94 ptrlist_grow(ptrlist_t
* list
)
96 if (list
->array
== NULL
) {
98 list
->size
= PTRLIST_NUMBER
;
100 list
->array
= my_malloc(sizeof(*list
->array
) * list
->size
);
102 else if (list
->size
== list
->count
) {
104 printf("doubling %d to %d\n", list
->size
, list
->size
* 2);
106 list
->array
= my_realloc(list
->array
,
107 sizeof(*list
->array
) * list
->size
,
108 sizeof(*list
->array
) * list
->size
* 2);
111 if (list
->array
== NULL
)
117 ptrlist_add(ptrlist_t
* list
, void * element
)
119 if (ptrlist_grow(list
) == FALSE
)
122 list
->array
[list
->count
++] = element
;
126 /* concatenates extra onto list */
128 ptrlist_concat(ptrlist_t
* list
, ptrlist_t
* extra
)
130 if (extra
->count
== 0)
133 if ((extra
->count
+ list
->count
) > list
->size
) {
134 int old_size
= list
->size
;
136 list
->size
= extra
->count
+ list
->count
;
137 if (list
->array
== NULL
)
138 list
->array
= my_malloc(sizeof(*list
->array
) * list
->size
);
140 list
->array
= my_realloc(list
->array
, old_size
,
141 sizeof(*list
->array
) * list
->size
);
143 if (list
->array
== NULL
)
145 bcopy(extra
->array
, list
->array
+ list
->count
,
146 extra
->count
* sizeof(*list
->array
));
147 list
->count
+= extra
->count
;
153 * Functions: dhcpol_*
156 * Routines to parse/access existing options buffers.
159 dhcpol_add(dhcpol_t
* list
, void * element
)
161 return (ptrlist_add((ptrlist_t
*)list
, element
));
165 dhcpol_count(dhcpol_t
* list
)
167 return (ptrlist_count((ptrlist_t
*)list
));
171 dhcpol_element(dhcpol_t
* list
, int i
)
173 return (ptrlist_element((ptrlist_t
*)list
, i
));
177 dhcpol_init(dhcpol_t
* list
)
179 ptrlist_init((ptrlist_t
*)list
);
183 dhcpol_free(dhcpol_t
* list
)
185 ptrlist_free((ptrlist_t
*)list
);
189 dhcpol_concat(dhcpol_t
* list
, dhcpol_t
* extra
)
191 return (ptrlist_concat((ptrlist_t
*)list
, (ptrlist_t
*)extra
));
195 * Function: dhcpol_parse_buffer
198 * Parse the given buffer into DHCP options, returning the
199 * list of option pointers in the given dhcpol_t.
200 * Parsing continues until we hit the end of the buffer or
204 dhcpol_parse_buffer(dhcpol_t
* list
, void * buffer
, int length
,
208 unsigned char * scan
;
218 for (scan
= (unsigned char *)buffer
; tag
!= dhcptag_end_e
&& len
> 0; ) {
220 tag
= scan
[DHCP_TAG_OFFSET
];
224 dhcpol_add(list
, scan
); /* remember that it was terminated */
228 case dhcptag_pad_e
: /* ignore pad */
233 unsigned char option_len
= scan
[DHCP_LEN_OFFSET
];
235 dhcpol_add(list
, scan
);
236 len
-= (option_len
+ 2);
237 scan
+= (option_len
+ 2);
243 /* ran off the end */
245 sprintf(err
, "parse failed near tag %d", tag
);
253 * Function: dhcpol_find
256 * Finds the first occurence of the given option, and returns its
257 * length and the option data pointer.
259 * The optional start parameter allows this function to
260 * return the next start point so that successive
261 * calls will retrieve the next occurence of the option.
262 * Before the first call, *start should be set to 0.
265 dhcpol_find(dhcpol_t
* list
, int tag
, int * len_p
, int * start
)
269 if (tag
== dhcptag_end_e
|| tag
== dhcptag_pad_e
)
275 for (; i
< dhcpol_count(list
); i
++) {
276 unsigned char * option
= dhcpol_element(list
, i
);
278 if (option
[DHCP_TAG_OFFSET
] == tag
) {
280 *len_p
= option
[DHCP_LEN_OFFSET
];
283 return (option
+ DHCP_OPTION_OFFSET
);
290 * Function: dhcpol_get
293 * Accumulate all occurences of the given option into a
294 * malloc'd buffer, and return its length. Used to get
295 * all occurrences of a particular option in a single
298 * Use _FREE(val, M_TEMP) to free the returned data area.
301 dhcpol_get(dhcpol_t
* list
, int tag
, int * len_p
)
307 if (tag
== dhcptag_end_e
|| tag
== dhcptag_pad_e
)
310 for (i
= 0; i
< dhcpol_count(list
); i
++) {
311 unsigned char * option
= dhcpol_element(list
, i
);
313 if (option
[DHCP_TAG_OFFSET
] == tag
) {
314 int len
= option
[DHCP_LEN_OFFSET
];
317 data
= my_malloc(len
);
320 data
= my_realloc(data
, data_len
, data_len
+ len
);
322 bcopy(option
+ DHCP_OPTION_OFFSET
, data
+ data_len
, len
);
331 * Function: dhcpol_parse_packet
334 * Parse the option areas in the DHCP packet.
335 * Verifies that the packet has the right magic number,
336 * then parses and accumulates the option areas.
337 * First the pkt->dp_options is parsed. If that contains
338 * the overload option, it parses pkt->dp_file if specified,
339 * then parses pkt->dp_sname if specified.
342 dhcpol_parse_packet(dhcpol_t
* options
, struct dhcp
* pkt
, int len
,
345 char rfc_magic
[4] = RFC_OPTIONS_MAGIC
;
347 dhcpol_init(options
); /* make sure it's empty */
352 if (len
< (sizeof(*pkt
) + RFC_MAGIC_SIZE
)) {
354 sprintf(err
, "packet is too short: %d < %d",
355 len
, (int)sizeof(*pkt
) + RFC_MAGIC_SIZE
);
359 if (bcmp(pkt
->dp_options
, rfc_magic
, RFC_MAGIC_SIZE
)) {
361 sprintf(err
, "missing magic number");
364 if (dhcpol_parse_buffer(options
, pkt
->dp_options
+ RFC_MAGIC_SIZE
,
365 len
- sizeof(*pkt
) - RFC_MAGIC_SIZE
, err
) == FALSE
)
367 { /* get overloaded options */
368 unsigned char * overload
;
371 overload
= (unsigned char *)
372 dhcpol_find(options
, dhcptag_option_overload_e
,
373 &overload_len
, NULL
);
374 if (overload
&& overload_len
== 1) { /* has overloaded options */
378 if (*overload
== DHCP_OVERLOAD_FILE
379 || *overload
== DHCP_OVERLOAD_BOTH
) {
380 if (dhcpol_parse_buffer(&extra
, pkt
->dp_file
,
381 sizeof(pkt
->dp_file
), NULL
)) {
382 dhcpol_concat(options
, &extra
);
386 if (*overload
== DHCP_OVERLOAD_SNAME
387 || *overload
== DHCP_OVERLOAD_BOTH
) {
388 if (dhcpol_parse_buffer(&extra
, pkt
->dp_sname
,
389 sizeof(pkt
->dp_sname
), NULL
)) {
390 dhcpol_concat(options
, &extra
);
400 * Function: dhcpol_parse_vendor
403 * Given a set of options, find the vendor specific option(s)
404 * and parse all of them into a single option list.
407 * TRUE if vendor specific options existed and were parsed succesfully,
411 dhcpol_parse_vendor(dhcpol_t
* vendor
, dhcpol_t
* options
,
415 boolean_t ret
= FALSE
;
428 data
= dhcpol_find(options
, dhcptag_vendor_specific_e
, &len
, &start
);
430 break; /* out of for */
433 if (dhcpol_parse_buffer(&extra
, data
, len
, err
) == FALSE
) {
437 if (dhcpol_concat(vendor
, &extra
) == FALSE
) {
439 sprintf(err
, "dhcpol_concat() failed at %d\n", start
);
447 strcpy(err
, "missing vendor specific options");
457 #ifdef TEST_DHCP_OPTIONS
458 char test_empty
[] = {
463 char test_simple
[] = {
465 1, 4, 255, 255, 252, 0,
466 3, 4, 17, 202, 40, 1,
470 char test_vendor
[] = {
472 1, 4, 255, 255, 252, 0,
473 3, 4, 17, 202, 40, 1,
474 43, 6, 1, 4, 1, 2, 3, 4,
475 43, 6, 1, 4, 1, 2, 3, 4,
479 char test_no_end
[] = {
480 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36,
481 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x33, 0x04, 0x80,
482 0x00, 0x80, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff,
483 0x00, 0x03, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x06,
484 0x0c, 0x18, 0x1a, 0xa3, 0x21, 0x18, 0x1a, 0xa3,
485 0x20, 0x18, 0x5e, 0xa3, 0x21, 0x00, 0x00, 0x00,
486 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
487 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
490 char test_too_short
[] = {
500 struct test tests
[] = {
501 { "empty", test_empty
, sizeof(test_empty
), TRUE
},
502 { "simple", test_simple
, sizeof(test_simple
), TRUE
},
503 { "vendor", test_vendor
, sizeof(test_vendor
), TRUE
},
504 { "no_end", test_no_end
, sizeof(test_no_end
), TRUE
},
505 { "too_short", test_too_short
, sizeof(test_too_short
), FALSE
},
506 { NULL
, NULL
, 0, FALSE
},
510 static char buf
[2048];
518 struct dhcp
* pkt
= (struct dhcp
*)buf
;
520 dhcpol_init(&options
);
522 for (i
= 0; tests
[i
].name
; i
++) {
523 printf("\nTest %d: ", i
);
524 bcopy(tests
[i
].data
, pkt
->dp_options
, tests
[i
].len
);
525 if (dhcpol_parse_packet(&options
, pkt
,
526 sizeof(*pkt
) + tests
[i
].len
,
527 error
) != tests
[i
].result
) {
528 printf("test '%s' FAILED\n", tests
[i
].name
);
529 if (tests
[i
].result
== TRUE
) {
530 printf("error message returned was %s\n", error
);
534 printf("test '%s' PASSED\n", tests
[i
].name
);
535 if (tests
[i
].result
== FALSE
) {
536 printf("error message returned was %s\n", error
);
539 dhcpol_free(&options
);
543 #endif /* TEST_DHCP_OPTIONS */