Allow for calling find_dhcp_option() with "options" set to NULL, in order
[people/mcb30/gpxe.git] / src / net / dhcpopts.c
1 /*
2  * Copyright (C) 2006 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 #include <stdint.h>
20 #include <byteswap.h>
21 #include <errno.h>
22 #include <string.h>
23 #include <malloc.h>
24 #include <assert.h>
25 #include <vsprintf.h>
26 #include <gpxe/list.h>
27 #include <gpxe/dhcp.h>
28
29 /** @file
30  *
31  * DHCP options
32  *
33  */
34
35 /** List of registered DHCP option blocks */
36 static LIST_HEAD ( option_blocks );
37
38 /**
39  * Obtain printable version of a DHCP option tag
40  *
41  * @v tag               DHCP option tag
42  * @ret name            String representation of the tag
43  *
44  */
45 static inline char * dhcp_tag_name ( unsigned int tag ) {
46         static char name[8];
47
48         if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
49                 snprintf ( name, sizeof ( name ), "%d.%d",
50                            DHCP_ENCAPSULATOR ( tag ),
51                            DHCP_ENCAPSULATED ( tag ) );
52         } else {
53                 snprintf ( name, sizeof ( name ), "%d", tag );
54         }
55         return name;
56 }
57
58 /**
59  * Obtain value of a numerical DHCP option
60  *
61  * @v option            DHCP option, or NULL
62  * @ret value           Numerical value of the option, or 0
63  *
64  * Parses the numerical value from a DHCP option, if present.  It is
65  * permitted to call dhcp_num_option() with @c option set to NULL; in
66  * this case 0 will be returned.
67  *
68  * The caller does not specify the size of the DHCP option data; this
69  * is implied by the length field stored within the DHCP option
70  * itself.
71  */
72 unsigned long dhcp_num_option ( struct dhcp_option *option ) {
73         unsigned long value = 0;
74         uint8_t *data;
75
76         if ( option ) {
77                 /* This is actually smaller code than using htons()
78                  * etc., and will also cope well with malformed
79                  * options (such as zero-length options).
80                  */
81                 for ( data = option->data.bytes ;
82                       data < ( option->data.bytes + option->len ) ; data++ )
83                         value = ( ( value << 8 ) | *data );
84         }
85         return value;
86 }
87
88 /**
89  * Calculate length of a normal DHCP option
90  *
91  * @v option            DHCP option
92  * @ret len             Length (including tag and length field)
93  *
94  * @c option may not be a @c DHCP_PAD or @c DHCP_END option.
95  */
96 static inline unsigned int dhcp_option_len ( struct dhcp_option *option ) {
97         assert ( option->tag != DHCP_PAD );
98         assert ( option->tag != DHCP_END );
99         return ( option->len + DHCP_OPTION_HEADER_LEN );
100 }
101
102 /**
103  * Calculate length of any DHCP option
104  *
105  * @v option            DHCP option
106  * @ret len             Length (including tag and length field)
107  */
108 static inline unsigned int dhcp_any_option_len ( struct dhcp_option *option ) {
109         if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) {
110                 return 1;
111         } else {
112                 return dhcp_option_len ( option );
113         }
114 }
115
116 /**
117  * Find DHCP option within DHCP options block, and its encapsulator (if any)
118  *
119  * @v options           DHCP options block
120  * @v tag               DHCP option tag to search for
121  * @ret encapsulator    Encapsulating DHCP option
122  * @ret option          DHCP option, or NULL if not found
123  *
124  * Searches for the DHCP option matching the specified tag within the
125  * DHCP option block.  Encapsulated options may be searched for by
126  * using DHCP_ENCAP_OPT() to construct the tag value.
127  *
128  * If the option is encapsulated, and @c encapsulator is non-NULL, it
129  * will be filled in with a pointer to the encapsulating option.
130  *
131  * This routine is designed to be paranoid.  It does not assume that
132  * the option data is well-formatted, and so must guard against flaws
133  * such as options missing a @c DHCP_END terminator, or options whose
134  * length would take them beyond the end of the data block.
135  */
136 static struct dhcp_option *
137 find_dhcp_option_with_encap ( struct dhcp_option_block *options,
138                               unsigned int tag,
139                               struct dhcp_option **encapsulator ) {
140         unsigned int original_tag __attribute__ (( unused )) = tag;
141         struct dhcp_option *option = options->data;
142         ssize_t remaining = options->len;
143         unsigned int option_len;
144
145         while ( remaining ) {
146                 /* Calculate length of this option.  Abort processing
147                  * if the length is malformed (i.e. takes us beyond
148                  * the end of the data block).
149                  */
150                 option_len = dhcp_any_option_len ( option );
151                 remaining -= option_len;
152                 if ( remaining < 0 )
153                         break;
154                 /* Check for matching tag */
155                 if ( option->tag == tag ) {
156                         DBG ( "Found DHCP option %s (length %d)\n",
157                               dhcp_tag_name ( original_tag ), option->len );
158                         return option;
159                 }
160                 /* Check for explicit end marker */
161                 if ( option->tag == DHCP_END )
162                         break;
163                 /* Check for start of matching encapsulation block */
164                 if ( DHCP_IS_ENCAP_OPT ( tag ) &&
165                      ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
166                         if ( encapsulator )
167                                 *encapsulator = option;
168                         /* Continue search within encapsulated option block */
169                         tag = DHCP_ENCAPSULATED ( tag );
170                         remaining = option->len;
171                         option = ( void * ) &option->data;
172                         continue;
173                 }
174                 option = ( ( ( void * ) option ) + option_len );
175         }
176         return NULL;
177 }
178
179 /**
180  * Find DHCP option within DHCP options block
181  *
182  * @v options           DHCP options block, or NULL
183  * @v tag               DHCP option tag to search for
184  * @ret option          DHCP option, or NULL if not found
185  *
186  * Searches for the DHCP option matching the specified tag within the
187  * DHCP option block.  Encapsulated options may be searched for by
188  * using DHCP_ENCAP_OPT() to construct the tag value.
189  *
190  * If @c options is NULL, all registered option blocks will be
191  * searched.  Where multiple option blocks contain the same DHCP
192  * option, the option from the highest-priority block will be
193  * returned.  (Priority of an options block is determined by the value
194  * of the @c DHCP_EB_PRIORITY option within the block, if present; the
195  * default priority is zero).
196  */
197 struct dhcp_option * find_dhcp_option ( struct dhcp_option_block *options,
198                                         unsigned int tag ) {
199         struct dhcp_option *option;
200
201         if ( options ) {
202                 return find_dhcp_option_with_encap ( options, tag, NULL );
203         } else {
204                 list_for_each_entry ( options, &option_blocks, list ) {
205                         if ( ( option = find_dhcp_option ( options, tag ) ) )
206                                 return option;
207                 }
208                 return NULL;
209         }
210 }
211
212 /**
213  * Register DHCP option block
214  *
215  * @v options           DHCP option block
216  *
217  * Register a block of DHCP options.
218  */
219 void register_dhcp_options ( struct dhcp_option_block *options ) {
220         struct dhcp_option_block *existing;
221
222         /* Determine priority of new block */
223         options->priority = find_dhcp_num_option ( options, DHCP_EB_PRIORITY );
224         DBG ( "Registering DHCP options block with priority %d\n",
225               options->priority );
226
227         /* Insert after any existing blocks which have a higher priority */
228         list_for_each_entry ( existing, &option_blocks, list ) {
229                 if ( options->priority > existing->priority )
230                         break;
231         }
232         list_add_tail ( &options->list, &existing->list );
233 }
234
235 /**
236  * Unregister DHCP option block
237  *
238  * @v options           DHCP option block
239  */
240 void unregister_dhcp_options ( struct dhcp_option_block *options ) {
241         list_del ( &options->list );
242 }
243
244 /**
245  * Initialise empty block of DHCP options
246  *
247  * @v options           Uninitialised DHCP option block
248  * @v data              Memory for DHCP option data
249  * @v max_len           Length of memory for DHCP option data
250  *
251  * Populates the DHCP option data with a single @c DHCP_END option and
252  * fills in the fields of the @c dhcp_option_block structure.
253  */
254 void init_dhcp_options ( struct dhcp_option_block *options,
255                          void *data, size_t max_len ) {
256         struct dhcp_option *option;
257
258         options->data = data;
259         options->max_len = max_len;
260         option = options->data;
261         option->tag = DHCP_END;
262         options->len = 1;
263 }
264
265 /**
266  * Allocate space for a block of DHCP options
267  *
268  * @v max_len           Maximum length of option block
269  * @ret options         DHCP option block, or NULL
270  *
271  * Creates a new DHCP option block and populates it with an empty
272  * options list.  This call does not register the options block.
273  */
274 struct dhcp_option_block * alloc_dhcp_options ( size_t max_len ) {
275         struct dhcp_option_block *options;
276
277         options = malloc ( sizeof ( *options ) + max_len );
278         if ( options ) {
279                 init_dhcp_options ( options, 
280                                     ( (void *) options + sizeof ( *options ) ),
281                                     max_len );
282         }
283         return options;
284 }
285
286 /**
287  * Free DHCP options block
288  *
289  * @v options           DHCP option block
290  */
291 void free_dhcp_options ( struct dhcp_option_block *options ) {
292         free ( options );
293 }
294
295 /**
296  * Resize a DHCP option
297  *
298  * @v options           DHCP option block
299  * @v option            DHCP option to resize
300  * @v encapsulator      Encapsulating option (or NULL)
301  * @v old_len           Old length (including header)
302  * @v new_len           New length (including header)
303  * @ret rc              Return status code
304  */
305 static int resize_dhcp_option ( struct dhcp_option_block *options,
306                                 struct dhcp_option *option,
307                                 struct dhcp_option *encapsulator,
308                                 size_t old_len, size_t new_len ) {
309         void *source = ( ( ( void * ) option ) + old_len );
310         void *dest = ( ( ( void * ) option ) + new_len );
311         void *end = ( options->data + options->max_len );
312         ssize_t delta = ( new_len - old_len );
313         size_t new_options_len;
314         size_t new_encapsulator_len;
315
316         /* Check for sufficient space, and update length fields */
317         if ( new_len > DHCP_MAX_LEN )
318                 return -ENOMEM;
319         new_options_len = ( options->len + delta );
320         if ( new_options_len > options->max_len )
321                 return -ENOMEM;
322         if ( encapsulator ) {
323                 new_encapsulator_len = ( encapsulator->len + delta );
324                 if ( new_encapsulator_len > DHCP_MAX_LEN )
325                         return -ENOMEM;
326                 encapsulator->len = new_encapsulator_len;
327         }
328         options->len = new_options_len;
329
330         /* Move remainder of option data */
331         memmove ( dest, source, ( end - dest ) );
332
333         return 0;
334 }
335
336 /**
337  * Set value of DHCP option
338  *
339  * @v options           DHCP option block
340  * @v tag               DHCP option tag
341  * @v data              New value for DHCP option
342  * @v len               Length of value, in bytes
343  * @ret option          DHCP option, or NULL
344  *
345  * Sets the value of a DHCP option within the options block.  The
346  * option may or may not already exist.  Encapsulators will be created
347  * (and deleted) as necessary.
348  *
349  * This call may fail due to insufficient space in the options block.
350  * If it does fail, and the option existed previously, the option will
351  * be left with its original value.
352  */
353 struct dhcp_option * set_dhcp_option ( struct dhcp_option_block *options,
354                                        unsigned int tag,
355                                        const void *data, size_t len ) {
356         static const uint8_t empty_encapsulator[] = { DHCP_END };
357         struct dhcp_option *option;
358         void *insertion_point = options->data;
359         struct dhcp_option *encapsulator = NULL;
360         unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag );
361         size_t old_len = 0;
362         size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 );
363
364         /* Find old instance of this option, if any */
365         option = find_dhcp_option_with_encap ( options, tag, &encapsulator );
366         if ( option ) {
367                 old_len = dhcp_option_len ( option );
368                 DBG ( "Resizing DHCP option %s from length %d to %d\n",
369                       dhcp_tag_name ( tag ), option->len, len );
370         } else {
371                 old_len = 0;
372                 DBG ( "Creating DHCP option %s (length %d)\n",
373                       dhcp_tag_name ( tag ), new_len );
374         }
375         
376         /* Ensure that encapsulator exists, if required */
377         if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
378                 if ( ! encapsulator )
379                         encapsulator = set_dhcp_option ( options, encap_tag,
380                                                          empty_encapsulator,
381                                                 sizeof ( empty_encapsulator) );
382                 if ( ! encapsulator )
383                         return NULL;
384                 insertion_point = &encapsulator->data;
385         }
386
387         /* Create new option if necessary */
388         if ( ! option )
389                 option = insertion_point;
390         
391         /* Resize option to fit new data */
392         if ( resize_dhcp_option ( options, option, encapsulator,
393                                   old_len, new_len ) != 0 )
394                 return NULL;
395
396         /* Copy new data into option, if applicable */
397         if ( len ) {
398                 option->tag = tag;
399                 option->len = len;
400                 memcpy ( &option->data, data, len );
401         }
402
403         /* Delete encapsulator if there's nothing else left in it */
404         if ( encapsulator && ( encapsulator->len <= 1 ) )
405                 set_dhcp_option ( options, encap_tag, NULL, 0 );
406
407         return option;
408 }
409
410 /**
411  * Find DHCP option within all registered DHCP options blocks
412  *
413  * @v tag               DHCP option tag to search for
414  * @ret option          DHCP option, or NULL if not found
415  *
416  * This function exists merely as a notational shorthand for
417  * find_dhcp_option() with @c options set to NULL.
418  */
419 struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
420         return find_dhcp_option ( NULL, tag );
421 }
422
423 /**
424  * Find DHCP numerical option, and return its value
425  *
426  * @v options           DHCP options block
427  * @v tag               DHCP option tag to search for
428  * @ret value           Numerical value of the option, or 0 if not found
429  *
430  * This function exists merely as a notational shorthand for a call to
431  * find_dhcp_option() followed by a call to dhcp_num_option().  It is
432  * not possible to distinguish between the cases "option not found"
433  * and "option has a value of zero" using this function; if this
434  * matters to you then issue the two constituent calls directly and
435  * check that find_dhcp_option() returns a non-NULL value.
436  */
437 unsigned long find_dhcp_num_option ( struct dhcp_option_block *options,
438                                      unsigned int tag ) {
439         return dhcp_num_option ( find_dhcp_option ( options, tag ) );
440 }
441
442 /**
443  * Find DHCP numerical option, and return its value
444  *
445  * @v tag               DHCP option tag to search for
446  * @ret value           Numerical value of the option, or 0 if not found
447  *
448  * This function exists merely as a notational shorthand for a call to
449  * find_global_dhcp_option() followed by a call to dhcp_num_option().
450  * It is not possible to distinguish between the cases "option not
451  * found" and "option has a value of zero" using this function; if
452  * this matters to you then issue the two constituent calls directly
453  * and check that find_global_dhcp_option() returns a non-NULL value.
454  */
455 unsigned long find_global_dhcp_num_option ( unsigned int tag ) {
456         return dhcp_num_option ( find_global_dhcp_option ( tag ) );
457 }
458
459 /**
460  * Delete DHCP option
461  *
462  * @v options           DHCP options block
463  * @v tag               DHCP option tag
464  *
465  * This function exists merely as a notational shorthand for a call to
466  * set_dhcp_option() with @c len set to zero.
467  */
468 void delete_dhcp_option ( struct dhcp_option_block *options,
469                           unsigned int tag ) {
470         set_dhcp_option ( options, tag, NULL, 0 );
471 }