ed53916c63d0e66ad847b9ed8efee5c32aa8dcb4
[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 <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 LIST_HEAD ( dhcp_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, &dhcp_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, &dhcp_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         /* Apply all registered DHCP options */
294         apply_global_dhcp_options();
295 }
296
297 /**
298  * Unregister DHCP option block
299  *
300  * @v options           DHCP option block
301  */
302 void unregister_dhcp_options ( struct dhcp_option_block *options ) {
303         list_del ( &options->list );
304         dhcpopt_put ( options );
305 }
306
307 /**
308  * Initialise empty block of DHCP options
309  *
310  * @v options           Uninitialised DHCP option block
311  * @v data              Memory for DHCP option data
312  * @v max_len           Length of memory for DHCP option data
313  *
314  * Populates the DHCP option data with a single @c DHCP_END option and
315  * fills in the fields of the @c dhcp_option_block structure.
316  */
317 void init_dhcp_options ( struct dhcp_option_block *options,
318                          void *data, size_t max_len ) {
319         struct dhcp_option *option;
320
321         options->data = data;
322         options->max_len = max_len;
323         option = options->data;
324         option->tag = DHCP_END;
325         options->len = 1;
326
327         DBG ( "DHCP options block %p initialised (data %p max_len %#zx)\n",
328               options, options->data, options->max_len );
329 }
330
331 /**
332  * Allocate space for a block of DHCP options
333  *
334  * @v max_len           Maximum length of option block
335  * @ret options         DHCP option block, or NULL
336  *
337  * Creates a new DHCP option block and populates it with an empty
338  * options list.  This call does not register the options block.
339  */
340 struct dhcp_option_block * alloc_dhcp_options ( size_t max_len ) {
341         struct dhcp_option_block *options;
342
343         options = malloc ( sizeof ( *options ) + max_len );
344         if ( options ) {
345                 init_dhcp_options ( options, 
346                                     ( (void *) options + sizeof ( *options ) ),
347                                     max_len );
348         }
349         return options;
350 }
351
352 /**
353  * Resize a DHCP option
354  *
355  * @v options           DHCP option block
356  * @v option            DHCP option to resize
357  * @v encapsulator      Encapsulating option (or NULL)
358  * @v old_len           Old length (including header)
359  * @v new_len           New length (including header)
360  * @ret rc              Return status code
361  */
362 static int resize_dhcp_option ( struct dhcp_option_block *options,
363                                 struct dhcp_option *option,
364                                 struct dhcp_option *encapsulator,
365                                 size_t old_len, size_t new_len ) {
366         void *source = ( ( ( void * ) option ) + old_len );
367         void *dest = ( ( ( void * ) option ) + new_len );
368         void *end = ( options->data + options->max_len );
369         ssize_t delta = ( new_len - old_len );
370         size_t new_options_len;
371         size_t new_encapsulator_len;
372
373         /* Check for sufficient space, and update length fields */
374         if ( new_len > DHCP_MAX_LEN )
375                 return -ENOMEM;
376         new_options_len = ( options->len + delta );
377         if ( new_options_len > options->max_len )
378                 return -ENOMEM;
379         if ( encapsulator ) {
380                 new_encapsulator_len = ( encapsulator->len + delta );
381                 if ( new_encapsulator_len > DHCP_MAX_LEN )
382                         return -ENOMEM;
383                 encapsulator->len = new_encapsulator_len;
384         }
385         options->len = new_options_len;
386
387         /* Move remainder of option data */
388         memmove ( dest, source, ( end - dest ) );
389
390         return 0;
391 }
392
393 /**
394  * Set value of DHCP option
395  *
396  * @v options           DHCP option block
397  * @v tag               DHCP option tag
398  * @v data              New value for DHCP option
399  * @v len               Length of value, in bytes
400  * @ret option          DHCP option, or NULL
401  *
402  * Sets the value of a DHCP option within the options block.  The
403  * option may or may not already exist.  Encapsulators will be created
404  * (and deleted) as necessary.
405  *
406  * This call may fail due to insufficient space in the options block.
407  * If it does fail, and the option existed previously, the option will
408  * be left with its original value.
409  */
410 struct dhcp_option * set_dhcp_option ( struct dhcp_option_block *options,
411                                        unsigned int tag,
412                                        const void *data, size_t len ) {
413         static const uint8_t empty_encapsulator[] = { DHCP_END };
414         struct dhcp_option *option;
415         void *insertion_point;
416         struct dhcp_option *encapsulator = NULL;
417         unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag );
418         size_t old_len = 0;
419         size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 );
420
421         /* Return NULL if no options block specified */
422         if ( ! options )
423                 return NULL;
424
425         /* Find old instance of this option, if any */
426         option = find_dhcp_option_with_encap ( options, tag, &encapsulator );
427         if ( option ) {
428                 old_len = dhcp_option_len ( option );
429                 DBG ( "Resizing DHCP option %s from length %d to %zd in block "
430                       "%p\n", dhcp_tag_name (tag), option->len, len, options );
431         } else {
432                 old_len = 0;
433                 DBG ( "Creating DHCP option %s (length %zd) in block %p\n",
434                       dhcp_tag_name ( tag ), len, options );
435         }
436         
437         /* Ensure that encapsulator exists, if required */
438         insertion_point = options->data;
439         if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
440                 if ( ! encapsulator )
441                         encapsulator = set_dhcp_option ( options, encap_tag,
442                                                          empty_encapsulator,
443                                                 sizeof ( empty_encapsulator) );
444                 if ( ! encapsulator )
445                         return NULL;
446                 insertion_point = &encapsulator->data;
447         }
448
449         /* Create new option if necessary */
450         if ( ! option )
451                 option = insertion_point;
452         
453         /* Resize option to fit new data */
454         if ( resize_dhcp_option ( options, option, encapsulator,
455                                   old_len, new_len ) != 0 )
456                 return NULL;
457
458         /* Copy new data into option, if applicable */
459         if ( len ) {
460                 option->tag = tag;
461                 option->len = len;
462                 memcpy ( &option->data, data, len );
463         }
464
465         /* Delete encapsulator if there's nothing else left in it */
466         if ( encapsulator && ( encapsulator->len <= 1 ) )
467                 set_dhcp_option ( options, encap_tag, NULL, 0 );
468
469         return option;
470 }
471
472 /**
473  * Find DHCP option within all registered DHCP options blocks
474  *
475  * @v tag               DHCP option tag to search for
476  * @ret option          DHCP option, or NULL if not found
477  *
478  * This function exists merely as a notational shorthand for
479  * find_dhcp_option() with @c options set to NULL.
480  */
481 struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
482         return find_dhcp_option ( NULL, tag );
483 }
484
485 /**
486  * Find DHCP numerical option, and return its value
487  *
488  * @v options           DHCP options block
489  * @v tag               DHCP option tag to search for
490  * @ret value           Numerical value of the option, or 0 if not found
491  *
492  * This function exists merely as a notational shorthand for a call to
493  * find_dhcp_option() followed by a call to dhcp_num_option().  It is
494  * not possible to distinguish between the cases "option not found"
495  * and "option has a value of zero" using this function; if this
496  * matters to you then issue the two constituent calls directly and
497  * check that find_dhcp_option() returns a non-NULL value.
498  */
499 unsigned long find_dhcp_num_option ( struct dhcp_option_block *options,
500                                      unsigned int tag ) {
501         return dhcp_num_option ( find_dhcp_option ( options, tag ) );
502 }
503
504 /**
505  * Find DHCP numerical option, and return its value
506  *
507  * @v tag               DHCP option tag to search for
508  * @ret value           Numerical value of the option, or 0 if not found
509  *
510  * This function exists merely as a notational shorthand for a call to
511  * find_global_dhcp_option() followed by a call to dhcp_num_option().
512  * It is not possible to distinguish between the cases "option not
513  * found" and "option has a value of zero" using this function; if
514  * this matters to you then issue the two constituent calls directly
515  * and check that find_global_dhcp_option() returns a non-NULL value.
516  */
517 unsigned long find_global_dhcp_num_option ( unsigned int tag ) {
518         return dhcp_num_option ( find_global_dhcp_option ( tag ) );
519 }
520
521 /**
522  * Find DHCP IPv4-address option, and return its value
523  *
524  * @v options           DHCP options block
525  * @v tag               DHCP option tag to search for
526  * @v inp               IPv4 address to fill in
527  * @ret value           Numerical value of the option, or 0 if not found
528  *
529  * This function exists merely as a notational shorthand for a call to
530  * find_dhcp_option() followed by a call to dhcp_ipv4_option().  It is
531  * not possible to distinguish between the cases "option not found"
532  * and "option has a value of 0.0.0.0" using this function; if this
533  * matters to you then issue the two constituent calls directly and
534  * check that find_dhcp_option() returns a non-NULL value.
535  */
536 void find_dhcp_ipv4_option ( struct dhcp_option_block *options,
537                              unsigned int tag, struct in_addr *inp ) {
538         dhcp_ipv4_option ( find_dhcp_option ( options, tag ), inp );
539 }
540
541 /**
542  * Find DHCP IPv4-address option, and return its value
543  *
544  * @v options           DHCP options block
545  * @v tag               DHCP option tag to search for
546  * @v inp               IPv4 address to fill in
547  * @ret value           Numerical value of the option, or 0 if not found
548  *
549  * This function exists merely as a notational shorthand for a call to
550  * find_dhcp_option() followed by a call to dhcp_ipv4_option().  It is
551  * not possible to distinguish between the cases "option not found"
552  * and "option has a value of 0.0.0.0" using this function; if this
553  * matters to you then issue the two constituent calls directly and
554  * check that find_dhcp_option() returns a non-NULL value.
555  */
556 void find_global_dhcp_ipv4_option ( unsigned int tag, struct in_addr *inp ) {
557         dhcp_ipv4_option ( find_global_dhcp_option ( tag ), inp );
558 }
559
560 /**
561  * Delete DHCP option
562  *
563  * @v options           DHCP options block
564  * @v tag               DHCP option tag
565  *
566  * This function exists merely as a notational shorthand for a call to
567  * set_dhcp_option() with @c len set to zero.
568  */
569 void delete_dhcp_option ( struct dhcp_option_block *options,
570                           unsigned int tag ) {
571         set_dhcp_option ( options, tag, NULL, 0 );
572 }
573
574 /**
575  * Apply DHCP options
576  *
577  * @v options           DHCP options block, or NULL
578  * @ret rc              Return status code
579  */
580 int apply_dhcp_options ( struct dhcp_option_block *options ) {
581         struct dhcp_option_applicator *applicator;
582         struct dhcp_option *option;
583         struct in_addr tftp_server;
584         struct uri *uri;
585         char uri_string[32];
586         unsigned int tag;
587         int rc;
588
589         /* Set current working URI based on TFTP server */
590         find_dhcp_ipv4_option ( options, DHCP_EB_SIADDR, &tftp_server );
591         snprintf ( uri_string, sizeof ( uri_string ),
592                    "tftp://%s/", inet_ntoa ( tftp_server ) );
593         uri = parse_uri ( uri_string );
594         if ( ! uri )
595                 return -ENOMEM;
596         churi ( uri );
597         uri_put ( uri );
598
599         /* Call all registered DHCP option applicators */
600         for ( applicator = dhcp_option_applicators ;
601               applicator < dhcp_option_applicators_end ; applicator++ ) {
602                 tag = applicator->tag;
603                 option = find_dhcp_option ( options, tag );
604                 if ( ! option )
605                         continue;
606                 if ( ( rc = applicator->apply ( tag, option ) ) != 0 ) {
607                         DBG ( "Could not apply DHCP option %s: %s\n",
608                               dhcp_tag_name ( tag ), strerror ( rc ) );
609                         return rc;
610                 }
611         }
612
613         return 0;
614 }
615
616 /**
617  * Apply global DHCP options
618  *
619  * @ret rc              Return status code
620  */
621 int apply_global_dhcp_options ( void ) {
622         return apply_dhcp_options ( NULL );
623 }