1 /* 2 * Copyright (C) 2008 Michael Brown <mbrown (at) 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 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 <gpxe/netdevice.h> 27 #include <gpxe/dhcp.h> 28 #include <gpxe/dhcpopts.h> 29 #include <gpxe/dhcppkt.h> 30 31 /** @file 32 * 33 * DHCP packets 34 * 35 */ 36 37 /**************************************************************************** 38 * 39 * DHCP packet raw interface 40 * 41 */ 42 43 /** 44 * Calculate used length of an IPv4 field within a DHCP packet 45 * 46 * @v data Field data 47 * @v len Length of field 48 * @ret used Used length of field 49 */ 50 static size_t used_len_ipv4 ( const void *data, size_t len __unused ) { 51 const struct in_addr *in = data; 52 53 return ( in->s_addr ? sizeof ( *in ) : 0 ); 54 } 55 56 /** 57 * Calculate used length of a string field within a DHCP packet 58 * 59 * @v data Field data 60 * @v len Length of field 61 * @ret used Used length of field 62 */ 63 static size_t used_len_string ( const void *data, size_t len ) { 64 return strnlen ( data, len ); 65 } 66 67 /** A dedicated field within a DHCP packet */ 68 struct dhcp_packet_field { 69 /** Settings tag number */ 70 unsigned int tag; 71 /** Offset within DHCP packet */ 72 uint16_t offset; 73 /** Length of field */ 74 uint16_t len; 75 /** Calculate used length of field 76 * 77 * @v data Field data 78 * @v len Length of field 79 * @ret used Used length of field 80 */ 81 size_t ( * used_len ) ( const void *data, size_t len ); 82 }; 83 84 /** Declare a dedicated field within a DHCP packet 85 * 86 * @v _tag Settings tag number 87 * @v _field Field name 88 * @v _used_len Function to calculate used length of field 89 */ 90 #define DHCP_PACKET_FIELD( _tag, _field, _used_len ) { \ 91 .tag = (_tag), \ 92 .offset = offsetof ( struct dhcphdr, _field ), \ 93 .len = sizeof ( ( ( struct dhcphdr * ) 0 )->_field ), \ 94 .used_len = _used_len, \ 95 } 96 97 /** Dedicated fields within a DHCP packet */ 98 static struct dhcp_packet_field dhcp_packet_fields[] = { 99 DHCP_PACKET_FIELD ( DHCP_EB_YIADDR, yiaddr, used_len_ipv4 ), 100 DHCP_PACKET_FIELD ( DHCP_EB_SIADDR, siaddr, used_len_ipv4 ), 101 DHCP_PACKET_FIELD ( DHCP_TFTP_SERVER_NAME, sname, used_len_string ), 102 DHCP_PACKET_FIELD ( DHCP_BOOTFILE_NAME, file, used_len_string ), 103 }; 104 105 /** 106 * Get address of a DHCP packet field 107 * 108 * @v dhcphdr DHCP packet header 109 * @v field DHCP packet field 110 * @ret data Packet field data 111 */ 112 static inline void * dhcp_packet_field ( struct dhcphdr *dhcphdr, 113 struct dhcp_packet_field *field ) { 114 return ( ( ( void * ) dhcphdr ) + field->offset ); 115 } 116 117 /** 118 * Find DHCP packet field corresponding to settings tag number 119 * 120 * @v tag Settings tag number 121 * @ret field DHCP packet field, or NULL 122 */ 123 static struct dhcp_packet_field * 124 find_dhcp_packet_field ( unsigned int tag ) { 125 struct dhcp_packet_field *field; 126 unsigned int i; 127 128 for ( i = 0 ; i < ( sizeof ( dhcp_packet_fields ) / 129 sizeof ( dhcp_packet_fields[0] ) ) ; i++ ) { 130 field = &dhcp_packet_fields[i]; 131 if ( field->tag == tag ) 132 return field; 133 } 134 return NULL; 135 } 136 137 /** 138 * Store value of DHCP packet setting 139 * 140 * @v dhcppkt DHCP packet 141 * @v tag Setting tag number 142 * @v data Setting data, or NULL to clear setting 143 * @v len Length of setting data 144 * @ret rc Return status code 145 */ 146 int dhcppkt_store ( struct dhcp_packet *dhcppkt, unsigned int tag, 147 const void *data, size_t len ) { 148 struct dhcp_packet_field *field; 149 void *field_data; 150 int rc; 151 152 /* If this is a special field, fill it in */ 153 if ( ( field = find_dhcp_packet_field ( tag ) ) != NULL ) { 154 if ( len > field->len ) 155 return -ENOSPC; 156 field_data = dhcp_packet_field ( dhcppkt->dhcphdr, field ); 157 memset ( field_data, 0, field->len ); 158 memcpy ( dhcp_packet_field ( dhcppkt->dhcphdr, field ), 159 data, len ); 160 /* Erase any equivalent option from the options block */ 161 dhcpopt_store ( &dhcppkt->options, tag, NULL, 0 ); 162 return 0; 163 } 164 165 /* Otherwise, use the generic options block */ 166 rc = dhcpopt_store ( &dhcppkt->options, tag, data, len ); 167 168 /* Update our used-length field */ 169 dhcppkt->len = ( offsetof ( struct dhcphdr, options ) + 170 dhcppkt->options.len ); 171 172 return rc; 173 } 174 175 /** 176 * Fetch value of DHCP packet setting 177 * 178 * @v dhcppkt DHCP packet 179 * @v tag Setting tag number 180 * @v data Buffer to fill with setting data 181 * @v len Length of buffer 182 * @ret len Length of setting data, or negative error 183 */ 184 int dhcppkt_fetch ( struct dhcp_packet *dhcppkt, unsigned int tag, 185 void *data, size_t len ) { 186 struct dhcp_packet_field *field; 187 void *field_data; 188 size_t field_len = 0; 189 190 /* Identify special field, if any */ 191 if ( ( field = find_dhcp_packet_field ( tag ) ) != NULL ) { 192 field_data = dhcp_packet_field ( dhcppkt->dhcphdr, field ); 193 field_len = field->used_len ( field_data, field->len ); 194 } 195 196 /* Return special field, if it exists and is populated */ 197 if ( field_len ) { 198 if ( len > field_len ) 199 len = field_len; 200 memcpy ( data, field_data, len ); 201 return field_len; 202 } 203 204 /* Otherwise, use the generic options block */ 205 return dhcpopt_fetch ( &dhcppkt->options, tag, data, len ); 206 } 207 208 /**************************************************************************** 209 * 210 * DHCP packet settings interface 211 * 212 */ 213 214 /** 215 * Store value of DHCP setting 216 * 217 * @v settings Settings block 218 * @v setting Setting to store 219 * @v data Setting data, or NULL to clear setting 220 * @v len Length of setting data 221 * @ret rc Return status code 222 */ 223 static int dhcppkt_settings_store ( struct settings *settings, 224 struct setting *setting, 225 const void *data, size_t len ) { 226 struct dhcp_packet *dhcppkt = 227 container_of ( settings, struct dhcp_packet, settings ); 228 229 return dhcppkt_store ( dhcppkt, setting->tag, data, len ); 230 } 231 232 /** 233 * Fetch value of DHCP setting 234 * 235 * @v settings Settings block, or NULL to search all blocks 236 * @v setting Setting to fetch 237 * @v data Buffer to fill with setting data 238 * @v len Length of buffer 239 * @ret len Length of setting data, or negative error 240 */ 241 static int dhcppkt_settings_fetch ( struct settings *settings, 242 struct setting *setting, 243 void *data, size_t len ) { 244 struct dhcp_packet *dhcppkt = 245 container_of ( settings, struct dhcp_packet, settings ); 246 247 return dhcppkt_fetch ( dhcppkt, setting->tag, data, len ); 248 } 249 250 /** DHCP settings operations */ 251 static struct settings_operations dhcppkt_settings_operations = { 252 .store = dhcppkt_settings_store, 253 .fetch = dhcppkt_settings_fetch, 254 }; 255 256 /**************************************************************************** 257 * 258 * Constructor 259 * 260 */ 261 262 /** 263 * Initialise DHCP packet 264 * 265 * @v dhcppkt DHCP packet structure to fill in 266 * @v data DHCP packet raw data 267 * @v max_len Length of raw data buffer 268 * 269 * Initialise a DHCP packet structure from a data buffer containing a 270 * DHCP packet. 271 */ 272 void dhcppkt_init ( struct dhcp_packet *dhcppkt, struct dhcphdr *data, 273 size_t len ) { 274 dhcppkt->dhcphdr = data; 275 dhcppkt->max_len = len; 276 dhcpopt_init ( &dhcppkt->options, &dhcppkt->dhcphdr->options, 277 ( len - offsetof ( struct dhcphdr, options ) ) ); 278 dhcppkt->len = ( offsetof ( struct dhcphdr, options ) + 279 dhcppkt->options.len ); 280 settings_init ( &dhcppkt->settings, 281 &dhcppkt_settings_operations, &dhcppkt->refcnt, 282 DHCP_SETTINGS_NAME, 0 ); 283 } 284