|
|
1.1 root 1: /*
2: * Copyright (C) 2008 Michael Brown <[email protected]>.
3: *
4: * This program is free software; you can redistribute it and/or
5: * modify it under the terms of the GNU General Public License as
6: * published by the Free Software Foundation; either version 2 of the
7: * License, or any later version.
8: *
9: * This program is distributed in the hope that it will be useful, but
10: * WITHOUT ANY WARRANTY; without even the implied warranty of
11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12: * General Public License for more details.
13: *
14: * You should have received a copy of the GNU General Public License
15: * along with this program; if not, write to the Free Software
16: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17: */
18:
19: FILE_LICENCE ( GPL2_OR_LATER );
20:
21: #include <stdint.h>
22: #include <stdlib.h>
23: #include <stdio.h>
24: #include <errno.h>
25: #include <string.h>
26: #include <ipxe/dhcp.h>
27: #include <ipxe/dhcpopts.h>
28:
29: /** @file
30: *
31: * DHCP options
32: *
33: */
34:
35: /**
36: * Obtain printable version of a DHCP option tag
37: *
38: * @v tag DHCP option tag
39: * @ret name String representation of the tag
40: *
41: */
42: static inline char * dhcp_tag_name ( unsigned int tag ) {
43: static char name[8];
44:
45: if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
46: snprintf ( name, sizeof ( name ), "%d.%d",
47: DHCP_ENCAPSULATOR ( tag ),
48: DHCP_ENCAPSULATED ( tag ) );
49: } else {
50: snprintf ( name, sizeof ( name ), "%d", tag );
51: }
52: return name;
53: }
54:
55: /**
56: * Get pointer to DHCP option
57: *
58: * @v options DHCP options block
59: * @v offset Offset within options block
60: * @ret option DHCP option
61: */
62: static inline __attribute__ (( always_inline )) struct dhcp_option *
63: dhcp_option ( struct dhcp_options *options, unsigned int offset ) {
64: return ( ( struct dhcp_option * ) ( options->data + offset ) );
65: }
66:
67: /**
68: * Get offset of a DHCP option
69: *
70: * @v options DHCP options block
71: * @v option DHCP option
72: * @ret offset Offset within options block
73: */
74: static inline __attribute__ (( always_inline )) int
75: dhcp_option_offset ( struct dhcp_options *options,
76: struct dhcp_option *option ) {
77: return ( ( ( void * ) option ) - options->data );
78: }
79:
80: /**
81: * Calculate length of any DHCP option
82: *
83: * @v option DHCP option
84: * @ret len Length (including tag and length field)
85: */
86: static unsigned int dhcp_option_len ( struct dhcp_option *option ) {
87: if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) {
88: return 1;
89: } else {
90: return ( option->len + DHCP_OPTION_HEADER_LEN );
91: }
92: }
93:
94: /**
95: * Find DHCP option within DHCP options block, and its encapsulator (if any)
96: *
97: * @v options DHCP options block
98: * @v tag DHCP option tag to search for
99: * @ret encap_offset Offset of encapsulating DHCP option
100: * @ret offset Offset of DHCP option, or negative error
101: *
102: * Searches for the DHCP option matching the specified tag within the
103: * DHCP option block. Encapsulated options may be searched for by
104: * using DHCP_ENCAP_OPT() to construct the tag value.
105: *
106: * If the option is encapsulated, and @c encap_offset is non-NULL, it
107: * will be filled in with the offset of the encapsulating option.
108: *
109: * This routine is designed to be paranoid. It does not assume that
110: * the option data is well-formatted, and so must guard against flaws
111: * such as options missing a @c DHCP_END terminator, or options whose
112: * length would take them beyond the end of the data block.
113: */
114: static int find_dhcp_option_with_encap ( struct dhcp_options *options,
115: unsigned int tag,
116: int *encap_offset ) {
117: unsigned int original_tag __attribute__ (( unused )) = tag;
118: struct dhcp_option *option;
119: int offset = 0;
120: ssize_t remaining = options->used_len;
121: unsigned int option_len;
122:
123: /* Sanity check */
124: if ( tag == DHCP_PAD )
125: return -ENOENT;
126:
127: /* Search for option */
128: while ( remaining ) {
129: /* Calculate length of this option. Abort processing
130: * if the length is malformed (i.e. takes us beyond
131: * the end of the data block).
132: */
133: option = dhcp_option ( options, offset );
134: option_len = dhcp_option_len ( option );
135: remaining -= option_len;
136: if ( remaining < 0 )
137: break;
138: /* Check for explicit end marker */
139: if ( option->tag == DHCP_END ) {
140: if ( tag == DHCP_END )
141: /* Special case where the caller is interested
142: * in whether we have this marker or not.
143: */
144: return offset;
145: else
146: break;
147: }
148: /* Check for matching tag */
149: if ( option->tag == tag ) {
150: DBGC ( options, "DHCPOPT %p found %s (length %d)\n",
151: options, dhcp_tag_name ( original_tag ),
152: option_len );
153: return offset;
154: }
155: /* Check for start of matching encapsulation block */
156: if ( DHCP_IS_ENCAP_OPT ( tag ) &&
157: ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
158: if ( encap_offset )
159: *encap_offset = offset;
160: /* Continue search within encapsulated option block */
161: tag = DHCP_ENCAPSULATED ( tag );
162: remaining = option_len;
163: offset += DHCP_OPTION_HEADER_LEN;
164: continue;
165: }
166: offset += option_len;
167: }
168:
169: return -ENOENT;
170: }
171:
172: /**
173: * Refuse to reallocate DHCP option block
174: *
175: * @v options DHCP option block
176: * @v len New length
177: * @ret rc Return status code
178: */
179: int dhcpopt_no_realloc ( struct dhcp_options *options, size_t len ) {
180: return ( ( len <= options->alloc_len ) ? 0 : -ENOSPC );
181: }
182:
183: /**
184: * Resize a DHCP option
185: *
186: * @v options DHCP option block
187: * @v offset Offset of option to resize
188: * @v encap_offset Offset of encapsulating offset (or -ve for none)
189: * @v old_len Old length (including header)
190: * @v new_len New length (including header)
191: * @ret rc Return status code
192: */
193: static int resize_dhcp_option ( struct dhcp_options *options,
194: int offset, int encap_offset,
195: size_t old_len, size_t new_len ) {
196: struct dhcp_option *encapsulator;
197: struct dhcp_option *option;
198: ssize_t delta = ( new_len - old_len );
199: size_t old_alloc_len;
200: size_t new_used_len;
201: size_t new_encapsulator_len;
202: void *source;
203: void *dest;
204: void *end;
205: int rc;
206:
207: /* Check for sufficient space */
208: if ( new_len > DHCP_MAX_LEN ) {
209: DBGC ( options, "DHCPOPT %p overlength option\n", options );
210: return -ENOSPC;
211: }
212: new_used_len = ( options->used_len + delta );
213:
214: /* Expand options block, if necessary */
215: if ( new_used_len > options->alloc_len ) {
216: /* Reallocate options block */
217: old_alloc_len = options->alloc_len;
218: if ( ( rc = options->realloc ( options, new_used_len ) ) != 0 ){
219: DBGC ( options, "DHCPOPT %p could not reallocate to "
220: "%zd bytes\n", options, new_used_len );
221: return rc;
222: }
223: /* Clear newly allocated space */
224: memset ( ( options->data + old_alloc_len ), 0,
225: ( options->alloc_len - old_alloc_len ) );
226: }
227:
228: /* Update encapsulator, if applicable */
229: if ( encap_offset >= 0 ) {
230: encapsulator = dhcp_option ( options, encap_offset );
231: new_encapsulator_len = ( encapsulator->len + delta );
232: if ( new_encapsulator_len > DHCP_MAX_LEN ) {
233: DBGC ( options, "DHCPOPT %p overlength encapsulator\n",
234: options );
235: return -ENOSPC;
236: }
237: encapsulator->len = new_encapsulator_len;
238: }
239:
240: /* Update used length */
241: options->used_len = new_used_len;
242:
243: /* Move remainder of option data */
244: option = dhcp_option ( options, offset );
245: source = ( ( ( void * ) option ) + old_len );
246: dest = ( ( ( void * ) option ) + new_len );
247: end = ( options->data + options->alloc_len );
248: memmove ( dest, source, ( end - dest ) );
249:
250: /* Shrink options block, if applicable */
251: if ( new_used_len < options->alloc_len ) {
252: if ( ( rc = options->realloc ( options, new_used_len ) ) != 0 ){
253: DBGC ( options, "DHCPOPT %p could not reallocate to "
254: "%zd bytes\n", options, new_used_len );
255: return rc;
256: }
257: }
258:
259: return 0;
260: }
261:
262: /**
263: * Set value of DHCP option
264: *
265: * @v options DHCP option block
266: * @v tag DHCP option tag
267: * @v data New value for DHCP option
268: * @v len Length of value, in bytes
269: * @ret offset Offset of DHCP option, or negative error
270: *
271: * Sets the value of a DHCP option within the options block. The
272: * option may or may not already exist. Encapsulators will be created
273: * (and deleted) as necessary.
274: *
275: * This call may fail due to insufficient space in the options block.
276: * If it does fail, and the option existed previously, the option will
277: * be left with its original value.
278: */
279: static int set_dhcp_option ( struct dhcp_options *options, unsigned int tag,
280: const void *data, size_t len ) {
281: static const uint8_t empty_encap[] = { DHCP_END };
282: int offset;
283: int encap_offset = -1;
284: int creation_offset;
285: struct dhcp_option *option;
286: unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag );
287: size_t old_len = 0;
288: size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 );
289: int rc;
290:
291: /* Sanity check */
292: if ( tag == DHCP_PAD )
293: return -ENOTTY;
294:
295: creation_offset = find_dhcp_option_with_encap ( options, DHCP_END,
296: NULL );
297: if ( creation_offset < 0 )
298: creation_offset = options->used_len;
299: /* Find old instance of this option, if any */
300: offset = find_dhcp_option_with_encap ( options, tag, &encap_offset );
301: if ( offset >= 0 ) {
302: old_len = dhcp_option_len ( dhcp_option ( options, offset ) );
303: DBGC ( options, "DHCPOPT %p resizing %s from %zd to %zd\n",
304: options, dhcp_tag_name ( tag ), old_len, new_len );
305: } else {
306: DBGC ( options, "DHCPOPT %p creating %s (length %zd)\n",
307: options, dhcp_tag_name ( tag ), new_len );
308: }
309:
310: /* Ensure that encapsulator exists, if required */
311: if ( encap_tag ) {
312: if ( encap_offset < 0 ) {
313: encap_offset =
314: set_dhcp_option ( options, encap_tag,
315: empty_encap,
316: sizeof ( empty_encap ) );
317: }
318: if ( encap_offset < 0 )
319: return encap_offset;
320: creation_offset = ( encap_offset + DHCP_OPTION_HEADER_LEN );
321: }
322:
323: /* Create new option if necessary */
324: if ( offset < 0 )
325: offset = creation_offset;
326:
327: /* Resize option to fit new data */
328: if ( ( rc = resize_dhcp_option ( options, offset, encap_offset,
329: old_len, new_len ) ) != 0 )
330: return rc;
331:
332: /* Copy new data into option, if applicable */
333: if ( len ) {
334: option = dhcp_option ( options, offset );
335: option->tag = tag;
336: option->len = len;
337: memcpy ( &option->data, data, len );
338: }
339:
340: /* Delete encapsulator if there's nothing else left in it */
341: if ( encap_offset >= 0 ) {
342: option = dhcp_option ( options, encap_offset );
343: if ( option->len <= 1 )
344: set_dhcp_option ( options, encap_tag, NULL, 0 );
345: }
346:
347: return offset;
348: }
349:
350: /**
351: * Check applicability of DHCP option setting
352: *
353: * @v tag Setting tag number
354: * @ret applies Setting applies to this option block
355: */
356: int dhcpopt_applies ( unsigned int tag ) {
357:
358: return ( tag && ( tag <= DHCP_ENCAP_OPT ( DHCP_MAX_OPTION,
359: DHCP_MAX_OPTION ) ) );
360: }
361:
362: /**
363: * Store value of DHCP option setting
364: *
365: * @v options DHCP option block
366: * @v tag Setting tag number
367: * @v data Setting data, or NULL to clear setting
368: * @v len Length of setting data
369: * @ret rc Return status code
370: */
371: int dhcpopt_store ( struct dhcp_options *options, unsigned int tag,
372: const void *data, size_t len ) {
373: int offset;
374:
375: offset = set_dhcp_option ( options, tag, data, len );
376: if ( offset < 0 )
377: return offset;
378: return 0;
379: }
380:
381: /**
382: * Fetch value of DHCP option setting
383: *
384: * @v options DHCP option block
385: * @v tag Setting tag number
386: * @v data Buffer to fill with setting data
387: * @v len Length of buffer
388: * @ret len Length of setting data, or negative error
389: */
390: int dhcpopt_fetch ( struct dhcp_options *options, unsigned int tag,
391: void *data, size_t len ) {
392: int offset;
393: struct dhcp_option *option;
394: size_t option_len;
395:
396: offset = find_dhcp_option_with_encap ( options, tag, NULL );
397: if ( offset < 0 )
398: return offset;
399:
400: option = dhcp_option ( options, offset );
401: option_len = option->len;
402: if ( len > option_len )
403: len = option_len;
404: memcpy ( data, option->data, len );
405:
406: return option_len;
407: }
408:
409: /**
410: * Recalculate length of DHCP options block
411: *
412: * @v options Uninitialised DHCP option block
413: *
414: * The "used length" field will be updated based on scanning through
415: * the block to find the end of the options.
416: */
417: void dhcpopt_update_used_len ( struct dhcp_options *options ) {
418: struct dhcp_option *option;
419: int offset = 0;
420: ssize_t remaining = options->alloc_len;
421: unsigned int option_len;
422:
423: /* Find last non-pad option */
424: options->used_len = 0;
425: while ( remaining ) {
426: option = dhcp_option ( options, offset );
427: option_len = dhcp_option_len ( option );
428: remaining -= option_len;
429: if ( remaining < 0 )
430: break;
431: offset += option_len;
432: if ( option->tag != DHCP_PAD )
433: options->used_len = offset;
434: }
435: }
436:
437: /**
438: * Initialise prepopulated block of DHCP options
439: *
440: * @v options Uninitialised DHCP option block
441: * @v data Memory for DHCP option data
442: * @v alloc_len Length of memory for DHCP option data
443: * @v realloc DHCP option block reallocator
444: *
445: * The memory content must already be filled with valid DHCP options.
446: * A zeroed block counts as a block of valid DHCP options.
447: */
448: void dhcpopt_init ( struct dhcp_options *options, void *data, size_t alloc_len,
449: int ( * realloc ) ( struct dhcp_options *options,
450: size_t len ) ) {
451:
452: /* Fill in fields */
453: options->data = data;
454: options->alloc_len = alloc_len;
455: options->realloc = realloc;
456:
457: /* Update length */
458: dhcpopt_update_used_len ( options );
459:
460: DBGC ( options, "DHCPOPT %p created (data %p lengths %#zx,%#zx)\n",
461: options, options->data, options->used_len, options->alloc_len );
462: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.