syslinux/gpxe/src/net/dhcpopts.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2008 Michael Brown <mbrown@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
  19FILE_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 */
  42static 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 */
  62static inline __attribute__ (( always_inline )) struct dhcp_option *
  63dhcp_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 */
  74static inline __attribute__ (( always_inline )) int
  75dhcp_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 */
  86static 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 */
 114static 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 */
 183static 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 */
 260static 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 */
 340int 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 */
 359int 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 */
 378int 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 */
 405static 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 */
 435void 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
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.