]> git.saurik.com Git - apple/xnu.git/blame - bsd/netinet/dhcp_options.c
xnu-2782.1.97.tar.gz
[apple/xnu.git] / bsd / netinet / dhcp_options.c
CommitLineData
2d21ac55
A
1/*
2 * Copyright (c) 2002-2007 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
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.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
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.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
9bccf70c
A
28/*
29 * dhcp_options.c
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
34 */
35
36/*
37 * Modification History
38 *
39 * March 15, 2002 Dieter Siegmund (dieter@apple)
40 * - imported from bootp project
41 */
42
2d21ac55 43#include <string.h>
9bccf70c
A
44#include <sys/types.h>
45#include <sys/param.h>
46#include <netinet/in.h>
47#include <sys/malloc.h>
2d21ac55 48#include <libkern/libkern.h>
9bccf70c
A
49
50#include <netinet/dhcp.h>
51#include <netinet/dhcp_options.h>
52
2d21ac55
A
53#ifdef DHCP_DEBUG
54#define dprintf(x) printf x;
55#else /* !DHCP_DEBUG */
56#define dprintf(x)
57#endif /* DHCP_DEBUG */
58
9bccf70c
A
59static __inline__ void
60my_free(void * ptr)
61{
62 _FREE(ptr, M_TEMP);
63}
64
65static __inline__ void *
66my_malloc(int size)
67{
68 void * data;
69 MALLOC(data, void *, size, M_TEMP, M_WAITOK);
70 return (data);
71}
72
73static __inline__ void *
74my_realloc(void * oldptr, int oldsize, int newsize)
75{
76 void * data;
77
78 MALLOC(data, void *, newsize, M_TEMP, M_WAITOK);
79 bcopy(oldptr, data, oldsize);
80 my_free(oldptr);
81 return (data);
82}
83
84/*
85 * Functions: ptrlist_*
86 * Purpose:
87 * A dynamically growable array of pointers.
88 */
89
90#define PTRLIST_NUMBER 16
91
92static void
93ptrlist_init(ptrlist_t * list)
94{
95 bzero(list, sizeof(*list));
96 return;
97}
98
99static void
100ptrlist_free(ptrlist_t * list)
101{
102 if (list->array)
103 my_free(list->array);
104 ptrlist_init(list);
105 return;
106}
107
108static int
109ptrlist_count(ptrlist_t * list)
110{
111 if (list == NULL || list->array == NULL)
112 return (0);
113
114 return (list->count);
115}
116
2d21ac55 117static const void *
9bccf70c
A
118ptrlist_element(ptrlist_t * list, int i)
119{
120 if (list->array == NULL)
121 return (NULL);
122 if (i < list->count)
123 return (list->array[i]);
124 return (NULL);
125}
126
127
128static boolean_t
129ptrlist_grow(ptrlist_t * list)
130{
131 if (list->array == NULL) {
132 if (list->size == 0)
133 list->size = PTRLIST_NUMBER;
134 list->count = 0;
135 list->array = my_malloc(sizeof(*list->array) * list->size);
136 }
137 else if (list->size == list->count) {
2d21ac55 138 dprintf(("doubling %d to %d\n", list->size, list->size * 2));
9bccf70c
A
139 list->array = my_realloc(list->array,
140 sizeof(*list->array) * list->size,
141 sizeof(*list->array) * list->size * 2);
142 list->size *= 2;
143 }
144 if (list->array == NULL)
145 return (FALSE);
146 return (TRUE);
147}
148
149static boolean_t
2d21ac55 150ptrlist_add(ptrlist_t * list, const void * element)
9bccf70c
A
151{
152 if (ptrlist_grow(list) == FALSE)
153 return (FALSE);
154
155 list->array[list->count++] = element;
156 return (TRUE);
157}
158
159/* concatenates extra onto list */
160static boolean_t
161ptrlist_concat(ptrlist_t * list, ptrlist_t * extra)
162{
163 if (extra->count == 0)
164 return (TRUE);
165
166 if ((extra->count + list->count) > list->size) {
167 int old_size = list->size;
168
169 list->size = extra->count + list->count;
170 if (list->array == NULL)
171 list->array = my_malloc(sizeof(*list->array) * list->size);
172 else
173 list->array = my_realloc(list->array, old_size,
174 sizeof(*list->array) * list->size);
175 }
176 if (list->array == NULL)
177 return (FALSE);
178 bcopy(extra->array, list->array + list->count,
179 extra->count * sizeof(*list->array));
180 list->count += extra->count;
181 return (TRUE);
182}
183
184
185/*
186 * Functions: dhcpol_*
187 *
188 * Purpose:
189 * Routines to parse/access existing options buffers.
190 */
191boolean_t
2d21ac55 192dhcpol_add(dhcpol_t * list, const void * element)
9bccf70c
A
193{
194 return (ptrlist_add((ptrlist_t *)list, element));
195}
196
197int
198dhcpol_count(dhcpol_t * list)
199{
200 return (ptrlist_count((ptrlist_t *)list));
201}
202
2d21ac55 203const void *
9bccf70c
A
204dhcpol_element(dhcpol_t * list, int i)
205{
206 return (ptrlist_element((ptrlist_t *)list, i));
207}
208
209void
210dhcpol_init(dhcpol_t * list)
211{
212 ptrlist_init((ptrlist_t *)list);
213}
214
215void
216dhcpol_free(dhcpol_t * list)
217{
218 ptrlist_free((ptrlist_t *)list);
219}
220
221boolean_t
222dhcpol_concat(dhcpol_t * list, dhcpol_t * extra)
223{
224 return (ptrlist_concat((ptrlist_t *)list, (ptrlist_t *)extra));
225}
226
227/*
228 * Function: dhcpol_parse_buffer
229 *
230 * Purpose:
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
234 * the end tag.
235 */
236boolean_t
2d21ac55 237dhcpol_parse_buffer(dhcpol_t * list, const void * buffer, int length)
9bccf70c
A
238{
239 int len;
2d21ac55
A
240 const uint8_t * scan;
241 uint8_t tag;
9bccf70c
A
242
243 dhcpol_init(list);
244
245 len = length;
246 tag = dhcptag_pad_e;
2d21ac55 247 for (scan = (const uint8_t *)buffer; tag != dhcptag_end_e && len > 0; ) {
9bccf70c
A
248
249 tag = scan[DHCP_TAG_OFFSET];
250
251 switch (tag) {
252 case dhcptag_end_e:
2d21ac55
A
253 /* remember that it was terminated */
254 dhcpol_add(list, scan);
9bccf70c
A
255 scan++;
256 len--;
257 break;
258 case dhcptag_pad_e: /* ignore pad */
259 scan++;
260 len--;
261 break;
262 default: {
2d21ac55 263 uint8_t option_len = scan[DHCP_LEN_OFFSET];
9bccf70c
A
264
265 dhcpol_add(list, scan);
266 len -= (option_len + 2);
267 scan += (option_len + 2);
268 break;
269 }
270 }
271 }
272 if (len < 0) {
273 /* ran off the end */
2d21ac55 274 dprintf(("dhcp_options: parse failed near tag %d", tag));
9bccf70c
A
275 dhcpol_free(list);
276 return (FALSE);
277 }
278 return (TRUE);
279}
280
281/*
282 * Function: dhcpol_find
283 *
284 * Purpose:
285 * Finds the first occurence of the given option, and returns its
286 * length and the option data pointer.
287 *
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.
292 */
2d21ac55 293const void *
9bccf70c
A
294dhcpol_find(dhcpol_t * list, int tag, int * len_p, int * start)
295{
296 int i = 0;
297
298 if (tag == dhcptag_end_e || tag == dhcptag_pad_e)
299 return (NULL);
300
301 if (start)
302 i = *start;
303
304 for (; i < dhcpol_count(list); i++) {
2d21ac55 305 const uint8_t * option = dhcpol_element(list, i);
9bccf70c
A
306
307 if (option[DHCP_TAG_OFFSET] == tag) {
308 if (len_p)
309 *len_p = option[DHCP_LEN_OFFSET];
310 if (start)
311 *start = i + 1;
312 return (option + DHCP_OPTION_OFFSET);
313 }
314 }
315 return (NULL);
316}
317
2d21ac55 318#if 0
9bccf70c
A
319/*
320 * Function: dhcpol_get
321 *
322 * Purpose:
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
326 * data area.
327 * Note:
328 * Use _FREE(val, M_TEMP) to free the returned data area.
329 */
330void *
331dhcpol_get(dhcpol_t * list, int tag, int * len_p)
332{
333 int i;
334 char * data = NULL;
335 int data_len = 0;
336
337 if (tag == dhcptag_end_e || tag == dhcptag_pad_e)
338 return (NULL);
339
340 for (i = 0; i < dhcpol_count(list); i++) {
2d21ac55 341 const uint8_t * option = dhcpol_element(list, i);
9bccf70c
A
342
343 if (option[DHCP_TAG_OFFSET] == tag) {
344 int len = option[DHCP_LEN_OFFSET];
345
346 if (data_len == 0) {
347 data = my_malloc(len);
348 }
349 else {
350 data = my_realloc(data, data_len, data_len + len);
351 }
b0d623f7 352 FIX ME: test data NULL
9bccf70c
A
353 bcopy(option + DHCP_OPTION_OFFSET, data + data_len, len);
354 data_len += len;
355 }
356 }
357 *len_p = data_len;
358 return (data);
359}
b0d623f7 360#endif
9bccf70c
A
361
362/*
363 * Function: dhcpol_parse_packet
364 *
365 * Purpose:
366 * Parse the option areas in the DHCP packet.
367 * Verifies that the packet has the right magic number,
368 * then parses and accumulates the option areas.
369 * First the pkt->dp_options is parsed. If that contains
370 * the overload option, it parses pkt->dp_file if specified,
371 * then parses pkt->dp_sname if specified.
372 */
373boolean_t
2d21ac55 374dhcpol_parse_packet(dhcpol_t * options, const struct dhcp * pkt, int len)
9bccf70c
A
375{
376 char rfc_magic[4] = RFC_OPTIONS_MAGIC;
377
378 dhcpol_init(options); /* make sure it's empty */
379
9bccf70c 380 if (len < (sizeof(*pkt) + RFC_MAGIC_SIZE)) {
2d21ac55
A
381 dprintf(("dhcp_options: packet is too short: %d < %d\n",
382 len, (int)sizeof(*pkt) + RFC_MAGIC_SIZE));
9bccf70c
A
383 return (FALSE);
384 }
385 if (bcmp(pkt->dp_options, rfc_magic, RFC_MAGIC_SIZE)) {
2d21ac55 386 dprintf(("dhcp_options: missing magic number\n"));
9bccf70c
A
387 return (FALSE);
388 }
389 if (dhcpol_parse_buffer(options, pkt->dp_options + RFC_MAGIC_SIZE,
2d21ac55 390 len - sizeof(*pkt) - RFC_MAGIC_SIZE) == FALSE)
9bccf70c
A
391 return (FALSE);
392 { /* get overloaded options */
2d21ac55 393 const uint8_t * overload;
9bccf70c
A
394 int overload_len;
395
2d21ac55
A
396 overload = dhcpol_find(options, dhcptag_option_overload_e,
397 &overload_len, NULL);
9bccf70c
A
398 if (overload && overload_len == 1) { /* has overloaded options */
399 dhcpol_t extra;
2d21ac55 400
9bccf70c
A
401 dhcpol_init(&extra);
402 if (*overload == DHCP_OVERLOAD_FILE
403 || *overload == DHCP_OVERLOAD_BOTH) {
404 if (dhcpol_parse_buffer(&extra, pkt->dp_file,
2d21ac55 405 sizeof(pkt->dp_file))) {
9bccf70c
A
406 dhcpol_concat(options, &extra);
407 dhcpol_free(&extra);
408 }
409 }
410 if (*overload == DHCP_OVERLOAD_SNAME
411 || *overload == DHCP_OVERLOAD_BOTH) {
412 if (dhcpol_parse_buffer(&extra, pkt->dp_sname,
2d21ac55 413 sizeof(pkt->dp_sname))) {
9bccf70c
A
414 dhcpol_concat(options, &extra);
415 dhcpol_free(&extra);
416 }
417 }
418 }
419 }
420 return (TRUE);
421}
422
423/*
2d21ac55 424 * Module: dhcpoa
9bccf70c
A
425 *
426 * Purpose:
2d21ac55 427 * Types and functions to create new dhcp option areas.
9bccf70c 428 */
2d21ac55
A
429
430/*
431 * Function: dhcpoa_{init_common, init_no_end, init}
432 *
433 * Purpose:
434 * Initialize an option area structure so that it can be used
435 * in calling the dhcpoa_* routines.
436 */
437static void
438dhcpoa_init_common(dhcpoa_t * oa_p, void * buffer, int size, int reserve)
439{
440 bzero(oa_p, sizeof(*oa_p));
441 oa_p->oa_buffer = buffer;
442 oa_p->oa_size = size;
443 oa_p->oa_reserve = reserve;
444}
445
446void
447dhcpoa_init_no_end(dhcpoa_t * oa_p, void * buffer, int size)
9bccf70c 448{
2d21ac55
A
449 dhcpoa_init_common(oa_p, buffer, size, 0);
450 return;
451}
9bccf70c 452
2d21ac55
A
453int
454dhcpoa_size(dhcpoa_t * oa_p)
455{
456 return (oa_p->oa_size);
457}
9bccf70c 458
2d21ac55
A
459void
460dhcpoa_init(dhcpoa_t * oa_p, void * buffer, int size)
461{
462 /* initialize the area, reserve space for the end tag */
463 dhcpoa_init_common(oa_p, buffer, size, 1);
464 return;
465}
466/*
467 * Function: dhcpoa_add
468 *
469 * Purpose:
470 * Add an option to the option area.
471 */
472dhcpoa_ret_t
473dhcpoa_add(dhcpoa_t * oa_p, dhcptag_t tag, int len, const void * option)
474{
475 if (len > DHCP_OPTION_SIZE_MAX) {
476 dprintf(("tag %d option %d > %d\n", tag, len, DHCP_OPTION_SIZE_MAX));
477 return (dhcpoa_failed_e);
478 }
9bccf70c 479
2d21ac55
A
480 if (oa_p->oa_end_tag) {
481 dprintf(("attempt to add data after end tag\n"));
482 return (dhcpoa_failed_e);
483 }
9bccf70c 484
2d21ac55
A
485 switch (tag) {
486 case dhcptag_end_e:
487 if ((oa_p->oa_offset + 1) > oa_p->oa_size) {
488 /* this can't happen since we're careful to leave space */
489 dprintf(("can't add end tag %d > %d\n",
490 oa_p->oa_offset + oa_p->oa_reserve, oa_p->oa_size));
491 return (dhcpoa_failed_e);
9bccf70c 492 }
2d21ac55
A
493 ((uint8_t *)oa_p->oa_buffer)[oa_p->oa_offset + DHCP_TAG_OFFSET] = tag;
494 oa_p->oa_offset++;
495 oa_p->oa_end_tag = 1;
496 break;
497
498 case dhcptag_pad_e:
499 /* 1 for pad tag */
500 if ((oa_p->oa_offset + oa_p->oa_reserve + 1) > oa_p->oa_size) {
501 dprintf(("can't add pad tag %d > %d\n",
502 oa_p->oa_offset + oa_p->oa_reserve + 1, oa_p->oa_size));
503 return (dhcpoa_full_e);
9bccf70c 504 }
2d21ac55
A
505 ((uint8_t *)oa_p->oa_buffer)[oa_p->oa_offset + DHCP_TAG_OFFSET] = tag;
506 oa_p->oa_offset++;
507 break;
508
509 default:
510 /* 2 for tag/len */
511 if ((oa_p->oa_offset + len + 2 + oa_p->oa_reserve) > oa_p->oa_size) {
512 dprintf(("can't add tag %d (%d > %d)\n", tag,
513 oa_p->oa_offset + len + 2 + oa_p->oa_reserve,
514 oa_p->oa_size));
515 return (dhcpoa_full_e);
9bccf70c 516 }
2d21ac55
A
517 ((uint8_t *)oa_p->oa_buffer)[oa_p->oa_offset + DHCP_TAG_OFFSET] = tag;
518 ((uint8_t *)oa_p->oa_buffer)[oa_p->oa_offset + DHCP_LEN_OFFSET] = (uint8_t)len;
519 if (len) {
520 memcpy(oa_p->oa_buffer + (DHCP_OPTION_OFFSET + oa_p->oa_offset),
521 option, len);
522 }
523 oa_p->oa_offset += len + DHCP_OPTION_OFFSET;
524 break;
9bccf70c 525 }
2d21ac55
A
526 oa_p->oa_option_count++;
527 return (dhcpoa_success_e);
528}
529
530/*
531 * Function: dhcpoa_add_dhcpmsg
532 *
533 * Purpose:
534 * Add a dhcp message option to the option area.
535 */
536dhcpoa_ret_t
537dhcpoa_add_dhcpmsg(dhcpoa_t * oa_p, dhcp_msgtype_t msgtype)
538{
539 return (dhcpoa_add(oa_p, dhcptag_dhcp_message_type_e,
540 sizeof(msgtype), &msgtype));
541}
542
543int
544dhcpoa_used(dhcpoa_t * oa_p)
545{
546 return (oa_p->oa_offset);
547}
548
549int
550dhcpoa_freespace(dhcpoa_t * oa_p)
551{
552 int freespace;
553
554 freespace = oa_p->oa_size - oa_p->oa_offset - oa_p->oa_reserve;
555 if (freespace < 0) {
556 freespace = 0;
9bccf70c 557 }
2d21ac55
A
558 return (freespace);
559}
9bccf70c 560
2d21ac55
A
561int
562dhcpoa_count(dhcpoa_t * oa_p)
563{
564 return (oa_p->oa_option_count);
9bccf70c
A
565}
566
2d21ac55
A
567void *
568dhcpoa_buffer(dhcpoa_t * oa_p)
569{
570 return (oa_p->oa_buffer);
571}
572
573
9bccf70c
A
574#ifdef TEST_DHCP_OPTIONS
575char test_empty[] = {
576 99, 130, 83, 99,
577 255,
578};
579
580char test_simple[] = {
581 99, 130, 83, 99,
582 1, 4, 255, 255, 252, 0,
583 3, 4, 17, 202, 40, 1,
584 255,
585};
586
587char test_vendor[] = {
588 99, 130, 83, 99,
589 1, 4, 255, 255, 252, 0,
590 3, 4, 17, 202, 40, 1,
591 43, 6, 1, 4, 1, 2, 3, 4,
592 43, 6, 1, 4, 1, 2, 3, 4,
593 255,
594};
595
596char test_no_end[] = {
597 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36,
598 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x33, 0x04, 0x80,
599 0x00, 0x80, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff,
600 0x00, 0x03, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x06,
601 0x0c, 0x18, 0x1a, 0xa3, 0x21, 0x18, 0x1a, 0xa3,
602 0x20, 0x18, 0x5e, 0xa3, 0x21, 0x00, 0x00, 0x00,
603 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
604 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
605};
606
607char test_too_short[] = {
608 0x1
609};
610struct test {
611 char * name;
612 char * data;
613 int len;
614 boolean_t result;
615};
616
617struct test tests[] = {
618 { "empty", test_empty, sizeof(test_empty), TRUE },
619 { "simple", test_simple, sizeof(test_simple), TRUE },
620 { "vendor", test_vendor, sizeof(test_vendor), TRUE },
621 { "no_end", test_no_end, sizeof(test_no_end), TRUE },
622 { "too_short", test_too_short, sizeof(test_too_short), FALSE },
623 { NULL, NULL, 0, FALSE },
624};
625
626
627static char buf[2048];
628
629int
630main()
631{
632 int i;
633 dhcpol_t options;
9bccf70c
A
634 struct dhcp * pkt = (struct dhcp *)buf;
635
636 dhcpol_init(&options);
637
638 for (i = 0; tests[i].name; i++) {
639 printf("\nTest %d: ", i);
640 bcopy(tests[i].data, pkt->dp_options, tests[i].len);
641 if (dhcpol_parse_packet(&options, pkt,
2d21ac55
A
642 sizeof(*pkt) + tests[i].len)
643 != tests[i].result) {
9bccf70c 644 printf("test '%s' FAILED\n", tests[i].name);
9bccf70c
A
645 }
646 else {
647 printf("test '%s' PASSED\n", tests[i].name);
9bccf70c
A
648 }
649 dhcpol_free(&options);
650 }
651 exit(0);
652}
55e303ae 653#endif /* TEST_DHCP_OPTIONS */