Provide a mechanism for returning the encapsulator as well as the
[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 <malloc.h>
23 #include <assert.h>
24 #include <gpxe/list.h>
25 #include <gpxe/dhcp.h>
26
27 /** @file
28  *
29  * DHCP options
30  *
31  */
32
33 /** List of registered DHCP option blocks */
34 static LIST_HEAD ( option_blocks );
35
36 /**
37  * Obtain value of a numerical DHCP option
38  *
39  * @v option            DHCP option, or NULL
40  * @ret value           Numerical value of the option, or 0
41  *
42  * Parses the numerical value from a DHCP option, if present.  It is
43  * permitted to call dhcp_num_option() with @c option set to NULL; in
44  * this case 0 will be returned.
45  *
46  * The caller does not specify the size of the DHCP option data; this
47  * is implied by the length field stored within the DHCP option
48  * itself.
49  */
50 unsigned long dhcp_num_option ( struct dhcp_option *option ) {
51         unsigned long value = 0;
52         uint8_t *data;
53
54         if ( option ) {
55                 /* This is actually smaller code than using htons()
56                  * etc., and will also cope well with malformed
57                  * options (such as zero-length options).
58                  */
59                 for ( data = option->data.bytes ;
60                       data < ( option->data.bytes + option->len ) ; data++ )
61                         value = ( ( value << 8 ) | *data );
62         }
63         return value;
64 }
65
66 /**
67  * Calculate length of a DHCP option
68  *
69  * @v option            DHCP option
70  * @ret len             Length (including tag and length field)
71  */
72 static inline unsigned int dhcp_option_len ( struct dhcp_option *option ) {
73         if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) {
74                 return 1;
75         } else {
76                 return ( option->len + 2 );
77         }
78 }
79
80 /**
81  * Find DHCP option within DHCP options block, and its encapsulator (if any)
82  *
83  * @v tag               DHCP option tag to search for
84  * @v options           DHCP options block
85  * @ret encapsulator    Encapsulating option (if applicable, may be NULL)
86  * @ret option          DHCP option, or NULL if not found
87  *
88  * Searches for the DHCP option matching the specified tag within the
89  * 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.
96  *
97  * This routine is designed to be paranoid.  It does not assume that
98  * the option data is well-formatted, and so must guard against flaws
99  * such as options missing a @c DHCP_END terminator, or options whose
100  * length would take them beyond the end of the data block.
101  */
102 static struct dhcp_option *
103 find_dhcp_option_encap ( unsigned int tag, struct dhcp_option_block *options,
104                          struct dhcp_option **encapsulator ) {
105         struct dhcp_option *option = options->data;
106         ssize_t remaining = options->len;
107         unsigned int option_len;
108
109         while ( remaining ) {
110                 /* Calculate length of this option.  Abort processing
111                  * if the length is malformed (i.e. takes us beyond
112                  * the end of the data block).
113                  */
114                 option_len = dhcp_option_len ( option );
115                 remaining -= option_len;
116                 if ( remaining < 0 )
117                         break;
118                 /* Check for matching tag */
119                 if ( option->tag == tag )
120                         return option;
121                 /* Check for explicit end marker */
122                 if ( option->tag == DHCP_END )
123                         break;
124                 /* Check for start of matching encapsulation block */
125                 if ( DHCP_ENCAPSULATOR ( tag ) &&
126                      ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
127                         /* Continue search within encapsulated option block */
128                         if ( encapsulator )
129                                 *encapsulator = option;
130                         remaining = option->len;
131                         option = ( void * ) &option->data;
132                         continue;
133                 }
134                 option = ( ( ( void * ) option ) + option_len );
135         }
136         return NULL;
137 }
138
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 /**
180  * Find DHCP option within all registered DHCP options blocks
181  *
182  * @v tag               DHCP option tag to search for
183  * @ret option          DHCP option, or NULL if not found
184  *
185  * Searches within all registered DHCP option blocks for the specified
186  * tag.  Encapsulated options may be searched for by using
187  * DHCP_ENCAP_OPT() to construct the tag value.
188  *
189  * Where multiple option blocks contain the same DHCP option, the
190  * option from the highest-priority block will be returned.  (Priority
191  * of an options block is determined by the value of the @c
192  * DHCP_EB_PRIORITY option within the block, if present; the default
193  * priority is zero).
194  */
195 struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
196         struct dhcp_option_block *options;
197         struct dhcp_option *option;
198
199         list_for_each_entry ( options, &option_blocks, list ) {
200                 if ( ( option = find_dhcp_option ( tag, options ) ) )
201                         return option;
202         }
203         return NULL;
204 }
205
206 /**
207  * Register DHCP option block
208  *
209  * @v options           DHCP option block
210  *
211  * Register a block of DHCP options.
212  */
213 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;
217
218         /* Determine priority of new block */
219         priority = find_dhcp_num_option ( DHCP_EB_PRIORITY, options );
220
221         /* 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 )
226                         break;
227         }
228         list_add_tail ( &options->list, &existing_options->list );
229 }
230
231 /**
232  * Unregister DHCP option block
233  *
234  * @v options           DHCP option block
235  */
236 void unregister_dhcp_options ( struct dhcp_option_block *options ) {
237         list_del ( &options->list );
238 }
239
240 /**
241  * Allocate space for a block of DHCP options
242  *
243  * @v len               Maximum length of option block
244  * @ret options         Option block, or NULL
245  *
246  * Creates a new DHCP option block and populates it with an empty
247  * options list.  This call does not register the options block.
248  */
249 struct dhcp_option_block * alloc_dhcp_options ( size_t len ) {
250         struct dhcp_option_block *options;
251         struct dhcp_option *option;
252
253         options = malloc ( sizeof ( *options ) + len );
254         if ( options ) {
255                 options->data = ( ( void * ) options + sizeof ( *options ) );
256                 options->len = len;
257                 if ( len ) {
258                         option = options->data;
259                         option->tag = DHCP_END;
260                 }
261         }
262         return options;
263 }
264
265 /**
266  * Free DHCP options block
267  *
268  * @v options           Option block
269  */
270 void free_dhcp_options ( struct dhcp_option_block *options ) {
271         free ( options );
272 }