Update DHCP to use data-xfer interface (not yet tested).
[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 <stdlib.h>
21 #include <stdio.h>
22 #include <byteswap.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <assert.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  * Print DHCP string option value into buffer
106  *
107  * @v buf               Buffer into which to write the string
108  * @v size              Size of buffer
109  * @v option            DHCP option, or NULL
110  * @ret len             Length of formatted string
111  *
112  * DHCP option strings are stored without a NUL terminator.  This
113  * function provides a convenient way to extract these DHCP strings
114  * into standard C strings.  It is permitted to call dhcp_snprintf()
115  * with @c option set to NULL; in this case the buffer will be filled
116  * with an empty string.
117  *
118  * The usual snprintf() semantics apply with regard to buffer size,
119  * return value when the buffer is too small, etc.
120  */
121 int dhcp_snprintf ( char *buf, size_t size, struct dhcp_option *option ) {
122         size_t len;
123         char *content = "";
124
125         if ( option ) {
126                 /* Shrink buffer size so that it is only just large
127                  * enough to contain the option data.  snprintf() will
128                  * take care of everything else (inserting the NUL etc.)
129                  */
130                 len = ( option->len + 1 );
131                 if ( len < size )
132                         size = len;
133                 content = option->data.string;
134         }
135         return snprintf ( buf, size, "%s", content );
136 }
137
138 /**
139  * Calculate length of a normal DHCP option
140  *
141  * @v option            DHCP option
142  * @ret len             Length (including tag and length field)
143  *
144  * @c option may not be a @c DHCP_PAD or @c DHCP_END option.
145  */
146 static inline unsigned int dhcp_option_len ( struct dhcp_option *option ) {
147         assert ( option->tag != DHCP_PAD );
148         assert ( option->tag != DHCP_END );
149         return ( option->len + DHCP_OPTION_HEADER_LEN );
150 }
151
152 /**
153  * Calculate length of any DHCP option
154  *
155  * @v option            DHCP option
156  * @ret len             Length (including tag and length field)
157  */
158 static inline unsigned int dhcp_any_option_len ( struct dhcp_option *option ) {
159         if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) {
160                 return 1;
161         } else {
162                 return dhcp_option_len ( option );
163         }
164 }
165
166 /**
167  * Find DHCP option within DHCP options block, and its encapsulator (if any)
168  *
169  * @v options           DHCP options block
170  * @v tag               DHCP option tag to search for
171  * @ret encapsulator    Encapsulating DHCP option
172  * @ret option          DHCP option, or NULL if not found
173  *
174  * Searches for the DHCP option matching the specified tag within the
175  * DHCP option block.  Encapsulated options may be searched for by
176  * using DHCP_ENCAP_OPT() to construct the tag value.
177  *
178  * If the option is encapsulated, and @c encapsulator is non-NULL, it
179  * will be filled in with a pointer to the encapsulating option.
180  *
181  * This routine is designed to be paranoid.  It does not assume that
182  * the option data is well-formatted, and so must guard against flaws
183  * such as options missing a @c DHCP_END terminator, or options whose
184  * length would take them beyond the end of the data block.
185  */
186 static struct dhcp_option *
187 find_dhcp_option_with_encap ( struct dhcp_option_block *options,
188                               unsigned int tag,
189                               struct dhcp_option **encapsulator ) {
190         unsigned int original_tag __attribute__ (( unused )) = tag;
191         struct dhcp_option *option = options->data;
192         ssize_t remaining = options->len;
193         unsigned int option_len;
194
195         while ( remaining ) {
196                 /* Calculate length of this option.  Abort processing
197                  * if the length is malformed (i.e. takes us beyond
198                  * the end of the data block).
199                  */
200                 option_len = dhcp_any_option_len ( option );
201                 remaining -= option_len;
202                 if ( remaining < 0 )
203                         break;
204                 /* Check for matching tag */
205                 if ( option->tag == tag ) {
206                         DBG ( "Found DHCP option %s (length %d) in block %p\n",
207                               dhcp_tag_name ( original_tag ), option->len,
208                               options );
209                         return option;
210                 }
211                 /* Check for explicit end marker */
212                 if ( option->tag == DHCP_END )
213                         break;
214                 /* Check for start of matching encapsulation block */
215                 if ( DHCP_IS_ENCAP_OPT ( tag ) &&
216                      ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
217                         if ( encapsulator )
218                                 *encapsulator = option;
219                         /* Continue search within encapsulated option block */
220                         tag = DHCP_ENCAPSULATED ( tag );
221                         remaining = option->len;
222                         option = ( void * ) &option->data;
223                         continue;
224                 }
225                 option = ( ( ( void * ) option ) + option_len );
226         }
227         return NULL;
228 }
229
230 /**
231  * Find DHCP option within DHCP options block
232  *
233  * @v options           DHCP options block, or NULL
234  * @v tag               DHCP option tag to search for
235  * @ret option          DHCP option, or NULL if not found
236  *
237  * Searches for the DHCP option matching the specified tag within the
238  * DHCP option block.  Encapsulated options may be searched for by
239  * using DHCP_ENCAP_OPT() to construct the tag value.
240  *
241  * If @c options is NULL, all registered option blocks will be
242  * searched.  Where multiple option blocks contain the same DHCP
243  * option, the option from the highest-priority block will be
244  * returned.  (Priority of an options block is determined by the value
245  * of the @c DHCP_EB_PRIORITY option within the block, if present; the
246  * default priority is zero).
247  */
248 struct dhcp_option * find_dhcp_option ( struct dhcp_option_block *options,
249                                         unsigned int tag ) {
250         struct dhcp_option *option;
251
252         if ( options ) {
253                 return find_dhcp_option_with_encap ( options, tag, NULL );
254         } else {
255                 list_for_each_entry ( options, &option_blocks, list ) {
256                         if ( ( option = find_dhcp_option ( options, tag ) ) )
257                                 return option;
258                 }
259                 return NULL;
260         }
261 }
262
263 /**
264  * Register DHCP option block
265  *
266  * @v options           DHCP option block
267  *
268  * Register a block of DHCP options.
269  */
270 void register_dhcp_options ( struct dhcp_option_block *options ) {
271         struct dhcp_option_block *existing;
272
273         /* Determine priority of new block */
274         options->priority = find_dhcp_num_option ( options, DHCP_EB_PRIORITY );
275         DBG ( "Registering DHCP options block %p with priority %d\n",
276               options, options->priority );
277
278         /* Insert after any existing blocks which have a higher priority */
279         list_for_each_entry ( existing, &option_blocks, list ) {
280                 if ( options->priority > existing->priority )
281                         break;
282         }
283         dhcpopt_get ( options );
284         list_add_tail ( &options->list, &existing->list );
285 }
286
287 /**
288  * Unregister DHCP option block
289  *
290  * @v options           DHCP option block
291  */
292 void unregister_dhcp_options ( struct dhcp_option_block *options ) {
293         list_del ( &options->list );
294         dhcpopt_put ( options );
295 }
296
297 /**
298  * Initialise empty block of DHCP options
299  *
300  * @v options           Uninitialised DHCP option block
301  * @v data              Memory for DHCP option data
302  * @v max_len           Length of memory for DHCP option data
303  *
304  * Populates the DHCP option data with a single @c DHCP_END option and
305  * fills in the fields of the @c dhcp_option_block structure.
306  */
307 void init_dhcp_options ( struct dhcp_option_block *options,
308                          void *data, size_t max_len ) {
309         struct dhcp_option *option;
310
311         options->data = data;
312         options->max_len = max_len;
313         option = options->data;
314         option->tag = DHCP_END;
315         options->len = 1;
316
317         DBG ( "DHCP options block %p initialised (data %p max_len %#zx)\n",
318               options, options->data, options->max_len );
319 }
320
321 /**
322  * Allocate space for a block of DHCP options
323  *
324  * @v max_len           Maximum length of option block
325  * @ret options         DHCP option block, or NULL
326  *
327  * Creates a new DHCP option block and populates it with an empty
328  * options list.  This call does not register the options block.
329  */
330 struct dhcp_option_block * alloc_dhcp_options ( size_t max_len ) {
331         struct dhcp_option_block *options;
332
333         options = malloc ( sizeof ( *options ) + max_len );
334         if ( options ) {
335                 init_dhcp_options ( options, 
336                                     ( (void *) options + sizeof ( *options ) ),
337                                     max_len );
338         }
339         return options;
340 }
341
342 /**
343  * Resize a DHCP option
344  *
345  * @v options           DHCP option block
346  * @v option            DHCP option to resize
347  * @v encapsulator      Encapsulating option (or NULL)
348  * @v old_len           Old length (including header)
349  * @v new_len           New length (including header)
350  * @ret rc              Return status code
351  */
352 static int resize_dhcp_option ( struct dhcp_option_block *options,
353                                 struct dhcp_option *option,
354                                 struct dhcp_option *encapsulator,
355                                 size_t old_len, size_t new_len ) {
356         void *source = ( ( ( void * ) option ) + old_len );
357         void *dest = ( ( ( void * ) option ) + new_len );
358         void *end = ( options->data + options->max_len );
359         ssize_t delta = ( new_len - old_len );
360         size_t new_options_len;
361         size_t new_encapsulator_len;
362
363         /* Check for sufficient space, and update length fields */
364         if ( new_len > DHCP_MAX_LEN )
365                 return -ENOMEM;
366         new_options_len = ( options->len + delta );
367         if ( new_options_len > options->max_len )
368                 return -ENOMEM;
369         if ( encapsulator ) {
370                 new_encapsulator_len = ( encapsulator->len + delta );
371                 if ( new_encapsulator_len > DHCP_MAX_LEN )
372                         return -ENOMEM;
373                 encapsulator->len = new_encapsulator_len;
374         }
375         options->len = new_options_len;
376
377         /* Move remainder of option data */
378         memmove ( dest, source, ( end - dest ) );
379
380         return 0;
381 }
382
383 /**
384  * Set value of DHCP option
385  *
386  * @v options           DHCP option block
387  * @v tag               DHCP option tag
388  * @v data              New value for DHCP option
389  * @v len               Length of value, in bytes
390  * @ret option          DHCP option, or NULL
391  *
392  * Sets the value of a DHCP option within the options block.  The
393  * option may or may not already exist.  Encapsulators will be created
394  * (and deleted) as necessary.
395  *
396  * This call may fail due to insufficient space in the options block.
397  * If it does fail, and the option existed previously, the option will
398  * be left with its original value.
399  */
400 struct dhcp_option * set_dhcp_option ( struct dhcp_option_block *options,
401                                        unsigned int tag,
402                                        const void *data, size_t len ) {
403         static const uint8_t empty_encapsulator[] = { DHCP_END };
404         struct dhcp_option *option;
405         void *insertion_point;
406         struct dhcp_option *encapsulator = NULL;
407         unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag );
408         size_t old_len = 0;
409         size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 );
410
411         /* Return NULL if no options block specified */
412         if ( ! options )
413                 return NULL;
414
415         /* Find old instance of this option, if any */
416         option = find_dhcp_option_with_encap ( options, tag, &encapsulator );
417         if ( option ) {
418                 old_len = dhcp_option_len ( option );
419                 DBG ( "Resizing DHCP option %s from length %d to %d in block "
420                       "%p\n", dhcp_tag_name (tag), option->len, len, options );
421         } else {
422                 old_len = 0;
423                 DBG ( "Creating DHCP option %s (length %d) in block %p\n",
424                       dhcp_tag_name ( tag ), len, options );
425         }
426         
427         /* Ensure that encapsulator exists, if required */
428         insertion_point = options->data;
429         if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
430                 if ( ! encapsulator )
431                         encapsulator = set_dhcp_option ( options, encap_tag,
432                                                          empty_encapsulator,
433                                                 sizeof ( empty_encapsulator) );
434                 if ( ! encapsulator )
435                         return NULL;
436                 insertion_point = &encapsulator->data;
437         }
438
439         /* Create new option if necessary */
440         if ( ! option )
441                 option = insertion_point;
442         
443         /* Resize option to fit new data */
444         if ( resize_dhcp_option ( options, option, encapsulator,
445                                   old_len, new_len ) != 0 )
446                 return NULL;
447
448         /* Copy new data into option, if applicable */
449         if ( len ) {
450                 option->tag = tag;
451                 option->len = len;
452                 memcpy ( &option->data, data, len );
453         }
454
455         /* Delete encapsulator if there's nothing else left in it */
456         if ( encapsulator && ( encapsulator->len <= 1 ) )
457                 set_dhcp_option ( options, encap_tag, NULL, 0 );
458
459         return option;
460 }
461
462 /**
463  * Find DHCP option within all registered DHCP options blocks
464  *
465  * @v tag               DHCP option tag to search for
466  * @ret option          DHCP option, or NULL if not found
467  *
468  * This function exists merely as a notational shorthand for
469  * find_dhcp_option() with @c options set to NULL.
470  */
471 struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
472         return find_dhcp_option ( NULL, tag );
473 }
474
475 /**
476  * Find DHCP numerical option, and return its value
477  *
478  * @v options           DHCP options block
479  * @v tag               DHCP option tag to search for
480  * @ret value           Numerical value of the option, or 0 if not found
481  *
482  * This function exists merely as a notational shorthand for a call to
483  * find_dhcp_option() followed by a call to dhcp_num_option().  It is
484  * not possible to distinguish between the cases "option not found"
485  * and "option has a value of zero" using this function; if this
486  * matters to you then issue the two constituent calls directly and
487  * check that find_dhcp_option() returns a non-NULL value.
488  */
489 unsigned long find_dhcp_num_option ( struct dhcp_option_block *options,
490                                      unsigned int tag ) {
491         return dhcp_num_option ( find_dhcp_option ( options, tag ) );
492 }
493
494 /**
495  * Find DHCP numerical option, and return its value
496  *
497  * @v tag               DHCP option tag to search for
498  * @ret value           Numerical value of the option, or 0 if not found
499  *
500  * This function exists merely as a notational shorthand for a call to
501  * find_global_dhcp_option() followed by a call to dhcp_num_option().
502  * It is not possible to distinguish between the cases "option not
503  * found" and "option has a value of zero" using this function; if
504  * this matters to you then issue the two constituent calls directly
505  * and check that find_global_dhcp_option() returns a non-NULL value.
506  */
507 unsigned long find_global_dhcp_num_option ( unsigned int tag ) {
508         return dhcp_num_option ( find_global_dhcp_option ( tag ) );
509 }
510
511 /**
512  * Find DHCP IPv4-address option, and return its value
513  *
514  * @v options           DHCP options block
515  * @v tag               DHCP option tag to search for
516  * @v inp               IPv4 address to fill in
517  * @ret value           Numerical value of the option, or 0 if not found
518  *
519  * This function exists merely as a notational shorthand for a call to
520  * find_dhcp_option() followed by a call to dhcp_ipv4_option().  It is
521  * not possible to distinguish between the cases "option not found"
522  * and "option has a value of 0.0.0.0" using this function; if this
523  * matters to you then issue the two constituent calls directly and
524  * check that find_dhcp_option() returns a non-NULL value.
525  */
526 void find_dhcp_ipv4_option ( struct dhcp_option_block *options,
527                              unsigned int tag, struct in_addr *inp ) {
528         dhcp_ipv4_option ( find_dhcp_option ( options, tag ), inp );
529 }
530
531 /**
532  * Find DHCP IPv4-address option, and return its value
533  *
534  * @v options           DHCP options block
535  * @v tag               DHCP option tag to search for
536  * @v inp               IPv4 address to fill in
537  * @ret value           Numerical value of the option, or 0 if not found
538  *
539  * This function exists merely as a notational shorthand for a call to
540  * find_dhcp_option() followed by a call to dhcp_ipv4_option().  It is
541  * not possible to distinguish between the cases "option not found"
542  * and "option has a value of 0.0.0.0" using this function; if this
543  * matters to you then issue the two constituent calls directly and
544  * check that find_dhcp_option() returns a non-NULL value.
545  */
546 void find_global_dhcp_ipv4_option ( unsigned int tag, struct in_addr *inp ) {
547         dhcp_ipv4_option ( find_global_dhcp_option ( tag ), inp );
548 }
549
550 /**
551  * Delete DHCP option
552  *
553  * @v options           DHCP options block
554  * @v tag               DHCP option tag
555  *
556  * This function exists merely as a notational shorthand for a call to
557  * set_dhcp_option() with @c len set to zero.
558  */
559 void delete_dhcp_option ( struct dhcp_option_block *options,
560                           unsigned int tag ) {
561         set_dhcp_option ( options, tag, NULL, 0 );
562 }