Add priority mechanism
[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 block of raw data
82  *
83  * @v tag               DHCP option tag to search for
84  * @v data              Data block
85  * @v len               Length of data block
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.
91  *
92  * This routine is designed to be paranoid.  It does not assume that
93  * the option data is well-formatted, and so must guard against flaws
94  * such as options missing a @c DHCP_END terminator, or options whose
95  * length would take them beyond the end of the data block.
96  */
97 static struct dhcp_option * find_dhcp_option_raw ( unsigned int tag,
98                                                    void *data, size_t len ) {
99         struct dhcp_option *option = data;
100         ssize_t remaining = len;
101         unsigned int option_len;
102
103         while ( remaining ) {
104                 /* Calculate length of this option.  Abort processing
105                  * if the length is malformed (i.e. takes us beyond
106                  * the end of the data block).
107                  */
108                 option_len = dhcp_option_len ( option );
109                 remaining -= option_len;
110                 if ( remaining < 0 )
111                         break;
112                 /* Check for matching tag */
113                 if ( option->tag == tag )
114                         return option;
115                 /* Check for explicit end marker */
116                 if ( option->tag == DHCP_END )
117                         break;
118                 /* Check for start of matching encapsulation block */
119                 if ( DHCP_ENCAPSULATOR ( tag ) &&
120                      ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
121                         /* Search within encapsulated option block */
122                         return find_dhcp_option_raw ( DHCP_ENCAPSULATED( tag ),
123                                                       &option->data,
124                                                       option->len );
125                 }
126                 option = ( ( ( void * ) option ) + option_len );
127         }
128         return NULL;
129 }
130
131 /**
132  * Find DHCP option within options block
133  *
134  * @v tag               DHCP option tag to search for
135  * @v options           DHCP options block
136  * @ret option          DHCP option, or NULL if not found
137  *
138  * Searches for the DHCP option matching the specified tag within the
139  * options block.  Encapsulated options may be searched for by using
140  * DHCP_ENCAP_OPT() to construct the tag value.
141  */
142 struct dhcp_option * find_dhcp_option ( unsigned int tag,
143                                         struct dhcp_option_block *options ) {
144         return find_dhcp_option_raw ( tag, options->data, options->len );
145 }
146
147 /**
148  * Find length of used portion of DHCP options block
149  *
150  * @v options           DHCP options block
151  * @ret len             Length of used portion of data block
152  *
153  * This searches for the @c DHCP_END marker within the options block.
154  * If found, the length of the used portion of the block (i.e. the
155  * portion containing everything @b before the @c DHCP_END marker, but
156  * excluding the @c DHCP_END marker itself) is returned.
157  *
158  * If no @c DHCP_END marker is present, the length of the whole
159  * options block is returned.
160  */
161 size_t dhcp_option_block_len ( struct dhcp_option_block *options ) {
162         void *dhcpend;
163
164         if ( ( dhcpend = find_dhcp_option ( DHCP_END, options ) ) ) {
165                 return ( dhcpend - options->data );
166         } else {
167                 return options->len;
168         }
169 }
170
171 /**
172  * Find DHCP option within all registered DHCP options blocks
173  *
174  * @v tag               DHCP option tag to search for
175  * @ret option          DHCP option, or NULL if not found
176  *
177  * Searches within all registered DHCP option blocks for the specified
178  * tag.  Encapsulated options may be searched for by using
179  * DHCP_ENCAP_OPT() to construct the tag value.
180  *
181  * Where multiple option blocks contain the same DHCP option, the
182  * option from the highest-priority block will be returned.  (Priority
183  * of an options block is determined by the value of the @c
184  * DHCP_EB_PRIORITY option within the block, if present; the default
185  * priority is zero).
186  */
187 struct dhcp_option * find_global_dhcp_option ( unsigned int tag ) {
188         struct dhcp_option_block *options;
189         struct dhcp_option *option;
190
191         list_for_each_entry ( options, &option_blocks, list ) {
192                 if ( ( option = find_dhcp_option ( tag, options ) ) )
193                         return option;
194         }
195         return NULL;
196 }
197
198 /**
199  * Register DHCP option block
200  *
201  * @v options           DHCP option block
202  *
203  * Register a block of DHCP options.
204  */
205 void register_dhcp_options ( struct dhcp_option_block *options ) {
206         struct dhcp_option_block *existing_options;
207         signed int existing_priority;
208         signed int priority;
209
210         /* Determine priority of new block */
211         priority = find_dhcp_num_option ( DHCP_EB_PRIORITY, options );
212
213         /* Insert after any existing blocks which have a higher priority */
214         list_for_each_entry ( existing_options, &option_blocks, list ) {
215                 existing_priority = find_dhcp_num_option ( DHCP_EB_PRIORITY,
216                                                            existing_options );
217                 if ( priority > existing_priority )
218                         break;
219         }
220         list_add_tail ( &options->list, &existing_options->list );
221 }
222
223 /**
224  * Unregister DHCP option block
225  *
226  * @v options           DHCP option block
227  */
228 void unregister_dhcp_options ( struct dhcp_option_block *options ) {
229         list_del ( &options->list );
230 }
231
232 /**
233  * Allocate space for a block of DHCP options
234  *
235  * @v len               Maximum length of option block
236  * @ret options         Option block, or NULL
237  *
238  * Creates a new DHCP option block and populates it with an empty
239  * options list.  This call does not register the options block.
240  */
241 struct dhcp_option_block * alloc_dhcp_options ( size_t len ) {
242         struct dhcp_option_block *options;
243         struct dhcp_option *option;
244
245         options = malloc ( sizeof ( *options ) + len );
246         if ( options ) {
247                 options->data = ( ( void * ) options + sizeof ( *options ) );
248                 options->len = len;
249                 if ( len ) {
250                         option = options->data;
251                         option->tag = DHCP_END;
252                 }
253         }
254         return options;
255 }
256
257 /**
258  * Free DHCP options block
259  *
260  * @v options           Option block
261  */
262 void free_dhcp_options ( struct dhcp_option_block *options ) {
263         free ( options );
264 }