/src/freeradius-server/src/protocols/dhcpv4/packet.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: be5011bf051d31eab6af075efd45770b875c691c $ |
19 | | * |
20 | | * @file protocols/dhcpv4/packet.c |
21 | | * @brief Functions to encode/decode DHCP packets. |
22 | | * |
23 | | * @copyright 2008,2017 The FreeRADIUS server project |
24 | | * @copyright 2008 Alan DeKok (aland@deployingradius.com) |
25 | | */ |
26 | | #include <freeradius-devel/util/pair.h> |
27 | | #include <freeradius-devel/util/rand.h> |
28 | | #include <freeradius-devel/protocol/dhcpv4/rfc2131.h> |
29 | | |
30 | | #include "dhcpv4.h" |
31 | | #include "attrs.h" |
32 | | |
33 | | /** Retrieve a DHCP option from a raw packet buffer |
34 | | * |
35 | | * |
36 | | */ |
37 | | uint8_t const *fr_dhcpv4_packet_get_option(dhcp_packet_t const *packet, size_t packet_size, fr_dict_attr_t const *da) |
38 | 1.06k | { |
39 | 1.06k | int overload = 0; |
40 | 1.06k | int field = DHCP_OPTION_FIELD; |
41 | 1.06k | size_t where, size; |
42 | 1.06k | uint8_t const *data; |
43 | | |
44 | 1.06k | if (packet_size < MIN_PACKET_SIZE) return NULL; |
45 | | |
46 | | /* |
47 | | * This is needed for UBSAN on MacOS, that doesn't |
48 | | * allow misaligned accesses. Because the packet |
49 | | * structure is flat, we don't need to deref the |
50 | | * packet pointer at any point, we just need to |
51 | | * calculate the offsets relative to the pointer |
52 | | * value and use those... Whatever actually deals |
53 | | * with the option is just expecting a uint8_t *. |
54 | | */ |
55 | 1.06k | #define ALIGNED_ACCESS(packet, field) \ |
56 | 1.74k | (uint8_t const *)packet + offsetof(dhcp_packet_t, field) |
57 | | |
58 | 1.06k | where = 0; |
59 | 1.06k | size = packet_size - offsetof(dhcp_packet_t, options); |
60 | | |
61 | | /* |
62 | | * Alignment fix. We can't just deref a pointer |
63 | | */ |
64 | 1.06k | data = ALIGNED_ACCESS(packet, options); |
65 | 18.4k | while (where < size) { |
66 | 18.4k | if (data[0] == 0) { /* padding */ |
67 | 4.61k | where++; |
68 | 4.61k | data++; |
69 | 4.61k | continue; |
70 | 4.61k | } |
71 | | |
72 | 13.8k | if (data[0] == 255) { /* end of options */ |
73 | 693 | if ((field == DHCP_OPTION_FIELD) && (overload & DHCP_FILE_FIELD)) { |
74 | 644 | data = ALIGNED_ACCESS(packet, file); |
75 | 644 | where = 0; |
76 | 644 | size = sizeof(packet->file); |
77 | 644 | field = DHCP_FILE_FIELD; |
78 | 644 | continue; |
79 | | |
80 | 644 | } else if ((field == DHCP_FILE_FIELD || field == DHCP_OPTION_FIELD) && (overload & DHCP_SNAME_FIELD)) { |
81 | 45 | data = ALIGNED_ACCESS(packet, sname); |
82 | 45 | where = 0; |
83 | 45 | size = sizeof(packet->sname); |
84 | 45 | field = DHCP_SNAME_FIELD; |
85 | 45 | continue; |
86 | 45 | } |
87 | | |
88 | 4 | return NULL; |
89 | 693 | } |
90 | | |
91 | | /* |
92 | | * We MUST have a real option here. |
93 | | */ |
94 | 13.1k | if ((where + 2) > size) { |
95 | 5 | fr_strerror_printf("Options overflow field at %u", |
96 | 5 | (unsigned int) (data - (uint8_t const *) packet)); |
97 | 5 | return NULL; |
98 | 5 | } |
99 | | |
100 | 13.1k | if ((where + 2 + data[1]) > size) { |
101 | 15 | fr_strerror_printf("Option length overflows field at %u", |
102 | 15 | (unsigned int) (data - (uint8_t const *) packet)); |
103 | 15 | return NULL; |
104 | 15 | } |
105 | | |
106 | 13.1k | if (data[0] == da->attr) return data; |
107 | | |
108 | 12.1k | if ((data[0] == 52) && (data[1] > 0)) { /* overload sname and/or file */ |
109 | 1.01k | overload = data[2]; |
110 | 1.01k | } |
111 | | |
112 | 12.1k | where += data[1] + 2; |
113 | 12.1k | data += data[1] + 2; |
114 | 12.1k | } |
115 | | |
116 | 6 | return NULL; |
117 | 1.06k | } |
118 | | |
119 | | int fr_dhcpv4_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, unsigned int *code) |
120 | 1.01k | { |
121 | 1.01k | size_t i; |
122 | 1.01k | uint8_t const *p = data; |
123 | 1.01k | uint32_t giaddr; |
124 | 1.01k | fr_pair_list_t tmp; |
125 | 1.01k | fr_pair_t *vp; |
126 | 1.01k | fr_pair_t *maxms, *mtu, *netaddr; |
127 | 1.01k | fr_value_box_t box; |
128 | 1.01k | fr_dhcpv4_ctx_t *packet_ctx; |
129 | | |
130 | 1.01k | fr_pair_list_init(&tmp); |
131 | | |
132 | 1.01k | fr_assert(data_len >= MIN_PACKET_SIZE); /* fr_dhcpv4_ok() MUST be called first */ |
133 | | |
134 | 1.01k | if (data[1] > 1) { |
135 | 0 | fr_strerror_printf("Packet is not Ethernet: %u", |
136 | 0 | data[1]); |
137 | 0 | return -1; |
138 | 0 | } |
139 | | |
140 | 1.01k | packet_ctx = talloc_zero(ctx, fr_dhcpv4_ctx_t); |
141 | 1.01k | if (!packet_ctx) return -1; |
142 | 1.01k | packet_ctx->tmp_ctx = talloc(packet_ctx, uint8_t); |
143 | 1.01k | packet_ctx->root = fr_dict_root(dict_dhcpv4); |
144 | | |
145 | | /* |
146 | | * Decode the header. |
147 | | */ |
148 | 15.2k | for (i = 0; i < dhcp_header_attrs_len; i++) { |
149 | 14.2k | fr_dict_attr_t const *da = *dhcp_header_attrs[i]; |
150 | | |
151 | 14.2k | vp = fr_pair_afrom_da(ctx, da); |
152 | 14.2k | if (!vp) { |
153 | 0 | fr_strerror_const_push("Cannot decode packet due to internal error"); |
154 | 0 | error_vp: |
155 | 0 | talloc_free(vp); |
156 | 8 | error: |
157 | 8 | fr_pair_list_free(&tmp); |
158 | 8 | talloc_free(packet_ctx); |
159 | 8 | return -1; |
160 | 0 | } |
161 | | |
162 | 14.2k | switch (vp->vp_type) { |
163 | 2.03k | case FR_TYPE_STRING: |
164 | | /* |
165 | | * According to RFC 2131, these are null terminated strings. |
166 | | * We don't trust everyone to abide by the RFC, though. |
167 | | */ |
168 | 2.03k | if (*p != '\0') { |
169 | 1.48k | uint8_t const *q; |
170 | | |
171 | 1.48k | q = memchr(p, '\0', dhcp_header_sizes[i]); |
172 | 1.48k | fr_pair_value_bstrndup(vp, (char const *)p, q ? q - p : dhcp_header_sizes[i], true); |
173 | 1.48k | } else { |
174 | 553 | TALLOC_FREE(vp); |
175 | 553 | } |
176 | 2.03k | break; |
177 | | |
178 | | /* |
179 | | * The DHCP header size for CHADDR is not |
180 | | * 6, so the value_box function doesn't |
181 | | * like it. Just do the copy manually. |
182 | | */ |
183 | 1.01k | case FR_TYPE_ETHERNET: |
184 | 1.01k | if ((data[1] != 1) || (data[2] != 6)) { |
185 | 976 | TALLOC_FREE(vp); |
186 | 976 | break; |
187 | 976 | } |
188 | | |
189 | 43 | memcpy(vp->vp_ether, p, sizeof(vp->vp_ether)); |
190 | 43 | break; |
191 | | |
192 | 11.2k | default: |
193 | 11.2k | if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da, |
194 | 11.2k | &FR_DBUFF_TMP(p, (size_t)dhcp_header_sizes[i]), |
195 | 11.2k | dhcp_header_sizes[i], true) < 0) goto error_vp; |
196 | 11.2k | break; |
197 | 14.2k | } |
198 | 14.2k | p += dhcp_header_sizes[i]; |
199 | | |
200 | 14.2k | if (!vp) continue; |
201 | | |
202 | 12.7k | fr_pair_append(&tmp, vp); |
203 | 12.7k | } |
204 | | |
205 | | /* |
206 | | * Nothing uses tail after this call, if it does in the future |
207 | | * it'll need to find the new tail... |
208 | | */ |
209 | 1.01k | { |
210 | 1.01k | uint8_t const *end; |
211 | 1.01k | ssize_t len; |
212 | | |
213 | 1.01k | p = data + 240; |
214 | 1.01k | end = p + (data_len - 240); |
215 | | |
216 | | /* |
217 | | * Loop over all the options data |
218 | | */ |
219 | 16.6k | while (p < end) { |
220 | 15.7k | len = fr_dhcpv4_decode_option(ctx, &tmp, p, (end - p), packet_ctx); |
221 | 15.7k | if (len <= 0) { |
222 | 414 | fail: |
223 | 414 | fr_pair_list_free(&tmp); |
224 | 414 | talloc_free(packet_ctx); |
225 | 414 | return -1; |
226 | 46 | } |
227 | 15.6k | p += len; |
228 | 15.6k | } |
229 | | |
230 | 973 | if (code) { |
231 | 973 | vp = fr_pair_find_by_da(&tmp, NULL, attr_dhcp_message_type); |
232 | 973 | if (vp) { |
233 | 353 | *code = vp->vp_uint8; |
234 | 353 | } |
235 | 973 | } |
236 | | |
237 | | /* |
238 | | * If option Overload is present in the 'options' field, then fields 'file' and/or 'sname' |
239 | | * are used to hold more options. They are partitioned and must be interpreted in sequence. |
240 | | */ |
241 | 973 | vp = fr_pair_find_by_da(&tmp, NULL, attr_dhcp_overload); |
242 | 973 | if (vp) { |
243 | 806 | if ((vp->vp_uint8 & 1) == 1) { |
244 | | /* |
245 | | * The 'file' field is used to hold options. |
246 | | * It must be interpreted before 'sname'. |
247 | | */ |
248 | 803 | p = data + offsetof(dhcp_packet_t, file); |
249 | 803 | end = p + DHCP_FILE_LEN; |
250 | 20.0k | while (p < end) { |
251 | 19.3k | len = fr_dhcpv4_decode_option(ctx, &tmp, |
252 | 19.3k | p, end - p, packet_ctx); |
253 | 19.3k | if (len <= 0) goto fail; |
254 | 19.2k | p += len; |
255 | 19.2k | } |
256 | 699 | fr_pair_delete_by_da(&tmp, attr_dhcp_boot_filename); |
257 | 699 | } |
258 | 702 | if ((vp->vp_uint8 & 2) == 2) { |
259 | | /* |
260 | | * The 'sname' field is used to hold options. |
261 | | */ |
262 | 691 | p = data + offsetof(dhcp_packet_t, sname); |
263 | 691 | end = p + DHCP_SNAME_LEN; |
264 | 8.72k | while (p < end) { |
265 | 8.29k | len = fr_dhcpv4_decode_option(ctx, &tmp, |
266 | 8.29k | p, end - p, packet_ctx); |
267 | 8.29k | if (len <= 0) goto fail; |
268 | 8.03k | p += len; |
269 | 8.03k | } |
270 | 427 | fr_pair_delete_by_da(&tmp, attr_dhcp_server_host_name); |
271 | 427 | } |
272 | 702 | } |
273 | 973 | } |
274 | | |
275 | | /* |
276 | | * If DHCP request, set ciaddr to zero. |
277 | | */ |
278 | | |
279 | | /* |
280 | | * Set broadcast flag for broken vendors, but only if |
281 | | * giaddr isn't set. |
282 | | */ |
283 | 605 | memcpy(&giaddr, data + 24, sizeof(giaddr)); |
284 | 605 | if (giaddr == htonl(INADDR_ANY)) { |
285 | | /* |
286 | | * DHCP Opcode is request |
287 | | */ |
288 | 113 | vp = fr_pair_find_by_da(&tmp, NULL, attr_dhcp_opcode); |
289 | 113 | if (vp && vp->vp_uint8 == 1) { |
290 | | /* |
291 | | * Vendor is "MSFT 98" |
292 | | */ |
293 | 73 | vp = fr_pair_find_by_da(&tmp, NULL, attr_dhcp_vendor_class_identifier); |
294 | 73 | if (vp && (vp->vp_length == 7) && (memcmp(vp->vp_strvalue, "MSFT 98", 7) == 0)) { |
295 | 2 | vp = fr_pair_find_by_da(&tmp, NULL, attr_dhcp_flags); |
296 | | |
297 | | /* |
298 | | * Reply should be broadcast. |
299 | | */ |
300 | 2 | if (vp) vp->vp_uint16 |= 0x8000; |
301 | 2 | } |
302 | 73 | } |
303 | 113 | } |
304 | | |
305 | | /* |
306 | | * Determine the address to use in looking up which subnet the |
307 | | * client belongs to based on packet data. The sequence here |
308 | | * is based on ISC DHCP behaviour and RFCs 3527 and 3011. We |
309 | | * store the found address in an internal attribute of |
310 | | * Network-Subnet |
311 | | * |
312 | | * |
313 | | * All of these options / fields are type "ipv4addr", so |
314 | | * we need to decode them as that. And then cast it to |
315 | | * "ipv4prefix". |
316 | | */ |
317 | 605 | vp = fr_pair_afrom_da(ctx, attr_dhcp_network_subnet); |
318 | 605 | if (!vp) goto error; |
319 | | |
320 | | /* |
321 | | * First look for Relay-Link-Selection |
322 | | */ |
323 | 605 | netaddr = fr_pair_find_by_da_nested(&tmp, NULL, attr_dhcp_relay_link_selection); |
324 | 605 | if (!netaddr) { |
325 | | /* |
326 | | * Next try Subnet-Selection-Option |
327 | | */ |
328 | 604 | netaddr = fr_pair_find_by_da(&tmp, NULL, attr_dhcp_subnet_selection_option); |
329 | 604 | } |
330 | | |
331 | 605 | if (netaddr) { |
332 | | /* |
333 | | * Store whichever address we found from options and ensure |
334 | | * the data type matches the pair, i.e address to prefix |
335 | | * conversion. |
336 | | */ |
337 | 3 | if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, &netaddr->data) < 0) goto error_vp; |
338 | | |
339 | 602 | } else if (giaddr != htonl(INADDR_ANY)) { |
340 | | /* |
341 | | * Gateway address is set - use that one |
342 | | */ |
343 | 491 | if (fr_value_box_from_network(vp, &box, FR_TYPE_IPV4_ADDR, NULL, |
344 | 491 | &FR_DBUFF_TMP(data + 24, 4), 4, true) < 0) goto error_vp; |
345 | 491 | if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, &box) < 0) goto error_vp; |
346 | | |
347 | 491 | } else { |
348 | | /* |
349 | | * else, store client address whatever it is |
350 | | */ |
351 | 111 | if (fr_value_box_from_network(vp, &box, FR_TYPE_IPV4_ADDR, NULL, |
352 | 111 | &FR_DBUFF_TMP(data + 12, 4), 4, true) < 0) goto error_vp; |
353 | 111 | if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, &box) < 0) goto error_vp; |
354 | 111 | } |
355 | | |
356 | 605 | fr_pair_append(&tmp, vp); |
357 | | |
358 | | /* |
359 | | * Client can request a LARGER size, but not a smaller |
360 | | * one. They also cannot request a size larger than MTU. |
361 | | */ |
362 | 605 | maxms = fr_pair_find_by_da(&tmp, NULL, attr_dhcp_dhcp_maximum_msg_size); |
363 | 605 | mtu = fr_pair_find_by_da(&tmp, NULL, attr_dhcp_interface_mtu_size); |
364 | | |
365 | 605 | if (mtu && (mtu->vp_uint16 < DEFAULT_PACKET_SIZE)) { |
366 | 8 | fr_strerror_const("Client says MTU is smaller than minimum permitted by the specification"); |
367 | 8 | goto error; |
368 | 8 | } |
369 | | |
370 | | /* |
371 | | * Client says maximum message size is smaller than minimum permitted |
372 | | * by the specification: fixing it. |
373 | | */ |
374 | 597 | if (maxms && (maxms->vp_uint16 < DEFAULT_PACKET_SIZE)) maxms->vp_uint16 = DEFAULT_PACKET_SIZE; |
375 | | |
376 | | /* |
377 | | * Client says MTU is smaller than maximum message size: fixing it |
378 | | */ |
379 | 597 | if (maxms && mtu && (maxms->vp_uint16 > mtu->vp_uint16)) maxms->vp_uint16 = mtu->vp_uint16; |
380 | | |
381 | | /* |
382 | | * FIXME: Nuke attributes that aren't used in the normal |
383 | | * header for discover/requests. |
384 | | */ |
385 | 597 | fr_pair_list_append(out, &tmp); |
386 | | |
387 | 597 | return 0; |
388 | 605 | } |
389 | | |
390 | | int fr_dhcpv4_packet_encode(fr_packet_t *packet, fr_pair_list_t *list) |
391 | 0 | { |
392 | 0 | ssize_t len; |
393 | 0 | fr_pair_t *vp; |
394 | |
|
395 | 0 | if (packet->data) return 0; |
396 | | |
397 | 0 | packet->data_len = MAX_PACKET_SIZE; |
398 | 0 | packet->data = talloc_zero_array(packet, uint8_t, packet->data_len); |
399 | | |
400 | | /* XXX Ugly ... should be set by the caller */ |
401 | 0 | if (packet->code == 0) packet->code = FR_DHCP_NAK; |
402 | | |
403 | | /* store xid */ |
404 | 0 | if ((vp = fr_pair_find_by_da(list, NULL, attr_dhcp_transaction_id))) { |
405 | 0 | packet->id = vp->vp_uint32; |
406 | 0 | } else { |
407 | 0 | packet->id = fr_rand(); |
408 | 0 | } |
409 | |
|
410 | 0 | len = fr_dhcpv4_encode(packet->data, packet->data_len, NULL, packet->code, packet->id, list); |
411 | 0 | if (len < 0) return -1; |
412 | | |
413 | 0 | packet->data_len = len; |
414 | |
|
415 | 0 | return 0; |
416 | 0 | } |
417 | | |
418 | | fr_packet_t *fr_dhcpv4_packet_alloc(uint8_t const *data, size_t data_len) |
419 | 0 | { |
420 | 0 | fr_packet_t *packet; |
421 | 0 | uint32_t magic; |
422 | 0 | uint8_t const *code; |
423 | |
|
424 | 0 | fr_assert(data_len >= MIN_PACKET_SIZE); /* fr_dhcpv4_ok() MUST be called first */ |
425 | |
|
426 | 0 | code = fr_dhcpv4_packet_get_option((dhcp_packet_t const *) data, data_len, attr_dhcp_message_type); |
427 | 0 | if (!code || (code[1] != 1)) return NULL; |
428 | | |
429 | | /* Now that checks are done, allocate packet */ |
430 | 0 | packet = fr_packet_alloc(NULL, false); |
431 | 0 | if (!packet) { |
432 | 0 | fr_strerror_const("Failed allocating packet"); |
433 | 0 | return NULL; |
434 | 0 | } |
435 | | |
436 | | /* |
437 | | * Get XID. |
438 | | */ |
439 | 0 | memcpy(&magic, data + 4, 4); |
440 | |
|
441 | 0 | packet->data_len = data_len; |
442 | 0 | packet->code = code[2]; |
443 | 0 | packet->id = ntohl(magic); |
444 | | |
445 | | /* |
446 | | * FIXME: for DISCOVER / REQUEST: src_port == dst_port + 1 |
447 | | * FIXME: for OFFER / ACK : src_port = dst_port - 1 |
448 | | */ |
449 | | |
450 | | /* |
451 | | * Unique keys are xid, client mac, and client ID? |
452 | | */ |
453 | 0 | return packet; |
454 | 0 | } |