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