Home | History | Annotate | Download | only in net
      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