2 * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
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.
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.
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.
24 #include <gpxe/dhcp.h>
25 #include <gpxe/dhcpopts.h>
34 * Obtain printable version of a DHCP option tag
36 * @v tag DHCP option tag
37 * @ret name String representation of the tag
40 static inline char * dhcp_tag_name ( unsigned int tag
) {
43 if ( DHCP_IS_ENCAP_OPT ( tag
) ) {
44 snprintf ( name
, sizeof ( name
), "%d.%d",
45 DHCP_ENCAPSULATOR ( tag
),
46 DHCP_ENCAPSULATED ( tag
) );
48 snprintf ( name
, sizeof ( name
), "%d", tag
);
54 * Get pointer to DHCP option
56 * @v options DHCP options block
57 * @v offset Offset within options block
58 * @ret option DHCP option
60 static inline __attribute__ (( always_inline
)) struct dhcp_option
*
61 dhcp_option ( struct dhcp_options
*options
, unsigned int offset
) {
62 return ( ( struct dhcp_option
* ) ( options
->data
+ offset
) );
66 * Get offset of a DHCP option
68 * @v options DHCP options block
69 * @v option DHCP option
70 * @ret offset Offset within options block
72 static inline __attribute__ (( always_inline
)) int
73 dhcp_option_offset ( struct dhcp_options
*options
,
74 struct dhcp_option
*option
) {
75 return ( ( ( void * ) option
) - options
->data
);
79 * Calculate length of any DHCP option
81 * @v option DHCP option
82 * @ret len Length (including tag and length field)
84 static unsigned int dhcp_option_len ( struct dhcp_option
*option
) {
85 if ( ( option
->tag
== DHCP_END
) || ( option
->tag
== DHCP_PAD
) ) {
88 return ( option
->len
+ DHCP_OPTION_HEADER_LEN
);
93 * Find DHCP option within DHCP options block, and its encapsulator (if any)
95 * @v options DHCP options block
96 * @v tag DHCP option tag to search for
97 * @ret encap_offset Offset of encapsulating DHCP option
98 * @ret offset Offset of DHCP option, or negative error
100 * Searches for the DHCP option matching the specified tag within the
101 * DHCP option block. Encapsulated options may be searched for by
102 * using DHCP_ENCAP_OPT() to construct the tag value.
104 * If the option is encapsulated, and @c encapsulator is non-NULL, it
105 * will be filled in with the offset of the encapsulating option.
107 * This routine is designed to be paranoid. It does not assume that
108 * the option data is well-formatted, and so must guard against flaws
109 * such as options missing a @c DHCP_END terminator, or options whose
110 * length would take them beyond the end of the data block.
112 static int find_dhcp_option_with_encap ( struct dhcp_options
*options
,
114 int *encap_offset
) {
115 unsigned int original_tag
__attribute__ (( unused
)) = tag
;
116 struct dhcp_option
*option
;
118 ssize_t remaining
= options
->len
;
119 unsigned int option_len
;
122 if ( tag
== DHCP_PAD
)
125 /* Search for option */
126 while ( remaining
) {
127 /* Calculate length of this option. Abort processing
128 * if the length is malformed (i.e. takes us beyond
129 * the end of the data block).
131 option
= dhcp_option ( options
, offset
);
132 option_len
= dhcp_option_len ( option
);
133 remaining
-= option_len
;
136 /* Check for explicit end marker */
137 if ( option
->tag
== DHCP_END
)
139 /* Check for matching tag */
140 if ( option
->tag
== tag
) {
141 DBGC ( options
, "DHCPOPT %p found %s (length %d)\n",
142 options
, dhcp_tag_name ( original_tag
),
146 /* Check for start of matching encapsulation block */
147 if ( DHCP_IS_ENCAP_OPT ( tag
) &&
148 ( option
->tag
== DHCP_ENCAPSULATOR ( tag
) ) ) {
150 *encap_offset
= offset
;
151 /* Continue search within encapsulated option block */
152 tag
= DHCP_ENCAPSULATED ( tag
);
153 remaining
= option_len
;
154 offset
+= DHCP_OPTION_HEADER_LEN
;
157 offset
+= option_len
;
164 * Resize a DHCP option
166 * @v options DHCP option block
167 * @v offset Offset of option to resize
168 * @v encap_offset Offset of encapsulating offset (or -ve for none)
169 * @v old_len Old length (including header)
170 * @v new_len New length (including header)
171 * @v can_realloc Can reallocate options data if necessary
172 * @ret rc Return status code
174 static int resize_dhcp_option ( struct dhcp_options
*options
,
175 int offset
, int encap_offset
,
176 size_t old_len
, size_t new_len
,
178 struct dhcp_option
*encapsulator
;
179 struct dhcp_option
*option
;
180 ssize_t delta
= ( new_len
- old_len
);
181 size_t new_options_len
;
182 size_t new_encapsulator_len
;
188 /* Check for sufficient space, and update length fields */
189 if ( new_len
> DHCP_MAX_LEN
) {
190 DBGC ( options
, "DHCPOPT %p overlength option\n", options
);
193 new_options_len
= ( options
->len
+ delta
);
194 if ( new_options_len
> options
->max_len
) {
195 /* Reallocate options block if allowed to do so. */
197 new_data
= realloc ( options
->data
, new_options_len
);
199 DBGC ( options
, "DHCPOPT %p could not "
200 "reallocate to %zd bytes\n", options
,
204 options
->data
= new_data
;
205 options
->max_len
= new_options_len
;
207 DBGC ( options
, "DHCPOPT %p out of space\n", options
);
211 if ( encap_offset
>= 0 ) {
212 encapsulator
= dhcp_option ( options
, encap_offset
);
213 new_encapsulator_len
= ( encapsulator
->len
+ delta
);
214 if ( new_encapsulator_len
> DHCP_MAX_LEN
) {
215 DBGC ( options
, "DHCPOPT %p overlength encapsulator\n",
219 encapsulator
->len
= new_encapsulator_len
;
221 options
->len
= new_options_len
;
223 /* Move remainder of option data */
224 option
= dhcp_option ( options
, offset
);
225 source
= ( ( ( void * ) option
) + old_len
);
226 dest
= ( ( ( void * ) option
) + new_len
);
227 end
= ( options
->data
+ options
->max_len
);
228 memmove ( dest
, source
, ( end
- dest
) );
234 * Set value of DHCP option
236 * @v options DHCP option block
237 * @v tag DHCP option tag
238 * @v data New value for DHCP option
239 * @v len Length of value, in bytes
240 * @v can_realloc Can reallocate options data if necessary
241 * @ret offset Offset of DHCP option, or negative error
243 * Sets the value of a DHCP option within the options block. The
244 * option may or may not already exist. Encapsulators will be created
245 * (and deleted) as necessary.
247 * This call may fail due to insufficient space in the options block.
248 * If it does fail, and the option existed previously, the option will
249 * be left with its original value.
251 static int set_dhcp_option ( struct dhcp_options
*options
, unsigned int tag
,
252 const void *data
, size_t len
,
254 static const uint8_t empty_encapsulator
[] = { DHCP_END
};
256 int encap_offset
= -1;
257 int creation_offset
= 0;
258 struct dhcp_option
*option
;
259 unsigned int encap_tag
= DHCP_ENCAPSULATOR ( tag
);
261 size_t new_len
= ( len
? ( len
+ DHCP_OPTION_HEADER_LEN
) : 0 );
265 if ( tag
== DHCP_PAD
)
268 /* Find old instance of this option, if any */
269 offset
= find_dhcp_option_with_encap ( options
, tag
, &encap_offset
);
271 old_len
= dhcp_option_len ( dhcp_option ( options
, offset
) );
272 DBGC ( options
, "DHCPOPT %p resizing %s from %zd to %zd\n",
273 options
, dhcp_tag_name ( tag
), old_len
, new_len
);
275 DBGC ( options
, "DHCPOPT %p creating %s (length %zd)\n",
276 options
, dhcp_tag_name ( tag
), new_len
);
279 /* Ensure that encapsulator exists, if required */
281 if ( encap_offset
< 0 )
282 encap_offset
= set_dhcp_option ( options
, encap_tag
,
283 empty_encapsulator
, 1,
285 if ( encap_offset
< 0 )
287 creation_offset
= ( encap_offset
+ DHCP_OPTION_HEADER_LEN
);
290 /* Create new option if necessary */
292 offset
= creation_offset
;
294 /* Resize option to fit new data */
295 if ( ( rc
= resize_dhcp_option ( options
, offset
, encap_offset
,
297 can_realloc
) ) != 0 )
300 /* Copy new data into option, if applicable */
302 option
= dhcp_option ( options
, offset
);
305 memcpy ( &option
->data
, data
, len
);
308 /* Delete encapsulator if there's nothing else left in it */
309 if ( encap_offset
>= 0 ) {
310 option
= dhcp_option ( options
, encap_offset
);
311 if ( option
->len
<= 1 )
312 set_dhcp_option ( options
, encap_tag
, NULL
, 0, 0 );
319 * Store value of DHCP option setting
321 * @v options DHCP option block
322 * @v tag Setting tag number
323 * @v data Setting data, or NULL to clear setting
324 * @v len Length of setting data
325 * @ret rc Return status code
327 int dhcpopt_store ( struct dhcp_options
*options
, unsigned int tag
,
328 const void *data
, size_t len
) {
331 offset
= set_dhcp_option ( options
, tag
, data
, len
, 0 );
338 * Store value of DHCP option setting, extending options block if necessary
340 * @v options DHCP option block
341 * @v tag Setting tag number
342 * @v data Setting data, or NULL to clear setting
343 * @v len Length of setting data
344 * @ret rc Return status code
346 int dhcpopt_extensible_store ( struct dhcp_options
*options
, unsigned int tag
,
347 const void *data
, size_t len
) {
350 offset
= set_dhcp_option ( options
, tag
, data
, len
, 1 );
357 * Fetch value of DHCP option setting
359 * @v options DHCP option block
360 * @v tag Setting tag number
361 * @v data Buffer to fill with setting data
362 * @v len Length of buffer
363 * @ret len Length of setting data, or negative error
365 int dhcpopt_fetch ( struct dhcp_options
*options
, unsigned int tag
,
366 void *data
, size_t len
) {
368 struct dhcp_option
*option
;
371 offset
= find_dhcp_option_with_encap ( options
, tag
, NULL
);
375 option
= dhcp_option ( options
, offset
);
376 option_len
= option
->len
;
377 if ( len
> option_len
)
379 memcpy ( data
, option
->data
, len
);
385 * Recalculate length of DHCP options block
387 * @v options Uninitialised DHCP option block
389 * The "used length" field will be updated based on scanning through
390 * the block to find the end of the options.
392 static void dhcpopt_update_len ( struct dhcp_options
*options
) {
393 struct dhcp_option
*option
;
395 ssize_t remaining
= options
->max_len
;
396 unsigned int option_len
;
398 /* Find last non-pad option */
400 while ( remaining
) {
401 option
= dhcp_option ( options
, offset
);
402 option_len
= dhcp_option_len ( option
);
403 remaining
-= option_len
;
406 offset
+= option_len
;
407 if ( option
->tag
!= DHCP_PAD
)
408 options
->len
= offset
;
413 * Initialise prepopulated block of DHCP options
415 * @v options Uninitialised DHCP option block
416 * @v data Memory for DHCP option data
417 * @v max_len Length of memory for DHCP option data
419 * The memory content must already be filled with valid DHCP options.
420 * A zeroed block counts as a block of valid DHCP options.
422 void dhcpopt_init ( struct dhcp_options
*options
, void *data
,
426 options
->data
= data
;
427 options
->max_len
= max_len
;
430 dhcpopt_update_len ( options
);
432 DBGC ( options
, "DHCPOPT %p created (data %p len %#zx max_len %#zx)\n",
433 options
, options
->data
, options
->len
, options
->max_len
);