Change dhcp_num_option() to return the numerical value directly.
[people/mcb30/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  * Searching for @c DHCP_PAD or @c DHCP_END tags, or using either @c
98  * DHCP_PAD or @c DHCP_END as the encapsulator when constructing the
99  * tag via DHCP_ENCAP_OPT() will produce undefined behaviour.
100  */
101 static struct dhcp_option * find_dhcp_option_raw ( unsigned int tag,
102                                                    void *data, size_t len ) {
103         struct dhcp_option *option = data;
104         ssize_t remaining = len;
105         unsigned int option_len;
106
107         assert ( tag != DHCP_PAD );
108         assert ( tag != DHCP_END );
109         assert ( DHCP_ENCAPSULATOR ( tag ) != DHCP_END );
110
111         while ( remaining ) {
112                 /* Check for explicit end marker */
113                 if ( option->tag == DHCP_END )
114                         break;
115                 /* Calculate length of this option.  Abort processing
116                  * if the length is malformed (i.e. takes us beyond
117                  * the end of the data block).
118                  */
119                 option_len = dhcp_option_len ( option );
120                 remaining -= option_len;
121                 if ( remaining < 0 )
122                         break;
123                 /* Check for matching tag */
124                 if ( option->tag == tag )
125                         return option;
126                 /* Check for start of matching encapsulation block */
127                 if ( DHCP_ENCAPSULATOR ( tag ) &&
128                      ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
129                         /* Search within encapsulated option block */
130                         return find_dhcp_option_raw ( DHCP_ENCAPSULATED( tag ),
131                                                       &option->data,
132                                                       option->len );
133                 }
134                 option = ( ( ( void * ) option ) + option_len );
135         }
136         return NULL;
137 }
138
139 /**
140  * Find DHCP option within all registered DHCP options blocks
141  *
142  * @v tag               DHCP option tag to search for
143  * @ret option          DHCP option, or NULL if not found
144  *
145  * Searches within all registered DHCP option blocks for the specified
146  * tag.  Encapsulated options may be searched for by using
147  * DHCP_ENCAP_OPT() to construct the tag value.
148  */
149 struct dhcp_option * find_dhcp_option ( unsigned int tag ) {
150         struct dhcp_option_block *options;
151         struct dhcp_option *option;
152
153         list_for_each_entry ( options, &option_blocks, list ) {
154                 if ( ( option = find_dhcp_option_raw ( tag, options->data,
155                                                        options->len ) ) )
156                         return option;
157         }
158         return NULL;
159 }
160
161 /**
162  * Register DHCP option block
163  *
164  * @v options           DHCP option block
165  *
166  * Register a block of DHCP options
167  */
168 void register_dhcp_options ( struct dhcp_option_block *options ) {
169         list_add ( &options->list, &option_blocks );
170 }
171
172 /**
173  * Unregister DHCP option block
174  *
175  * @v options           DHCP option block
176  */
177 void unregister_dhcp_options ( struct dhcp_option_block *options ) {
178         list_del ( &options->list );
179 }
180
181 /**
182  * Allocate space for a block of DHCP options
183  *
184  * @v len               Maximum length of option block
185  * @ret options         Option block, or NULL
186  *
187  * Creates a new DHCP option block and populates it with an empty
188  * options list.  This call does not register the options block.
189  */
190 struct dhcp_option_block * alloc_dhcp_options ( size_t len ) {
191         struct dhcp_option_block *options;
192         struct dhcp_option *option;
193
194         options = malloc ( sizeof ( *options ) + len );
195         if ( options ) {
196                 options->data = ( ( void * ) options + sizeof ( *options ) );
197                 options->len = len;
198                 if ( len ) {
199                         option = options->data;
200                         option->tag = DHCP_END;
201                 }
202         }
203         return options;
204 }
205
206 /**
207  * Free DHCP options block
208  *
209  * @v options           Option block
210  */
211 void free_dhcp_options ( struct dhcp_option_block *options ) {
212         free ( options );
213 }