1 /*
2  * Copyright (C) 2008 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., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301, USA.
18  *
19  * You can also choose to distribute this program under the terms of
20  * the Unmodified Binary Distribution Licence (as given in the file
21  * COPYING.UBDL), provided that you have satisfied its requirements.
22  */
23 
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25 
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <errno.h>
30 #include <string.h>
31 #include <ipxe/netdevice.h>
32 #include <ipxe/dhcp.h>
33 #include <ipxe/dhcpopts.h>
34 #include <ipxe/dhcppkt.h>
35 
36 /** @file
37  *
38  * DHCP packets
39  *
40  */
41 
42 /****************************************************************************
43  *
44  * DHCP packet raw interface
45  *
46  */
47 
48 /**
49  * Calculate used length of an IPv4 field within a DHCP packet
50  *
51  * @v data		Field data
52  * @v len		Length of field
53  * @ret used		Used length of field
54  */
used_len_ipv4(const void * data,size_t len __unused)55 static size_t used_len_ipv4 ( const void *data, size_t len __unused ) {
56 	const struct in_addr *in = data;
57 
58 	return ( in->s_addr ? sizeof ( *in ) : 0 );
59 }
60 
61 /**
62  * Calculate used length of a string field within a DHCP packet
63  *
64  * @v data		Field data
65  * @v len		Length of field
66  * @ret used		Used length of field
67  */
used_len_string(const void * data,size_t len)68 static size_t used_len_string ( const void *data, size_t len ) {
69 	return strnlen ( data, len );
70 }
71 
72 /** A dedicated field within a DHCP packet */
73 struct dhcp_packet_field {
74 	/** Settings tag number */
75 	unsigned int tag;
76 	/** Offset within DHCP packet */
77 	uint16_t offset;
78 	/** Length of field */
79 	uint16_t len;
80 	/** Calculate used length of field
81 	 *
82 	 * @v data	Field data
83 	 * @v len	Length of field
84 	 * @ret used	Used length of field
85 	 */
86 	size_t ( * used_len ) ( const void *data, size_t len );
87 };
88 
89 /** Declare a dedicated field within a DHCP packet
90  *
91  * @v _tag		Settings tag number
92  * @v _field		Field name
93  * @v _used_len		Function to calculate used length of field
94  */
95 #define DHCP_PACKET_FIELD( _tag, _field, _used_len ) {			\
96 		.tag = (_tag),						\
97 		.offset = offsetof ( struct dhcphdr, _field ),		\
98 		.len = sizeof ( ( ( struct dhcphdr * ) 0 )->_field ),	\
99 		.used_len = _used_len,					\
100 	}
101 
102 /** Dedicated fields within a DHCP packet */
103 static struct dhcp_packet_field dhcp_packet_fields[] = {
104 	DHCP_PACKET_FIELD ( DHCP_EB_YIADDR, yiaddr, used_len_ipv4 ),
105 	DHCP_PACKET_FIELD ( DHCP_EB_SIADDR, siaddr, used_len_ipv4 ),
106 	DHCP_PACKET_FIELD ( DHCP_TFTP_SERVER_NAME, sname, used_len_string ),
107 	DHCP_PACKET_FIELD ( DHCP_BOOTFILE_NAME, file, used_len_string ),
108 };
109 
110 /**
111  * Get address of a DHCP packet field
112  *
113  * @v dhcphdr		DHCP packet header
114  * @v field		DHCP packet field
115  * @ret data		Packet field data
116  */
dhcp_packet_field(struct dhcphdr * dhcphdr,struct dhcp_packet_field * field)117 static inline void * dhcp_packet_field ( struct dhcphdr *dhcphdr,
118 					 struct dhcp_packet_field *field ) {
119 	return ( ( ( void * ) dhcphdr ) + field->offset );
120 }
121 
122 /**
123  * Find DHCP packet field corresponding to settings tag number
124  *
125  * @v tag		Settings tag number
126  * @ret field		DHCP packet field, or NULL
127  */
128 static struct dhcp_packet_field *
find_dhcp_packet_field(unsigned int tag)129 find_dhcp_packet_field ( unsigned int tag ) {
130 	struct dhcp_packet_field *field;
131 	unsigned int i;
132 
133 	for ( i = 0 ; i < ( sizeof ( dhcp_packet_fields ) /
134 			    sizeof ( dhcp_packet_fields[0] ) ) ; i++ ) {
135 		field = &dhcp_packet_fields[i];
136 		if ( field->tag == tag )
137 			return field;
138 	}
139 	return NULL;
140 }
141 
142 /**
143  * Check applicability of DHCP setting
144  *
145  * @v dhcppkt		DHCP packet
146  * @v tag		Setting tag number
147  * @ret applies		Setting applies within this settings block
148  */
dhcppkt_applies(struct dhcp_packet * dhcppkt __unused,unsigned int tag)149 static int dhcppkt_applies ( struct dhcp_packet *dhcppkt __unused,
150 			     unsigned int tag ) {
151 
152 	return dhcpopt_applies ( tag );
153 }
154 
155 /**
156  * Store value of DHCP packet setting
157  *
158  * @v dhcppkt		DHCP packet
159  * @v tag		Setting tag number
160  * @v data		Setting data, or NULL to clear setting
161  * @v len		Length of setting data
162  * @ret rc		Return status code
163  */
dhcppkt_store(struct dhcp_packet * dhcppkt,unsigned int tag,const void * data,size_t len)164 int dhcppkt_store ( struct dhcp_packet *dhcppkt, unsigned int tag,
165 		    const void *data, size_t len ) {
166 	struct dhcp_packet_field *field;
167 	void *field_data;
168 
169 	/* If this is a special field, fill it in */
170 	if ( ( field = find_dhcp_packet_field ( tag ) ) != NULL ) {
171 		if ( len > field->len )
172 			return -ENOSPC;
173 		field_data = dhcp_packet_field ( dhcppkt->dhcphdr, field );
174 		memset ( field_data, 0, field->len );
175 		memcpy ( dhcp_packet_field ( dhcppkt->dhcphdr, field ),
176 			 data, len );
177 		/* Erase any equivalent option from the options block */
178 		dhcpopt_store ( &dhcppkt->options, tag, NULL, 0 );
179 		return 0;
180 	}
181 
182 	/* Otherwise, use the generic options block */
183 	return dhcpopt_store ( &dhcppkt->options, tag, data, len );
184 }
185 
186 /**
187  * Fetch value of DHCP packet setting
188  *
189  * @v dhcppkt		DHCP packet
190  * @v tag		Setting tag number
191  * @v data		Buffer to fill with setting data
192  * @v len		Length of buffer
193  * @ret len		Length of setting data, or negative error
194  */
dhcppkt_fetch(struct dhcp_packet * dhcppkt,unsigned int tag,void * data,size_t len)195 int dhcppkt_fetch ( struct dhcp_packet *dhcppkt, unsigned int tag,
196 		    void *data, size_t len ) {
197 	struct dhcp_packet_field *field;
198 	void *field_data;
199 	size_t field_len = 0;
200 
201 	/* Identify special field, if any */
202 	if ( ( field = find_dhcp_packet_field ( tag ) ) != NULL ) {
203 		field_data = dhcp_packet_field ( dhcppkt->dhcphdr, field );
204 		field_len = field->used_len ( field_data, field->len );
205 	}
206 
207 	/* Return special field, if it exists and is populated */
208 	if ( field_len ) {
209 		if ( len > field_len )
210 			len = field_len;
211 		memcpy ( data, field_data, len );
212 		return field_len;
213 	}
214 
215 	/* Otherwise, use the generic options block */
216 	return dhcpopt_fetch ( &dhcppkt->options, tag, data, len );
217 }
218 
219 /****************************************************************************
220  *
221  * DHCP packet settings interface
222  *
223  */
224 
225 /**
226  * Check applicability of DHCP setting
227  *
228  * @v settings		Settings block
229  * @v setting		Setting
230  * @ret applies		Setting applies within this settings block
231  */
dhcppkt_settings_applies(struct settings * settings,const struct setting * setting)232 static int dhcppkt_settings_applies ( struct settings *settings,
233 				      const struct setting *setting ) {
234 	struct dhcp_packet *dhcppkt =
235 		container_of ( settings, struct dhcp_packet, settings );
236 
237 	return ( ( setting->scope == NULL ) &&
238 		 dhcppkt_applies ( dhcppkt, setting->tag ) );
239 }
240 
241 /**
242  * Store value of DHCP setting
243  *
244  * @v settings		Settings block
245  * @v setting		Setting to store
246  * @v data		Setting data, or NULL to clear setting
247  * @v len		Length of setting data
248  * @ret rc		Return status code
249  */
dhcppkt_settings_store(struct settings * settings,const struct setting * setting,const void * data,size_t len)250 static int dhcppkt_settings_store ( struct settings *settings,
251 				    const struct setting *setting,
252 				    const void *data, size_t len ) {
253 	struct dhcp_packet *dhcppkt =
254 		container_of ( settings, struct dhcp_packet, settings );
255 
256 	return dhcppkt_store ( dhcppkt, setting->tag, data, len );
257 }
258 
259 /**
260  * Fetch value of DHCP setting
261  *
262  * @v settings		Settings block, or NULL to search all blocks
263  * @v setting		Setting to fetch
264  * @v data		Buffer to fill with setting data
265  * @v len		Length of buffer
266  * @ret len		Length of setting data, or negative error
267  */
dhcppkt_settings_fetch(struct settings * settings,struct setting * setting,void * data,size_t len)268 static int dhcppkt_settings_fetch ( struct settings *settings,
269 				    struct setting *setting,
270 				    void *data, size_t len ) {
271 	struct dhcp_packet *dhcppkt =
272 		container_of ( settings, struct dhcp_packet, settings );
273 
274 	return dhcppkt_fetch ( dhcppkt, setting->tag, data, len );
275 }
276 
277 /** DHCP settings operations */
278 static struct settings_operations dhcppkt_settings_operations = {
279 	.applies = dhcppkt_settings_applies,
280 	.store = dhcppkt_settings_store,
281 	.fetch = dhcppkt_settings_fetch,
282 };
283 
284 /****************************************************************************
285  *
286  * Constructor
287  *
288  */
289 
290 /**
291  * Initialise DHCP packet
292  *
293  * @v dhcppkt		DHCP packet structure to fill in
294  * @v data		DHCP packet raw data
295  * @v max_len		Length of raw data buffer
296  *
297  * Initialise a DHCP packet structure from a data buffer containing a
298  * DHCP packet.
299  */
dhcppkt_init(struct dhcp_packet * dhcppkt,struct dhcphdr * data,size_t len)300 void dhcppkt_init ( struct dhcp_packet *dhcppkt, struct dhcphdr *data,
301 		    size_t len ) {
302 	ref_init ( &dhcppkt->refcnt, NULL );
303 	dhcppkt->dhcphdr = data;
304 	dhcpopt_init ( &dhcppkt->options, &dhcppkt->dhcphdr->options,
305 		       ( len - offsetof ( struct dhcphdr, options ) ),
306 		       dhcpopt_no_realloc );
307 	settings_init ( &dhcppkt->settings, &dhcppkt_settings_operations,
308 			&dhcppkt->refcnt, NULL );
309 }
310