|
@@ -19,8 +19,10 @@
|
19
|
19
|
#include <stdint.h>
|
20
|
20
|
#include <byteswap.h>
|
21
|
21
|
#include <errno.h>
|
|
22
|
+#include <string.h>
|
22
|
23
|
#include <malloc.h>
|
23
|
24
|
#include <assert.h>
|
|
25
|
+#include <vsprintf.h>
|
24
|
26
|
#include <gpxe/list.h>
|
25
|
27
|
#include <gpxe/dhcp.h>
|
26
|
28
|
|
|
@@ -33,6 +35,26 @@
|
33
|
35
|
/** List of registered DHCP option blocks */
|
34
|
36
|
static LIST_HEAD ( option_blocks );
|
35
|
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
|
+
|
36
|
58
|
/**
|
37
|
59
|
* Obtain value of a numerical DHCP option
|
38
|
60
|
*
|
|
@@ -64,44 +86,52 @@ unsigned long dhcp_num_option ( struct dhcp_option *option ) {
|
64
|
86
|
}
|
65
|
87
|
|
66
|
88
|
/**
|
67
|
|
- * Calculate length of a DHCP option
|
|
89
|
+ * Calculate length of a normal DHCP option
|
68
|
90
|
*
|
69
|
91
|
* @v option DHCP option
|
70
|
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.
|
71
|
95
|
*/
|
72
|
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 ) {
|
73
|
109
|
if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) {
|
74
|
110
|
return 1;
|
75
|
111
|
} else {
|
76
|
|
- return ( option->len + 2 );
|
|
112
|
+ return dhcp_option_len ( option );
|
77
|
113
|
}
|
78
|
114
|
}
|
79
|
115
|
|
80
|
116
|
/**
|
81
|
117
|
* Find DHCP option within DHCP options block, and its encapsulator (if any)
|
82
|
118
|
*
|
83
|
|
- * @v tag DHCP option tag to search for
|
84
|
119
|
* @v options DHCP options block
|
85
|
|
- * @ret encapsulator Encapsulating option (if applicable, may be NULL)
|
|
120
|
+ * @v tag DHCP option tag to search for
|
86
|
121
|
* @ret option DHCP option, or NULL if not found
|
87
|
122
|
*
|
88
|
123
|
* Searches for the DHCP option matching the specified tag within the
|
89
|
124
|
* block of data. Encapsulated options may be searched for by using
|
90
|
|
- * DHCP_ENCAP_OPT() to construct the tag value. If the option is an
|
91
|
|
- * encapsulated option, and @c encapsulator is non-NULL, it will be
|
92
|
|
- * filled in with a pointer to the encapsulating option, if present.
|
93
|
|
- * Note that the encapsulating option may be present even if the
|
94
|
|
- * encapsulated option is absent, in which case @c encapsulator will
|
95
|
|
- * be set but the function will return NULL.
|
|
125
|
+ * DHCP_ENCAP_OPT() to construct the tag value.
|
96
|
126
|
*
|
97
|
127
|
* This routine is designed to be paranoid. It does not assume that
|
98
|
128
|
* the option data is well-formatted, and so must guard against flaws
|
99
|
129
|
* such as options missing a @c DHCP_END terminator, or options whose
|
100
|
130
|
* length would take them beyond the end of the data block.
|
101
|
131
|
*/
|
102
|
|
-static struct dhcp_option *
|
103
|
|
-find_dhcp_option_encap ( unsigned int tag, struct dhcp_option_block *options,
|
104
|
|
- struct dhcp_option **encapsulator ) {
|
|
132
|
+struct dhcp_option * find_dhcp_option ( struct dhcp_option_block *options,
|
|
133
|
+ unsigned int tag ) {
|
|
134
|
+ unsigned int original_tag __attribute__ (( unused )) = tag;
|
105
|
135
|
struct dhcp_option *option = options->data;
|
106
|
136
|
ssize_t remaining = options->len;
|
107
|
137
|
unsigned int option_len;
|
|
@@ -111,22 +141,24 @@ find_dhcp_option_encap ( unsigned int tag, struct dhcp_option_block *options,
|
111
|
141
|
* if the length is malformed (i.e. takes us beyond
|
112
|
142
|
* the end of the data block).
|
113
|
143
|
*/
|
114
|
|
- option_len = dhcp_option_len ( option );
|
|
144
|
+ option_len = dhcp_any_option_len ( option );
|
115
|
145
|
remaining -= option_len;
|
116
|
146
|
if ( remaining < 0 )
|
117
|
147
|
break;
|
118
|
148
|
/* Check for matching tag */
|
119
|
|
- if ( option->tag == tag )
|
|
149
|
+ if ( option->tag == tag ) {
|
|
150
|
+ DBG ( "Found DHCP option %s (length %d)\n",
|
|
151
|
+ dhcp_tag_name ( original_tag ), option->len );
|
120
|
152
|
return option;
|
|
153
|
+ }
|
121
|
154
|
/* Check for explicit end marker */
|
122
|
155
|
if ( option->tag == DHCP_END )
|
123
|
156
|
break;
|
124
|
157
|
/* Check for start of matching encapsulation block */
|
125
|
|
- if ( DHCP_ENCAPSULATOR ( tag ) &&
|
|
158
|
+ if ( DHCP_IS_ENCAP_OPT ( tag ) &&
|
126
|
159
|
( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
|
127
|
160
|
/* Continue search within encapsulated option block */
|
128
|
|
- if ( encapsulator )
|
129
|
|
- *encapsulator = option;
|
|
161
|
+ tag = DHCP_ENCAPSULATED ( tag );
|
130
|
162
|
remaining = option->len;
|
131
|
163
|
option = ( void * ) &option->data;
|
132
|
164
|
continue;
|
|
@@ -136,46 +168,6 @@ find_dhcp_option_encap ( unsigned int tag, struct dhcp_option_block *options,
|
136
|
168
|
return NULL;
|
137
|
169
|
}
|
138
|
170
|
|
139
|
|
-/**
|
140
|
|
- * Find DHCP option within DHCP options block
|
141
|
|
- *
|
142
|
|
- * @v tag DHCP option tag to search for
|
143
|
|
- * @v options DHCP options block
|
144
|
|
- * @ret option DHCP option, or NULL if not found
|
145
|
|
- *
|
146
|
|
- * Searches for the DHCP option matching the specified tag within the
|
147
|
|
- * block of data. Encapsulated options may be searched for by using
|
148
|
|
- * DHCP_ENCAP_OPT() to construct the tag value.
|
149
|
|
- */
|
150
|
|
-struct dhcp_option * find_dhcp_option ( unsigned int tag,
|
151
|
|
- struct dhcp_option_block *options ) {
|
152
|
|
- return find_dhcp_option_encap ( tag, options, NULL );
|
153
|
|
-}
|
154
|
|
-
|
155
|
|
-/**
|
156
|
|
- * Find length of used portion of DHCP options block
|
157
|
|
- *
|
158
|
|
- * @v options DHCP options block
|
159
|
|
- * @ret len Length of used portion of data block
|
160
|
|
- *
|
161
|
|
- * This searches for the @c DHCP_END marker within the options block.
|
162
|
|
- * If found, the length of the used portion of the block (i.e. the
|
163
|
|
- * portion containing everything @b before the @c DHCP_END marker, but
|
164
|
|
- * excluding the @c DHCP_END marker itself) is returned.
|
165
|
|
- *
|
166
|
|
- * If no @c DHCP_END marker is present, the length of the whole
|
167
|
|
- * options block is returned.
|
168
|
|
- */
|
169
|
|
-size_t dhcp_option_block_len ( struct dhcp_option_block *options ) {
|
170
|
|
- void *dhcpend;
|
171
|
|
-
|
172
|
|
- if ( ( dhcpend = find_dhcp_option ( DHCP_END, options ) ) ) {
|
173
|
|
- return ( dhcpend - options->data );
|
174
|
|
- } else {
|
175
|
|
- return options->len;
|
176
|
|
- }
|
177
|
|
-}
|
178
|
|
-
|
179
|
171
|
/**
|
180
|
172
|
* Find DHCP option within all registered DHCP options blocks
|
181
|
173
|
*
|
|
@@ -197,7 +189,7 @@ struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
|
197
|
189
|
struct dhcp_option *option;
|
198
|
190
|
|
199
|
191
|
list_for_each_entry ( options, &option_blocks, list ) {
|
200
|
|
- if ( ( option = find_dhcp_option ( tag, options ) ) )
|
|
192
|
+ if ( ( option = find_dhcp_option ( options, tag ) ) )
|
201
|
193
|
return option;
|
202
|
194
|
}
|
203
|
195
|
return NULL;
|
|
@@ -211,21 +203,19 @@ struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
|
211
|
203
|
* Register a block of DHCP options.
|
212
|
204
|
*/
|
213
|
205
|
void register_dhcp_options ( struct dhcp_option_block *options ) {
|
214
|
|
- struct dhcp_option_block *existing_options;
|
215
|
|
- signed int existing_priority;
|
216
|
|
- signed int priority;
|
|
206
|
+ struct dhcp_option_block *existing;
|
217
|
207
|
|
218
|
208
|
/* Determine priority of new block */
|
219
|
|
- priority = find_dhcp_num_option ( DHCP_EB_PRIORITY, options );
|
|
209
|
+ options->priority = find_dhcp_num_option ( options, DHCP_EB_PRIORITY );
|
|
210
|
+ DBG ( "Registering DHCP options block with priority %d\n",
|
|
211
|
+ options->priority );
|
220
|
212
|
|
221
|
213
|
/* Insert after any existing blocks which have a higher priority */
|
222
|
|
- list_for_each_entry ( existing_options, &option_blocks, list ) {
|
223
|
|
- existing_priority = find_dhcp_num_option ( DHCP_EB_PRIORITY,
|
224
|
|
- existing_options );
|
225
|
|
- if ( priority > existing_priority )
|
|
214
|
+ list_for_each_entry ( existing, &option_blocks, list ) {
|
|
215
|
+ if ( options->priority > existing->priority )
|
226
|
216
|
break;
|
227
|
217
|
}
|
228
|
|
- list_add_tail ( &options->list, &existing_options->list );
|
|
218
|
+ list_add_tail ( &options->list, &existing->list );
|
229
|
219
|
}
|
230
|
220
|
|
231
|
221
|
/**
|
|
@@ -240,23 +230,24 @@ void unregister_dhcp_options ( struct dhcp_option_block *options ) {
|
240
|
230
|
/**
|
241
|
231
|
* Allocate space for a block of DHCP options
|
242
|
232
|
*
|
243
|
|
- * @v len Maximum length of option block
|
244
|
|
- * @ret options Option block, or NULL
|
|
233
|
+ * @v max_len Maximum length of option block
|
|
234
|
+ * @ret options DHCP option block, or NULL
|
245
|
235
|
*
|
246
|
236
|
* Creates a new DHCP option block and populates it with an empty
|
247
|
237
|
* options list. This call does not register the options block.
|
248
|
238
|
*/
|
249
|
|
-struct dhcp_option_block * alloc_dhcp_options ( size_t len ) {
|
|
239
|
+struct dhcp_option_block * alloc_dhcp_options ( size_t max_len ) {
|
250
|
240
|
struct dhcp_option_block *options;
|
251
|
241
|
struct dhcp_option *option;
|
252
|
242
|
|
253
|
|
- options = malloc ( sizeof ( *options ) + len );
|
|
243
|
+ options = malloc ( sizeof ( *options ) + max_len );
|
254
|
244
|
if ( options ) {
|
255
|
245
|
options->data = ( ( void * ) options + sizeof ( *options ) );
|
256
|
|
- options->len = len;
|
257
|
|
- if ( len ) {
|
|
246
|
+ options->max_len = max_len;
|
|
247
|
+ if ( max_len ) {
|
258
|
248
|
option = options->data;
|
259
|
249
|
option->tag = DHCP_END;
|
|
250
|
+ options->len = 1;
|
260
|
251
|
}
|
261
|
252
|
}
|
262
|
253
|
return options;
|
|
@@ -265,8 +256,124 @@ struct dhcp_option_block * alloc_dhcp_options ( size_t len ) {
|
265
|
256
|
/**
|
266
|
257
|
* Free DHCP options block
|
267
|
258
|
*
|
268
|
|
- * @v options Option block
|
|
259
|
+ * @v options DHCP option block
|
269
|
260
|
*/
|
270
|
261
|
void free_dhcp_options ( struct dhcp_option_block *options ) {
|
271
|
262
|
free ( options );
|
272
|
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
|
+}
|