1 /* 2 * Copyright (C) 2008 Michael Brown <mbrown (at) fensystems.co.uk>. 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 <gpxe/dhcp.h> 27 #include <gpxe/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->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 * Resize a DHCP option 174 * 175 * @v options DHCP option block 176 * @v offset Offset of option to resize 177 * @v encap_offset Offset of encapsulating offset (or -ve for none) 178 * @v old_len Old length (including header) 179 * @v new_len New length (including header) 180 * @v can_realloc Can reallocate options data if necessary 181 * @ret rc Return status code 182 */ 183 static int resize_dhcp_option ( struct dhcp_options *options, 184 int offset, int encap_offset, 185 size_t old_len, size_t new_len, 186 int can_realloc ) { 187 struct dhcp_option *encapsulator; 188 struct dhcp_option *option; 189 ssize_t delta = ( new_len - old_len ); 190 size_t new_options_len; 191 size_t new_encapsulator_len; 192 void *new_data; 193 void *source; 194 void *dest; 195 void *end; 196 197 /* Check for sufficient space, and update length fields */ 198 if ( new_len > DHCP_MAX_LEN ) { 199 DBGC ( options, "DHCPOPT %p overlength option\n", options ); 200 return -ENOSPC; 201 } 202 new_options_len = ( options->len + delta ); 203 if ( new_options_len > options->max_len ) { 204 /* Reallocate options block if allowed to do so. */ 205 if ( can_realloc ) { 206 new_data = realloc ( options->data, new_options_len ); 207 if ( ! new_data ) { 208 DBGC ( options, "DHCPOPT %p could not " 209 "reallocate to %zd bytes\n", options, 210 new_options_len ); 211 return -ENOMEM; 212 } 213 options->data = new_data; 214 options->max_len = new_options_len; 215 } else { 216 DBGC ( options, "DHCPOPT %p out of space\n", options ); 217 return -ENOMEM; 218 } 219 } 220 if ( encap_offset >= 0 ) { 221 encapsulator = dhcp_option ( options, encap_offset ); 222 new_encapsulator_len = ( encapsulator->len + delta ); 223 if ( new_encapsulator_len > DHCP_MAX_LEN ) { 224 DBGC ( options, "DHCPOPT %p overlength encapsulator\n", 225 options ); 226 return -ENOSPC; 227 } 228 encapsulator->len = new_encapsulator_len; 229 } 230 options->len = new_options_len; 231 232 /* Move remainder of option data */ 233 option = dhcp_option ( options, offset ); 234 source = ( ( ( void * ) option ) + old_len ); 235 dest = ( ( ( void * ) option ) + new_len ); 236 end = ( options->data + options->max_len ); 237 memmove ( dest, source, ( end - dest ) ); 238 239 return 0; 240 } 241 242 /** 243 * Set value of DHCP option 244 * 245 * @v options DHCP option block 246 * @v tag DHCP option tag 247 * @v data New value for DHCP option 248 * @v len Length of value, in bytes 249 * @v can_realloc Can reallocate options data if necessary 250 * @ret offset Offset of DHCP option, or negative error 251 * 252 * Sets the value of a DHCP option within the options block. The 253 * option may or may not already exist. Encapsulators will be created 254 * (and deleted) as necessary. 255 * 256 * This call may fail due to insufficient space in the options block. 257 * If it does fail, and the option existed previously, the option will 258 * be left with its original value. 259 */ 260 static int set_dhcp_option ( struct dhcp_options *options, unsigned int tag, 261 const void *data, size_t len, 262 int can_realloc ) { 263 static const uint8_t empty_encapsulator[] = { DHCP_END }; 264 int offset; 265 int encap_offset = -1; 266 int creation_offset; 267 struct dhcp_option *option; 268 unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag ); 269 size_t old_len = 0; 270 size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 ); 271 int rc; 272 273 /* Sanity check */ 274 if ( tag == DHCP_PAD ) 275 return -ENOTTY; 276 277 creation_offset = find_dhcp_option_with_encap ( options, DHCP_END, 278 NULL ); 279 if ( creation_offset < 0 ) 280 creation_offset = options->len; 281 /* Find old instance of this option, if any */ 282 offset = find_dhcp_option_with_encap ( options, tag, &encap_offset ); 283 if ( offset >= 0 ) { 284 old_len = dhcp_option_len ( dhcp_option ( options, offset ) ); 285 DBGC ( options, "DHCPOPT %p resizing %s from %zd to %zd\n", 286 options, dhcp_tag_name ( tag ), old_len, new_len ); 287 } else { 288 DBGC ( options, "DHCPOPT %p creating %s (length %zd)\n", 289 options, dhcp_tag_name ( tag ), new_len ); 290 } 291 292 /* Ensure that encapsulator exists, if required */ 293 if ( encap_tag ) { 294 if ( encap_offset < 0 ) 295 encap_offset = set_dhcp_option ( options, encap_tag, 296 empty_encapsulator, 1, 297 can_realloc ); 298 if ( encap_offset < 0 ) 299 return encap_offset; 300 creation_offset = ( encap_offset + DHCP_OPTION_HEADER_LEN ); 301 } 302 303 /* Create new option if necessary */ 304 if ( offset < 0 ) 305 offset = creation_offset; 306 307 /* Resize option to fit new data */ 308 if ( ( rc = resize_dhcp_option ( options, offset, encap_offset, 309 old_len, new_len, 310 can_realloc ) ) != 0 ) 311 return rc; 312 313 /* Copy new data into option, if applicable */ 314 if ( len ) { 315 option = dhcp_option ( options, offset ); 316 option->tag = tag; 317 option->len = len; 318 memcpy ( &option->data, data, len ); 319 } 320 321 /* Delete encapsulator if there's nothing else left in it */ 322 if ( encap_offset >= 0 ) { 323 option = dhcp_option ( options, encap_offset ); 324 if ( option->len <= 1 ) 325 set_dhcp_option ( options, encap_tag, NULL, 0, 0 ); 326 } 327 328 return offset; 329 } 330 331 /** 332 * Store value of DHCP option setting 333 * 334 * @v options DHCP option block 335 * @v tag Setting tag number 336 * @v data Setting data, or NULL to clear setting 337 * @v len Length of setting data 338 * @ret rc Return status code 339 */ 340 int dhcpopt_store ( struct dhcp_options *options, unsigned int tag, 341 const void *data, size_t len ) { 342 int offset; 343 344 offset = set_dhcp_option ( options, tag, data, len, 0 ); 345 if ( offset < 0 ) 346 return offset; 347 return 0; 348 } 349 350 /** 351 * Store value of DHCP option setting, extending options block if necessary 352 * 353 * @v options DHCP option block 354 * @v tag Setting tag number 355 * @v data Setting data, or NULL to clear setting 356 * @v len Length of setting data 357 * @ret rc Return status code 358 */ 359 int dhcpopt_extensible_store ( struct dhcp_options *options, unsigned int tag, 360 const void *data, size_t len ) { 361 int offset; 362 363 offset = set_dhcp_option ( options, tag, data, len, 1 ); 364 if ( offset < 0 ) 365 return offset; 366 return 0; 367 } 368 369 /** 370 * Fetch value of DHCP option setting 371 * 372 * @v options DHCP option block 373 * @v tag Setting tag number 374 * @v data Buffer to fill with setting data 375 * @v len Length of buffer 376 * @ret len Length of setting data, or negative error 377 */ 378 int dhcpopt_fetch ( struct dhcp_options *options, unsigned int tag, 379 void *data, size_t len ) { 380 int offset; 381 struct dhcp_option *option; 382 size_t option_len; 383 384 offset = find_dhcp_option_with_encap ( options, tag, NULL ); 385 if ( offset < 0 ) 386 return offset; 387 388 option = dhcp_option ( options, offset ); 389 option_len = option->len; 390 if ( len > option_len ) 391 len = option_len; 392 memcpy ( data, option->data, len ); 393 394 return option_len; 395 } 396 397 /** 398 * Recalculate length of DHCP options block 399 * 400 * @v options Uninitialised DHCP option block 401 * 402 * The "used length" field will be updated based on scanning through 403 * the block to find the end of the options. 404 */ 405 static void dhcpopt_update_len ( struct dhcp_options *options ) { 406 struct dhcp_option *option; 407 int offset = 0; 408 ssize_t remaining = options->max_len; 409 unsigned int option_len; 410 411 /* Find last non-pad option */ 412 options->len = 0; 413 while ( remaining ) { 414 option = dhcp_option ( options, offset ); 415 option_len = dhcp_option_len ( option ); 416 remaining -= option_len; 417 if ( remaining < 0 ) 418 break; 419 offset += option_len; 420 if ( option->tag != DHCP_PAD ) 421 options->len = offset; 422 } 423 } 424 425 /** 426 * Initialise prepopulated block of DHCP options 427 * 428 * @v options Uninitialised DHCP option block 429 * @v data Memory for DHCP option data 430 * @v max_len Length of memory for DHCP option data 431 * 432 * The memory content must already be filled with valid DHCP options. 433 * A zeroed block counts as a block of valid DHCP options. 434 */ 435 void dhcpopt_init ( struct dhcp_options *options, void *data, 436 size_t max_len ) { 437 438 /* Fill in fields */ 439 options->data = data; 440 options->max_len = max_len; 441 442 /* Update length */ 443 dhcpopt_update_len ( options ); 444 445 DBGC ( options, "DHCPOPT %p created (data %p len %#zx max_len %#zx)\n", 446 options, options->data, options->len, options->max_len ); 447 } 448