typo in macro fixed
[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  * @v value             Unsigned long for storing the result
41  * @ret rc              Return status code
42  *
43  * Parses the numerical value from a DHCP option, if present.  It is
44  * permitted to call dhcp_num_option() with @c option set to NULL; in
45  * this case the result value will not be modified and an error will
46  * be returned.
47  *
48  * The caller does not specify the size of the DHCP option data; this
49  * is implied by the length field stored within the DHCP option
50  * itself.
51  */
52 int dhcp_num_option ( struct dhcp_option *option, unsigned long *value ) {
53         uint8_t *data;
54         unsigned long tmp = 0;
55
56         if ( ! option )
57                 return -EINVAL;
58
59         /* This is actually smaller code than using htons() etc., and
60          * will also cope well with malformed options (such as
61          * zero-length options).
62          */
63         for ( data = option->data.bytes ;
64               data < ( option->data.bytes + option->len ) ; data++ )
65                 tmp = ( ( tmp << 8 ) | *data );
66         *value = tmp;
67         return 0;
68 }
69
70 /**
71  * Calculate length of a DHCP option
72  *
73  * @v option            DHCP option
74  * @ret len             Length (including tag and length field)
75  */
76 static inline unsigned int dhcp_option_len ( struct dhcp_option *option ) {
77         if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) {
78                 return 1;
79         } else {
80                 return ( option->len + 2 );
81         }
82 }
83
84 /**
85  * Find DHCP option within block of raw data
86  *
87  * @v tag               DHCP option tag to search for
88  * @v data              Data block
89  * @v len               Length of data block
90  * @ret option          DHCP option, or NULL if not found
91  *
92  * Searches for the DHCP option matching the specified tag within the
93  * block of data.  Encapsulated options may be searched for by using
94  * DHCP_ENCAP_OPT() to construct the tag value.
95  *
96  * This routine is designed to be paranoid.  It does not assume that
97  * the option data is well-formatted, and so must guard against flaws
98  * such as options missing a @c DHCP_END terminator, or options whose
99  * length would take them beyond the end of the data block.
100  *
101  * Searching for @c DHCP_PAD or @c DHCP_END tags, or using either @c
102  * DHCP_PAD or @c DHCP_END as the encapsulator when constructing the
103  * tag via DHCP_ENCAP_OPT() will produce undefined behaviour.
104  */
105 static struct dhcp_option * find_dhcp_option_raw ( unsigned int tag,
106                                                    void *data, size_t len ) {
107         struct dhcp_option *option = data;
108         ssize_t remaining = len;
109         unsigned int option_len;
110
111         assert ( tag != DHCP_PAD );
112         assert ( tag != DHCP_END );
113         assert ( DHCP_ENCAPSULATOR ( tag ) != DHCP_END );
114
115         while ( remaining ) {
116                 /* Check for explicit end marker */
117                 if ( option->tag == DHCP_END )
118                         break;
119                 /* Calculate length of this option.  Abort processing
120                  * if the length is malformed (i.e. takes us beyond
121                  * the end of the data block).
122                  */
123                 option_len = dhcp_option_len ( option );
124                 remaining -= option_len;
125                 if ( remaining < 0 )
126                         break;
127                 /* Check for matching tag */
128                 if ( option->tag == tag )
129                         return option;
130                 /* Check for start of matching encapsulation block */
131                 if ( DHCP_ENCAPSULATOR ( tag ) &&
132                      ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
133                         /* Search within encapsulated option block */
134                         return find_dhcp_option_raw ( DHCP_ENCAPSULATED( tag ),
135                                                       &option->data,
136                                                       option->len );
137                 }
138                 option = ( ( ( void * ) option ) + option_len );
139         }
140         return NULL;
141 }
142
143 /**
144  * Find DHCP option within all registered DHCP options blocks
145  *
146  * @v tag               DHCP option tag to search for
147  * @ret option          DHCP option, or NULL if not found
148  *
149  * Searches within all registered DHCP option blocks for the specified
150  * tag.  Encapsulated options may be searched for by using
151  * DHCP_ENCAP_OPT() to construct the tag value.
152  */
153 struct dhcp_option * find_dhcp_option ( unsigned int tag ) {
154         struct dhcp_option_block *options;
155         struct dhcp_option *option;
156
157         list_for_each_entry ( options, &option_blocks, list ) {
158                 if ( ( option = find_dhcp_option_raw ( tag, options->data,
159                                                        options->len ) ) )
160                         return option;
161         }
162         return NULL;
163 }
164
165 /**
166  * Register DHCP option block
167  *
168  * @v options           DHCP option block
169  *
170  * Register a block of DHCP options
171  */
172 void register_dhcp_options ( struct dhcp_option_block *options ) {
173         list_add ( &options->list, &option_blocks );
174 }
175
176 /**
177  * Unregister DHCP option block
178  *
179  * @v options           DHCP option block
180  */
181 void unregister_dhcp_options ( struct dhcp_option_block *options ) {
182         list_del ( &options->list );
183 }
184
185 /**
186  * Allocate space for a block of DHCP options
187  *
188  * @v len               Maximum length of option block
189  * @ret options         Option block, or NULL
190  *
191  * Creates a new DHCP option block and populates it with an empty
192  * options list.  This call does not register the options block.
193  */
194 struct dhcp_option_block * alloc_dhcp_options ( size_t len ) {
195         struct dhcp_option_block *options;
196         struct dhcp_option *option;
197
198         options = malloc ( sizeof ( *options ) + len );
199         if ( options ) {
200                 options->data = ( ( void * ) options + sizeof ( *options ) );
201                 options->len = len;
202                 if ( len ) {
203                         option = options->data;
204                         option->tag = DHCP_END;
205                 }
206         }
207         return options;
208 }
209
210 /**
211  * Free DHCP options block
212  *
213  * @v options           Option block
214  */
215 void free_dhcp_options ( struct dhcp_option_block *options ) {
216         free ( options );
217 }