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