/* * Copyright (C) 2006 Michael Brown . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include /** @file * * Dynamic Host Configuration Protocol * */ /** * Calculate used length of a field containing DHCP options * * @v data Field containing DHCP options * @v max_len Field length * @ret len Used length (excluding the @c DHCP_END tag) */ static size_t dhcp_field_len ( const void *data, size_t max_len ) { struct dhcp_option_block options; struct dhcp_option *end; options.data = ( ( void * ) data ); options.len = max_len; end = find_dhcp_option ( &options, DHCP_END ); return ( end ? ( ( ( void * ) end ) - data ) : 0 ); } /** * Merge field containing DHCP options or string into DHCP options block * * @v options DHCP option block * @v data Field containing DHCP options * @v max_len Field length * @v tag DHCP option tag, or 0 * * If @c tag is non-zero, the field will be treated as a * NUL-terminated string representing the value of the specified DHCP * option. If @c tag is zero, the field will be treated as a block of * DHCP options, and simply appended to the existing options in the * option block. * * The caller must ensure that there is enough space in the options * block to perform the merge. */ static void merge_dhcp_field ( struct dhcp_option_block *options, const void *data, size_t max_len, unsigned int tag ) { size_t len; void *dest; struct dhcp_option *end; if ( tag ) { set_dhcp_option ( options, tag, data, strlen ( data ) ); } else { len = dhcp_field_len ( data, max_len ); dest = ( options->data + options->len - 1 ); memcpy ( dest, data, len ); options->len += len; end = ( dest + len ); end->tag = DHCP_END; } } /** * Parse DHCP packet and construct DHCP options block * * @v data DHCP packet * @v len Length of DHCP packet * @ret options DHCP options block, or NULL * * Parses a received DHCP packet and canonicalises its contents into a * single DHCP options block. The "file" and "sname" fields are * converted into the corresponding DHCP options (@c * DHCP_BOOTFILE_NAME and @c DHCP_TFTP_SERVER_NAME respectively). If * these fields are used for option overloading, their options are * merged in to the options block. The values of the "yiaddr" and * "siaddr" fields will be stored within the options block as the * options @c DHCP_EB_YIADDR and @c DHCP_EB_SIADDR. * * Note that this call allocates new memory for the constructed DHCP * options block; it is the responsibility of the caller to eventually * free this memory. */ struct dhcp_option_block * dhcp_parse ( const void *data, size_t len ) { const struct dhcp_packet *dhcppkt = data; struct dhcp_option_block *options; size_t options_len; unsigned int overloading; /* Calculate size of resulting concatenated option block: * * The "options" field : length of the field minus the DHCP_END tag. * * The "file" field : maximum length of the field minus the * NUL terminator, plus a 2-byte DHCP header or, if used for * option overloading, the length of the field minus the * DHCP_END tag. * * The "sname" field : as for the "file" field. * * 15 bytes for an encapsulated options field to contain the * value of the "yiaddr" and "siaddr" fields * * 1 byte for a final terminating DHCP_END tag. */ options_len = ( ( len - offsetof ( typeof ( *dhcppkt ), options ) ) - 1 + ( sizeof ( dhcppkt->file ) + 1 ) + ( sizeof ( dhcppkt->sname ) + 1 ) + 15 /* yiaddr and siaddr */ + 1 /* DHCP_END tag */ ); /* Allocate empty options block of required size */ options = alloc_dhcp_options ( options_len ); if ( ! options ) { DBG ( "DHCP could not allocate %d-byte option block\n", options_len ); return NULL; } /* Merge in "options" field, if this is a DHCP packet */ if ( dhcppkt->magic == htonl ( DHCP_MAGIC_COOKIE ) ) { merge_dhcp_field ( options, dhcppkt->options, ( len - offsetof ( typeof (*dhcppkt), options ) ), 0 /* Always contains options */ ); } /* Identify overloaded fields */ overloading = find_dhcp_num_option ( options, DHCP_OPTION_OVERLOAD ); /* Merge in "file" and "sname" fields */ merge_dhcp_field ( options, dhcppkt->file, sizeof ( dhcppkt->file ), ( ( overloading & DHCP_OPTION_OVERLOAD_FILE ) ? DHCP_BOOTFILE_NAME : 0 ) ); merge_dhcp_field ( options, dhcppkt->sname, sizeof ( dhcppkt->sname ), ( ( overloading & DHCP_OPTION_OVERLOAD_SNAME ) ? DHCP_TFTP_SERVER_NAME : 0 ) ); /* Set options for "yiaddr" and "siaddr", if present */ if ( dhcppkt->yiaddr.s_addr ) { set_dhcp_option ( options, DHCP_EB_YIADDR, &dhcppkt->yiaddr, sizeof (dhcppkt->yiaddr) ); } if ( dhcppkt->siaddr.s_addr ) { set_dhcp_option ( options, DHCP_EB_SIADDR, &dhcppkt->siaddr, sizeof (dhcppkt->siaddr) ); } assert ( options->len <= options->max_len ); return options; }