iPXE
dhcpopts.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 #include <stdint.h>
00027 #include <stdlib.h>
00028 #include <stdio.h>
00029 #include <errno.h>
00030 #include <string.h>
00031 #include <ipxe/dhcp.h>
00032 #include <ipxe/dhcpopts.h>
00033 
00034 /** @file
00035  *
00036  * DHCP options
00037  *
00038  */
00039 
00040 /**
00041  * Obtain printable version of a DHCP option tag
00042  *
00043  * @v tag               DHCP option tag
00044  * @ret name            String representation of the tag
00045  *
00046  */
00047 static inline char * dhcp_tag_name ( unsigned int tag ) {
00048         static char name[8];
00049 
00050         if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
00051                 snprintf ( name, sizeof ( name ), "%d.%d",
00052                            DHCP_ENCAPSULATOR ( tag ),
00053                            DHCP_ENCAPSULATED ( tag ) );
00054         } else {
00055                 snprintf ( name, sizeof ( name ), "%d", tag );
00056         }
00057         return name;
00058 }
00059 
00060 /**
00061  * Get pointer to DHCP option
00062  *
00063  * @v options           DHCP options block
00064  * @v offset            Offset within options block
00065  * @ret option          DHCP option
00066  */
00067 static inline __attribute__ (( always_inline )) struct dhcp_option *
00068 dhcp_option ( struct dhcp_options *options, unsigned int offset ) {
00069         return ( ( struct dhcp_option * ) ( options->data + offset ) );
00070 }
00071 
00072 /**
00073  * Get offset of a DHCP option
00074  *
00075  * @v options           DHCP options block
00076  * @v option            DHCP option
00077  * @ret offset          Offset within options block
00078  */
00079 static inline __attribute__ (( always_inline )) int
00080 dhcp_option_offset ( struct dhcp_options *options,
00081                      struct dhcp_option *option ) {
00082         return ( ( ( void * ) option ) - options->data );
00083 }
00084 
00085 /**
00086  * Calculate length of any DHCP option
00087  *
00088  * @v option            DHCP option
00089  * @ret len             Length (including tag and length field)
00090  */
00091 static unsigned int dhcp_option_len ( struct dhcp_option *option ) {
00092         if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) {
00093                 return 1;
00094         } else {
00095                 return ( option->len + DHCP_OPTION_HEADER_LEN );
00096         }
00097 }
00098 
00099 /**
00100  * Find DHCP option within DHCP options block, and its encapsulator (if any)
00101  *
00102  * @v options           DHCP options block
00103  * @v tag               DHCP option tag to search for
00104  * @ret encap_offset    Offset of encapsulating DHCP option
00105  * @ret offset          Offset of DHCP option, or negative error
00106  *
00107  * Searches for the DHCP option matching the specified tag within the
00108  * DHCP option block.  Encapsulated options may be searched for by
00109  * using DHCP_ENCAP_OPT() to construct the tag value.
00110  *
00111  * If the option is encapsulated, and @c encap_offset is non-NULL, it
00112  * will be filled in with the offset of the encapsulating option.
00113  *
00114  * This routine is designed to be paranoid.  It does not assume that
00115  * the option data is well-formatted, and so must guard against flaws
00116  * such as options missing a @c DHCP_END terminator, or options whose
00117  * length would take them beyond the end of the data block.
00118  */
00119 static int find_dhcp_option_with_encap ( struct dhcp_options *options,
00120                                          unsigned int tag,
00121                                          int *encap_offset ) {
00122         unsigned int original_tag __attribute__ (( unused )) = tag;
00123         struct dhcp_option *option;
00124         int offset = 0;
00125         ssize_t remaining = options->used_len;
00126         unsigned int option_len;
00127 
00128         /* Sanity check */
00129         if ( tag == DHCP_PAD )
00130                 return -ENOENT;
00131 
00132         /* Search for option */
00133         while ( remaining ) {
00134                 /* Calculate length of this option.  Abort processing
00135                  * if the length is malformed (i.e. takes us beyond
00136                  * the end of the data block).
00137                  */
00138                 option = dhcp_option ( options, offset );
00139                 option_len = dhcp_option_len ( option );
00140                 remaining -= option_len;
00141                 if ( remaining < 0 )
00142                         break;
00143                 /* Check for explicit end marker */
00144                 if ( option->tag == DHCP_END ) {
00145                         if ( tag == DHCP_END )
00146                                 /* Special case where the caller is interested
00147                                  * in whether we have this marker or not.
00148                                  */
00149                                 return offset;
00150                         else
00151                                 break;
00152                 }
00153                 /* Check for matching tag */
00154                 if ( option->tag == tag ) {
00155                         DBGC ( options, "DHCPOPT %p found %s (length %d)\n",
00156                                options, dhcp_tag_name ( original_tag ),
00157                                option_len );
00158                         return offset;
00159                 }
00160                 /* Check for start of matching encapsulation block */
00161                 if ( DHCP_IS_ENCAP_OPT ( tag ) &&
00162                      ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
00163                         if ( encap_offset )
00164                                 *encap_offset = offset;
00165                         /* Continue search within encapsulated option block */
00166                         tag = DHCP_ENCAPSULATED ( tag );
00167                         remaining = option_len;
00168                         offset += DHCP_OPTION_HEADER_LEN;
00169                         continue;
00170                 }
00171                 offset += option_len;
00172         }
00173 
00174         return -ENOENT;
00175 }
00176 
00177 /**
00178  * Refuse to reallocate DHCP option block
00179  *
00180  * @v options           DHCP option block
00181  * @v len               New length
00182  * @ret rc              Return status code
00183  */
00184 int dhcpopt_no_realloc ( struct dhcp_options *options, size_t len ) {
00185         return ( ( len <= options->alloc_len ) ? 0 : -ENOSPC );
00186 }
00187 
00188 /**
00189  * Resize a DHCP option
00190  *
00191  * @v options           DHCP option block
00192  * @v offset            Offset of option to resize
00193  * @v encap_offset      Offset of encapsulating offset (or -ve for none)
00194  * @v old_len           Old length (including header)
00195  * @v new_len           New length (including header)
00196  * @ret rc              Return status code
00197  */
00198 static int resize_dhcp_option ( struct dhcp_options *options,
00199                                 int offset, int encap_offset,
00200                                 size_t old_len, size_t new_len ) {
00201         struct dhcp_option *encapsulator;
00202         struct dhcp_option *option;
00203         ssize_t delta = ( new_len - old_len );
00204         size_t old_alloc_len;
00205         size_t new_used_len;
00206         size_t new_encapsulator_len;
00207         void *source;
00208         void *dest;
00209         int rc;
00210 
00211         /* Check for sufficient space */
00212         if ( new_len > DHCP_MAX_LEN ) {
00213                 DBGC ( options, "DHCPOPT %p overlength option\n", options );
00214                 return -ENOSPC;
00215         }
00216         new_used_len = ( options->used_len + delta );
00217 
00218         /* Expand options block, if necessary */
00219         if ( new_used_len > options->alloc_len ) {
00220                 /* Reallocate options block */
00221                 old_alloc_len = options->alloc_len;
00222                 if ( ( rc = options->realloc ( options, new_used_len ) ) != 0 ){
00223                         DBGC ( options, "DHCPOPT %p could not reallocate to "
00224                                "%zd bytes\n", options, new_used_len );
00225                         return rc;
00226                 }
00227                 /* Clear newly allocated space */
00228                 memset ( ( options->data + old_alloc_len ), 0,
00229                          ( options->alloc_len - old_alloc_len ) );
00230         }
00231 
00232         /* Update encapsulator, if applicable */
00233         if ( encap_offset >= 0 ) {
00234                 encapsulator = dhcp_option ( options, encap_offset );
00235                 new_encapsulator_len = ( encapsulator->len + delta );
00236                 if ( new_encapsulator_len > DHCP_MAX_LEN ) {
00237                         DBGC ( options, "DHCPOPT %p overlength encapsulator\n",
00238                                options );
00239                         return -ENOSPC;
00240                 }
00241                 encapsulator->len = new_encapsulator_len;
00242         }
00243 
00244         /* Update used length */
00245         options->used_len = new_used_len;
00246 
00247         /* Move remainder of option data */
00248         option = dhcp_option ( options, offset );
00249         source = ( ( ( void * ) option ) + old_len );
00250         dest = ( ( ( void * ) option ) + new_len );
00251         memmove ( dest, source, ( new_used_len - offset - new_len ) );
00252 
00253         /* Shrink options block, if applicable */
00254         if ( new_used_len < options->alloc_len ) {
00255                 if ( ( rc = options->realloc ( options, new_used_len ) ) != 0 ){
00256                         DBGC ( options, "DHCPOPT %p could not reallocate to "
00257                                "%zd bytes\n", options, new_used_len );
00258                         return rc;
00259                 }
00260         }
00261 
00262         return 0;
00263 }
00264 
00265 /**
00266  * Set value of DHCP option
00267  *
00268  * @v options           DHCP option block
00269  * @v tag               DHCP option tag
00270  * @v data              New value for DHCP option
00271  * @v len               Length of value, in bytes
00272  * @ret offset          Offset of DHCP option, or negative error
00273  *
00274  * Sets the value of a DHCP option within the options block.  The
00275  * option may or may not already exist.  Encapsulators will be created
00276  * (and deleted) as necessary.
00277  *
00278  * This call may fail due to insufficient space in the options block.
00279  * If it does fail, and the option existed previously, the option will
00280  * be left with its original value.
00281  */
00282 static int set_dhcp_option ( struct dhcp_options *options, unsigned int tag,
00283                              const void *data, size_t len ) {
00284         static const uint8_t empty_encap[] = { DHCP_END };
00285         int offset;
00286         int encap_offset = -1;
00287         int creation_offset;
00288         struct dhcp_option *option;
00289         unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag );
00290         size_t old_len = 0;
00291         size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 );
00292         int rc;
00293 
00294         /* Sanity check */
00295         if ( tag == DHCP_PAD )
00296                 return -ENOTTY;
00297 
00298         creation_offset = find_dhcp_option_with_encap ( options, DHCP_END,
00299                                                         NULL );
00300         if ( creation_offset < 0 )
00301                 creation_offset = options->used_len;
00302         /* Find old instance of this option, if any */
00303         offset = find_dhcp_option_with_encap ( options, tag, &encap_offset );
00304         if ( offset >= 0 ) {
00305                 old_len = dhcp_option_len ( dhcp_option ( options, offset ) );
00306                 DBGC ( options, "DHCPOPT %p resizing %s from %zd to %zd\n",
00307                        options, dhcp_tag_name ( tag ), old_len, new_len );
00308         } else {
00309                 DBGC ( options, "DHCPOPT %p creating %s (length %zd)\n",
00310                        options, dhcp_tag_name ( tag ), new_len );
00311         }
00312 
00313         /* Ensure that encapsulator exists, if required */
00314         if ( encap_tag ) {
00315                 if ( encap_offset < 0 ) {
00316                         encap_offset =
00317                                 set_dhcp_option ( options, encap_tag,
00318                                                   empty_encap,
00319                                                   sizeof ( empty_encap ) );
00320                 }
00321                 if ( encap_offset < 0 )
00322                         return encap_offset;
00323                 creation_offset = ( encap_offset + DHCP_OPTION_HEADER_LEN );
00324         }
00325 
00326         /* Create new option if necessary */
00327         if ( offset < 0 )
00328                 offset = creation_offset;
00329 
00330         /* Resize option to fit new data */
00331         if ( ( rc = resize_dhcp_option ( options, offset, encap_offset,
00332                                          old_len, new_len ) ) != 0 )
00333                 return rc;
00334 
00335         /* Copy new data into option, if applicable */
00336         if ( len ) {
00337                 option = dhcp_option ( options, offset );
00338                 option->tag = tag;
00339                 option->len = len;
00340                 memcpy ( &option->data, data, len );
00341         }
00342 
00343         /* Delete encapsulator if there's nothing else left in it */
00344         if ( encap_offset >= 0 ) {
00345                 option = dhcp_option ( options, encap_offset );
00346                 if ( option->len <= 1 )
00347                         set_dhcp_option ( options, encap_tag, NULL, 0 );
00348         }
00349 
00350         return offset;
00351 }
00352 
00353 /**
00354  * Check applicability of DHCP option setting
00355  *
00356  * @v tag               Setting tag number
00357  * @ret applies         Setting applies to this option block
00358  */
00359 int dhcpopt_applies ( unsigned int tag ) {
00360 
00361         return ( tag && ( tag <= DHCP_ENCAP_OPT ( DHCP_MAX_OPTION,
00362                                                   DHCP_MAX_OPTION ) ) );
00363 }
00364 
00365 /**
00366  * Store value of DHCP option setting
00367  *
00368  * @v options           DHCP option block
00369  * @v tag               Setting tag number
00370  * @v data              Setting data, or NULL to clear setting
00371  * @v len               Length of setting data
00372  * @ret rc              Return status code
00373  */
00374 int dhcpopt_store ( struct dhcp_options *options, unsigned int tag,
00375                     const void *data, size_t len ) {
00376         int offset;
00377 
00378         offset = set_dhcp_option ( options, tag, data, len );
00379         if ( offset < 0 )
00380                 return offset;
00381         return 0;
00382 }
00383 
00384 /**
00385  * Fetch value of DHCP option setting
00386  *
00387  * @v options           DHCP option block
00388  * @v tag               Setting tag number
00389  * @v data              Buffer to fill with setting data
00390  * @v len               Length of buffer
00391  * @ret len             Length of setting data, or negative error
00392  */
00393 int dhcpopt_fetch ( struct dhcp_options *options, unsigned int tag,
00394                     void *data, size_t len ) {
00395         int offset;
00396         struct dhcp_option *option;
00397         size_t option_len;
00398 
00399         offset = find_dhcp_option_with_encap ( options, tag, NULL );
00400         if ( offset < 0 )
00401                 return offset;
00402 
00403         option = dhcp_option ( options, offset );
00404         option_len = option->len;
00405         if ( len > option_len )
00406                 len = option_len;
00407         memcpy ( data, option->data, len );
00408 
00409         return option_len;
00410 }
00411 
00412 /**
00413  * Recalculate length of DHCP options block
00414  *
00415  * @v options           Uninitialised DHCP option block
00416  *
00417  * The "used length" field will be updated based on scanning through
00418  * the block to find the end of the options.
00419  */
00420 void dhcpopt_update_used_len ( struct dhcp_options *options ) {
00421         struct dhcp_option *option;
00422         int offset = 0;
00423         ssize_t remaining = options->alloc_len;
00424         unsigned int option_len;
00425 
00426         /* Find last non-pad option */
00427         options->used_len = 0;
00428         while ( remaining ) {
00429                 option = dhcp_option ( options, offset );
00430                 option_len = dhcp_option_len ( option );
00431                 remaining -= option_len;
00432                 if ( remaining < 0 )
00433                         break;
00434                 offset += option_len;
00435                 if ( option->tag != DHCP_PAD )
00436                         options->used_len = offset;
00437         }
00438 }
00439 
00440 /**
00441  * Initialise prepopulated block of DHCP options
00442  *
00443  * @v options           Uninitialised DHCP option block
00444  * @v data              Memory for DHCP option data
00445  * @v alloc_len         Length of memory for DHCP option data
00446  * @v realloc           DHCP option block reallocator
00447  *
00448  * The memory content must already be filled with valid DHCP options.
00449  * A zeroed block counts as a block of valid DHCP options.
00450  */
00451 void dhcpopt_init ( struct dhcp_options *options, void *data, size_t alloc_len,
00452                     int ( * realloc ) ( struct dhcp_options *options,
00453                                         size_t len ) ) {
00454 
00455         /* Fill in fields */
00456         options->data = data;
00457         options->alloc_len = alloc_len;
00458         options->realloc = realloc;
00459 
00460         /* Update length */
00461         dhcpopt_update_used_len ( options );
00462 
00463         DBGC ( options, "DHCPOPT %p created (data %p lengths %#zx,%#zx)\n",
00464                options, options->data, options->used_len, options->alloc_len );
00465 }