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