/src/freeradius-server/src/protocols/radius/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: df2c6f246ce53d8bbd4be4add2edfb6831c3855e $ |
19 | | * |
20 | | * @file protocols/radius/packet.c |
21 | | * @brief Functions to deal with fr_packet_t data structures. |
22 | | * |
23 | | * @copyright 2000-2017 The FreeRADIUS server project |
24 | | */ |
25 | | RCSID("$Id: df2c6f246ce53d8bbd4be4add2edfb6831c3855e $") |
26 | | |
27 | | #include "attrs.h" |
28 | | |
29 | | #include <freeradius-devel/util/udp.h> |
30 | | #include <freeradius-devel/util/syserror.h> |
31 | | |
32 | | #include <fcntl.h> |
33 | | |
34 | | /* |
35 | | * Some messages get printed out only in debugging mode. |
36 | | */ |
37 | 0 | #define FR_DEBUG_STRERROR_PRINTF if (fr_debug_lvl) fr_strerror_printf |
38 | | |
39 | | |
40 | | /** Encode a packet |
41 | | * |
42 | | */ |
43 | | ssize_t fr_radius_packet_encode(fr_packet_t *packet, fr_pair_list_t *list, |
44 | | fr_packet_t const *original, char const *secret) |
45 | 0 | { |
46 | 0 | ssize_t slen; |
47 | 0 | fr_radius_ctx_t common = {}; |
48 | 0 | fr_radius_encode_ctx_t packet_ctx; |
49 | | |
50 | | /* |
51 | | * A 4K packet, aligned on 64-bits. |
52 | | */ |
53 | 0 | uint8_t data[MAX_PACKET_LEN]; |
54 | |
|
55 | 0 | #ifndef NDEBUG |
56 | 0 | if (fr_debug_lvl >= L_DBG_LVL_4) fr_packet_log_hex(&default_log, packet); |
57 | 0 | #endif |
58 | |
|
59 | 0 | common.secret = secret; |
60 | 0 | common.secret_length = talloc_strlen(secret); |
61 | |
|
62 | 0 | packet_ctx = (fr_radius_encode_ctx_t) { |
63 | 0 | .common = &common, |
64 | 0 | .request_authenticator = original ? original->data + 4 : NULL, |
65 | 0 | .rand_ctx = (fr_fast_rand_t) { |
66 | 0 | .a = fr_rand(), |
67 | 0 | .b = fr_rand(), |
68 | 0 | }, |
69 | 0 | .request_code = original ? original->data[0] : 0, |
70 | 0 | .code = packet->code, |
71 | 0 | .id = packet->id, |
72 | 0 | }; |
73 | |
|
74 | 0 | slen = fr_radius_encode(&FR_DBUFF_TMP(data, sizeof(data)), list, &packet_ctx); |
75 | 0 | if (slen < 0) return slen; |
76 | | |
77 | | /* |
78 | | * Fill in the rest of the fields, and copy the data over |
79 | | * from the local stack to the newly allocated memory. |
80 | | * |
81 | | * Yes, all this 'memcpy' is slow, but it means |
82 | | * that we only allocate the minimum amount of |
83 | | * memory for a request. |
84 | | */ |
85 | 0 | packet->data_len = (size_t) slen; |
86 | 0 | packet->data = talloc_array(packet, uint8_t, packet->data_len); |
87 | 0 | if (!packet->data) { |
88 | 0 | fr_strerror_const("Out of memory"); |
89 | 0 | return -1; |
90 | 0 | } |
91 | | |
92 | 0 | memcpy(packet->data, data, packet->data_len); |
93 | |
|
94 | 0 | return 0; |
95 | 0 | } |
96 | | |
97 | | /** See if the data pointed to by PTR is a valid RADIUS packet. |
98 | | * |
99 | | * Packet is not 'const * const' because we may update data_len, if there's more data |
100 | | * in the UDP packet than in the RADIUS packet. |
101 | | * |
102 | | * @param[in] packet to check. |
103 | | * @param[in] max_attributes to decode. |
104 | | * @param[in] require_message_authenticator to require Message-Authenticator. |
105 | | * @param[out] reason if not NULL, will have the failure reason written to where it points. |
106 | | * @return |
107 | | * - True on success. |
108 | | * - False on failure. |
109 | | */ |
110 | | bool fr_packet_ok(fr_packet_t *packet, uint32_t max_attributes, bool require_message_authenticator, fr_radius_decode_fail_t *reason) |
111 | 0 | { |
112 | 0 | if (!fr_radius_ok(packet->data, &packet->data_len, max_attributes, require_message_authenticator, reason)) { |
113 | 0 | return false; |
114 | 0 | } |
115 | | |
116 | | /* |
117 | | * Fill RADIUS header fields |
118 | | */ |
119 | 0 | packet->code = packet->data[0]; |
120 | 0 | packet->id = packet->data[1]; |
121 | 0 | memcpy(packet->vector, packet->data + 4, sizeof(packet->vector)); |
122 | 0 | return true; |
123 | 0 | } |
124 | | |
125 | | |
126 | | /** Verify the Request/Response Authenticator (and Message-Authenticator if present) of a packet |
127 | | * |
128 | | */ |
129 | | int fr_radius_packet_verify(fr_packet_t *packet, fr_packet_t *original, char const *secret) |
130 | 0 | { |
131 | 0 | char buffer[INET6_ADDRSTRLEN]; |
132 | |
|
133 | 0 | if (!packet->data) return -1; |
134 | | |
135 | 0 | if (fr_radius_verify(packet->data, original ? original->data + 4 : NULL, |
136 | 0 | (uint8_t const *) secret, talloc_strlen(secret), false, false) < 0) { |
137 | 0 | fr_strerror_printf_push("Received invalid packet from %s", |
138 | 0 | inet_ntop(packet->socket.inet.src_ipaddr.af, &packet->socket.inet.src_ipaddr.addr, |
139 | 0 | buffer, sizeof(buffer))); |
140 | 0 | return -1; |
141 | 0 | } |
142 | | |
143 | 0 | return 0; |
144 | 0 | } |
145 | | |
146 | | |
147 | | /** Sign a previously encoded packet |
148 | | * |
149 | | */ |
150 | | int fr_radius_packet_sign(fr_packet_t *packet, fr_packet_t const *original, |
151 | | char const *secret) |
152 | 0 | { |
153 | 0 | int ret; |
154 | |
|
155 | 0 | ret = fr_radius_sign(packet->data, original ? original->data + 4 : NULL, |
156 | 0 | (uint8_t const *) secret, talloc_strlen(secret)); |
157 | 0 | if (ret < 0) return ret; |
158 | | |
159 | 0 | memcpy(packet->vector, packet->data + 4, RADIUS_AUTH_VECTOR_LENGTH); |
160 | 0 | return 0; |
161 | 0 | } |
162 | | |
163 | | |
164 | | /** Wrapper for recvfrom, which handles recvfromto, IPv6, and all possible combinations |
165 | | * |
166 | | */ |
167 | | static ssize_t rad_recvfrom(int sockfd, fr_packet_t *packet, int flags) |
168 | 0 | { |
169 | 0 | ssize_t data_len; |
170 | |
|
171 | 0 | data_len = fr_radius_recv_header(sockfd, &packet->socket.inet.src_ipaddr, &packet->socket.inet.src_port, &packet->code); |
172 | 0 | if (data_len < 0) { |
173 | 0 | if ((errno == EAGAIN) || (errno == EINTR)) return 0; |
174 | 0 | return -1; |
175 | 0 | } |
176 | | |
177 | 0 | if (data_len == 0) return -1; /* invalid packet */ |
178 | | |
179 | 0 | packet->data = talloc_array(packet, uint8_t, data_len); |
180 | 0 | if (!packet->data) return -1; |
181 | | |
182 | 0 | packet->data_len = data_len; |
183 | |
|
184 | 0 | return udp_recv(sockfd, flags, &packet->socket, packet->data, packet->data_len, &packet->timestamp); |
185 | 0 | } |
186 | | |
187 | | |
188 | | /** Receive UDP client requests, and fill in the basics of a fr_packet_t structure |
189 | | * |
190 | | */ |
191 | | fr_packet_t *fr_packet_recv(TALLOC_CTX *ctx, int fd, int flags, uint32_t max_attributes, bool require_message_authenticator) |
192 | 0 | { |
193 | 0 | ssize_t data_len; |
194 | 0 | fr_packet_t *packet; |
195 | | |
196 | | /* |
197 | | * Allocate the new request data structure |
198 | | */ |
199 | 0 | packet = fr_packet_alloc(ctx, false); |
200 | 0 | if (!packet) { |
201 | 0 | fr_strerror_const("out of memory"); |
202 | 0 | return NULL; |
203 | 0 | } |
204 | | |
205 | 0 | data_len = rad_recvfrom(fd, packet, flags); |
206 | 0 | if (data_len < 0) { |
207 | 0 | FR_DEBUG_STRERROR_PRINTF("Error receiving packet: %s", fr_syserror(errno)); |
208 | 0 | fr_packet_free(&packet); |
209 | 0 | return NULL; |
210 | 0 | } |
211 | | |
212 | 0 | #ifdef WITH_VERIFY_PTR |
213 | | /* |
214 | | * Double-check that the fields we want are filled in. |
215 | | */ |
216 | 0 | if ((packet->socket.inet.src_ipaddr.af == AF_UNSPEC) || |
217 | 0 | (packet->socket.inet.src_port == 0) || |
218 | 0 | (packet->socket.inet.dst_ipaddr.af == AF_UNSPEC) || |
219 | 0 | (packet->socket.inet.dst_port == 0)) { |
220 | 0 | FR_DEBUG_STRERROR_PRINTF("Error receiving packet: %s", fr_syserror(errno)); |
221 | 0 | fr_packet_free(&packet); |
222 | 0 | return NULL; |
223 | 0 | } |
224 | 0 | #endif |
225 | | |
226 | 0 | packet->data_len = data_len; /* unsigned vs signed */ |
227 | | |
228 | | /* |
229 | | * If the packet is too big, then rad_recvfrom did NOT |
230 | | * allocate memory. Instead, it just discarded the |
231 | | * packet. |
232 | | */ |
233 | 0 | if (packet->data_len > MAX_PACKET_LEN) { |
234 | 0 | FR_DEBUG_STRERROR_PRINTF("Discarding packet: Larger than RFC limitation of 4096 bytes"); |
235 | 0 | fr_packet_free(&packet); |
236 | 0 | return NULL; |
237 | 0 | } |
238 | | |
239 | | /* |
240 | | * Read no data. Continue. |
241 | | * This check is AFTER the MAX_PACKET_LEN check above, because |
242 | | * if the packet is larger than MAX_PACKET_LEN, we also have |
243 | | * packet->data == NULL |
244 | | */ |
245 | 0 | if ((packet->data_len == 0) || !packet->data) { |
246 | 0 | FR_DEBUG_STRERROR_PRINTF("Empty packet: Socket is not ready"); |
247 | 0 | fr_packet_free(&packet); |
248 | 0 | return NULL; |
249 | 0 | } |
250 | | |
251 | | /* |
252 | | * See if it's a well-formed RADIUS packet. |
253 | | */ |
254 | 0 | if (!fr_packet_ok(packet, max_attributes, require_message_authenticator, NULL)) { |
255 | 0 | fr_packet_free(&packet); |
256 | 0 | return NULL; |
257 | 0 | } |
258 | | |
259 | | /* |
260 | | * Remember which socket we read the packet from. |
261 | | */ |
262 | 0 | packet->socket.fd = fd; |
263 | | |
264 | | /* |
265 | | * FIXME: Do even more filtering by only permitting |
266 | | * certain IP's. The problem is that we don't know |
267 | | * how to do this properly for all possible clients... |
268 | | */ |
269 | |
|
270 | 0 | return packet; |
271 | 0 | } |
272 | | |
273 | | /** Reply to the request |
274 | | * |
275 | | * Also attach reply attribute value pairs and any user message provided. |
276 | | */ |
277 | | int fr_radius_packet_send(fr_packet_t *packet, fr_pair_list_t *list, |
278 | | fr_packet_t const *original, char const *secret) |
279 | 0 | { |
280 | | /* |
281 | | * Maybe it's a fake packet. Don't send it. |
282 | | */ |
283 | 0 | if (packet->socket.fd < 0) { |
284 | 0 | return 0; |
285 | 0 | } |
286 | | |
287 | | /* |
288 | | * First time through, allocate room for the packet |
289 | | */ |
290 | 0 | if (!packet->data) { |
291 | | /* |
292 | | * Encode the packet. |
293 | | */ |
294 | 0 | if (fr_radius_packet_encode(packet, list, original, secret) < 0) { |
295 | 0 | return -1; |
296 | 0 | } |
297 | | |
298 | | /* |
299 | | * Re-sign it, including updating the |
300 | | * Message-Authenticator. |
301 | | */ |
302 | 0 | if (fr_radius_packet_sign(packet, original, secret) < 0) { |
303 | 0 | return -1; |
304 | 0 | } |
305 | | |
306 | | /* |
307 | | * If packet->data points to data, then we print out |
308 | | * the VP list again only for debugging. |
309 | | */ |
310 | 0 | } |
311 | | |
312 | | /* |
313 | | * If the socket is TCP, call write(). Calling sendto() |
314 | | * is allowed on some platforms, but it's not nice. |
315 | | */ |
316 | 0 | if (packet->socket.type == SOCK_STREAM) { |
317 | 0 | ssize_t ret; |
318 | |
|
319 | 0 | ret = write(packet->socket.fd, packet->data, packet->data_len); |
320 | 0 | if (ret >= 0) return ret; |
321 | | |
322 | 0 | fr_strerror_printf("sendto failed: %s", fr_syserror(errno)); |
323 | 0 | return -1; |
324 | 0 | } |
325 | | |
326 | | /* |
327 | | * And send it on it's way. |
328 | | * |
329 | | * No need to call fr_socket_addr_swap as apparently |
330 | | * the address is already inverted. |
331 | | */ |
332 | 0 | return udp_send(&packet->socket, 0, packet->data, packet->data_len); |
333 | 0 | } |
334 | | |
335 | | void _fr_packet_log_hex(fr_log_t const *log, fr_packet_t const *packet, char const *file, int line) |
336 | 0 | { |
337 | 0 | uint8_t const *attr, *end; |
338 | 0 | char buffer[1024]; |
339 | |
|
340 | 0 | if (!packet->data) return; |
341 | | |
342 | 0 | fr_log(log, L_DBG, file, line, " Socket : %d", packet->socket.fd); |
343 | 0 | fr_log(log, L_DBG, file, line, " Proto : %d", (packet->socket.type == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP); |
344 | |
|
345 | 0 | if ((packet->socket.inet.src_ipaddr.af == AF_INET) || (packet->socket.inet.src_ipaddr.af == AF_INET6)) { |
346 | 0 | fr_log(log, L_DBG, file, line, " Src IP : %pV", fr_box_ipaddr(packet->socket.inet.src_ipaddr)); |
347 | 0 | fr_log(log, L_DBG, file, line, " Src Port : %u", packet->socket.inet.src_port); |
348 | 0 | fr_log(log, L_DBG, file, line, " Dst IP : %pV", fr_box_ipaddr(packet->socket.inet.dst_ipaddr)); |
349 | 0 | fr_log(log, L_DBG, file, line, " Dst Port : %u", packet->socket.inet.dst_port); |
350 | 0 | } |
351 | |
|
352 | 0 | if ((packet->data[0] > 0) && (packet->data[0] < FR_RADIUS_CODE_MAX)) { |
353 | 0 | fr_log(log, L_DBG, file, line, " Code : %s", fr_radius_packet_name[packet->data[0]]); |
354 | 0 | } else { |
355 | 0 | fr_log(log, L_DBG, file, line, " Code : %u", packet->data[0]); |
356 | 0 | } |
357 | |
|
358 | 0 | fr_log(log, L_DBG, file, line, " Id : %u", packet->data[1]); |
359 | 0 | fr_log(log, L_DBG, file, line, " Length : %u", fr_nbo_to_uint16(packet->data + 2)); |
360 | 0 | fr_log(log, L_DBG, file, line, " Vector : %pH", fr_box_octets(packet->data + 4, RADIUS_AUTH_VECTOR_LENGTH)); |
361 | |
|
362 | 0 | if (packet->data_len <= 20) return; |
363 | | |
364 | 0 | for (attr = packet->data + 20, end = packet->data + packet->data_len; |
365 | 0 | attr < end; |
366 | 0 | attr += attr[1]) { |
367 | 0 | int i, len, offset = 2; |
368 | 0 | unsigned int vendor = 0; |
369 | 0 | char *p; |
370 | 0 | char const *truncated = ""; |
371 | | |
372 | | /* |
373 | | * rad_packet_ok() already checks, but let's do defense in depth. |
374 | | */ |
375 | 0 | if (attr[1] < 2) break; |
376 | | |
377 | 0 | snprintf(buffer, sizeof(buffer), "%02x %02x ", attr[0], attr[1]); |
378 | 0 | p = buffer + strlen(buffer); |
379 | 0 | if ((attr[0] == FR_VENDOR_SPECIFIC) && |
380 | 0 | (attr[1] > 6)) { |
381 | 0 | vendor = fr_nbo_to_uint32(attr + 2); |
382 | |
|
383 | 0 | snprintf(p, buffer + sizeof(buffer) - p, "%02x%02x%02x%02x (%u) ", |
384 | 0 | attr[2], attr[3], attr[4], attr[5], vendor); |
385 | 0 | offset = 6; |
386 | 0 | p += strlen(p); |
387 | 0 | } |
388 | |
|
389 | 0 | len = attr[1] - offset; |
390 | 0 | if (len > 15) { |
391 | 0 | len = 15; |
392 | 0 | truncated = "..."; |
393 | 0 | } |
394 | |
|
395 | 0 | for (i = 0; i < len; i++) { |
396 | 0 | snprintf(p, buffer + sizeof(buffer) - p, "%02x ", attr[offset + i]); |
397 | 0 | p += 3; |
398 | 0 | } |
399 | |
|
400 | 0 | fr_log(log, L_DBG, file, line, " %s%s\n", buffer, truncated); |
401 | 0 | } |
402 | 0 | } |
403 | | |
404 | | /* |
405 | | * Debug the packet if requested. |
406 | | */ |
407 | | void fr_radius_packet_header_log(fr_log_t const *log, fr_packet_t *packet, bool received) |
408 | 0 | { |
409 | 0 | char src_ipaddr[FR_IPADDR_STRLEN]; |
410 | 0 | char dst_ipaddr[FR_IPADDR_STRLEN]; |
411 | 0 | #ifdef WITH_IFINDEX_NAME_RESOLUTION |
412 | 0 | char if_name[IFNAMSIZ]; |
413 | 0 | #endif |
414 | |
|
415 | 0 | if (!log) return; |
416 | 0 | if (!packet) return; |
417 | | |
418 | | /* |
419 | | * Client-specific debugging re-prints the input |
420 | | * packet into the client log. |
421 | | * |
422 | | * This really belongs in a utility library |
423 | | */ |
424 | 0 | if (FR_RADIUS_PACKET_CODE_VALID(packet->code)) { |
425 | 0 | fr_log(log, L_DBG, __FILE__, __LINE__, |
426 | 0 | "%s %s Id %i from %s%s%s:%i to %s%s%s:%i " |
427 | 0 | #ifdef WITH_IFINDEX_NAME_RESOLUTION |
428 | 0 | "%s%s%s" |
429 | 0 | #endif |
430 | 0 | "length %zu\n", |
431 | 0 | received ? "Received" : "Sent", |
432 | 0 | fr_radius_packet_name[packet->code], |
433 | 0 | packet->id, |
434 | 0 | packet->socket.inet.src_ipaddr.af == AF_INET6 ? "[" : "", |
435 | 0 | fr_inet_ntop(src_ipaddr, sizeof(src_ipaddr), &packet->socket.inet.src_ipaddr), |
436 | 0 | packet->socket.inet.src_ipaddr.af == AF_INET6 ? "]" : "", |
437 | 0 | packet->socket.inet.src_port, |
438 | 0 | packet->socket.inet.dst_ipaddr.af == AF_INET6 ? "[" : "", |
439 | 0 | fr_inet_ntop(dst_ipaddr, sizeof(dst_ipaddr), &packet->socket.inet.dst_ipaddr), |
440 | 0 | packet->socket.inet.dst_ipaddr.af == AF_INET6 ? "]" : "", |
441 | 0 | packet->socket.inet.dst_port, |
442 | 0 | #ifdef WITH_IFINDEX_NAME_RESOLUTION |
443 | 0 | received ? "via " : "", |
444 | 0 | received ? fr_ifname_from_ifindex(if_name, packet->socket.inet.ifindex) : "", |
445 | 0 | received ? " " : "", |
446 | 0 | #endif |
447 | 0 | packet->data_len); |
448 | 0 | } else { |
449 | 0 | fr_log(log, L_DBG, __FILE__, __LINE__, |
450 | 0 | "%s code %u Id %i from %s%s%s:%i to %s%s%s:%i " |
451 | 0 | #ifdef WITH_IFINDEX_NAME_RESOLUTION |
452 | 0 | "%s%s%s" |
453 | 0 | #endif |
454 | 0 | "length %zu\n", |
455 | 0 | received ? "Received" : "Sent", |
456 | 0 | packet->code, |
457 | 0 | packet->id, |
458 | 0 | packet->socket.inet.src_ipaddr.af == AF_INET6 ? "[" : "", |
459 | 0 | fr_inet_ntop(src_ipaddr, sizeof(src_ipaddr), &packet->socket.inet.src_ipaddr), |
460 | 0 | packet->socket.inet.src_ipaddr.af == AF_INET6 ? "]" : "", |
461 | 0 | packet->socket.inet.src_port, |
462 | 0 | packet->socket.inet.dst_ipaddr.af == AF_INET6 ? "[" : "", |
463 | 0 | fr_inet_ntop(dst_ipaddr, sizeof(dst_ipaddr), &packet->socket.inet.dst_ipaddr), |
464 | 0 | packet->socket.inet.dst_ipaddr.af == AF_INET6 ? "]" : "", |
465 | 0 | packet->socket.inet.dst_port, |
466 | 0 | #ifdef WITH_IFINDEX_NAME_RESOLUTION |
467 | 0 | received ? "via " : "", |
468 | 0 | received ? fr_ifname_from_ifindex(if_name, packet->socket.inet.ifindex) : "", |
469 | 0 | received ? " " : "", |
470 | 0 | #endif |
471 | 0 | packet->data_len); |
472 | 0 | } |
473 | 0 | } |
474 | | |
475 | | /* |
476 | | * Debug the packet header and all attributes. This function is only called by the client code. |
477 | | */ |
478 | | void fr_radius_packet_log(fr_log_t const *log, fr_packet_t *packet, fr_pair_list_t *list, bool received) |
479 | 0 | { |
480 | 0 | fr_radius_packet_header_log(log, packet, received); |
481 | |
|
482 | 0 | if (!fr_debug_lvl) return; |
483 | | |
484 | | /* |
485 | | * If we're auto-adding Message Authenticator, then print |
486 | | * out that we're auto-adding it. |
487 | | */ |
488 | 0 | if (!received) switch (packet->code) { |
489 | 0 | case FR_RADIUS_CODE_ACCESS_REQUEST: |
490 | 0 | case FR_RADIUS_CODE_STATUS_SERVER: |
491 | 0 | if (!fr_pair_find_by_da(list, NULL, attr_message_authenticator)) { |
492 | 0 | fr_log(log, L_DBG, __FILE__, __LINE__, "\tMessage-Authenticator = 0x\n"); |
493 | 0 | } |
494 | 0 | break; |
495 | | |
496 | 0 | default: |
497 | 0 | break; |
498 | 0 | } |
499 | | |
500 | 0 | fr_pair_list_log(log, 4, list); |
501 | 0 | #ifndef NDEBUG |
502 | 0 | if (fr_debug_lvl >= L_DBG_LVL_4) fr_packet_log_hex(log, packet); |
503 | 0 | #endif |
504 | 0 | } |