Add code to modify DHCP option values within a block.
[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 <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  * Allocate space for a block of DHCP options
232  *
233  * @v max_len           Maximum length of option block
234  * @ret options         DHCP option block, or NULL
235  *
236  * Creates a new DHCP option block and populates it with an empty
237  * options list.  This call does not register the options block.
238  */
239 struct dhcp_option_block * alloc_dhcp_options ( size_t max_len ) {
240         struct dhcp_option_block *options;
241         struct dhcp_option *option;
242
243         options = malloc ( sizeof ( *options ) + max_len );
244         if ( options ) {
245                 options->data = ( ( void * ) options + sizeof ( *options ) );
246                 options->max_len = max_len;
247                 if ( max_len ) {
248                         option = options->data;
249                         option->tag = DHCP_END;
250                         options->len = 1;
251                 }
252         }
253         return options;
254 }
255
256 /**
257  * Free DHCP options block
258  *
259  * @v options           DHCP option block
260  */
261 void free_dhcp_options ( struct dhcp_option_block *options ) {
262         free ( options );
263 }
264
265 /**
266  * Resize a DHCP option
267  *
268  * @v options           DHCP option block
269  * @v option            DHCP option to resize
270  * @v encapsulator      Encapsulating option (or NULL)
271  * @v old_len           Old length (including header)
272  * @v new_len           New length (including header)
273  * @ret rc              Return status code
274  */
275 static int resize_dhcp_option ( struct dhcp_option_block *options,
276                                 struct dhcp_option *option,
277                                 struct dhcp_option *encapsulator,
278                                 size_t old_len, size_t new_len ) {
279         void *source = ( ( ( void * ) option ) + old_len );
280         void *dest = ( ( ( void * ) option ) + new_len );
281         void *end = ( options->data + options->max_len );
282         ssize_t delta = ( new_len - old_len );
283         size_t new_options_len;
284         size_t new_encapsulator_len;
285
286         /* Check for sufficient space, and update length fields */
287         if ( new_len > DHCP_MAX_LEN )
288                 return -ENOMEM;
289         new_options_len = ( options->len + delta );
290         if ( new_options_len > options->max_len )
291                 return -ENOMEM;
292         if ( encapsulator ) {
293                 new_encapsulator_len = ( encapsulator->len + delta );
294                 if ( new_encapsulator_len > DHCP_MAX_LEN )
295                         return -ENOMEM;
296                 encapsulator->len = new_encapsulator_len;
297         }
298         options->len = new_options_len;
299
300         /* Move remainder of option data */
301         memmove ( dest, source, ( end - dest ) );
302
303         return 0;
304 }
305
306 /**
307  * Set value of DHCP option
308  *
309  * @v options           DHCP option block
310  * @v tag               DHCP option tag
311  * @v data              New value for DHCP option
312  * @v len               Length of value, in bytes
313  * @ret option          DHCP option, or NULL
314  *
315  * Sets the value of a DHCP option within the options block.  The
316  * option may or may not already exist.  Encapsulators will be created
317  * (and deleted) as necessary.
318  *
319  * This call may fail due to insufficient space in the options block.
320  * If it does fail, and the option existed previously, the option will
321  * be left with its original value.
322  */
323 struct dhcp_option * set_dhcp_option ( struct dhcp_option_block *options,
324                                        unsigned int tag,
325                                        const void *data, size_t len ) {
326         static const uint8_t empty_encapsulator[] = { DHCP_END };
327         struct dhcp_option *option;
328         void *insertion_point = options->data;
329         struct dhcp_option *encapsulator = NULL;
330         unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag );
331         size_t old_len = 0;
332         size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 );
333
334         /* Find old instance of this option, if any */
335         option = find_dhcp_option ( options, tag );
336         if ( option ) {
337                 old_len = dhcp_option_len ( option );
338                 DBG ( "Resizing DHCP option %s from length %d to %d\n",
339                       dhcp_tag_name ( tag ), option->len, len );
340         } else {
341                 old_len = 0;
342                 DBG ( "Creating DHCP option %s (length %d)\n",
343                       dhcp_tag_name ( tag ), new_len );
344         }
345         
346         /* Ensure that encapsulator exists, if required */
347         if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
348                 encapsulator = find_dhcp_option ( options, encap_tag );
349                 if ( ! encapsulator )
350                         encapsulator = set_dhcp_option ( options, encap_tag,
351                                                          empty_encapsulator,
352                                                 sizeof ( empty_encapsulator) );
353                 if ( ! encapsulator )
354                         return NULL;
355                 insertion_point = &encapsulator->data;
356         }
357
358         /* Create new option if necessary */
359         if ( ! option )
360                 option = insertion_point;
361         
362         /* Resize option to fit new data */
363         if ( resize_dhcp_option ( options, option, encapsulator,
364                                   old_len, new_len ) != 0 )
365                 return NULL;
366
367         /* Copy new data into option, if applicable */
368         if ( len ) {
369                 option->tag = tag;
370                 option->len = len;
371                 memcpy ( &option->data, data, len );
372         }
373
374         /* Delete encapsulator if there's nothing else left in it */
375         if ( encapsulator && ( encapsulator->len <= 1 ) )
376                 set_dhcp_option ( options, encap_tag, NULL, 0 );
377
378         return option;
379 }