[Settings] Remove assumption that all settings have DHCP tag values
[people/asdlkf/gpxe.git] / src / net / dhcppkt.c
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., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <errno.h>
23 #include <string.h>
24 #include <gpxe/netdevice.h>
25 #include <gpxe/dhcp.h>
26 #include <gpxe/dhcpopts.h>
27 #include <gpxe/dhcppkt.h>
28
29 /** @file
30  *
31  * DHCP packets
32  *
33  */
34
35 /** A dedicated field within a DHCP packet */
36 struct dhcp_packet_field {
37         /** Settings tag number */
38         unsigned int tag;
39         /** Offset within DHCP packet */
40         uint16_t offset;
41         /** Length of field */
42         uint16_t len;
43 };
44
45 /** Declare a dedicated field within a DHCP packet
46  *
47  * @v _tag              Settings tag number
48  * @v _field            Field name
49  */
50 #define DHCP_PACKET_FIELD( _tag, _field ) {                             \
51                 .tag = (_tag),                                          \
52                 .offset = offsetof ( struct dhcphdr, _field ),          \
53                 .len = sizeof ( ( ( struct dhcphdr * ) 0 )->_field ),   \
54         }
55
56 /** Dedicated fields within a DHCP packet */
57 static struct dhcp_packet_field dhcp_packet_fields[] = {
58         DHCP_PACKET_FIELD ( DHCP_EB_YIADDR, yiaddr ),
59         DHCP_PACKET_FIELD ( DHCP_EB_SIADDR, siaddr ),
60         DHCP_PACKET_FIELD ( DHCP_TFTP_SERVER_NAME, sname ),
61         DHCP_PACKET_FIELD ( DHCP_BOOTFILE_NAME, file ),
62 };
63
64 /**
65  * Get address of a DHCP packet field
66  *
67  * @v dhcphdr           DHCP packet header
68  * @v field             DHCP packet field
69  * @ret data            Packet field data
70  */
71 static inline void * dhcp_packet_field ( struct dhcphdr *dhcphdr,
72                                          struct dhcp_packet_field *field ) {
73         return ( ( ( void * ) dhcphdr ) + field->offset );
74 }
75
76 /**
77  * Find DHCP packet field corresponding to settings tag number
78  *
79  * @v tag               Settings tag number
80  * @ret field           DHCP packet field, or NULL
81  */
82 static struct dhcp_packet_field *
83 find_dhcp_packet_field ( unsigned int tag ) {
84         struct dhcp_packet_field *field;
85         unsigned int i;
86
87         for ( i = 0 ; i < ( sizeof ( dhcp_packet_fields ) /
88                             sizeof ( dhcp_packet_fields[0] ) ) ; i++ ) {
89                 field = &dhcp_packet_fields[i];
90                 if ( field->tag == tag )
91                         return field;
92         }
93         return NULL;
94 }
95
96 /**
97  * Store value of DHCP packet setting
98  *
99  * @v dhcppkt           DHCP packet
100  * @v tag               Setting tag number
101  * @v data              Setting data, or NULL to clear setting
102  * @v len               Length of setting data
103  * @ret rc              Return status code
104  */
105 int dhcppkt_store ( struct dhcp_packet *dhcppkt, unsigned int tag,
106                     const void *data, size_t len ) {
107         struct dhcp_packet_field *field;
108         int rc;
109
110         /* If this is a special field, fill it in */
111         if ( ( field = find_dhcp_packet_field ( tag ) ) != NULL ) {
112                 if ( len > field->len )
113                         return -ENOSPC;
114                 memcpy ( dhcp_packet_field ( dhcppkt->dhcphdr, field ),
115                          data, len );
116                 return 0;
117         }
118
119         /* Otherwise, use the generic options block */
120         rc = dhcpopt_store ( &dhcppkt->options, tag, data, len );
121
122         /* Update our used-length field */
123         dhcppkt->len = ( offsetof ( struct dhcphdr, options ) +
124                          dhcppkt->options.len );
125
126         return rc;
127 }
128
129 /**
130  * Fetch value of DHCP packet setting
131  *
132  * @v dhcppkt           DHCP packet
133  * @v tag               Setting tag number
134  * @v data              Buffer to fill with setting data
135  * @v len               Length of buffer
136  * @ret len             Length of setting data, or negative error
137  */
138 int dhcppkt_fetch ( struct dhcp_packet *dhcppkt, unsigned int tag,
139                     void *data, size_t len ) {
140         struct dhcp_packet_field *field;
141         
142         /* If this is a special field, return it */
143         if ( ( field = find_dhcp_packet_field ( tag ) ) != NULL ) {
144                 if ( len > field->len )
145                         len = field->len;
146                 memcpy ( data,
147                          dhcp_packet_field ( dhcppkt->dhcphdr, field ), len );
148                 return field->len;
149         }
150
151         /* Otherwise, use the generic options block */
152         return dhcpopt_fetch ( &dhcppkt->options, tag, data, len );
153 }
154
155 /**
156  * Initialise prepopulated DHCP packet
157  *
158  * @v dhcppkt           Uninitialised DHCP packet
159  * @v data              Memory for DHCP packet data
160  * @v max_len           Length of memory for DHCP packet data
161  *
162  * The memory content must already be filled with valid DHCP options.
163  * A zeroed block counts as a block of valid DHCP options.
164  */
165 void dhcppkt_init ( struct dhcp_packet *dhcppkt, void *data, size_t len ) {
166         dhcppkt->dhcphdr = data;
167         dhcppkt->max_len = len;
168         dhcpopt_init ( &dhcppkt->options, &dhcppkt->dhcphdr->options,
169                        ( len - offsetof ( struct dhcphdr, options ) ) );
170         dhcppkt->len = ( offsetof ( struct dhcphdr, options ) +
171                          dhcppkt->options.len );
172 }