| /* | |
| * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>. | |
| * | |
| * 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., 51 Franklin Street, Fifth Floor, Boston, MA | |
| * 02110-1301, USA. | |
| * | |
| * You can also choose to distribute this program under the terms of | |
| * the Unmodified Binary Distribution Licence (as given in the file | |
| * COPYING.UBDL), provided that you have satisfied its requirements. | |
| */ | |
| FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #include <stdint.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <errno.h> | |
| #include <string.h> | |
| #include <ipxe/netdevice.h> | |
| #include <ipxe/dhcp.h> | |
| #include <ipxe/dhcpopts.h> | |
| #include <ipxe/dhcppkt.h> | |
| /** @file | |
| * | |
| * DHCP packets | |
| * | |
| */ | |
| /**************************************************************************** | |
| * | |
| * DHCP packet raw interface | |
| * | |
| */ | |
| /** | |
| * Calculate used length of an IPv4 field within a DHCP packet | |
| * | |
| * @v data Field data | |
| * @v len Length of field | |
| * @ret used Used length of field | |
| */ | |
| static size_t used_len_ipv4 ( const void *data, size_t len __unused ) { | |
| const struct in_addr *in = data; | |
| return ( in->s_addr ? sizeof ( *in ) : 0 ); | |
| } | |
| /** | |
| * Calculate used length of a string field within a DHCP packet | |
| * | |
| * @v data Field data | |
| * @v len Length of field | |
| * @ret used Used length of field | |
| */ | |
| static size_t used_len_string ( const void *data, size_t len ) { | |
| return strnlen ( data, len ); | |
| } | |
| /** A dedicated field within a DHCP packet */ | |
| struct dhcp_packet_field { | |
| /** Settings tag number */ | |
| unsigned int tag; | |
| /** Offset within DHCP packet */ | |
| uint16_t offset; | |
| /** Length of field */ | |
| uint16_t len; | |
| /** Calculate used length of field | |
| * | |
| * @v data Field data | |
| * @v len Length of field | |
| * @ret used Used length of field | |
| */ | |
| size_t ( * used_len ) ( const void *data, size_t len ); | |
| }; | |
| /** Declare a dedicated field within a DHCP packet | |
| * | |
| * @v _tag Settings tag number | |
| * @v _field Field name | |
| * @v _used_len Function to calculate used length of field | |
| */ | |
| #define DHCP_PACKET_FIELD( _tag, _field, _used_len ) { \ | |
| .tag = (_tag), \ | |
| .offset = offsetof ( struct dhcphdr, _field ), \ | |
| .len = sizeof ( ( ( struct dhcphdr * ) 0 )->_field ), \ | |
| .used_len = _used_len, \ | |
| } | |
| /** Dedicated fields within a DHCP packet */ | |
| static struct dhcp_packet_field dhcp_packet_fields[] = { | |
| DHCP_PACKET_FIELD ( DHCP_EB_YIADDR, yiaddr, used_len_ipv4 ), | |
| DHCP_PACKET_FIELD ( DHCP_EB_SIADDR, siaddr, used_len_ipv4 ), | |
| DHCP_PACKET_FIELD ( DHCP_TFTP_SERVER_NAME, sname, used_len_string ), | |
| DHCP_PACKET_FIELD ( DHCP_BOOTFILE_NAME, file, used_len_string ), | |
| }; | |
| /** | |
| * Get address of a DHCP packet field | |
| * | |
| * @v dhcphdr DHCP packet header | |
| * @v field DHCP packet field | |
| * @ret data Packet field data | |
| */ | |
| static inline void * dhcp_packet_field ( struct dhcphdr *dhcphdr, | |
| struct dhcp_packet_field *field ) { | |
| return ( ( ( void * ) dhcphdr ) + field->offset ); | |
| } | |
| /** | |
| * Find DHCP packet field corresponding to settings tag number | |
| * | |
| * @v tag Settings tag number | |
| * @ret field DHCP packet field, or NULL | |
| */ | |
| static struct dhcp_packet_field * | |
| find_dhcp_packet_field ( unsigned int tag ) { | |
| struct dhcp_packet_field *field; | |
| unsigned int i; | |
| for ( i = 0 ; i < ( sizeof ( dhcp_packet_fields ) / | |
| sizeof ( dhcp_packet_fields[0] ) ) ; i++ ) { | |
| field = &dhcp_packet_fields[i]; | |
| if ( field->tag == tag ) | |
| return field; | |
| } | |
| return NULL; | |
| } | |
| /** | |
| * Check applicability of DHCP setting | |
| * | |
| * @v dhcppkt DHCP packet | |
| * @v tag Setting tag number | |
| * @ret applies Setting applies within this settings block | |
| */ | |
| static int dhcppkt_applies ( struct dhcp_packet *dhcppkt __unused, | |
| unsigned int tag ) { | |
| return dhcpopt_applies ( tag ); | |
| } | |
| /** | |
| * Store value of DHCP packet setting | |
| * | |
| * @v dhcppkt DHCP packet | |
| * @v tag Setting tag number | |
| * @v data Setting data, or NULL to clear setting | |
| * @v len Length of setting data | |
| * @ret rc Return status code | |
| */ | |
| int dhcppkt_store ( struct dhcp_packet *dhcppkt, unsigned int tag, | |
| const void *data, size_t len ) { | |
| struct dhcp_packet_field *field; | |
| void *field_data; | |
| /* If this is a special field, fill it in */ | |
| if ( ( field = find_dhcp_packet_field ( tag ) ) != NULL ) { | |
| if ( len > field->len ) | |
| return -ENOSPC; | |
| field_data = dhcp_packet_field ( dhcppkt->dhcphdr, field ); | |
| memset ( field_data, 0, field->len ); | |
| memcpy ( dhcp_packet_field ( dhcppkt->dhcphdr, field ), | |
| data, len ); | |
| /* Erase any equivalent option from the options block */ | |
| dhcpopt_store ( &dhcppkt->options, tag, NULL, 0 ); | |
| return 0; | |
| } | |
| /* Otherwise, use the generic options block */ | |
| return dhcpopt_store ( &dhcppkt->options, tag, data, len ); | |
| } | |
| /** | |
| * Fetch value of DHCP packet setting | |
| * | |
| * @v dhcppkt DHCP packet | |
| * @v tag Setting tag number | |
| * @v data Buffer to fill with setting data | |
| * @v len Length of buffer | |
| * @ret len Length of setting data, or negative error | |
| */ | |
| int dhcppkt_fetch ( struct dhcp_packet *dhcppkt, unsigned int tag, | |
| void *data, size_t len ) { | |
| struct dhcp_packet_field *field; | |
| void *field_data; | |
| size_t field_len = 0; | |
| /* Identify special field, if any */ | |
| if ( ( field = find_dhcp_packet_field ( tag ) ) != NULL ) { | |
| field_data = dhcp_packet_field ( dhcppkt->dhcphdr, field ); | |
| field_len = field->used_len ( field_data, field->len ); | |
| } | |
| /* Return special field, if it exists and is populated */ | |
| if ( field_len ) { | |
| if ( len > field_len ) | |
| len = field_len; | |
| memcpy ( data, field_data, len ); | |
| return field_len; | |
| } | |
| /* Otherwise, use the generic options block */ | |
| return dhcpopt_fetch ( &dhcppkt->options, tag, data, len ); | |
| } | |
| /**************************************************************************** | |
| * | |
| * DHCP packet settings interface | |
| * | |
| */ | |
| /** | |
| * Check applicability of DHCP setting | |
| * | |
| * @v settings Settings block | |
| * @v setting Setting | |
| * @ret applies Setting applies within this settings block | |
| */ | |
| static int dhcppkt_settings_applies ( struct settings *settings, | |
| const struct setting *setting ) { | |
| struct dhcp_packet *dhcppkt = | |
| container_of ( settings, struct dhcp_packet, settings ); | |
| return ( ( setting->scope == NULL ) && | |
| dhcppkt_applies ( dhcppkt, setting->tag ) ); | |
| } | |
| /** | |
| * Store value of DHCP setting | |
| * | |
| * @v settings Settings block | |
| * @v setting Setting to store | |
| * @v data Setting data, or NULL to clear setting | |
| * @v len Length of setting data | |
| * @ret rc Return status code | |
| */ | |
| static int dhcppkt_settings_store ( struct settings *settings, | |
| const struct setting *setting, | |
| const void *data, size_t len ) { | |
| struct dhcp_packet *dhcppkt = | |
| container_of ( settings, struct dhcp_packet, settings ); | |
| return dhcppkt_store ( dhcppkt, setting->tag, data, len ); | |
| } | |
| /** | |
| * Fetch value of DHCP setting | |
| * | |
| * @v settings Settings block, or NULL to search all blocks | |
| * @v setting Setting to fetch | |
| * @v data Buffer to fill with setting data | |
| * @v len Length of buffer | |
| * @ret len Length of setting data, or negative error | |
| */ | |
| static int dhcppkt_settings_fetch ( struct settings *settings, | |
| struct setting *setting, | |
| void *data, size_t len ) { | |
| struct dhcp_packet *dhcppkt = | |
| container_of ( settings, struct dhcp_packet, settings ); | |
| return dhcppkt_fetch ( dhcppkt, setting->tag, data, len ); | |
| } | |
| /** DHCP settings operations */ | |
| static struct settings_operations dhcppkt_settings_operations = { | |
| .applies = dhcppkt_settings_applies, | |
| .store = dhcppkt_settings_store, | |
| .fetch = dhcppkt_settings_fetch, | |
| }; | |
| /**************************************************************************** | |
| * | |
| * Constructor | |
| * | |
| */ | |
| /** | |
| * Initialise DHCP packet | |
| * | |
| * @v dhcppkt DHCP packet structure to fill in | |
| * @v data DHCP packet raw data | |
| * @v max_len Length of raw data buffer | |
| * | |
| * Initialise a DHCP packet structure from a data buffer containing a | |
| * DHCP packet. | |
| */ | |
| void dhcppkt_init ( struct dhcp_packet *dhcppkt, struct dhcphdr *data, | |
| size_t len ) { | |
| /* Sanity check */ | |
| assert ( len >= sizeof ( *data ) ); | |
| /* Initialise packet */ | |
| ref_init ( &dhcppkt->refcnt, NULL ); | |
| dhcppkt->dhcphdr = data; | |
| dhcpopt_init ( &dhcppkt->options, &dhcppkt->dhcphdr->options, | |
| ( len - offsetof ( struct dhcphdr, options ) ), | |
| dhcpopt_no_realloc ); | |
| /* Initialise settings content */ | |
| settings_init ( &dhcppkt->settings, &dhcppkt_settings_operations, | |
| &dhcppkt->refcnt, NULL ); | |
| } |