• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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., 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/dhcp.h>
27 #include <gpxe/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  */
dhcp_tag_name(unsigned int tag)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 *
dhcp_option(struct dhcp_options * options,unsigned int offset)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
dhcp_option_offset(struct dhcp_options * options,struct dhcp_option * option)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  */
dhcp_option_len(struct dhcp_option * option)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  */
find_dhcp_option_with_encap(struct dhcp_options * options,unsigned int tag,int * encap_offset)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->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  * Resize a DHCP option
174  *
175  * @v options		DHCP option block
176  * @v offset		Offset of option to resize
177  * @v encap_offset	Offset of encapsulating offset (or -ve for none)
178  * @v old_len		Old length (including header)
179  * @v new_len		New length (including header)
180  * @v can_realloc	Can reallocate options data if necessary
181  * @ret rc		Return status code
182  */
resize_dhcp_option(struct dhcp_options * options,int offset,int encap_offset,size_t old_len,size_t new_len,int can_realloc)183 static int resize_dhcp_option ( struct dhcp_options *options,
184 				int offset, int encap_offset,
185 				size_t old_len, size_t new_len,
186 				int can_realloc ) {
187 	struct dhcp_option *encapsulator;
188 	struct dhcp_option *option;
189 	ssize_t delta = ( new_len - old_len );
190 	size_t new_options_len;
191 	size_t new_encapsulator_len;
192 	void *new_data;
193 	void *source;
194 	void *dest;
195 	void *end;
196 
197 	/* Check for sufficient space, and update length fields */
198 	if ( new_len > DHCP_MAX_LEN ) {
199 		DBGC ( options, "DHCPOPT %p overlength option\n", options );
200 		return -ENOSPC;
201 	}
202 	new_options_len = ( options->len + delta );
203 	if ( new_options_len > options->max_len ) {
204 		/* Reallocate options block if allowed to do so. */
205 		if ( can_realloc ) {
206 			new_data = realloc ( options->data, new_options_len );
207 			if ( ! new_data ) {
208 				DBGC ( options, "DHCPOPT %p could not "
209 				       "reallocate to %zd bytes\n", options,
210 				       new_options_len );
211 				return -ENOMEM;
212 			}
213 			options->data = new_data;
214 			options->max_len = new_options_len;
215 		} else {
216 			DBGC ( options, "DHCPOPT %p out of space\n", options );
217 			return -ENOMEM;
218 		}
219 	}
220 	if ( encap_offset >= 0 ) {
221 		encapsulator = dhcp_option ( options, encap_offset );
222 		new_encapsulator_len = ( encapsulator->len + delta );
223 		if ( new_encapsulator_len > DHCP_MAX_LEN ) {
224 			DBGC ( options, "DHCPOPT %p overlength encapsulator\n",
225 			       options );
226 			return -ENOSPC;
227 		}
228 		encapsulator->len = new_encapsulator_len;
229 	}
230 	options->len = new_options_len;
231 
232 	/* Move remainder of option data */
233 	option = dhcp_option ( options, offset );
234 	source = ( ( ( void * ) option ) + old_len );
235 	dest = ( ( ( void * ) option ) + new_len );
236 	end = ( options->data + options->max_len );
237 	memmove ( dest, source, ( end - dest ) );
238 
239 	return 0;
240 }
241 
242 /**
243  * Set value of DHCP option
244  *
245  * @v options		DHCP option block
246  * @v tag		DHCP option tag
247  * @v data		New value for DHCP option
248  * @v len		Length of value, in bytes
249  * @v can_realloc	Can reallocate options data if necessary
250  * @ret offset		Offset of DHCP option, or negative error
251  *
252  * Sets the value of a DHCP option within the options block.  The
253  * option may or may not already exist.  Encapsulators will be created
254  * (and deleted) as necessary.
255  *
256  * This call may fail due to insufficient space in the options block.
257  * If it does fail, and the option existed previously, the option will
258  * be left with its original value.
259  */
set_dhcp_option(struct dhcp_options * options,unsigned int tag,const void * data,size_t len,int can_realloc)260 static int set_dhcp_option ( struct dhcp_options *options, unsigned int tag,
261 			     const void *data, size_t len,
262 			     int can_realloc ) {
263 	static const uint8_t empty_encapsulator[] = { DHCP_END };
264 	int offset;
265 	int encap_offset = -1;
266 	int creation_offset;
267 	struct dhcp_option *option;
268 	unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag );
269 	size_t old_len = 0;
270 	size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 );
271 	int rc;
272 
273 	/* Sanity check */
274 	if ( tag == DHCP_PAD )
275 		return -ENOTTY;
276 
277 	creation_offset = find_dhcp_option_with_encap ( options, DHCP_END,
278 							NULL );
279 	if ( creation_offset < 0 )
280 		creation_offset = options->len;
281 	/* Find old instance of this option, if any */
282 	offset = find_dhcp_option_with_encap ( options, tag, &encap_offset );
283 	if ( offset >= 0 ) {
284 		old_len = dhcp_option_len ( dhcp_option ( options, offset ) );
285 		DBGC ( options, "DHCPOPT %p resizing %s from %zd to %zd\n",
286 		       options, dhcp_tag_name ( tag ), old_len, new_len );
287 	} else {
288 		DBGC ( options, "DHCPOPT %p creating %s (length %zd)\n",
289 		       options, dhcp_tag_name ( tag ), new_len );
290 	}
291 
292 	/* Ensure that encapsulator exists, if required */
293 	if ( encap_tag ) {
294 		if ( encap_offset < 0 )
295 			encap_offset = set_dhcp_option ( options, encap_tag,
296 							 empty_encapsulator, 1,
297 							 can_realloc );
298 		if ( encap_offset < 0 )
299 			return encap_offset;
300 		creation_offset = ( encap_offset + DHCP_OPTION_HEADER_LEN );
301 	}
302 
303 	/* Create new option if necessary */
304 	if ( offset < 0 )
305 		offset = creation_offset;
306 
307 	/* Resize option to fit new data */
308 	if ( ( rc = resize_dhcp_option ( options, offset, encap_offset,
309 					 old_len, new_len,
310 					 can_realloc ) ) != 0 )
311 		return rc;
312 
313 	/* Copy new data into option, if applicable */
314 	if ( len ) {
315 		option = dhcp_option ( options, offset );
316 		option->tag = tag;
317 		option->len = len;
318 		memcpy ( &option->data, data, len );
319 	}
320 
321 	/* Delete encapsulator if there's nothing else left in it */
322 	if ( encap_offset >= 0 ) {
323 		option = dhcp_option ( options, encap_offset );
324 		if ( option->len <= 1 )
325 			set_dhcp_option ( options, encap_tag, NULL, 0, 0 );
326 	}
327 
328 	return offset;
329 }
330 
331 /**
332  * Store value of DHCP option setting
333  *
334  * @v options		DHCP option block
335  * @v tag		Setting tag number
336  * @v data		Setting data, or NULL to clear setting
337  * @v len		Length of setting data
338  * @ret rc		Return status code
339  */
dhcpopt_store(struct dhcp_options * options,unsigned int tag,const void * data,size_t len)340 int dhcpopt_store ( struct dhcp_options *options, unsigned int tag,
341 		    const void *data, size_t len ) {
342 	int offset;
343 
344 	offset = set_dhcp_option ( options, tag, data, len, 0 );
345 	if ( offset < 0 )
346 		return offset;
347 	return 0;
348 }
349 
350 /**
351  * Store value of DHCP option setting, extending options block if necessary
352  *
353  * @v options		DHCP option block
354  * @v tag		Setting tag number
355  * @v data		Setting data, or NULL to clear setting
356  * @v len		Length of setting data
357  * @ret rc		Return status code
358  */
dhcpopt_extensible_store(struct dhcp_options * options,unsigned int tag,const void * data,size_t len)359 int dhcpopt_extensible_store ( struct dhcp_options *options, unsigned int tag,
360 			       const void *data, size_t len ) {
361 	int offset;
362 
363 	offset = set_dhcp_option ( options, tag, data, len, 1 );
364 	if ( offset < 0 )
365 		return offset;
366 	return 0;
367 }
368 
369 /**
370  * Fetch value of DHCP option setting
371  *
372  * @v options		DHCP option block
373  * @v tag		Setting tag number
374  * @v data		Buffer to fill with setting data
375  * @v len		Length of buffer
376  * @ret len		Length of setting data, or negative error
377  */
dhcpopt_fetch(struct dhcp_options * options,unsigned int tag,void * data,size_t len)378 int dhcpopt_fetch ( struct dhcp_options *options, unsigned int tag,
379 		    void *data, size_t len ) {
380 	int offset;
381 	struct dhcp_option *option;
382 	size_t option_len;
383 
384 	offset = find_dhcp_option_with_encap ( options, tag, NULL );
385 	if ( offset < 0 )
386 		return offset;
387 
388 	option = dhcp_option ( options, offset );
389 	option_len = option->len;
390 	if ( len > option_len )
391 		len = option_len;
392 	memcpy ( data, option->data, len );
393 
394 	return option_len;
395 }
396 
397 /**
398  * Recalculate length of DHCP options block
399  *
400  * @v options		Uninitialised DHCP option block
401  *
402  * The "used length" field will be updated based on scanning through
403  * the block to find the end of the options.
404  */
dhcpopt_update_len(struct dhcp_options * options)405 static void dhcpopt_update_len ( struct dhcp_options *options ) {
406 	struct dhcp_option *option;
407 	int offset = 0;
408 	ssize_t remaining = options->max_len;
409 	unsigned int option_len;
410 
411 	/* Find last non-pad option */
412 	options->len = 0;
413 	while ( remaining ) {
414 		option = dhcp_option ( options, offset );
415 		option_len = dhcp_option_len ( option );
416 		remaining -= option_len;
417 		if ( remaining < 0 )
418 			break;
419 		offset += option_len;
420 		if ( option->tag != DHCP_PAD )
421 			options->len = offset;
422 	}
423 }
424 
425 /**
426  * Initialise prepopulated block of DHCP options
427  *
428  * @v options		Uninitialised DHCP option block
429  * @v data		Memory for DHCP option data
430  * @v max_len		Length of memory for DHCP option data
431  *
432  * The memory content must already be filled with valid DHCP options.
433  * A zeroed block counts as a block of valid DHCP options.
434  */
dhcpopt_init(struct dhcp_options * options,void * data,size_t max_len)435 void dhcpopt_init ( struct dhcp_options *options, void *data,
436 		    size_t max_len ) {
437 
438 	/* Fill in fields */
439 	options->data = data;
440 	options->max_len = max_len;
441 
442 	/* Update length */
443 	dhcpopt_update_len ( options );
444 
445 	DBGC ( options, "DHCPOPT %p created (data %p len %#zx max_len %#zx)\n",
446 	       options, options->data, options->len, options->max_len );
447 }
448