|
|
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.