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