Procházet zdrojové kódy

Add code to modify DHCP option values within a block.

tags/v0.9.3
Michael Brown před 18 roky
rodič
revize
19e8b41562
2 změnil soubory, kde provedl 247 přidání a 80 odebrání
  1. 65
    5
      src/include/gpxe/dhcp.h
  2. 182
    75
      src/net/dhcpopts.c

+ 65
- 5
src/include/gpxe/dhcp.h Zobrazit soubor

@@ -23,6 +23,8 @@
23 23
 /** Extract encapsulated option tag from encapsulated tag value */
24 24
 #define DHCP_ENCAPSULATED( encap_opt ) ( (encap_opt) & 0xff )
25 25
 
26
+#define DHCP_IS_ENCAP_OPT( opt ) DHCP_ENCAPSULATOR( opt )
27
+
26 28
 /**
27 29
  * @defgroup dhcpopts DHCP option tags
28 30
  * @{
@@ -74,6 +76,17 @@ struct dhcp_option {
74 76
 	} data;
75 77
 } __attribute__ (( packed ));
76 78
 
79
+/**
80
+ * Length of a DHCP option header
81
+ *
82
+ * The header is the portion excluding the data, i.e. the tag and the
83
+ * length.
84
+ */
85
+#define DHCP_OPTION_HEADER_LEN ( offsetof ( struct dhcp_option, data ) )
86
+
87
+/** Maximum length for a single DHCP option */
88
+#define DHCP_MAX_LEN 0xff
89
+
77 90
 /** A DHCP options block */
78 91
 struct dhcp_option_block {
79 92
 	/** List of option blocks */
@@ -82,17 +95,35 @@ struct dhcp_option_block {
82 95
 	void *data;
83 96
 	/** Option block length */
84 97
 	size_t len;
98
+	/** Option block maximum length */
99
+	size_t max_len;
100
+	/** Block priority
101
+	 *
102
+	 * This is determined at the time of the call to
103
+	 * register_options() by searching for the @c DHCP_EB_PRIORITY
104
+	 * option.
105
+	 */
106
+	signed int priority;
85 107
 };
86 108
 
87 109
 extern unsigned long dhcp_num_option ( struct dhcp_option *option );
88
-extern struct dhcp_option * find_dhcp_option ( unsigned int tag,
89
-					   struct dhcp_option_block *options );
110
+extern struct dhcp_option *
111
+find_dhcp_option ( struct dhcp_option_block *options, unsigned int tag );
112
+extern struct dhcp_option * find_global_dhcp_option ( unsigned int tag );
113
+extern void register_dhcp_options ( struct dhcp_option_block *options );
114
+extern void unregister_dhcp_options ( struct dhcp_option_block *options );
115
+extern struct dhcp_option_block * alloc_dhcp_options ( size_t max_len );
116
+extern void free_dhcp_options ( struct dhcp_option_block *options );
117
+extern struct dhcp_option *
118
+set_dhcp_option ( struct dhcp_option_block *options, unsigned int tag,
119
+		  const void *data, size_t len );
120
+
90 121
 
91 122
 /**
92 123
  * Find DHCP numerical option, and return its value
93 124
  *
94
- * @v tag		DHCP option tag to search for
95 125
  * @v options		DHCP options block
126
+ * @v tag		DHCP option tag to search for
96 127
  * @ret value		Numerical value of the option, or 0 if not found
97 128
  *
98 129
  * This function exists merely as a notational shorthand for a call to
@@ -103,8 +134,37 @@ extern struct dhcp_option * find_dhcp_option ( unsigned int tag,
103 134
  * check that find_dhcp_option() returns a non-NULL value.
104 135
  */
105 136
 static inline unsigned long
106
-find_dhcp_num_option ( unsigned int tag, struct dhcp_option_block *options ) {
107
-	return dhcp_num_option ( find_dhcp_option ( tag, options ) );
137
+find_dhcp_num_option ( struct dhcp_option_block *options, unsigned int tag ) {
138
+	return dhcp_num_option ( find_dhcp_option ( options, tag ) );
139
+}
140
+
141
+/**
142
+ * Find DHCP numerical option, and return its value
143
+ *
144
+ * @v tag		DHCP option tag to search for
145
+ * @ret value		Numerical value of the option, or 0 if not found
146
+ *
147
+ * This function exists merely as a notational shorthand for a call to
148
+ * find_global_dhcp_option() followed by a call to dhcp_num_option().
149
+ * It is not possible to distinguish between the cases "option not
150
+ * found" and "option has a value of zero" using this function; if
151
+ * this matters to you then issue the two constituent calls directly
152
+ * and check that find_global_dhcp_option() returns a non-NULL value.
153
+ */
154
+static inline unsigned long
155
+find_global_dhcp_num_option ( unsigned int tag ) {
156
+	return dhcp_num_option ( find_global_dhcp_option ( tag ) );
157
+}
158
+
159
+/**
160
+ * Delete DHCP option
161
+ *
162
+ * @v options		DHCP options block
163
+ * @v tag		DHCP option tag
164
+ */
165
+static inline void delete_dhcp_option ( struct dhcp_option_block *options,
166
+					unsigned int tag ) {
167
+	set_dhcp_option ( options, tag, NULL, 0 );
108 168
 }
109 169
 
110 170
 #endif /* _GPXE_DHCP_H */

+ 182
- 75
src/net/dhcpopts.c Zobrazit soubor

@@ -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
+}

Načítá se…
Zrušit
Uložit