/src/freeradius-server/src/protocols/dhcpv4/base.c
Line | Count | Source |
1 | | /* |
2 | | * This library is free software; you can redistribute it and/or |
3 | | * modify it under the terms of the GNU Lesser General Public |
4 | | * License as published by the Free Software Foundation; either |
5 | | * version 2.1 of the License, or (at your option) any later version. |
6 | | * |
7 | | * This library is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
10 | | * Lesser General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU Lesser General Public |
13 | | * License along with this library; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: 1f716612b59640f94466c13fa077746535721108 $ |
19 | | * |
20 | | * @file protocols/dhcpv4/base.c |
21 | | * @brief Functions to send/receive dhcp packets. |
22 | | * |
23 | | * @copyright 2008 The FreeRADIUS server project |
24 | | * @copyright 2008 Alan DeKok (aland@deployingradius.com) |
25 | | */ |
26 | | RCSID("$Id: 1f716612b59640f94466c13fa077746535721108 $") |
27 | | |
28 | | #include <freeradius-devel/dhcpv4/dhcpv4.h> |
29 | | #include <freeradius-devel/util/net.h> |
30 | | #include <freeradius-devel/util/proto.h> |
31 | | #include "attrs.h" |
32 | | |
33 | | static uint32_t instance_count = 0; |
34 | | static bool instantiated = false; |
35 | | |
36 | | typedef struct { |
37 | | uint8_t code; |
38 | | uint8_t length; |
39 | | } dhcp_option_t; |
40 | | |
41 | | fr_dict_t const *dict_dhcpv4; |
42 | | |
43 | | extern fr_dict_autoload_t dhcpv4_dict[]; |
44 | | fr_dict_autoload_t dhcpv4_dict[] = { |
45 | | { .out = &dict_dhcpv4, .proto = "dhcpv4" }, |
46 | | |
47 | | DICT_AUTOLOAD_TERMINATOR |
48 | | }; |
49 | | |
50 | | fr_dict_attr_t const *attr_dhcp_boot_filename; |
51 | | fr_dict_attr_t const *attr_dhcp_client_hardware_address; |
52 | | fr_dict_attr_t const *attr_dhcp_client_ip_address; |
53 | | fr_dict_attr_t const *attr_dhcp_flags; |
54 | | fr_dict_attr_t const *attr_dhcp_gateway_ip_address; |
55 | | fr_dict_attr_t const *attr_dhcp_hardware_address_length; |
56 | | fr_dict_attr_t const *attr_dhcp_hardware_type; |
57 | | fr_dict_attr_t const *attr_dhcp_hop_count; |
58 | | fr_dict_attr_t const *attr_dhcp_number_of_seconds; |
59 | | fr_dict_attr_t const *attr_dhcp_opcode; |
60 | | fr_dict_attr_t const *attr_dhcp_server_host_name; |
61 | | fr_dict_attr_t const *attr_dhcp_server_ip_address; |
62 | | fr_dict_attr_t const *attr_dhcp_transaction_id; |
63 | | fr_dict_attr_t const *attr_dhcp_your_ip_address; |
64 | | fr_dict_attr_t const *attr_dhcp_dhcp_maximum_msg_size; |
65 | | fr_dict_attr_t const *attr_dhcp_interface_mtu_size; |
66 | | fr_dict_attr_t const *attr_dhcp_message_type; |
67 | | fr_dict_attr_t const *attr_dhcp_parameter_request_list; |
68 | | fr_dict_attr_t const *attr_dhcp_overload; |
69 | | fr_dict_attr_t const *attr_dhcp_vendor_class_identifier; |
70 | | fr_dict_attr_t const *attr_dhcp_relay_link_selection; |
71 | | fr_dict_attr_t const *attr_dhcp_subnet_selection_option; |
72 | | fr_dict_attr_t const *attr_dhcp_network_subnet; |
73 | | fr_dict_attr_t const *attr_dhcp_option_82; |
74 | | |
75 | | extern fr_dict_attr_autoload_t dhcpv4_dict_attr[]; |
76 | | fr_dict_attr_autoload_t dhcpv4_dict_attr[] = { |
77 | | { .out = &attr_dhcp_boot_filename, .name = "Boot-Filename", .type = FR_TYPE_STRING, .dict = &dict_dhcpv4 }, |
78 | | { .out = &attr_dhcp_client_hardware_address, .name = "Client-Hardware-Address", .type = FR_TYPE_ETHERNET, .dict = &dict_dhcpv4 }, |
79 | | { .out = &attr_dhcp_client_ip_address, .name = "Client-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 }, |
80 | | { .out = &attr_dhcp_flags, .name = "Flags", .type = FR_TYPE_UINT16, .dict = &dict_dhcpv4 }, |
81 | | { .out = &attr_dhcp_gateway_ip_address, .name = "Gateway-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 }, |
82 | | { .out = &attr_dhcp_hardware_address_length, .name = "Hardware-Address-Length", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 }, |
83 | | { .out = &attr_dhcp_hardware_type, .name = "Hardware-Type", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 }, |
84 | | { .out = &attr_dhcp_hop_count, .name = "Hop-Count", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 }, |
85 | | { .out = &attr_dhcp_number_of_seconds, .name = "Number-of-Seconds", .type = FR_TYPE_UINT16, .dict = &dict_dhcpv4 }, |
86 | | { .out = &attr_dhcp_opcode, .name = "Opcode", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 }, |
87 | | { .out = &attr_dhcp_server_host_name, .name = "Server-Host-Name", .type = FR_TYPE_STRING, .dict = &dict_dhcpv4 }, |
88 | | { .out = &attr_dhcp_server_ip_address, .name = "Server-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 }, |
89 | | { .out = &attr_dhcp_transaction_id, .name = "Transaction-Id", .type = FR_TYPE_UINT32, .dict = &dict_dhcpv4 }, |
90 | | { .out = &attr_dhcp_your_ip_address, .name = "Your-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 }, |
91 | | { .out = &attr_dhcp_dhcp_maximum_msg_size, .name = "Maximum-Msg-Size", .type = FR_TYPE_UINT16, .dict = &dict_dhcpv4 }, |
92 | | { .out = &attr_dhcp_interface_mtu_size, .name = "Interface-MTU-Size", .type = FR_TYPE_UINT16, .dict = &dict_dhcpv4 }, |
93 | | { .out = &attr_dhcp_message_type, .name = "Message-Type", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 }, |
94 | | { .out = &attr_dhcp_parameter_request_list, .name = "Parameter-Request-List", .type = FR_TYPE_ATTR, .dict = &dict_dhcpv4 }, |
95 | | { .out = &attr_dhcp_overload, .name = "Overload", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 }, |
96 | | { .out = &attr_dhcp_vendor_class_identifier, .name = "Vendor-Class-Identifier", .type = FR_TYPE_OCTETS, .dict = &dict_dhcpv4 }, |
97 | | { .out = &attr_dhcp_relay_link_selection, .name = "Relay-Agent-Information.Relay-Link-Selection", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 }, |
98 | | { .out = &attr_dhcp_subnet_selection_option, .name = "Subnet-Selection-Option", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 }, |
99 | | { .out = &attr_dhcp_network_subnet, .name = "Network-Subnet", .type = FR_TYPE_IPV4_PREFIX, .dict = &dict_dhcpv4 }, |
100 | | { .out = &attr_dhcp_option_82, .name = "Relay-Agent-Information", .type = FR_TYPE_TLV, .dict = &dict_dhcpv4 }, |
101 | | |
102 | | DICT_AUTOLOAD_TERMINATOR |
103 | | }; |
104 | | |
105 | | /* |
106 | | * INADDR_ANY : 68 -> INADDR_BROADCAST : 67 DISCOVER |
107 | | * INADDR_BROADCAST : 68 <- SERVER_IP : 67 OFFER |
108 | | * INADDR_ANY : 68 -> INADDR_BROADCAST : 67 REQUEST |
109 | | * INADDR_BROADCAST : 68 <- SERVER_IP : 67 ACK |
110 | | */ |
111 | | fr_dict_attr_t const **dhcp_header_attrs[] = { |
112 | | &attr_dhcp_opcode, |
113 | | &attr_dhcp_hardware_type, |
114 | | &attr_dhcp_hardware_address_length, |
115 | | &attr_dhcp_hop_count, |
116 | | &attr_dhcp_transaction_id, |
117 | | &attr_dhcp_number_of_seconds, |
118 | | &attr_dhcp_flags, |
119 | | &attr_dhcp_client_ip_address, |
120 | | &attr_dhcp_your_ip_address, |
121 | | &attr_dhcp_server_ip_address, |
122 | | &attr_dhcp_gateway_ip_address, |
123 | | &attr_dhcp_client_hardware_address, |
124 | | &attr_dhcp_server_host_name, |
125 | | &attr_dhcp_boot_filename, |
126 | | }; |
127 | | size_t dhcp_header_attrs_len = NUM_ELEMENTS(dhcp_header_attrs); |
128 | | |
129 | | char const *dhcp_message_types[] = { |
130 | | "invalid", |
131 | | "Discover", |
132 | | "Offer", |
133 | | "Request", |
134 | | "Decline", |
135 | | "Ack", |
136 | | "NAK", |
137 | | "Release", |
138 | | "Inform", |
139 | | "Force-Renew", |
140 | | "Lease-Query", |
141 | | "Lease-Unassigned", |
142 | | "Lease-Unknown", |
143 | | "Lease-Active", |
144 | | "Bulk-Lease-Query", |
145 | | "Lease-Query-Done" |
146 | | }; |
147 | | |
148 | 201 | #define DHCP_MAX_MESSAGE_TYPE (NUM_ELEMENTS(dhcp_message_types)) |
149 | | |
150 | | int dhcp_header_sizes[] = { |
151 | | 1, /* op */ |
152 | | 1, /* htype */ |
153 | | 1, /* hlen */ |
154 | | 1, /* hops */ |
155 | | 4, /* xid */ |
156 | | 2, /* secs */ |
157 | | 2, /* flags */ |
158 | | 4, /* ciaddr */ |
159 | | 4, /* yiaddr */ |
160 | | 4, /* siaddr */ |
161 | | 4, /* giaddr */ |
162 | | DHCP_CHADDR_LEN, /* chaddr */ |
163 | | DHCP_SNAME_LEN, /* sname */ |
164 | | DHCP_FILE_LEN /* file */ |
165 | | }; |
166 | | |
167 | | uint8_t eth_bcast[ETH_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; |
168 | | |
169 | | FR_DICT_ATTR_FLAG_FUNC(fr_dhcpv4_attr_flags_t, dns_label) |
170 | | FR_DICT_ATTR_FLAG_FUNC(fr_dhcpv4_attr_flags_t, exists) |
171 | | |
172 | | static int dict_flag_prefix(fr_dict_attr_t **da_p, char const *value, UNUSED fr_dict_flag_parser_rule_t const *rules) |
173 | 8 | { |
174 | 8 | static fr_table_num_sorted_t const table[] = { |
175 | 8 | { L("bits"), DHCPV4_FLAG_PREFIX_BITS }, |
176 | 8 | { L("split"), DHCPV4_FLAG_PREFIX_SPLIT } |
177 | 8 | }; |
178 | 8 | static size_t table_len = NUM_ELEMENTS(table); |
179 | | |
180 | 8 | fr_dhcpv4_attr_flags_t *flags = fr_dict_attr_ext(*da_p, FR_DICT_ATTR_EXT_PROTOCOL_SPECIFIC); |
181 | 8 | fr_dhcpv4_attr_flags_prefix_t flag; |
182 | | |
183 | 8 | flag = fr_table_value_by_str(table, value, DHCPV4_FLAG_PREFIX_INVALID); |
184 | 8 | if (flag == DHCPV4_FLAG_PREFIX_INVALID) { |
185 | 0 | fr_strerror_printf("Unknown prefix type '%s'", value); |
186 | 0 | return -1; |
187 | 0 | } |
188 | 8 | flags->prefix = flag; |
189 | | |
190 | 8 | return 0; |
191 | 8 | } |
192 | | |
193 | | static fr_dict_flag_parser_t const dhcpv4_flags[] = { |
194 | | { L("dns_label"), { .func = dict_flag_dns_label } }, |
195 | | { L("exists"), { .func = dict_flag_exists } }, |
196 | | { L("prefix"), { .func = dict_flag_prefix } } |
197 | | }; |
198 | | |
199 | | /* |
200 | | * @todo - arguably we don't want to mutate the input list. |
201 | | * Instead, the encoder should just do 3 passes, where middle one |
202 | | * ignores the message-type and option 82. |
203 | | */ |
204 | | int8_t fr_dhcpv4_attr_cmp(void const *a, void const *b) |
205 | 0 | { |
206 | 0 | fr_pair_t const *my_a = a, *my_b = b; |
207 | |
|
208 | 0 | PAIR_VERIFY(my_a); |
209 | 0 | PAIR_VERIFY(my_b); |
210 | | |
211 | | /* |
212 | | * Message-Type is first, for simplicity. |
213 | | */ |
214 | 0 | if ((my_a->da == attr_dhcp_message_type) && (my_b->da != attr_dhcp_message_type)) return -1; |
215 | 0 | if ((my_a->da != attr_dhcp_message_type) && (my_b->da == attr_dhcp_message_type)) return +1; |
216 | | |
217 | | /* |
218 | | * Relay-Agent is last. |
219 | | * |
220 | | * RFC 3046: |
221 | | * Servers SHOULD copy the Relay Agent Information |
222 | | * option as the last DHCP option in the response. |
223 | | * |
224 | | * Some crazy DHCP relay agents idea of how to strip option 82 in |
225 | | * a reply packet is to simply overwrite the 82 with 255 - the |
226 | | * "Eod of Options" option - causing the client to then ignore |
227 | | * any subsequent options. |
228 | | * |
229 | | * Check if either of the options are option 82 |
230 | | */ |
231 | 0 | if ((my_a->da == attr_dhcp_option_82) && (my_b->da != attr_dhcp_option_82)) return +1; |
232 | 0 | if ((my_a->da != attr_dhcp_option_82) && (my_b->da == attr_dhcp_option_82)) return -1; |
233 | | |
234 | 0 | return fr_dict_attr_cmp(my_a->da, my_b->da); |
235 | 0 | } |
236 | | |
237 | | /** Check received DHCP request is valid and build fr_packet_t structure if it is |
238 | | * |
239 | | * @param data pointer to received packet. |
240 | | * @param data_len length of received data, and then length of the actual DHCP data. |
241 | | * @param[out] message_type where the message type will be stored (if used) |
242 | | * @param[out] xid where the xid will be stored (if used) |
243 | | * |
244 | | * @return |
245 | | * - true if the packet is well-formed |
246 | | * - false if it's a bad packet |
247 | | */ |
248 | | bool fr_dhcpv4_ok(uint8_t const *data, ssize_t data_len, uint8_t *message_type, uint32_t *xid) |
249 | 1.23k | { |
250 | 1.23k | uint32_t magic; |
251 | 1.23k | uint8_t const *code; |
252 | 1.23k | size_t hlen; |
253 | | |
254 | 1.23k | if (data_len < MIN_PACKET_SIZE) { |
255 | 16 | fr_strerror_printf("DHCP packet is too small (%zu < %d)", data_len, MIN_PACKET_SIZE); |
256 | 16 | return false; |
257 | 16 | } |
258 | | |
259 | 1.21k | if (data_len > MAX_PACKET_SIZE) { |
260 | 23 | fr_strerror_printf("DHCP packet is too large (%zd > %d)", data_len, MAX_PACKET_SIZE); |
261 | 23 | return false; |
262 | 23 | } |
263 | | |
264 | 1.19k | if (data[1] != 1) { |
265 | 703 | fr_strerror_printf("DHCP can only process ethernet requests, not type %02x", data[1]); |
266 | 703 | return false; |
267 | 703 | } |
268 | | |
269 | 492 | hlen = data[2]; |
270 | 492 | if ((hlen != 0) && (hlen != 6)) { |
271 | 6 | fr_strerror_printf("Ethernet HW length incorrect. Expected 6 got %zu", hlen); |
272 | 6 | return false; |
273 | 6 | } |
274 | | |
275 | 486 | memcpy(&magic, data + 236, 4); |
276 | 486 | magic = ntohl(magic); |
277 | 486 | if (magic != DHCP_OPTION_MAGIC_NUMBER) { |
278 | 23 | fr_strerror_const("BOOTP not supported"); |
279 | 23 | return false; |
280 | 23 | } |
281 | | |
282 | 463 | code = fr_dhcpv4_packet_get_option((dhcp_packet_t const *) data, data_len, attr_dhcp_message_type); |
283 | 463 | if (!code || (code[1] != 1)) { |
284 | 262 | fr_strerror_const("No message-type, or invalid option was found in the packet"); |
285 | 262 | return false; |
286 | 262 | } |
287 | | |
288 | 201 | if ((code[2] == 0) || (code[2] >= DHCP_MAX_MESSAGE_TYPE)) { |
289 | 1 | fr_strerror_printf("Unknown value %d for message-type option", code[2]); |
290 | 1 | return false; |
291 | 1 | } |
292 | | |
293 | | /* |
294 | | * @todo - data_len MAY be larger than the data in the |
295 | | * packet. In which case, we should update data_len with |
296 | | * the true size of the packet. |
297 | | */ |
298 | | |
299 | 200 | if (message_type) *message_type = code[2]; |
300 | | |
301 | 200 | if (xid) { |
302 | 0 | memcpy(&magic, data + 4, 4); |
303 | 0 | *xid = ntohl(magic); |
304 | 0 | } |
305 | | |
306 | 200 | return true; |
307 | 201 | } |
308 | | |
309 | | /** Evaluation function for DCHPV4-encodability |
310 | | * |
311 | | * @param item pointer to a fr_pair_t |
312 | | * @param uctx context |
313 | | * |
314 | | * @return true if the underlying fr_pair_t is DHCPv4 encodable, false otherwise |
315 | | */ |
316 | | bool fr_dhcpv4_is_encodable(void const *item, UNUSED void const *uctx) |
317 | 0 | { |
318 | 0 | fr_pair_t const *vp = item; |
319 | |
|
320 | 0 | PAIR_VERIFY(vp); |
321 | 0 | return (vp->da->dict == dict_dhcpv4) && (!vp->da->flags.internal); |
322 | 0 | } |
323 | | |
324 | | /** DHCPV4-specific iterator |
325 | | * |
326 | | */ |
327 | | void *fr_dhcpv4_next_encodable(fr_dcursor_t *cursor, void *current, void *uctx) |
328 | 0 | { |
329 | 0 | fr_pair_t *c = current; |
330 | 0 | fr_dict_t *dict = talloc_get_type_abort(uctx, fr_dict_t); |
331 | |
|
332 | 0 | while ((c = fr_dlist_next(cursor->dlist, c))) { |
333 | 0 | PAIR_VERIFY(c); |
334 | 0 | if (c->da->dict != dict || c->da->flags.internal) continue; |
335 | | |
336 | 0 | if (c->vp_type == FR_TYPE_BOOL && fr_dhcpv4_flag_exists(c->da) && !c->vp_bool) continue; |
337 | | |
338 | | /* |
339 | | * The VSIO encoder expects to see VENDOR inside of VSA, and has an assertion to that |
340 | | * effect. Until we fix that, we simply ignore all attributes which do not fit into the |
341 | | * established hierarchy. |
342 | | */ |
343 | 0 | if (c->da->flags.is_raw && c->da->parent && (c->da->parent->type == FR_TYPE_VSA)) continue; |
344 | | |
345 | 0 | fr_assert_msg((c->da->type != FR_TYPE_VENDOR) || (c->da->attr <= 255), "Cursor found unencodable attribute"); |
346 | |
|
347 | 0 | break; |
348 | 0 | } |
349 | |
|
350 | 0 | return c; |
351 | 0 | } |
352 | | |
353 | | ssize_t fr_dhcpv4_encode(uint8_t *buffer, size_t buflen, dhcp_packet_t *original, int code, uint32_t xid, fr_pair_list_t *vps) |
354 | 0 | { |
355 | 0 | return fr_dhcpv4_encode_dbuff(&FR_DBUFF_TMP(buffer, buflen), original, code, xid, vps); |
356 | 0 | } |
357 | | |
358 | | ssize_t fr_dhcpv4_encode_dbuff(fr_dbuff_t *dbuff, dhcp_packet_t *original, int code, uint32_t xid, fr_pair_list_t *vps) |
359 | 0 | { |
360 | 0 | fr_dcursor_t cursor; |
361 | 0 | fr_pair_t *vp; |
362 | 0 | ssize_t len; |
363 | 0 | fr_dbuff_t work_dbuff = FR_DBUFF(dbuff); |
364 | | |
365 | | /* |
366 | | * @todo: Make this work again. |
367 | | */ |
368 | | #if 0 |
369 | | mms = DEFAULT_PACKET_SIZE; /* maximum message size */ |
370 | | |
371 | | /* |
372 | | * Clients can request a LARGER size, but not a |
373 | | * smaller one. They also cannot request a size |
374 | | * larger than MTU. |
375 | | */ |
376 | | |
377 | | /* Maximum-Msg-Size */ |
378 | | vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_dhcp_maximum_msg_size); |
379 | | if (vp && (vp->vp_uint16 > mms)) { |
380 | | mms = vp->vp_uint16; |
381 | | |
382 | | if (mms > MAX_PACKET_SIZE) mms = MAX_PACKET_SIZE; |
383 | | } |
384 | | #endif |
385 | |
|
386 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_opcode); |
387 | 0 | if (vp) { |
388 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, vp->vp_uint8); |
389 | 0 | } else { |
390 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, (uint8_t)0x01); /* client message */ |
391 | 0 | } |
392 | | |
393 | | /* Hardware-Type */ |
394 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_hardware_type); |
395 | 0 | if (vp) { |
396 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, vp->vp_uint8); |
397 | |
|
398 | 0 | } else if (original) { |
399 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, original->htype); |
400 | |
|
401 | 0 | } else { /* we are ALWAYS ethernet */ |
402 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, (uint8_t)0x01); |
403 | 0 | } |
404 | | |
405 | | /* Hardware-Address-len */ |
406 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_hardware_address_length); |
407 | 0 | if (vp) { |
408 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, vp->vp_uint8); |
409 | |
|
410 | 0 | } else if (original) { |
411 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, original->hlen); |
412 | |
|
413 | 0 | } else { /* we are ALWAYS ethernet */ |
414 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, (uint8_t)0x06); |
415 | 0 | } |
416 | | |
417 | | /* Hop-Count */ |
418 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_hop_count); |
419 | 0 | if (vp) { |
420 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, vp->vp_uint8); |
421 | |
|
422 | 0 | } else if (original) { |
423 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, original->hops); |
424 | |
|
425 | 0 | } else { |
426 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, (uint8_t)0x00); |
427 | 0 | } |
428 | | |
429 | | /* Transaction-Id */ |
430 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, xid); |
431 | | |
432 | | /* Number-of-Seconds */ |
433 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_number_of_seconds); |
434 | 0 | if (vp) { |
435 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, vp->vp_uint16); |
436 | 0 | } else { |
437 | 0 | FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, sizeof(vp->vp_uint16)); |
438 | 0 | } |
439 | | |
440 | | /* Flags */ |
441 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_flags); |
442 | 0 | if (vp) { |
443 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, vp->vp_uint16); |
444 | 0 | } else if (original) { /* Original flags, still in network order */ |
445 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t *)&original->flags, sizeof(original->flags)); |
446 | 0 | } else { |
447 | 0 | FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, sizeof(vp->vp_uint16)); |
448 | 0 | } |
449 | | |
450 | | /* Client-IP-Address */ |
451 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_client_ip_address); |
452 | 0 | if (vp) { |
453 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv4addr, sizeof(vp->vp_ipv4addr)); |
454 | 0 | } else { |
455 | 0 | FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, sizeof(vp->vp_ipv4addr)); |
456 | 0 | } |
457 | | |
458 | | /* Your-IP-address */ |
459 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_your_ip_address); |
460 | 0 | if (vp) { |
461 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv4addr, sizeof(vp->vp_ipv4addr)); |
462 | 0 | } else { |
463 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t) INADDR_ANY); |
464 | 0 | } |
465 | | |
466 | | /* Server-IP-Address */ |
467 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_server_ip_address); |
468 | 0 | if (vp) { |
469 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv4addr, sizeof(vp->vp_ipv4addr)); |
470 | 0 | } else { |
471 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t) INADDR_ANY); |
472 | 0 | } |
473 | | |
474 | | /* |
475 | | * Gateway-IP-Address |
476 | | */ |
477 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_gateway_ip_address); |
478 | 0 | if (vp) { |
479 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv4addr, sizeof(vp->vp_ipv4addr)); |
480 | |
|
481 | 0 | } else if (original) { /* copy whatever value was in the original */ |
482 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&original->giaddr, sizeof(original->giaddr)); |
483 | |
|
484 | 0 | } else { |
485 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t) INADDR_ANY); |
486 | 0 | } |
487 | | |
488 | | /* Client-Hardware-Address */ |
489 | 0 | if ((vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_client_hardware_address))) { |
490 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)vp->vp_ether, sizeof(vp->vp_ether)); |
491 | 0 | FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DHCP_CHADDR_LEN - sizeof(vp->vp_ether)); |
492 | |
|
493 | 0 | } else if (original) { /* copy whatever value was in the original */ |
494 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, &original->chaddr[0], sizeof(original->chaddr)); |
495 | |
|
496 | 0 | } else { |
497 | 0 | FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DHCP_CHADDR_LEN); |
498 | 0 | } |
499 | | |
500 | | /* Server-Host-Name */ |
501 | 0 | if ((vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_server_host_name))) { |
502 | 0 | if (vp->vp_length > DHCP_SNAME_LEN) { |
503 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_strvalue, DHCP_SNAME_LEN); |
504 | 0 | } else { |
505 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_strvalue, vp->vp_length); |
506 | 0 | FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DHCP_SNAME_LEN - vp->vp_length); |
507 | 0 | } |
508 | 0 | } else { |
509 | 0 | FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DHCP_SNAME_LEN); |
510 | 0 | } |
511 | | |
512 | | /* |
513 | | * Copy over Boot-Filename. |
514 | | * |
515 | | * FIXME: This copy should be delayed until AFTER the options |
516 | | * have been processed. If there are too many options for |
517 | | * the packet, then they go into the sname && filename fields. |
518 | | * When that happens, the boot filename is passed as an option, |
519 | | * instead of being placed verbatim in the filename field. |
520 | | */ |
521 | | |
522 | | /* Boot-Filename */ |
523 | 0 | if ((vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_boot_filename))) { |
524 | 0 | if (vp->vp_length > DHCP_FILE_LEN) { |
525 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_strvalue, DHCP_FILE_LEN); |
526 | 0 | } else { |
527 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_strvalue, vp->vp_length); |
528 | 0 | FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DHCP_FILE_LEN - vp->vp_length); |
529 | 0 | } |
530 | 0 | } else { |
531 | 0 | FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DHCP_FILE_LEN); |
532 | 0 | } |
533 | | |
534 | | /* DHCP magic number */ |
535 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t) DHCP_OPTION_MAGIC_NUMBER); |
536 | | |
537 | | /* |
538 | | * Pre-sort attributes into contiguous blocks so that fr_dhcpv4_encode_option |
539 | | * operates correctly. This changes the order of the list, but never mind... |
540 | | * |
541 | | * If attr_dhcp_message_type is present it will have been sorted as the first |
542 | | * option, so we don't need to search for it. |
543 | | */ |
544 | 0 | fr_pair_list_sort(vps, fr_dhcpv4_attr_cmp); |
545 | 0 | fr_pair_dcursor_iter_init(&cursor, vps, fr_dhcpv4_next_encodable, dict_dhcpv4); |
546 | |
|
547 | 0 | vp = fr_dcursor_head(&cursor); |
548 | 0 | if (vp && vp->da == attr_dhcp_message_type) { |
549 | 0 | FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, FR_MESSAGE_TYPE, 0x01, vp->vp_uint8); |
550 | 0 | fr_dcursor_next(&cursor); /* Skip message type so it doesn't get double encoded */ |
551 | 0 | } else { |
552 | 0 | FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, FR_MESSAGE_TYPE, 0x01, (uint8_t)code); |
553 | 0 | } |
554 | | |
555 | | /* |
556 | | * Each call to fr_dhcpv4_encode_option will encode one complete DHCP option, |
557 | | * and sub options. |
558 | | */ |
559 | 0 | while ((vp = fr_dcursor_current(&cursor))) { |
560 | | /* |
561 | | * The encoder skips message type, and returns |
562 | | * "len==0" for it. We want to allow that, BUT |
563 | | * stop when the encoder returns "len==0" for |
564 | | * other attributes. So we need to skip it |
565 | | * manually, too. |
566 | | */ |
567 | 0 | if (vp->da == attr_dhcp_message_type) { |
568 | 0 | (void) fr_dcursor_next(&cursor); |
569 | 0 | continue; |
570 | 0 | } |
571 | | |
572 | 0 | len = fr_dhcpv4_encode_option(&work_dbuff, |
573 | 0 | &cursor, &(fr_dhcpv4_ctx_t){ .root = fr_dict_root(dict_dhcpv4) }); |
574 | 0 | if (len <= 0) break; |
575 | 0 | } |
576 | |
|
577 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, (uint8_t)FR_END_OF_OPTIONS); |
578 | | |
579 | | /* |
580 | | * FIXME: if (fr_dbuff_used(&work_dbuff) > mms), |
581 | | * then we put the extra options into the "sname" and "file" |
582 | | * fields, AND set the "end option option" in the "options" |
583 | | * field. We also set the "overload option", |
584 | | * and put options into the "file" field, followed by |
585 | | * the "sname" field. Where each option is completely |
586 | | * enclosed in the "file" and/or "sname" field, AND |
587 | | * followed by the "end of option", and MUST be followed |
588 | | * by padding option. |
589 | | * |
590 | | * Yuck. That sucks... |
591 | | */ |
592 | 0 | if (fr_dbuff_used(&work_dbuff) < DEFAULT_PACKET_SIZE) { |
593 | 0 | FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DEFAULT_PACKET_SIZE - fr_dbuff_used(&work_dbuff)); |
594 | 0 | } |
595 | | |
596 | 0 | return fr_dbuff_set(dbuff, fr_dbuff_used(&work_dbuff)); |
597 | 0 | } |
598 | | |
599 | | |
600 | | /** Resolve/cache attributes in the DHCP dictionary |
601 | | * |
602 | | * @return |
603 | | * - 0 on success. |
604 | | * - -1 on failure. |
605 | | */ |
606 | | int fr_dhcpv4_global_init(void) |
607 | 6 | { |
608 | 6 | if (instance_count > 0) { |
609 | 2 | instance_count++; |
610 | 2 | return 0; |
611 | 2 | } |
612 | | |
613 | 4 | instance_count++; |
614 | | |
615 | 4 | if (fr_dict_autoload(dhcpv4_dict) < 0) { |
616 | 0 | fail: |
617 | 0 | instance_count--; |
618 | 0 | return -1; |
619 | 0 | } |
620 | | |
621 | 4 | if (fr_dict_attr_autoload(dhcpv4_dict_attr) < 0) { |
622 | 0 | fr_dict_autofree(dhcpv4_dict); |
623 | 0 | goto fail; |
624 | 0 | } |
625 | | |
626 | 4 | instantiated = true; |
627 | 4 | return 0; |
628 | 4 | } |
629 | | |
630 | | void fr_dhcpv4_global_free(void) |
631 | 4 | { |
632 | 4 | if (!instantiated) return; |
633 | | |
634 | 4 | fr_assert(instance_count > 0); |
635 | | |
636 | 4 | if (--instance_count > 0) return; |
637 | | |
638 | 2 | fr_dict_autofree(dhcpv4_dict); |
639 | 2 | instantiated = false; |
640 | 2 | } |
641 | | |
642 | | |
643 | | static char const *short_header_names[] = { |
644 | | "opcode", |
645 | | "hwtype", |
646 | | "hwaddrlen", |
647 | | "hop_count", |
648 | | "xid", |
649 | | "seconds", |
650 | | "flags", |
651 | | "ciaddr", |
652 | | "yiaddr", |
653 | | "siaddr", |
654 | | "giaddr", |
655 | | "chaddr", |
656 | | "server_hostname", |
657 | | "boot_filename", |
658 | | }; |
659 | | |
660 | | static void print_hex_data(FILE *fp, uint8_t const *ptr, int attrlen, int depth) |
661 | 0 | { |
662 | 0 | int i; |
663 | 0 | static char const tabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; |
664 | |
|
665 | 0 | for (i = 0; i < attrlen; i++) { |
666 | 0 | if ((i > 0) && ((i & 0x0f) == 0x00)) |
667 | 0 | fprintf(fp, "%.*s", depth, tabs); |
668 | 0 | fprintf(fp, "%02x ", ptr[i]); |
669 | 0 | if ((i & 0x0f) == 0x0f) fprintf(fp, "\n"); |
670 | 0 | } |
671 | 0 | if ((i & 0x0f) != 0) fprintf(fp, "\n"); |
672 | 0 | } |
673 | | |
674 | | /** Print a raw DHCP packet as hex. |
675 | | * |
676 | | */ |
677 | | void fr_dhcpv4_print_hex(FILE *fp, uint8_t const *packet, size_t packet_len) |
678 | 0 | { |
679 | 0 | int i; |
680 | 0 | uint8_t const *attr, *end; |
681 | |
|
682 | 0 | end = packet + packet_len; |
683 | 0 | attr = packet; |
684 | |
|
685 | 0 | for (i = 0; i < 14; i++) { |
686 | 0 | fprintf(fp, "\t%s: ", short_header_names[i]); |
687 | 0 | print_hex_data(fp, attr, dhcp_header_sizes[i], 2); |
688 | 0 | attr += dhcp_header_sizes[i]; |
689 | 0 | } |
690 | |
|
691 | 0 | fprintf(fp, "\tmagic:\t%02x %02x %02x %02x\n", attr[0], attr[1], attr[2], attr[3]); |
692 | 0 | attr += 4; |
693 | |
|
694 | 0 | fprintf(fp, "\toptions\n"); |
695 | 0 | while (attr < end) { |
696 | 0 | fprintf(fp, "\t\t"); |
697 | | |
698 | | /* |
699 | | * The caller should already have called fr_dhcpv4_ok(). |
700 | | */ |
701 | 0 | fr_assert((attr + 2) <= end); |
702 | | |
703 | | /* |
704 | | * End of options. |
705 | | */ |
706 | 0 | if ((attr[0] == 0) || (attr[1] == 255)) { |
707 | 0 | fprintf(fp, "%02x\n", attr[0]); |
708 | 0 | break; |
709 | 0 | } |
710 | | |
711 | 0 | fprintf(fp, "%02x %02x ", attr[0], attr[1]); |
712 | |
|
713 | 0 | print_hex_data(fp, attr + 2, attr[1], 3); |
714 | |
|
715 | 0 | attr += attr[1] + 2; |
716 | 0 | } |
717 | |
|
718 | 0 | fprintf(fp, "\n"); |
719 | 0 | } |
720 | | |
721 | | static bool attr_valid(fr_dict_attr_t *da) |
722 | 1.62k | { |
723 | | /* |
724 | | * DNS labels are strings, but are known width. |
725 | | */ |
726 | 1.62k | if (fr_dhcpv4_flag_dns_label(da)) { |
727 | 40 | if (da->type != FR_TYPE_STRING) { |
728 | 0 | fr_strerror_const("The 'dns_label' flag can only be used with attributes of type 'string'"); |
729 | 0 | return false; |
730 | 0 | } |
731 | | |
732 | 40 | da->flags.is_known_width = true; |
733 | 40 | da->flags.length = 0; |
734 | 40 | } |
735 | | |
736 | 1.62k | if (da->type == FR_TYPE_ATTR) { |
737 | 4 | da->flags.is_known_width = true; |
738 | 4 | da->flags.length = 1; |
739 | 4 | } |
740 | | |
741 | 1.62k | if (da_is_length_field16(da)) { |
742 | 0 | fr_strerror_const("The 'length=uint16' flag cannot be used for DHCPv4"); |
743 | 0 | return false; |
744 | 0 | } |
745 | | |
746 | | /* |
747 | | * "arrays" of string/octets are encoded as a 8-bit |
748 | | * length, followed by the actual data. |
749 | | */ |
750 | 1.62k | if (da->flags.array) { |
751 | 228 | if ((da->type == FR_TYPE_STRING) || (da->type == FR_TYPE_OCTETS)) { |
752 | 32 | if (da->flags.extra && !da_is_length_field8(da)) { |
753 | 0 | fr_strerror_const("Invalid flags"); |
754 | 0 | return false; |
755 | 0 | } |
756 | | |
757 | 32 | da->flags.is_known_width = true; |
758 | 32 | da->flags.extra = true; |
759 | 32 | da->flags.subtype = FLAG_LENGTH_UINT8; |
760 | 32 | } |
761 | | |
762 | 228 | if (!da->flags.is_known_width) { |
763 | 0 | fr_strerror_const("DHCPv4 arrays require data types which have known width"); |
764 | 0 | return false; |
765 | 0 | } |
766 | 228 | } |
767 | | |
768 | | /* |
769 | | * "extra" signifies that subtype is being used by the |
770 | | * dictionaries itself. |
771 | | */ |
772 | 1.62k | if (da->flags.extra || !da->flags.subtype) return true; |
773 | | |
774 | 0 | if ((da->type != FR_TYPE_IPV4_PREFIX) && |
775 | 0 | (fr_dhcpv4_flag_prefix(da))) { |
776 | 0 | fr_strerror_const("The 'prefix=...' flag can only be used with attributes of type 'ipv4prefix'"); |
777 | 0 | return false; |
778 | 0 | } |
779 | | |
780 | 0 | if ((da->type != FR_TYPE_BOOL) && fr_dhcpv4_flag_exists(da)) { |
781 | 0 | fr_strerror_const("The 'exists' flag can only be used with attributes of type 'bool'"); |
782 | 0 | return false; |
783 | 0 | } |
784 | | |
785 | 0 | if ((da->type == FR_TYPE_ATTR) && !da->parent->flags.is_root) { |
786 | 0 | fr_strerror_const("The 'attribute' data type can only be used at the dictionary root"); |
787 | 0 | return false; |
788 | 0 | } |
789 | | |
790 | 0 | return true; |
791 | 0 | } |
792 | | |
793 | | extern fr_dict_protocol_t libfreeradius_dhcpv4_dict_protocol; |
794 | | fr_dict_protocol_t libfreeradius_dhcpv4_dict_protocol = { |
795 | | .name = "dhcpv4", |
796 | | .default_type_size = 1, |
797 | | .default_type_length = 1, |
798 | | .attr = { |
799 | | .flags = { |
800 | | .table = dhcpv4_flags, |
801 | | .table_len = NUM_ELEMENTS(dhcpv4_flags), |
802 | | .len = sizeof(fr_dhcpv4_attr_flags_t) |
803 | | }, |
804 | | .valid = attr_valid |
805 | | }, |
806 | | |
807 | | .init = fr_dhcpv4_global_init, |
808 | | .free = fr_dhcpv4_global_free, |
809 | | |
810 | | .encode = fr_dhcpv4_encode_foreign, |
811 | | .decode = fr_dhcpv4_decode_foreign, |
812 | | }; |