These changes are the raw update to qemu-2.6.
[kvmfornfv.git] / qemu / roms / ipxe / src / net / dhcpopts.c
1 /*
2  * Copyright (C) 2008 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., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301, USA.
18  *
19  * You can also choose to distribute this program under the terms of
20  * the Unmodified Binary Distribution Licence (as given in the file
21  * COPYING.UBDL), provided that you have satisfied its requirements.
22  */
23
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <errno.h>
30 #include <string.h>
31 #include <ipxe/dhcp.h>
32 #include <ipxe/dhcpopts.h>
33
34 /** @file
35  *
36  * DHCP options
37  *
38  */
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  * Get pointer to DHCP option
62  *
63  * @v options           DHCP options block
64  * @v offset            Offset within options block
65  * @ret option          DHCP option
66  */
67 static inline __attribute__ (( always_inline )) struct dhcp_option *
68 dhcp_option ( struct dhcp_options *options, unsigned int offset ) {
69         return ( ( struct dhcp_option * ) ( options->data + offset ) );
70 }
71
72 /**
73  * Get offset of a DHCP option
74  *
75  * @v options           DHCP options block
76  * @v option            DHCP option
77  * @ret offset          Offset within options block
78  */
79 static inline __attribute__ (( always_inline )) int
80 dhcp_option_offset ( struct dhcp_options *options,
81                      struct dhcp_option *option ) {
82         return ( ( ( void * ) option ) - options->data );
83 }
84
85 /**
86  * Calculate length of any DHCP option
87  *
88  * @v option            DHCP option
89  * @ret len             Length (including tag and length field)
90  */
91 static unsigned int dhcp_option_len ( struct dhcp_option *option ) {
92         if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) {
93                 return 1;
94         } else {
95                 return ( option->len + DHCP_OPTION_HEADER_LEN );
96         }
97 }
98
99 /**
100  * Find DHCP option within DHCP options block, and its encapsulator (if any)
101  *
102  * @v options           DHCP options block
103  * @v tag               DHCP option tag to search for
104  * @ret encap_offset    Offset of encapsulating DHCP option
105  * @ret offset          Offset of DHCP option, or negative error
106  *
107  * Searches for the DHCP option matching the specified tag within the
108  * DHCP option block.  Encapsulated options may be searched for by
109  * using DHCP_ENCAP_OPT() to construct the tag value.
110  *
111  * If the option is encapsulated, and @c encap_offset is non-NULL, it
112  * will be filled in with the offset of the encapsulating option.
113  *
114  * This routine is designed to be paranoid.  It does not assume that
115  * the option data is well-formatted, and so must guard against flaws
116  * such as options missing a @c DHCP_END terminator, or options whose
117  * length would take them beyond the end of the data block.
118  */
119 static int find_dhcp_option_with_encap ( struct dhcp_options *options,
120                                          unsigned int tag,
121                                          int *encap_offset ) {
122         unsigned int original_tag __attribute__ (( unused )) = tag;
123         struct dhcp_option *option;
124         int offset = 0;
125         ssize_t remaining = options->used_len;
126         unsigned int option_len;
127
128         /* Sanity check */
129         if ( tag == DHCP_PAD )
130                 return -ENOENT;
131
132         /* Search for option */
133         while ( remaining ) {
134                 /* Calculate length of this option.  Abort processing
135                  * if the length is malformed (i.e. takes us beyond
136                  * the end of the data block).
137                  */
138                 option = dhcp_option ( options, offset );
139                 option_len = dhcp_option_len ( option );
140                 remaining -= option_len;
141                 if ( remaining < 0 )
142                         break;
143                 /* Check for explicit end marker */
144                 if ( option->tag == DHCP_END ) {
145                         if ( tag == DHCP_END )
146                                 /* Special case where the caller is interested
147                                  * in whether we have this marker or not.
148                                  */
149                                 return offset;
150                         else
151                                 break;
152                 }
153                 /* Check for matching tag */
154                 if ( option->tag == tag ) {
155                         DBGC ( options, "DHCPOPT %p found %s (length %d)\n",
156                                options, dhcp_tag_name ( original_tag ),
157                                option_len );
158                         return offset;
159                 }
160                 /* Check for start of matching encapsulation block */
161                 if ( DHCP_IS_ENCAP_OPT ( tag ) &&
162                      ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
163                         if ( encap_offset )
164                                 *encap_offset = offset;
165                         /* Continue search within encapsulated option block */
166                         tag = DHCP_ENCAPSULATED ( tag );
167                         remaining = option_len;
168                         offset += DHCP_OPTION_HEADER_LEN;
169                         continue;
170                 }
171                 offset += option_len;
172         }
173
174         return -ENOENT;
175 }
176
177 /**
178  * Refuse to reallocate DHCP option block
179  *
180  * @v options           DHCP option block
181  * @v len               New length
182  * @ret rc              Return status code
183  */
184 int dhcpopt_no_realloc ( struct dhcp_options *options, size_t len ) {
185         return ( ( len <= options->alloc_len ) ? 0 : -ENOSPC );
186 }
187
188 /**
189  * Resize a DHCP option
190  *
191  * @v options           DHCP option block
192  * @v offset            Offset of option to resize
193  * @v encap_offset      Offset of encapsulating offset (or -ve for none)
194  * @v old_len           Old length (including header)
195  * @v new_len           New length (including header)
196  * @ret rc              Return status code
197  */
198 static int resize_dhcp_option ( struct dhcp_options *options,
199                                 int offset, int encap_offset,
200                                 size_t old_len, size_t new_len ) {
201         struct dhcp_option *encapsulator;
202         struct dhcp_option *option;
203         ssize_t delta = ( new_len - old_len );
204         size_t old_alloc_len;
205         size_t new_used_len;
206         size_t new_encapsulator_len;
207         void *source;
208         void *dest;
209         int rc;
210
211         /* Check for sufficient space */
212         if ( new_len > DHCP_MAX_LEN ) {
213                 DBGC ( options, "DHCPOPT %p overlength option\n", options );
214                 return -ENOSPC;
215         }
216         new_used_len = ( options->used_len + delta );
217
218         /* Expand options block, if necessary */
219         if ( new_used_len > options->alloc_len ) {
220                 /* Reallocate options block */
221                 old_alloc_len = options->alloc_len;
222                 if ( ( rc = options->realloc ( options, new_used_len ) ) != 0 ){
223                         DBGC ( options, "DHCPOPT %p could not reallocate to "
224                                "%zd bytes\n", options, new_used_len );
225                         return rc;
226                 }
227                 /* Clear newly allocated space */
228                 memset ( ( options->data + old_alloc_len ), 0,
229                          ( options->alloc_len - old_alloc_len ) );
230         }
231
232         /* Update encapsulator, if applicable */
233         if ( encap_offset >= 0 ) {
234                 encapsulator = dhcp_option ( options, encap_offset );
235                 new_encapsulator_len = ( encapsulator->len + delta );
236                 if ( new_encapsulator_len > DHCP_MAX_LEN ) {
237                         DBGC ( options, "DHCPOPT %p overlength encapsulator\n",
238                                options );
239                         return -ENOSPC;
240                 }
241                 encapsulator->len = new_encapsulator_len;
242         }
243
244         /* Update used length */
245         options->used_len = new_used_len;
246
247         /* Move remainder of option data */
248         option = dhcp_option ( options, offset );
249         source = ( ( ( void * ) option ) + old_len );
250         dest = ( ( ( void * ) option ) + new_len );
251         memmove ( dest, source, ( new_used_len - offset - new_len ) );
252
253         /* Shrink options block, if applicable */
254         if ( new_used_len < options->alloc_len ) {
255                 if ( ( rc = options->realloc ( options, new_used_len ) ) != 0 ){
256                         DBGC ( options, "DHCPOPT %p could not reallocate to "
257                                "%zd bytes\n", options, new_used_len );
258                         return rc;
259                 }
260         }
261
262         return 0;
263 }
264
265 /**
266  * Set value of DHCP option
267  *
268  * @v options           DHCP option block
269  * @v tag               DHCP option tag
270  * @v data              New value for DHCP option
271  * @v len               Length of value, in bytes
272  * @ret offset          Offset of DHCP option, or negative error
273  *
274  * Sets the value of a DHCP option within the options block.  The
275  * option may or may not already exist.  Encapsulators will be created
276  * (and deleted) as necessary.
277  *
278  * This call may fail due to insufficient space in the options block.
279  * If it does fail, and the option existed previously, the option will
280  * be left with its original value.
281  */
282 static int set_dhcp_option ( struct dhcp_options *options, unsigned int tag,
283                              const void *data, size_t len ) {
284         static const uint8_t empty_encap[] = { DHCP_END };
285         int offset;
286         int encap_offset = -1;
287         int creation_offset;
288         struct dhcp_option *option;
289         unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag );
290         size_t old_len = 0;
291         size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 );
292         int rc;
293
294         /* Sanity check */
295         if ( tag == DHCP_PAD )
296                 return -ENOTTY;
297
298         creation_offset = find_dhcp_option_with_encap ( options, DHCP_END,
299                                                         NULL );
300         if ( creation_offset < 0 )
301                 creation_offset = options->used_len;
302         /* Find old instance of this option, if any */
303         offset = find_dhcp_option_with_encap ( options, tag, &encap_offset );
304         if ( offset >= 0 ) {
305                 old_len = dhcp_option_len ( dhcp_option ( options, offset ) );
306                 DBGC ( options, "DHCPOPT %p resizing %s from %zd to %zd\n",
307                        options, dhcp_tag_name ( tag ), old_len, new_len );
308         } else {
309                 DBGC ( options, "DHCPOPT %p creating %s (length %zd)\n",
310                        options, dhcp_tag_name ( tag ), new_len );
311         }
312
313         /* Ensure that encapsulator exists, if required */
314         if ( encap_tag ) {
315                 if ( encap_offset < 0 ) {
316                         encap_offset =
317                                 set_dhcp_option ( options, encap_tag,
318                                                   empty_encap,
319                                                   sizeof ( empty_encap ) );
320                 }
321                 if ( encap_offset < 0 )
322                         return encap_offset;
323                 creation_offset = ( encap_offset + DHCP_OPTION_HEADER_LEN );
324         }
325
326         /* Create new option if necessary */
327         if ( offset < 0 )
328                 offset = creation_offset;
329
330         /* Resize option to fit new data */
331         if ( ( rc = resize_dhcp_option ( options, offset, encap_offset,
332                                          old_len, new_len ) ) != 0 )
333                 return rc;
334
335         /* Copy new data into option, if applicable */
336         if ( len ) {
337                 option = dhcp_option ( options, offset );
338                 option->tag = tag;
339                 option->len = len;
340                 memcpy ( &option->data, data, len );
341         }
342
343         /* Delete encapsulator if there's nothing else left in it */
344         if ( encap_offset >= 0 ) {
345                 option = dhcp_option ( options, encap_offset );
346                 if ( option->len <= 1 )
347                         set_dhcp_option ( options, encap_tag, NULL, 0 );
348         }
349
350         return offset;
351 }
352
353 /**
354  * Check applicability of DHCP option setting
355  *
356  * @v tag               Setting tag number
357  * @ret applies         Setting applies to this option block
358  */
359 int dhcpopt_applies ( unsigned int tag ) {
360
361         return ( tag && ( tag <= DHCP_ENCAP_OPT ( DHCP_MAX_OPTION,
362                                                   DHCP_MAX_OPTION ) ) );
363 }
364
365 /**
366  * Store value of DHCP option setting
367  *
368  * @v options           DHCP option block
369  * @v tag               Setting tag number
370  * @v data              Setting data, or NULL to clear setting
371  * @v len               Length of setting data
372  * @ret rc              Return status code
373  */
374 int dhcpopt_store ( struct dhcp_options *options, unsigned int tag,
375                     const void *data, size_t len ) {
376         int offset;
377
378         offset = set_dhcp_option ( options, tag, data, len );
379         if ( offset < 0 )
380                 return offset;
381         return 0;
382 }
383
384 /**
385  * Fetch value of DHCP option setting
386  *
387  * @v options           DHCP option block
388  * @v tag               Setting tag number
389  * @v data              Buffer to fill with setting data
390  * @v len               Length of buffer
391  * @ret len             Length of setting data, or negative error
392  */
393 int dhcpopt_fetch ( struct dhcp_options *options, unsigned int tag,
394                     void *data, size_t len ) {
395         int offset;
396         struct dhcp_option *option;
397         size_t option_len;
398
399         offset = find_dhcp_option_with_encap ( options, tag, NULL );
400         if ( offset < 0 )
401                 return offset;
402
403         option = dhcp_option ( options, offset );
404         option_len = option->len;
405         if ( len > option_len )
406                 len = option_len;
407         memcpy ( data, option->data, len );
408
409         return option_len;
410 }
411
412 /**
413  * Recalculate length of DHCP options block
414  *
415  * @v options           Uninitialised DHCP option block
416  *
417  * The "used length" field will be updated based on scanning through
418  * the block to find the end of the options.
419  */
420 void dhcpopt_update_used_len ( struct dhcp_options *options ) {
421         struct dhcp_option *option;
422         int offset = 0;
423         ssize_t remaining = options->alloc_len;
424         unsigned int option_len;
425
426         /* Find last non-pad option */
427         options->used_len = 0;
428         while ( remaining ) {
429                 option = dhcp_option ( options, offset );
430                 option_len = dhcp_option_len ( option );
431                 remaining -= option_len;
432                 if ( remaining < 0 )
433                         break;
434                 offset += option_len;
435                 if ( option->tag != DHCP_PAD )
436                         options->used_len = offset;
437         }
438 }
439
440 /**
441  * Initialise prepopulated block of DHCP options
442  *
443  * @v options           Uninitialised DHCP option block
444  * @v data              Memory for DHCP option data
445  * @v alloc_len         Length of memory for DHCP option data
446  * @v realloc           DHCP option block reallocator
447  *
448  * The memory content must already be filled with valid DHCP options.
449  * A zeroed block counts as a block of valid DHCP options.
450  */
451 void dhcpopt_init ( struct dhcp_options *options, void *data, size_t alloc_len,
452                     int ( * realloc ) ( struct dhcp_options *options,
453                                         size_t len ) ) {
454
455         /* Fill in fields */
456         options->data = data;
457         options->alloc_len = alloc_len;
458         options->realloc = realloc;
459
460         /* Update length */
461         dhcpopt_update_used_len ( options );
462
463         DBGC ( options, "DHCPOPT %p created (data %p lengths %#zx,%#zx)\n",
464                options, options->data, options->used_len, options->alloc_len );
465 }