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