Annotation of qemu/roms/ipxe/src/net/dhcpopts.c, revision 1.1.1.1

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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.