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

Generated on Tue Apr 6 20:01:10 2010 for gPXE by  doxygen 1.5.7.1