]> git.saurik.com Git - apple/xnu.git/blob - bsd/netinet/dhcp_options.c
xnu-7195.101.1.tar.gz
[apple/xnu.git] / bsd / netinet / dhcp_options.c
1 /*
2 * Copyright (c) 2002-2019 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 */
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
43 #include <string.h>
44 #include <sys/types.h>
45 #include <sys/param.h>
46 #include <netinet/in.h>
47 #include <sys/malloc.h>
48 #include <netinet/dhcp.h>
49 #include <netinet/dhcp_options.h>
50
51 #ifndef TEST_DHCP_OPTIONS
52 #include <libkern/libkern.h>
53
54 #ifdef DHCP_DEBUG
55 #define dprintf(x) printf x;
56 #else /* !DHCP_DEBUG */
57 #define dprintf(x)
58 #endif /* DHCP_DEBUG */
59
60 static __inline__ void
61 my_free(void * ptr)
62 {
63 _FREE(ptr, M_TEMP);
64 }
65
66 static __inline__ void *
67 my_malloc(int size)
68 {
69 void * data;
70 MALLOC(data, void *, size, M_TEMP, M_WAITOK);
71 return data;
72 }
73
74 static __inline__ void *
75 my_realloc(void * oldptr, int oldsize, int newsize)
76 {
77 void * data;
78
79 MALLOC(data, void *, newsize, M_TEMP, M_WAITOK);
80 bcopy(oldptr, data, oldsize);
81 my_free(oldptr);
82 return data;
83 }
84 #else
85 /*
86 * To build:
87 * xcrun -sdk macosx.internal cc -DTEST_DHCP_OPTIONS -o /tmp/dhcp_options dhcp_options.c -I ..
88 */
89 #include <stdlib.h>
90 #include <unistd.h>
91 #include <stdio.h>
92 #define my_free free
93 #define my_malloc malloc
94 #define my_realloc(ptr, old_size, new_size) realloc(ptr, new_size)
95 #define dprintf(x) printf x;
96 #endif
97
98 /*
99 * Functions: ptrlist_*
100 * Purpose:
101 * A dynamically growable array of pointers.
102 */
103
104 #define PTRLIST_NUMBER 16
105
106 static void
107 ptrlist_init(ptrlist_t * list)
108 {
109 bzero(list, sizeof(*list));
110 return;
111 }
112
113 static void
114 ptrlist_free(ptrlist_t * list)
115 {
116 if (list->array) {
117 my_free(list->array);
118 }
119 ptrlist_init(list);
120 return;
121 }
122
123 static int
124 ptrlist_count(ptrlist_t * list)
125 {
126 if (list == NULL || list->array == NULL) {
127 return 0;
128 }
129
130 return list->count;
131 }
132
133 static const void *
134 ptrlist_element(ptrlist_t * list, int i)
135 {
136 if (list->array == NULL) {
137 return NULL;
138 }
139 if (i < list->count) {
140 return list->array[i];
141 }
142 return NULL;
143 }
144
145
146 static boolean_t
147 ptrlist_grow(ptrlist_t * list)
148 {
149 if (list->array == NULL) {
150 if (list->size == 0) {
151 list->size = PTRLIST_NUMBER;
152 }
153 list->count = 0;
154 list->array = my_malloc(sizeof(*list->array) * list->size);
155 } else if (list->size == list->count) {
156 dprintf(("doubling %d to %d\n", list->size, list->size * 2));
157 list->array = my_realloc(list->array,
158 sizeof(*list->array) * list->size,
159 sizeof(*list->array) * list->size * 2);
160 list->size *= 2;
161 }
162 if (list->array == NULL) {
163 return FALSE;
164 }
165 return TRUE;
166 }
167
168 static boolean_t
169 ptrlist_add(ptrlist_t * list, const void * element)
170 {
171 if (ptrlist_grow(list) == FALSE) {
172 return FALSE;
173 }
174
175 list->array[list->count++] = element;
176 return TRUE;
177 }
178
179 /* concatenates extra onto list */
180 static boolean_t
181 ptrlist_concat(ptrlist_t * list, ptrlist_t * extra)
182 {
183 if (extra->count == 0) {
184 return TRUE;
185 }
186
187 if ((extra->count + list->count) > list->size) {
188 int old_size = list->size;
189
190 list->size = extra->count + list->count;
191 if (list->array == NULL) {
192 list->array = my_malloc(sizeof(*list->array) * list->size);
193 } else {
194 list->array = my_realloc(list->array, old_size,
195 sizeof(*list->array) * list->size);
196 }
197 }
198 if (list->array == NULL) {
199 return FALSE;
200 }
201 bcopy(extra->array, list->array + list->count,
202 extra->count * sizeof(*list->array));
203 list->count += extra->count;
204 return TRUE;
205 }
206
207
208 /*
209 * Functions: dhcpol_*
210 *
211 * Purpose:
212 * Routines to parse/access existing options buffers.
213 */
214 boolean_t
215 dhcpol_add(dhcpol_t * list, const void * element)
216 {
217 return ptrlist_add((ptrlist_t *)list, element);
218 }
219
220 int
221 dhcpol_count(dhcpol_t * list)
222 {
223 return ptrlist_count((ptrlist_t *)list);
224 }
225
226 const void *
227 dhcpol_element(dhcpol_t * list, int i)
228 {
229 return ptrlist_element((ptrlist_t *)list, i);
230 }
231
232 void
233 dhcpol_init(dhcpol_t * list)
234 {
235 ptrlist_init((ptrlist_t *)list);
236 }
237
238 void
239 dhcpol_free(dhcpol_t * list)
240 {
241 ptrlist_free((ptrlist_t *)list);
242 }
243
244 boolean_t
245 dhcpol_concat(dhcpol_t * list, dhcpol_t * extra)
246 {
247 return ptrlist_concat((ptrlist_t *)list, (ptrlist_t *)extra);
248 }
249
250 /*
251 * Function: dhcpol_parse_buffer
252 *
253 * Purpose:
254 * Parse the given buffer into DHCP options, returning the
255 * list of option pointers in the given dhcpol_t.
256 * Parsing continues until we hit the end of the buffer or
257 * the end tag.
258 */
259 boolean_t
260 dhcpol_parse_buffer(dhcpol_t * list, const void * buffer, int length)
261 {
262 int len;
263 const uint8_t * scan;
264 uint8_t tag;
265
266 dhcpol_init(list);
267
268 len = length;
269 tag = dhcptag_pad_e;
270 for (scan = (const uint8_t *)buffer;
271 tag != dhcptag_end_e && len > DHCP_TAG_OFFSET;) {
272 tag = scan[DHCP_TAG_OFFSET];
273
274 switch (tag) {
275 case dhcptag_end_e:
276 /* remember that it was terminated */
277 dhcpol_add(list, scan);
278 scan++;
279 len--;
280 break;
281 case dhcptag_pad_e: /* ignore pad */
282 scan++;
283 len--;
284 break;
285 default:
286 if (len > DHCP_LEN_OFFSET) {
287 uint8_t option_len;
288
289 option_len = scan[DHCP_LEN_OFFSET];
290 dhcpol_add(list, scan);
291 len -= (option_len + DHCP_OPTION_OFFSET);
292 scan += (option_len + DHCP_OPTION_OFFSET);
293 } else {
294 len = -1;
295 }
296 break;
297 }
298 }
299 if (len < 0) {
300 /* ran off the end */
301 dprintf(("dhcp_options: parse failed near tag %d\n", tag));
302 dhcpol_free(list);
303 return FALSE;
304 }
305 return TRUE;
306 }
307
308 /*
309 * Function: dhcpol_find
310 *
311 * Purpose:
312 * Finds the first occurence of the given option, and returns its
313 * length and the option data pointer.
314 *
315 * The optional start parameter allows this function to
316 * return the next start point so that successive
317 * calls will retrieve the next occurence of the option.
318 * Before the first call, *start should be set to 0.
319 */
320 const void *
321 dhcpol_find(dhcpol_t * list, int tag, int * len_p, int * start)
322 {
323 int i = 0;
324
325 if (tag == dhcptag_end_e || tag == dhcptag_pad_e) {
326 return NULL;
327 }
328
329 if (start) {
330 i = *start;
331 }
332
333 for (; i < dhcpol_count(list); i++) {
334 const uint8_t * option = dhcpol_element(list, i);
335
336 if (option[DHCP_TAG_OFFSET] == tag) {
337 if (len_p) {
338 *len_p = option[DHCP_LEN_OFFSET];
339 }
340 if (start) {
341 *start = i + 1;
342 }
343 return option + DHCP_OPTION_OFFSET;
344 }
345 }
346 return NULL;
347 }
348
349 /*
350 * Function: dhcpol_parse_packet
351 *
352 * Purpose:
353 * Parse the option areas in the DHCP packet.
354 * Verifies that the packet has the right magic number,
355 * then parses and accumulates the option areas.
356 * First the pkt->dp_options is parsed. If that contains
357 * the overload option, it parses pkt->dp_file if specified,
358 * then parses pkt->dp_sname if specified.
359 */
360 boolean_t
361 dhcpol_parse_packet(dhcpol_t * options, const struct dhcp * pkt, int len)
362 {
363 char rfc_magic[4] = RFC_OPTIONS_MAGIC;
364
365 dhcpol_init(options); /* make sure it's empty */
366
367 if (len < (sizeof(*pkt) + RFC_MAGIC_SIZE)) {
368 dprintf(("dhcp_options: packet is too short: %d < %d\n",
369 len, (int)sizeof(*pkt) + RFC_MAGIC_SIZE));
370 return FALSE;
371 }
372 if (bcmp(pkt->dp_options, rfc_magic, RFC_MAGIC_SIZE)) {
373 dprintf(("dhcp_options: missing magic number\n"));
374 return FALSE;
375 }
376 if (dhcpol_parse_buffer(options, pkt->dp_options + RFC_MAGIC_SIZE,
377 len - sizeof(*pkt) - RFC_MAGIC_SIZE) == FALSE) {
378 return FALSE;
379 }
380 { /* get overloaded options */
381 const uint8_t * overload;
382 int overload_len;
383
384 overload = dhcpol_find(options, dhcptag_option_overload_e,
385 &overload_len, NULL);
386 if (overload && overload_len == 1) { /* has overloaded options */
387 dhcpol_t extra;
388
389 dhcpol_init(&extra);
390 if (*overload == DHCP_OVERLOAD_FILE
391 || *overload == DHCP_OVERLOAD_BOTH) {
392 if (dhcpol_parse_buffer(&extra, pkt->dp_file,
393 sizeof(pkt->dp_file))) {
394 dhcpol_concat(options, &extra);
395 dhcpol_free(&extra);
396 }
397 }
398 if (*overload == DHCP_OVERLOAD_SNAME
399 || *overload == DHCP_OVERLOAD_BOTH) {
400 if (dhcpol_parse_buffer(&extra, pkt->dp_sname,
401 sizeof(pkt->dp_sname))) {
402 dhcpol_concat(options, &extra);
403 dhcpol_free(&extra);
404 }
405 }
406 }
407 }
408 return TRUE;
409 }
410
411 #ifdef TEST_DHCP_OPTIONS
412 char test_empty[] = {
413 99, 130, 83, 99,
414 255,
415 };
416
417 char test_short[] = {
418 99, 130, 83, 99,
419 1,
420 };
421
422 char test_simple[] = {
423 99, 130, 83, 99,
424 1, 4, 255, 255, 252, 0,
425 3, 4, 17, 202, 40, 1,
426 255,
427 };
428
429 char test_vendor[] = {
430 99, 130, 83, 99,
431 1, 4, 255, 255, 252, 0,
432 3, 4, 17, 202, 40, 1,
433 43, 6, 1, 4, 1, 2, 3, 4,
434 43, 6, 1, 4, 1, 2, 3, 4,
435 255,
436 };
437
438 char test_no_end[] = {
439 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36,
440 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x33, 0x04, 0x80,
441 0x00, 0x80, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff,
442 0x00, 0x03, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x06,
443 0x0c, 0x18, 0x1a, 0xa3, 0x21, 0x18, 0x1a, 0xa3,
444 0x20, 0x18, 0x5e, 0xa3, 0x21, 0x00, 0x00, 0x00,
445 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
446 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
447 };
448
449 char test_no_magic[] = {
450 0x1
451 };
452 struct test {
453 char * name;
454 char * data;
455 int len;
456 boolean_t result;
457 };
458
459 struct test tests[] = {
460 { .name = "empty", .data = test_empty, .len = sizeof(test_empty), .result = TRUE },
461 { .name = "simple", .data = test_simple, .len = sizeof(test_simple), .result = TRUE },
462 { .name = "vendor", .data = test_vendor, .len = sizeof(test_vendor), .result = TRUE },
463 { .name = "no_end", .data = test_no_end, .len = sizeof(test_no_end), .result = TRUE },
464 { .name = "no magic", .data = test_no_magic, .len = sizeof(test_no_magic), .result = FALSE },
465 { .name = "short", .data = test_short, .len = sizeof(test_short), .result = FALSE },
466 { .name = NULL, .data = NULL, .len = 0, .result = FALSE },
467 };
468
469
470 static char buf[2048];
471
472 int
473 main(void)
474 {
475 int i;
476 dhcpol_t options;
477 struct dhcp * pkt = (struct dhcp *)buf;
478
479 dhcpol_init(&options);
480
481 for (i = 0; tests[i].name; i++) {
482 printf("\nTest %d: ", i);
483 bcopy(tests[i].data, pkt->dp_options, tests[i].len);
484 if (dhcpol_parse_packet(&options, pkt,
485 sizeof(*pkt) + tests[i].len)
486 != tests[i].result) {
487 printf("test '%s' FAILED\n", tests[i].name);
488 } else {
489 printf("test '%s' PASSED\n", tests[i].name);
490 }
491 dhcpol_free(&options);
492 }
493 exit(0);
494 }
495 #endif /* TEST_DHCP_OPTIONS */