Annotation of qemu/roms/ipxe/src/net/dhcpopts.c, revision 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.