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