The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
packet.c
Go to the documentation of this file.
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: 56095c7cbb572b7fac49e083d4165843cccdf142 $
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 */
37uint8_t const *fr_dhcpv4_packet_get_option(dhcp_packet_t const *packet, size_t packet_size, fr_dict_attr_t const *da)
38{
39 int overload = 0;
40 int field = DHCP_OPTION_FIELD;
41 size_t where, size;
42 uint8_t const *data;
43
44 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#define ALIGNED_ACCESS(packet, field) \
56 (uint8_t const *)packet + offsetof(dhcp_packet_t, field)
57
58 where = 0;
59 size = packet_size - offsetof(dhcp_packet_t, options);
60
61 /*
62 * Alignment fix. We can't just deref a pointer
63 */
64 data = ALIGNED_ACCESS(packet, options);
65 while (where < size) {
66 if (data[0] == 0) { /* padding */
67 where++;
68 data++;
69 continue;
70 }
71
72 if (data[0] == 255) { /* end of options */
73 if ((field == DHCP_OPTION_FIELD) && (overload & DHCP_FILE_FIELD)) {
74 data = ALIGNED_ACCESS(packet, file);
75 where = 0;
76 size = sizeof(packet->file);
77 field = DHCP_FILE_FIELD;
78 continue;
79
80 } else if ((field == DHCP_FILE_FIELD || field == DHCP_OPTION_FIELD) && (overload & DHCP_SNAME_FIELD)) {
81 data = ALIGNED_ACCESS(packet, sname);
82 where = 0;
83 size = sizeof(packet->sname);
84 field = DHCP_SNAME_FIELD;
85 continue;
86 }
87
88 return NULL;
89 }
90
91 /*
92 * We MUST have a real option here.
93 */
94 if ((where + 2) > size) {
95 fr_strerror_printf("Options overflow field at %u",
96 (unsigned int) (data - (uint8_t const *) packet));
97 return NULL;
98 }
99
100 if ((where + 2 + data[1]) > size) {
101 fr_strerror_printf("Option length overflows field at %u",
102 (unsigned int) (data - (uint8_t const *) packet));
103 return NULL;
104 }
105
106 if (data[0] == da->attr) return data;
107
108 if ((data[0] == 52) && (data[1] > 0)) { /* overload sname and/or file */
109 overload = data[2];
110 }
111
112 where += data[1] + 2;
113 data += data[1] + 2;
114 }
115
116 return NULL;
117}
118
119int fr_dhcpv4_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, unsigned int *code)
120{
121 size_t i;
122 uint8_t const *p = data;
123 uint32_t giaddr;
124 fr_pair_list_t tmp;
125 fr_pair_t *vp;
126 fr_pair_t *maxms, *mtu, *netaddr;
127 fr_value_box_t box;
128 fr_dhcpv4_ctx_t *packet_ctx;
129
130 fr_pair_list_init(&tmp);
131
132 fr_assert(data_len >= MIN_PACKET_SIZE); /* fr_dhcpv4_ok() MUST be called first */
133
134 if (data[1] > 1) {
135 fr_strerror_printf("Packet is not Ethernet: %u",
136 data[1]);
137 return -1;
138 }
139
140 packet_ctx = talloc_zero(ctx, fr_dhcpv4_ctx_t);
141 if (!packet_ctx) return -1;
142 packet_ctx->tmp_ctx = talloc(packet_ctx, uint8_t);
143 packet_ctx->root = fr_dict_root(dict_dhcpv4);
144
145 /*
146 * Decode the header.
147 */
148 for (i = 0; i < dhcp_header_attrs_len; i++) {
149 fr_dict_attr_t const *da = *dhcp_header_attrs[i];
150
151 vp = fr_pair_afrom_da(ctx, da);
152 if (!vp) {
153 fr_strerror_const_push("Cannot decode packet due to internal error");
154 error_vp:
156 error:
157 fr_pair_list_free(&tmp);
158 talloc_free(packet_ctx);
159 return -1;
160 }
161
162 switch (vp->vp_type) {
163 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 if (*p != '\0') {
169 uint8_t *q;
170
171 q = memchr(p, '\0', dhcp_header_sizes[i]);
172 fr_pair_value_bstrndup(vp, (char const *)p, q ? q - p : dhcp_header_sizes[i], true);
173 } else {
174 TALLOC_FREE(vp);
175 }
176 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 case FR_TYPE_ETHERNET:
184 if ((data[1] != 1) || (data[2] != 6)) {
185 TALLOC_FREE(vp);
186 break;
187 }
188
189 memcpy(vp->vp_ether, p, sizeof(vp->vp_ether));
190 break;
191
192 default:
193 if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
194 &FR_DBUFF_TMP(p, (size_t)dhcp_header_sizes[i]),
195 dhcp_header_sizes[i], true) < 0) goto error_vp;
196 break;
197 }
198 p += dhcp_header_sizes[i];
199
200 if (!vp) continue;
201
202 fr_pair_append(&tmp, vp);
203 }
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 {
210 uint8_t const *end;
211 ssize_t len;
212
213 p = data + 240;
214 end = p + (data_len - 240);
215
216 /*
217 * Loop over all the options data
218 */
219 while (p < end) {
220 len = fr_dhcpv4_decode_option(ctx, &tmp, p, (end - p), packet_ctx);
221 if (len <= 0) {
222 fail:
223 fr_pair_list_free(&tmp);
224 talloc_free(packet_ctx);
225 return -1;
226 }
227 p += len;
228 }
229
230 if (code) {
232 if (vp) {
233 *code = vp->vp_uint8;
234 }
235 }
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 */
242 if (vp) {
243 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 p = data + offsetof(dhcp_packet_t, file);
249 end = p + DHCP_FILE_LEN;
250 while (p < end) {
251 len = fr_dhcpv4_decode_option(ctx, &tmp,
252 p, end - p, packet_ctx);
253 if (len <= 0) goto fail;
254 p += len;
255 }
257 }
258 if ((vp->vp_uint8 & 2) == 2) {
259 /*
260 * The 'sname' field is used to hold options.
261 */
262 p = data + offsetof(dhcp_packet_t, sname);
263 end = p + DHCP_SNAME_LEN;
264 while (p < end) {
265 len = fr_dhcpv4_decode_option(ctx, &tmp,
266 p, end - p, packet_ctx);
267 if (len <= 0) goto fail;
268 p += len;
269 }
271 }
272 }
273 }
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 memcpy(&giaddr, data + 24, sizeof(giaddr));
284 if (giaddr == htonl(INADDR_ANY)) {
285 /*
286 * DHCP Opcode is request
287 */
289 if (vp && vp->vp_uint8 == 1) {
290 /*
291 * Vendor is "MSFT 98"
292 */
294 if (vp && (vp->vp_length == 7) && (memcmp(vp->vp_strvalue, "MSFT 98", 7) == 0)) {
296
297 /*
298 * Reply should be broadcast.
299 */
300 if (vp) vp->vp_uint16 |= 0x8000;
301 }
302 }
303 }
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 */
318 if (!vp) goto error;
319
320 /*
321 * First look for Relay-Link-Selection
322 */
324 if (!netaddr) {
325 /*
326 * Next try Subnet-Selection-Option
327 */
329 }
330
331 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 if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, &netaddr->data) < 0) goto error_vp;
338
339 } else if (giaddr != htonl(INADDR_ANY)) {
340 /*
341 * Gateway address is set - use that one
342 */
344 &FR_DBUFF_TMP(data + 24, 4), 4, true) < 0) goto error_vp;
345 if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, &box) < 0) goto error_vp;
346
347 } else {
348 /*
349 * else, store client address whatever it is
350 */
352 &FR_DBUFF_TMP(data + 12, 4), 4, true) < 0) goto error_vp;
353 if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, &box) < 0) goto error_vp;
354 }
355
356 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 */
364
365 if (mtu && (mtu->vp_uint16 < DEFAULT_PACKET_SIZE)) {
366 fr_strerror_const("Client says MTU is smaller than minimum permitted by the specification");
367 goto error;
368 }
369
370 /*
371 * Client says maximum message size is smaller than minimum permitted
372 * by the specification: fixing it.
373 */
374 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 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 */
386
387 return 0;
388}
389
391{
392 ssize_t len;
393 fr_pair_t *vp;
394
395 if (packet->data) return 0;
396
397 packet->data_len = MAX_PACKET_SIZE;
398 packet->data = talloc_zero_array(packet, uint8_t, packet->data_len);
399
400 /* XXX Ugly ... should be set by the caller */
401 if (packet->code == 0) packet->code = FR_DHCP_NAK;
402
403 /* store xid */
404 if ((vp = fr_pair_find_by_da(list, NULL, attr_dhcp_transaction_id))) {
405 packet->id = vp->vp_uint32;
406 } else {
407 packet->id = fr_rand();
408 }
409
410 len = fr_dhcpv4_encode(packet->data, packet->data_len, NULL, packet->code, packet->id, list);
411 if (len < 0) return -1;
412
413 packet->data_len = len;
414
415 return 0;
416}
417
419{
420 fr_packet_t *packet;
421 uint32_t magic;
422 uint8_t const *code;
423
424 fr_assert(data_len >= MIN_PACKET_SIZE); /* fr_dhcpv4_ok() MUST be called first */
425
427 if (!code || (code[1] != 1)) return NULL;
428
429 /* Now that checks are done, allocate packet */
430 packet = fr_packet_alloc(NULL, false);
431 if (!packet) {
432 fr_strerror_const("Failed allocating packet");
433 return NULL;
434 }
435
436 /*
437 * Get XID.
438 */
439 memcpy(&magic, data + 4, 4);
440
441 packet->data_len = data_len;
442 packet->code = code[2];
443 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 return packet;
454}
int const char * file
Definition acutest.h:704
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:524
static fr_dict_attr_t const * attr_dhcp_message_type
Definition dhcpclient.c:90
static fr_dict_t const * dict_dhcpv4
Definition dhcpclient.c:80
Implementation of the DHCPv4 protocol.
TALLOC_CTX * tmp_ctx
for temporary things cleaned up during decoding
Definition dhcpv4.h:136
#define DEFAULT_PACKET_SIZE
Definition dhcpv4.h:89
#define MIN_PACKET_SIZE
Definition dhcpv4.h:88
#define DHCP_OPTION_FIELD
Definition dhcpv4.h:93
#define DHCP_SNAME_FIELD
Definition dhcpv4.h:95
#define DHCP_SNAME_LEN
Definition dhcpv4.h:38
#define DHCP_FILE_LEN
Definition dhcpv4.h:39
#define DHCP_FILE_FIELD
Definition dhcpv4.h:94
uint8_t file[DHCP_FILE_LEN]
Definition dhcpv4.h:80
@ FR_DHCP_NAK
Definition dhcpv4.h:50
#define MAX_PACKET_SIZE
Definition dhcpv4.h:90
uint8_t sname[DHCP_SNAME_LEN]
Definition dhcpv4.h:79
fr_dict_attr_t const * root
Definition dhcpv4.h:135
Used as the decoder ctx.
Definition dhcpv4.h:134
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition dict_util.c:2666
talloc_free(hp)
fr_packet_t * fr_packet_alloc(TALLOC_CTX *ctx, bool new_vector)
Allocate a new fr_packet_t.
Definition packet.c:38
@ FR_TYPE_IPV4_ADDR
32 Bit IPv4 Address.
@ FR_TYPE_ETHERNET
48 Bit Mac-Address.
@ FR_TYPE_STRING
String of printable characters.
unsigned int uint32_t
long int ssize_t
unsigned char uint8_t
fr_pair_t * fr_pair_find_by_da_nested(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find a pair with a matching fr_dict_attr_t, by walking the nested fr_dict_attr_t tree.
Definition pair.c:784
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition pair.c:707
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1352
int fr_pair_delete_by_da(fr_pair_list_t *list, fr_dict_attr_t const *da)
Delete matching pairs from the specified list.
Definition pair.c:1696
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:290
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:46
int fr_pair_value_bstrndup(fr_pair_t *vp, char const *src, size_t len, bool tainted)
Copy data into a "string" type value pair.
Definition pair.c:2816
HIDDEN fr_dict_attr_t const * attr_dhcp_overload
Definition base.c:68
HIDDEN fr_dict_attr_t const * attr_dhcp_boot_filename
Definition base.c:50
HIDDEN fr_dict_attr_t const * attr_dhcp_opcode
Definition base.c:59
HIDDEN fr_dict_attr_t const * attr_dhcp_interface_mtu_size
Definition base.c:65
HIDDEN fr_dict_attr_t const * attr_dhcp_subnet_selection_option
Definition base.c:71
HIDDEN fr_dict_attr_t const * attr_dhcp_network_subnet
Definition base.c:72
HIDDEN fr_dict_attr_t const * attr_dhcp_relay_link_selection
Definition base.c:70
HIDDEN fr_dict_attr_t const * attr_dhcp_transaction_id
Definition base.c:62
HIDDEN fr_dict_attr_t const * attr_dhcp_dhcp_maximum_msg_size
Definition base.c:64
HIDDEN fr_dict_attr_t const * attr_dhcp_vendor_class_identifier
Definition base.c:69
HIDDEN fr_dict_attr_t const * attr_dhcp_flags
Definition base.c:53
HIDDEN fr_dict_attr_t const * attr_dhcp_server_host_name
Definition base.c:60
size_t dhcp_header_attrs_len
Definition base.c:127
int dhcp_header_sizes[]
Definition base.c:150
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)
Definition base.c:353
fr_dict_attr_t const ** dhcp_header_attrs[]
Definition base.c:111
ssize_t fr_dhcpv4_decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, void *decode_ctx)
Decode DHCP option.
Definition decode.c:588
uint8_t const * fr_dhcpv4_packet_get_option(dhcp_packet_t const *packet, size_t packet_size, fr_dict_attr_t const *da)
Retrieve a DHCP option from a raw packet buffer.
Definition packet.c:37
fr_packet_t * fr_dhcpv4_packet_alloc(uint8_t const *data, size_t data_len)
Definition packet.c:418
#define ALIGNED_ACCESS(packet, field)
int fr_dhcpv4_packet_encode(fr_packet_t *packet, fr_pair_list_t *list)
Definition packet.c:390
int fr_dhcpv4_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, unsigned int *code)
Definition packet.c:119
VQP attributes.
#define fr_assert(_expr)
Definition rad_assert.h:38
uint32_t fr_rand(void)
Return a 32-bit random number.
Definition rand.c:105
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
fr_dict_attr_t const *_CONST da
Dictionary attribute defines the attribute number, vendor and type of the pair.
Definition pair.h:69
unsigned int code
Packet code (type).
Definition packet.h:61
int id
Packet ID (used to link requests/responses).
Definition packet.h:60
uint8_t * data
Packet data (body).
Definition packet.h:63
size_t data_len
Length of packet data.
Definition packet.h:64
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
void fr_pair_list_append(fr_pair_list_t *dst, fr_pair_list_t *src)
Appends a list of fr_pair_t from a temporary list to a destination list.
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_const_push(_msg)
Definition strerror.h:227
#define fr_strerror_const(_msg)
Definition strerror.h:223
ssize_t fr_value_box_from_network(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t type, fr_dict_attr_t const *enumv, fr_dbuff_t *dbuff, size_t len, bool tainted)
Decode a fr_value_box_t from serialized binary data.
Definition value.c:1922
int fr_value_box_cast(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, fr_value_box_t const *src)
Convert one type of fr_value_box_t to another.
Definition value.c:3961
static fr_slen_t data
Definition value.h:1340
static size_t char ** out
Definition value.h:1030