1 /*
2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program 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
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; 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: e0d2b650f184c29588d371addb7f9e355502374c $
19 * @file radsniff.c
20 * @brief Capture, filter, and generate statistics for RADIUS traffic
21 *
22 * @copyright 2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23 * @copyright 2006 The FreeRADIUS server project
24 * @copyright 2006 Nicolas Baradakis <nicolas.baradakis@cegetel.net>
25 */
26
27 RCSID("$Id: e0d2b650f184c29588d371addb7f9e355502374c $")
28
29 #define _LIBRADIUS 1
30 #include <time.h>
31 #include <math.h>
32 #include <freeradius-devel/libradius.h>
33 #include <freeradius-devel/event.h>
34
35 #include <freeradius-devel/radpaths.h>
36 #include <freeradius-devel/conf.h>
37 #include <freeradius-devel/pcap.h>
38 #include <freeradius-devel/radsniff.h>
39
40 #ifdef HAVE_COLLECTDC_H
41 # include <collectd/client.h>
42 #endif
43
44 #define RS_ASSERT(_x) if (!(_x) && !fr_assert(_x)) exit(1)
45
46 static rs_t *conf;
47 static struct timeval start_pcap = {0, 0};
48 static char timestr[50];
49
50 static rbtree_t *request_tree = NULL;
51 static rbtree_t *link_tree = NULL;
52 static fr_event_list_t *events;
53 static bool cleanup;
54
55 static int self_pipe[2] = {-1, -1}; //!< Signals from sig handlers
56
57 typedef int (*rbcmp)(void const *, void const *);
58
59 static char const *radsniff_version = "radsniff version " RADIUSD_VERSION_STRING
60 #ifdef RADIUSD_VERSION_COMMIT
61 " (git #" STRINGIFY(RADIUSD_VERSION_COMMIT) ")"
62 #endif
63 #ifndef ENABLE_REPRODUCIBLE_BUILDS
64 ", built on " __DATE__ " at " __TIME__
65 #endif
66 ;
67
68 static int rs_useful_codes[] = {
69 PW_CODE_ACCESS_REQUEST, //!< RFC2865 - Authentication request
70 PW_CODE_ACCESS_ACCEPT, //!< RFC2865 - Access-Accept
71 PW_CODE_ACCESS_REJECT, //!< RFC2865 - Access-Reject
72 PW_CODE_ACCOUNTING_REQUEST, //!< RFC2866 - Accounting-Request
73 PW_CODE_ACCOUNTING_RESPONSE, //!< RFC2866 - Accounting-Response
74 PW_CODE_ACCESS_CHALLENGE, //!< RFC2865 - Access-Challenge
75 PW_CODE_STATUS_SERVER, //!< RFC2865/RFC5997 - Status Server (request)
76 PW_CODE_STATUS_CLIENT, //!< RFC2865/RFC5997 - Status Server (response)
77 PW_CODE_DISCONNECT_REQUEST, //!< RFC3575/RFC5176 - Disconnect-Request
78 PW_CODE_DISCONNECT_ACK, //!< RFC3575/RFC5176 - Disconnect-Ack (positive)
79 PW_CODE_DISCONNECT_NAK, //!< RFC3575/RFC5176 - Disconnect-Nak (not willing to perform)
80 PW_CODE_COA_REQUEST, //!< RFC3575/RFC5176 - CoA-Request
81 PW_CODE_COA_ACK, //!< RFC3575/RFC5176 - CoA-Ack (positive)
82 PW_CODE_COA_NAK, //!< RFC3575/RFC5176 - CoA-Nak (not willing to perform)
83 };
84
85 static const FR_NAME_NUMBER rs_events[] = {
86 { "received", RS_NORMAL },
87 { "norsp", RS_LOST },
88 { "rtx", RS_RTX },
89 { "noreq", RS_UNLINKED },
90 { "reused", RS_REUSED },
91 { "error", RS_ERROR },
92 { NULL , -1 }
93 };
94
95 static void NEVER_RETURNS usage(int status);
96
97 /** Fork and kill the parent process, writing out our PID
98 *
99 * @param pidfile the PID file to write our PID to
100 */
rs_daemonize(char const * pidfile)101 static void rs_daemonize(char const *pidfile)
102 {
103 FILE *fp;
104 pid_t pid, sid;
105
106 pid = fork();
107 if (pid < 0) {
108 exit(EXIT_FAILURE);
109 }
110
111 /*
112 * Kill the parent...
113 */
114 if (pid > 0) {
115 close(self_pipe[0]);
116 close(self_pipe[1]);
117 exit(EXIT_SUCCESS);
118 }
119
120 /*
121 * Continue as the child.
122 */
123
124 /* Create a new SID for the child process */
125 sid = setsid();
126 if (sid < 0) {
127 exit(EXIT_FAILURE);
128 }
129
130 /*
131 * Change the current working directory. This prevents the current
132 * directory from being locked; hence not being able to remove it.
133 */
134 if ((chdir("/")) < 0) {
135 exit(EXIT_FAILURE);
136 }
137
138 /*
139 * And write it AFTER we've forked, so that we write the
140 * correct PID.
141 */
142 fp = fopen(pidfile, "w");
143 if (fp != NULL) {
144 fprintf(fp, "%d\n", (int) sid);
145 fclose(fp);
146 } else {
147 ERROR("Failed creating PID file %s: %s", pidfile, fr_syserror(errno));
148 exit(EXIT_FAILURE);
149 }
150
151 /*
152 * Close stdout and stderr if they've not been redirected.
153 */
154 if (isatty(fileno(stdout))) {
155 if (!freopen("/dev/null", "w", stdout)) {
156 exit(EXIT_FAILURE);
157 }
158 }
159
160 if (isatty(fileno(stderr))) {
161 if (!freopen("/dev/null", "w", stderr)) {
162 exit(EXIT_FAILURE);
163 }
164 }
165 }
166
167 #define USEC 1000000
rs_tv_sub(struct timeval const * end,struct timeval const * start,struct timeval * elapsed)168 static void rs_tv_sub(struct timeval const *end, struct timeval const *start, struct timeval *elapsed)
169 {
170 elapsed->tv_sec = end->tv_sec - start->tv_sec;
171 if (elapsed->tv_sec > 0) {
172 elapsed->tv_sec--;
173 elapsed->tv_usec = USEC;
174 } else {
175 elapsed->tv_usec = 0;
176 }
177 elapsed->tv_usec += end->tv_usec;
178 elapsed->tv_usec -= start->tv_usec;
179
180 if (elapsed->tv_usec >= USEC) {
181 elapsed->tv_usec -= USEC;
182 elapsed->tv_sec++;
183 }
184 }
185
rs_tv_add_ms(struct timeval const * start,unsigned long interval,struct timeval * result)186 static void rs_tv_add_ms(struct timeval const *start, unsigned long interval, struct timeval *result) {
187 result->tv_sec = start->tv_sec + (interval / 1000);
188 result->tv_usec = start->tv_usec + ((interval % 1000) * 1000);
189
190 if (result->tv_usec > USEC) {
191 result->tv_usec -= USEC;
192 result->tv_sec++;
193 }
194 }
195
rs_time_print(char * out,size_t len,struct timeval const * t)196 static void rs_time_print(char *out, size_t len, struct timeval const *t)
197 {
198 size_t ret;
199 struct timeval now;
200 uint32_t usec;
201
202 if (!t) {
203 gettimeofday(&now, NULL);
204 t = &now;
205 }
206
207 ret = strftime(out, len, "%Y-%m-%d %H:%M:%S", localtime(&t->tv_sec));
208 if (ret >= len) {
209 return;
210 }
211
212 usec = t->tv_usec;
213
214 if (usec) {
215 while (usec < 100000) usec *= 10;
216 snprintf(out + ret, len - ret, ".%i", usec);
217 } else {
218 snprintf(out + ret, len - ret, ".000000");
219 }
220 }
221
rs_prints_csv(char * out,size_t outlen,char const * in,size_t inlen)222 static size_t rs_prints_csv(char *out, size_t outlen, char const *in, size_t inlen)
223 {
224 char const *start = out;
225 uint8_t const *str = (uint8_t const *) in;
226
227 if (!in) {
228 if (outlen) {
229 *out = '\0';
230 }
231
232 return 0;
233 }
234
235 if (inlen == 0) {
236 inlen = strlen(in);
237 }
238
239 while ((inlen > 0) && (outlen > 2)) {
240 /*
241 * Escape double quotes with... MORE DOUBLE QUOTES!
242 */
243 if (*str == '"') {
244 *out++ = '"';
245 outlen--;
246 }
247
248 /*
249 * Safe chars which require no escaping
250 */
251 if ((*str == '\r') || (*str == '\n') || ((*str >= '\x20') && (*str <= '\x7E'))) {
252 *out++ = *str++;
253 outlen--;
254 inlen--;
255
256 continue;
257 }
258
259 /*
260 * Everything else is dropped
261 */
262 str++;
263 inlen--;
264 }
265 *out = '\0';
266
267 return out - start;
268 }
269
rs_packet_print_csv_header(void)270 static void rs_packet_print_csv_header(void)
271 {
272 char buffer[2048];
273 char *p = buffer;
274 int i;
275
276 ssize_t len, s = sizeof(buffer);
277
278 len = strlcpy(p, "\"Status\",\"Count\",\"Time\",\"Latency\",\"Type\",\"Interface\","
279 "\"Src IP\",\"Src Port\",\"Dst IP\",\"Dst Port\",\"ID\",", s);
280 p += len;
281 s -= len;
282
283 if (s <= 0) return;
284
285 for (i = 0; i < conf->list_da_num; i++) {
286 char const *in;
287
288 *p++ = '"';
289 s -= 1;
290 if (s <= 0) return;
291
292 for (in = conf->list_da[i]->name; *in; in++) {
293 *p++ = *in;
294 s -= len;
295 if (s <= 0) return;
296 }
297
298 *p++ = '"';
299 s -= 1;
300 if (s <= 0) return;
301 *p++ = ',';
302 s -= 1;
303 if (s <= 0) return;
304 }
305
306 *--p = '\0';
307
308 fprintf(stdout , "%s\n", buffer);
309 }
310
rs_packet_print_csv(uint64_t count,rs_status_t status,fr_pcap_t * handle,RADIUS_PACKET * packet,UNUSED struct timeval * elapsed,struct timeval * latency,UNUSED bool response,bool body)311 static void rs_packet_print_csv(uint64_t count, rs_status_t status, fr_pcap_t *handle, RADIUS_PACKET *packet,
312 UNUSED struct timeval *elapsed, struct timeval *latency, UNUSED bool response,
313 bool body)
314 {
315 char const *status_str;
316 char buffer[2048];
317 char *p = buffer;
318
319 char src[INET6_ADDRSTRLEN];
320 char dst[INET6_ADDRSTRLEN];
321
322 ssize_t len, s = sizeof(buffer);
323
324 inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr, src, sizeof(src));
325 inet_ntop(packet->dst_ipaddr.af, &packet->dst_ipaddr.ipaddr, dst, sizeof(dst));
326
327 status_str = fr_int2str(rs_events, status, NULL);
328 RS_ASSERT(status_str);
329
330 len = snprintf(p, s, "%s,%" PRIu64 ",%s,", status_str, count, timestr);
331 p += len;
332 s -= len;
333
334 if (s <= 0) return;
335
336 if (latency) {
337 len = snprintf(p, s, "%u.%03u,",
338 (unsigned int) latency->tv_sec, ((unsigned int) latency->tv_usec / 1000));
339 p += len;
340 s -= len;
341 } else {
342 *p = ',';
343 p += 1;
344 s -= 1;
345 }
346
347 if (s <= 0) return;
348
349 /* Status, Type, Interface, Src, Src port, Dst, Dst port, ID */
350 if (is_radius_code(packet->code)) {
351 len = snprintf(p, s, "%s,%s,%s,%i,%s,%i,%i,", fr_packet_codes[packet->code], handle->name,
352 src, packet->src_port, dst, packet->dst_port, packet->id);
353 } else {
354 len = snprintf(p, s, "%u,%s,%s,%i,%s,%i,%i,", packet->code, handle->name,
355 src, packet->src_port, dst, packet->dst_port, packet->id);
356 }
357 p += len;
358 s -= len;
359
360 if (s <= 0) return;
361
362 if (body) {
363 int i;
364 VALUE_PAIR *vp;
365
366 for (i = 0; i < conf->list_da_num; i++) {
367 vp = fr_pair_find_by_da(packet->vps, conf->list_da[i], TAG_ANY);
368 if (vp && (vp->vp_length > 0)) {
369 if (conf->list_da[i]->type == PW_TYPE_STRING) {
370 *p++ = '"';
371 s--;
372 if (s <= 0) return;
373
374 len = rs_prints_csv(p, s, vp->vp_strvalue, vp->vp_length);
375 p += len;
376 s -= len;
377 if (s <= 0) return;
378
379 *p++ = '"';
380 s--;
381 if (s <= 0) return;
382 } else {
383 len = vp_prints_value(p, s, vp, 0);
384 p += len;
385 s -= len;
386 if (s <= 0) return;
387 }
388 }
389
390 *p++ = ',';
391 s -= 1;
392 if (s <= 0) return;
393 }
394 } else {
395 s -= conf->list_da_num;
396 if (s <= 0) return;
397
398 memset(p, ',', conf->list_da_num);
399 p += conf->list_da_num;
400 }
401
402 *--p = '\0';
403 fprintf(stdout , "%s\n", buffer);
404 }
405
rs_packet_print_fancy(uint64_t count,rs_status_t status,fr_pcap_t * handle,RADIUS_PACKET * packet,struct timeval * elapsed,struct timeval * latency,bool response,bool body)406 static void rs_packet_print_fancy(uint64_t count, rs_status_t status, fr_pcap_t *handle, RADIUS_PACKET *packet,
407 struct timeval *elapsed, struct timeval *latency, bool response, bool body)
408 {
409 char buffer[2048];
410 char *p = buffer;
411
412 char src[INET6_ADDRSTRLEN];
413 char dst[INET6_ADDRSTRLEN];
414
415 ssize_t len, s = sizeof(buffer);
416
417 inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr, src, sizeof(src));
418 inet_ntop(packet->dst_ipaddr.af, &packet->dst_ipaddr.ipaddr, dst, sizeof(dst));
419
420 /* Only print out status str if something's not right */
421 if (status != RS_NORMAL) {
422 char const *status_str;
423
424 status_str = fr_int2str(rs_events, status, NULL);
425 RS_ASSERT(status_str);
426
427 len = snprintf(p, s, "** %s ** ", status_str);
428 p += len;
429 s -= len;
430 if (s <= 0) return;
431 }
432
433 if (is_radius_code(packet->code)) {
434 len = snprintf(p, s, "%s Id %i %s:%s:%d %s %s:%i ",
435 fr_packet_codes[packet->code],
436 packet->id,
437 handle->name,
438 response ? dst : src,
439 response ? packet->dst_port : packet->src_port,
440 response ? "<-" : "->",
441 response ? src : dst ,
442 response ? packet->src_port : packet->dst_port);
443 } else {
444 len = snprintf(p, s, "%u Id %i %s:%s:%i %s %s:%i ",
445 packet->code,
446 packet->id,
447 handle->name,
448 response ? dst : src,
449 response ? packet->dst_port : packet->src_port,
450 response ? "<-" : "->",
451 response ? src : dst ,
452 response ? packet->src_port : packet->dst_port);
453 }
454 p += len;
455 s -= len;
456 if (s <= 0) return;
457
458 if (elapsed) {
459 len = snprintf(p, s, "+%u.%03u ",
460 (unsigned int) elapsed->tv_sec, ((unsigned int) elapsed->tv_usec / 1000));
461 p += len;
462 s -= len;
463 if (s <= 0) return;
464 }
465
466 if (latency) {
467 len = snprintf(p, s, "+%u.%03u ",
468 (unsigned int) latency->tv_sec, ((unsigned int) latency->tv_usec / 1000));
469 p += len;
470 s -= len;
471 if (s <= 0) return;
472 }
473
474 *--p = '\0';
475
476 RIDEBUG("%s", buffer);
477
478 if (body) {
479 /*
480 * Print out verbose HEX output
481 */
482 if (conf->print_packet && (fr_debug_lvl > 3)) {
483 rad_print_hex(packet);
484 }
485
486 if (conf->print_packet && (fr_debug_lvl > 1)) {
487 char vector[(AUTH_VECTOR_LEN * 2) + 1];
488
489 if (packet->vps) {
490 fr_pair_list_sort(&packet->vps, fr_pair_cmp_by_da_tag);
491 vp_printlist(fr_log_fp, packet->vps);
492 }
493
494 fr_bin2hex(vector, packet->vector, AUTH_VECTOR_LEN);
495 INFO("\tAuthenticator-Field = 0x%s", vector);
496 }
497 }
498 }
499
rs_packet_print(rs_request_t * request,uint64_t count,rs_status_t status,fr_pcap_t * handle,RADIUS_PACKET * packet,struct timeval * elapsed,struct timeval * latency,bool response,bool body)500 static inline void rs_packet_print(rs_request_t *request, uint64_t count, rs_status_t status, fr_pcap_t *handle,
501 RADIUS_PACKET *packet, struct timeval *elapsed, struct timeval *latency,
502 bool response, bool body)
503 {
504 if (!conf->logger) return;
505
506 if (request) request->logged = true;
507 conf->logger(count, status, handle, packet, elapsed, latency, response, body);
508 }
509
rs_stats_print(rs_latency_t * stats,PW_CODE code)510 static void rs_stats_print(rs_latency_t *stats, PW_CODE code)
511 {
512 int i;
513 bool have_rt = false;
514
515 for (i = 0; i <= RS_RETRANSMIT_MAX; i++) {
516 if (stats->interval.rt[i]) {
517 have_rt = true;
518 }
519 }
520
521 if (!stats->interval.received && !have_rt && !stats->interval.reused) {
522 return;
523 }
524
525 if (stats->interval.received || stats->interval.linked) {
526 INFO("%s counters:", fr_packet_codes[code]);
527 if (stats->interval.received > 0) {
528 INFO("\tTotal : %.3lf/s" , stats->interval.received);
529 }
530 }
531
532 if (stats->interval.linked > 0) {
533 INFO("\tLinked : %.3lf/s", stats->interval.linked);
534 INFO("\tUnlinked : %.3lf/s", stats->interval.unlinked);
535 INFO("%s latency:", fr_packet_codes[code]);
536 INFO("\tHigh : %.3lfms", stats->interval.latency_high);
537 INFO("\tLow : %.3lfms", stats->interval.latency_low);
538 INFO("\tAverage : %.3lfms", stats->interval.latency_average);
539 INFO("\tMA : %.3lfms", stats->latency_smoothed);
540 }
541
542 if (have_rt || stats->interval.lost || stats->interval.reused) {
543 INFO("%s retransmits & loss:", fr_packet_codes[code]);
544
545 if (stats->interval.lost) {
546 INFO("\tLost : %.3lf/s", stats->interval.lost);
547 }
548
549 if (stats->interval.reused) {
550 INFO("\tID Reused : %.3lf/s", stats->interval.reused);
551 }
552
553 for (i = 0; i <= RS_RETRANSMIT_MAX; i++) {
554 if (!stats->interval.rt[i]) {
555 continue;
556 }
557
558 if (i != RS_RETRANSMIT_MAX) {
559 INFO("\tRT (%i) : %.3lf/s", i, stats->interval.rt[i]);
560 } else {
561 INFO("\tRT (%i+) : %.3lf/s", i, stats->interval.rt[i]);
562 }
563 }
564 }
565 }
566
567 /** Query libpcap to see if it dropped any packets
568 *
569 * We need to check to see if libpcap dropped any packets and if it did, we need to stop stats output for long
570 * enough for inaccurate statistics to be cleared out.
571 *
572 * @param in pcap handle to check.
573 * @param interval time between checks (used for debug output)
574 * @return 0, no drops, -1 we couldn't check, -2 dropped because of buffer exhaustion, -3 dropped because of NIC.
575 */
rs_check_pcap_drop(fr_pcap_t * in,int interval)576 static int rs_check_pcap_drop(fr_pcap_t *in, int interval) {
577 int ret = 0;
578 struct pcap_stat pstats;
579
580 if (pcap_stats(in->handle, &pstats) != 0) {
581 ERROR("%s failed retrieving pcap stats: %s", in->name, pcap_geterr(in->handle));
582 return -1;
583 }
584
585 INFO("\t%s%*s: %.3lf/s", in->name, (int) (10 - strlen(in->name)), "",
586 ((double) (pstats.ps_recv - in->pstats.ps_recv)) / interval);
587
588 if (pstats.ps_drop - in->pstats.ps_drop > 0) {
589 ERROR("%s dropped %i packets: Buffer exhaustion", in->name, pstats.ps_drop - in->pstats.ps_drop);
590 ret = -2;
591 }
592
593 if (pstats.ps_ifdrop - in->pstats.ps_ifdrop > 0) {
594 ERROR("%s dropped %i packets: Interface", in->name, pstats.ps_ifdrop - in->pstats.ps_ifdrop);
595 ret = -3;
596 }
597
598 in->pstats = pstats;
599
600 return ret;
601 }
602
603 /** Update smoothed average
604 *
605 */
rs_stats_process_latency(rs_latency_t * stats)606 static void rs_stats_process_latency(rs_latency_t *stats)
607 {
608 /*
609 * If we didn't link any packets during this interval, we don't have a value to return.
610 * returning 0 is misleading as it would be like saying the latency had dropped to 0.
611 * We instead set NaN which libcollectd converts to a 'U' or unknown value.
612 *
613 * This will cause gaps in graphs, but is completely legitimate as we are missing data.
614 * This is unfortunately an effect of being just a passive observer.
615 */
616 if (stats->interval.linked_total == 0) {
617 double unk = strtod("NAN()", (char **) NULL);
618
619 stats->interval.latency_average = unk;
620 stats->interval.latency_high = unk;
621 stats->interval.latency_low = unk;
622
623 /*
624 * We've not yet been able to determine latency, so latency_smoothed is also NaN
625 */
626 if (stats->latency_smoothed_count == 0) {
627 stats->latency_smoothed = unk;
628 }
629 return;
630 }
631
632 if (stats->interval.linked_total && stats->interval.latency_total) {
633 stats->interval.latency_average = (stats->interval.latency_total / stats->interval.linked_total);
634 }
635
636 if (isnan(stats->latency_smoothed)) {
637 stats->latency_smoothed = 0;
638 }
639 if (stats->interval.latency_average > 0) {
640 stats->latency_smoothed_count++;
641 stats->latency_smoothed += ((stats->interval.latency_average - stats->latency_smoothed) /
642 ((stats->latency_smoothed_count < 100) ? stats->latency_smoothed_count : 100));
643 }
644 }
645
rs_stats_process_counters(rs_latency_t * stats)646 static void rs_stats_process_counters(rs_latency_t *stats)
647 {
648 int i;
649
650 stats->interval.received = ((long double) stats->interval.received_total) / conf->stats.interval;
651 stats->interval.linked = ((long double) stats->interval.linked_total) / conf->stats.interval;
652 stats->interval.unlinked = ((long double) stats->interval.unlinked_total) / conf->stats.interval;
653 stats->interval.reused = ((long double) stats->interval.reused_total) / conf->stats.interval;
654 stats->interval.lost = ((long double) stats->interval.lost_total) / conf->stats.interval;
655
656 for (i = 0; i < RS_RETRANSMIT_MAX; i++) {
657 stats->interval.rt[i] = ((long double) stats->interval.rt_total[i]) / conf->stats.interval;
658 }
659 }
660
661 /** Process stats for a single interval
662 *
663 */
rs_stats_process(void * ctx)664 static void rs_stats_process(void *ctx)
665 {
666 size_t i;
667 size_t rs_codes_len = (sizeof(rs_useful_codes) / sizeof(*rs_useful_codes));
668 fr_pcap_t *in_p;
669 rs_update_t *this = ctx;
670 rs_stats_t *stats = this->stats;
671 struct timeval now;
672
673 gettimeofday(&now, NULL);
674
675 stats->intervals++;
676
677 INFO("######### Stats Iteration %i #########", stats->intervals);
678
679 /*
680 * Verify that none of the pcap handles have dropped packets.
681 */
682 INFO("Interface capture rate:");
683 for (in_p = this->in;
684 in_p;
685 in_p = in_p->next) {
686 if (rs_check_pcap_drop(in_p, conf->stats.interval) < 0) {
687 ERROR("Muting stats for the next %i milliseconds", conf->stats.timeout);
688
689 rs_tv_add_ms(&now, conf->stats.timeout, &stats->quiet);
690 goto clear;
691 }
692 }
693
694 if ((stats->quiet.tv_sec + (stats->quiet.tv_usec / 1000000.0)) -
695 (now.tv_sec + (now.tv_usec / 1000000.0)) > 0) {
696 INFO("Stats muted because of warmup, or previous error");
697 goto clear;
698 }
699
700 /*
701 * Latency stats need a bit more work to calculate the SMA.
702 *
703 * No further work is required for codes.
704 */
705 for (i = 0; i < rs_codes_len; i++) {
706 rs_stats_process_latency(&stats->exchange[rs_useful_codes[i]]);
707 rs_stats_process_counters(&stats->exchange[rs_useful_codes[i]]);
708 if (fr_debug_lvl > 0) {
709 rs_stats_print(&stats->exchange[rs_useful_codes[i]], rs_useful_codes[i]);
710 }
711 }
712
713 #ifdef HAVE_COLLECTDC_H
714 /*
715 * Update stats in collectd using the complex structures we
716 * initialised earlier.
717 */
718 if ((conf->stats.out == RS_STATS_OUT_COLLECTD) && conf->stats.handle) {
719 rs_stats_collectd_do_stats(conf, conf->stats.tmpl, &now);
720 }
721 #endif
722
723 clear:
724 /*
725 * Rinse and repeat...
726 */
727 for (i = 0; i < rs_codes_len; i++) {
728 memset(&stats->exchange[rs_useful_codes[i]].interval, 0,
729 sizeof(stats->exchange[rs_useful_codes[i]].interval));
730 }
731
732 {
733 static fr_event_t *event;
734
735 now.tv_sec += conf->stats.interval;
736 now.tv_usec = 0;
737
738 if (!fr_event_insert(this->list, rs_stats_process, ctx, &now, &event)) {
739 ERROR("Failed inserting stats interval event");
740 }
741 }
742 }
743
744
745 /** Update latency statistics for request/response and forwarded packets
746 *
747 */
rs_stats_update_latency(rs_latency_t * stats,struct timeval * latency)748 static void rs_stats_update_latency(rs_latency_t *stats, struct timeval *latency)
749 {
750 double lint;
751
752 stats->interval.linked_total++;
753 /* More useful is this in milliseconds */
754 lint = (latency->tv_sec + (latency->tv_usec / 1000000.0)) * 1000;
755 if (lint > stats->interval.latency_high) {
756 stats->interval.latency_high = lint;
757 }
758 if (!stats->interval.latency_low || (lint < stats->interval.latency_low)) {
759 stats->interval.latency_low = lint;
760 }
761 stats->interval.latency_total += lint;
762
763 }
764
765 /** Copy a subset of attributes from one list into the other
766 *
767 * Should be O(n) if all the attributes exist. List must be pre-sorted.
768 */
rs_get_pairs(TALLOC_CTX * ctx,VALUE_PAIR ** out,VALUE_PAIR * vps,DICT_ATTR const * da[],int num)769 static int rs_get_pairs(TALLOC_CTX *ctx, VALUE_PAIR **out, VALUE_PAIR *vps, DICT_ATTR const *da[], int num)
770 {
771 vp_cursor_t list_cursor, out_cursor;
772 VALUE_PAIR *match, *last_match, *copy;
773 uint64_t count = 0;
774 int i;
775
776 last_match = vps;
777
778 fr_cursor_init(&list_cursor, &last_match);
779 fr_cursor_init(&out_cursor, out);
780 for (i = 0; i < num; i++) {
781 match = fr_cursor_next_by_da(&list_cursor, da[i], TAG_ANY);
782 if (!match) {
783 fr_cursor_init(&list_cursor, &last_match);
784 continue;
785 }
786
787 do {
788 copy = fr_pair_copy(ctx, match);
789 if (!copy) {
790 fr_pair_list_free(out);
791 return -1;
792 }
793 fr_cursor_insert(&out_cursor, copy);
794 last_match = match;
795
796 count++;
797 } while ((match = fr_cursor_next_by_da(&list_cursor, da[i], TAG_ANY)));
798 }
799
800 return count;
801 }
802
_request_free(rs_request_t * request)803 static int _request_free(rs_request_t *request)
804 {
805 bool ret;
806
807 /*
808 * If were attempting to cleanup the request, and it's no longer in the request_tree
809 * something has gone very badly wrong.
810 */
811 if (request->in_request_tree) {
812 ret = rbtree_deletebydata(request_tree, request);
813 RS_ASSERT(ret);
814 }
815
816 if (request->in_link_tree) {
817 ret = rbtree_deletebydata(link_tree, request);
818 RS_ASSERT(ret);
819 }
820
821 if (request->event) {
822 ret = fr_event_delete(events, &request->event);
823 RS_ASSERT(ret);
824 }
825
826 rad_free(&request->packet);
827 rad_free(&request->expect);
828 rad_free(&request->linked);
829
830 return 0;
831 }
832
rs_packet_cleanup(rs_request_t * request)833 static void rs_packet_cleanup(rs_request_t *request)
834 {
835
836 RADIUS_PACKET *packet = request->packet;
837 uint64_t count = request->id;
838
839 RS_ASSERT(request->stats_req);
840 RS_ASSERT(!request->rt_rsp || request->stats_rsp);
841 RS_ASSERT(packet);
842
843 /*
844 * Don't pollute stats or print spurious messages as radsniff closes.
845 */
846 if (cleanup) {
847 talloc_free(request);
848 return;
849 }
850
851 if (RIDEBUG_ENABLED()) {
852 rs_time_print(timestr, sizeof(timestr), &request->when);
853 }
854
855 /*
856 * Were at packet cleanup time which is when the packet was received + timeout
857 * and it's not been linked with a forwarded packet or a response.
858 *
859 * We now count it as lost.
860 */
861 if (!request->silent_cleanup) {
862 if (!request->linked) {
863 if (!request->stats_req) return;
864
865 request->stats_req->interval.lost_total++;
866
867 if (conf->event_flags & RS_LOST) {
868 /* @fixme We should use flags in the request to indicate whether it's been dumped
869 * to a PCAP file or logged yet, this simplifies the body logging logic */
870 rs_packet_print(request, request->id, RS_LOST, request->in, packet, NULL, NULL, false,
871 conf->filter_response_vps || !(conf->event_flags & RS_NORMAL));
872 }
873 }
874
875 if ((request->in->type == PCAP_INTERFACE_IN) && request->logged) {
876 RDEBUG("Cleaning up request packet ID %i", request->expect->id);
877 }
878 }
879
880 /*
881 * Now the request is done, we can update the retransmission stats
882 */
883 if (request->rt_req > RS_RETRANSMIT_MAX) {
884 request->stats_req->interval.rt_total[RS_RETRANSMIT_MAX]++;
885 } else {
886 request->stats_req->interval.rt_total[request->rt_req]++;
887 }
888
889 if (request->rt_rsp) {
890 if (request->rt_rsp > RS_RETRANSMIT_MAX) {
891 request->stats_rsp->interval.rt_total[RS_RETRANSMIT_MAX]++;
892 } else {
893 request->stats_rsp->interval.rt_total[request->rt_rsp]++;
894 }
895 }
896
897 talloc_free(request);
898 }
899
_rs_event(void * ctx)900 static void _rs_event(void *ctx)
901 {
902 rs_request_t *request = talloc_get_type_abort(ctx, rs_request_t);
903 request->event = NULL;
904 rs_packet_cleanup(request);
905 }
906
907 /** Wrapper around fr_packet_cmp to strip off the outer request struct
908 *
909 */
rs_packet_cmp(rs_request_t const * a,rs_request_t const * b)910 static int rs_packet_cmp(rs_request_t const *a, rs_request_t const *b)
911 {
912 return fr_packet_cmp(a->expect, b->expect);
913 }
914
rs_response_to_pcap(rs_event_t * event,rs_request_t * request,struct pcap_pkthdr const * header,uint8_t const * data)915 static inline int rs_response_to_pcap(rs_event_t *event, rs_request_t *request, struct pcap_pkthdr const *header,
916 uint8_t const *data)
917 {
918 if (!event->out) return 0;
919
920 /*
921 * If we're filtering by response then the requests then the capture buffer
922 * associated with the request should contain buffered request packets.
923 */
924 if (conf->filter_response && request) {
925 rs_capture_t *start;
926
927 /*
928 * Record the current position in the header
929 */
930 start = request->capture_p;
931
932 /*
933 * Buffer hasn't looped set capture_p to the start of the buffer
934 */
935 if (!start->header) request->capture_p = request->capture;
936
937 /*
938 * If where capture_p points to, has a header set, write out the
939 * packet to the PCAP file, looping over the buffer until we
940 * hit our start point.
941 */
942 if (request->capture_p->header) do {
943 pcap_dump((void *)event->out->dumper, request->capture_p->header,
944 request->capture_p->data);
945 TALLOC_FREE(request->capture_p->header);
946 TALLOC_FREE(request->capture_p->data);
947
948 /* Reset the pointer to the start of the circular buffer */
949 if (request->capture_p++ >=
950 (request->capture +
951 sizeof(request->capture) / sizeof(*request->capture))) {
952 request->capture_p = request->capture;
953 }
954 } while (request->capture_p != start);
955 }
956
957 /*
958 * Now log the response
959 */
960 pcap_dump((void *)event->out->dumper, header, data);
961
962 return 0;
963 }
964
rs_request_to_pcap(rs_event_t * event,rs_request_t * request,struct pcap_pkthdr const * header,uint8_t const * data)965 static inline int rs_request_to_pcap(rs_event_t *event, rs_request_t *request, struct pcap_pkthdr const *header,
966 uint8_t const *data)
967 {
968 if (!event->out) return 0;
969
970 /*
971 * If we're filtering by response, then we need to wait to write out the requests
972 */
973 if (conf->filter_response) {
974 /* Free the old capture */
975 if (request->capture_p->header) {
976 talloc_free(request->capture_p->header);
977 TALLOC_FREE(request->capture_p->data);
978 }
979
980 if (!(request->capture_p->header = talloc(request, struct pcap_pkthdr))) return -1;
981 if (!(request->capture_p->data = talloc_array(request, uint8_t, header->caplen))) {
982 TALLOC_FREE(request->capture_p->header);
983 return -1;
984 }
985 memcpy(request->capture_p->header, header, sizeof(struct pcap_pkthdr));
986 memcpy(request->capture_p->data, data, header->caplen);
987
988 /* Reset the pointer to the start of the circular buffer */
989 if (++request->capture_p >=
990 (request->capture +
991 sizeof(request->capture) / sizeof(*request->capture))) {
992 request->capture_p = request->capture;
993 }
994 return 0;
995 }
996
997 pcap_dump((void *)event->out->dumper, header, data);
998
999 return 0;
1000 }
1001
1002 /* This is the same as immediately scheduling the cleanup event */
1003 #define RS_CLEANUP_NOW(_x, _s)\
1004 {\
1005 _x->silent_cleanup = _s;\
1006 _x->when = header->ts;\
1007 rs_packet_cleanup(_x);\
1008 _x = NULL;\
1009 } while (0)
1010
rs_packet_process(uint64_t count,rs_event_t * event,struct pcap_pkthdr const * header,uint8_t const * data)1011 static void rs_packet_process(uint64_t count, rs_event_t *event, struct pcap_pkthdr const *header, uint8_t const *data)
1012 {
1013 rs_stats_t *stats = event->stats;
1014 struct timeval elapsed = {0, 0};
1015 struct timeval latency;
1016
1017 /*
1018 * Pointers into the packet data we just received
1019 */
1020 ssize_t len;
1021 uint8_t const *p = data;
1022
1023 ip_header_t const *ip = NULL; /* The IP header */
1024 ip_header6_t const *ip6 = NULL; /* The IPv6 header */
1025 udp_header_t const *udp; /* The UDP header */
1026 uint8_t version; /* IP header version */
1027 bool response; /* Was it a response code */
1028
1029 decode_fail_t reason; /* Why we failed decoding the packet */
1030 static uint64_t captured = 0;
1031
1032 rs_status_t status = RS_NORMAL; /* Any special conditions (RTX, Unlinked, ID-Reused) */
1033 RADIUS_PACKET *current; /* Current packet were processing */
1034 rs_request_t *original = NULL;
1035
1036 rs_request_t search;
1037
1038 memset(&search, 0, sizeof(search));
1039
1040 if (!start_pcap.tv_sec) {
1041 start_pcap = header->ts;
1042 }
1043
1044 if (RIDEBUG_ENABLED()) {
1045 rs_time_print(timestr, sizeof(timestr), &header->ts);
1046 }
1047
1048 len = fr_pcap_link_layer_offset(data, header->caplen, event->in->link_layer);
1049 if (len < 0) {
1050 REDEBUG("Failed determining link layer header offset");
1051 return;
1052 }
1053 p += len;
1054
1055 version = (p[0] & 0xf0) >> 4;
1056 switch (version) {
1057 case 4:
1058 ip = (ip_header_t const *)p;
1059 len = (0x0f & ip->ip_vhl) * 4; /* ip_hl specifies length in 32bit words */
1060 p += len;
1061 break;
1062
1063 case 6:
1064 ip6 = (ip_header6_t const *)p;
1065 p += sizeof(ip_header6_t);
1066
1067 break;
1068
1069 default:
1070 REDEBUG("IP version invalid %i", version);
1071 return;
1072 }
1073
1074 /*
1075 * End of variable length bits, do basic check now to see if packet looks long enough
1076 */
1077 len = (p - data) + sizeof(udp_header_t) + sizeof(radius_packet_t); /* length value */
1078 if ((size_t) len > header->caplen) {
1079 REDEBUG("Packet too small, we require at least %zu bytes, captured %i bytes",
1080 (size_t) len, header->caplen);
1081 return;
1082 }
1083
1084 /*
1085 * UDP header validation.
1086 */
1087 udp = (udp_header_t const *)p;
1088 {
1089 uint16_t udp_len;
1090 ssize_t diff;
1091
1092 udp_len = ntohs(udp->len);
1093 diff = udp_len - (header->caplen - (p - data));
1094 /* Truncated data */
1095 if (diff > 0) {
1096 REDEBUG("Packet too small by %zi bytes, UDP header + Payload should be %hu bytes",
1097 diff, udp_len);
1098 return;
1099 }
1100
1101 #if 0
1102 /*
1103 * It seems many probes add trailing garbage to the end
1104 * of each capture frame. This has been observed with
1105 * the F5 and Netscout.
1106 *
1107 * Leaving the code here in case it's ever needed for
1108 * debugging.
1109 */
1110 else if (diff < 0) {
1111 REDEBUG("Packet too big by %zi bytes, UDP header + Payload should be %hu bytes",
1112 diff * -1, udp_len);
1113 return;
1114 }
1115 #endif
1116 }
1117 if ((version == 4) && conf->verify_udp_checksum) {
1118 uint16_t expected;
1119
1120 expected = fr_udp_checksum((uint8_t const *) udp, ntohs(udp->len), udp->checksum,
1121 ip->ip_src, ip->ip_dst);
1122 if (udp->checksum != expected) {
1123 REDEBUG("UDP checksum invalid, packet: 0x%04hx calculated: 0x%04hx",
1124 ntohs(udp->checksum), ntohs(expected));
1125 /* Not a fatal error */
1126 }
1127 }
1128 p += sizeof(udp_header_t);
1129
1130 /*
1131 * With artificial talloc memory limits there's a good chance we can
1132 * recover once some requests timeout, so make an effort to deal
1133 * with allocation failures gracefully.
1134 */
1135 current = rad_alloc(conf, false);
1136 if (!current) {
1137 REDEBUG("Failed allocating memory to hold decoded packet");
1138 rs_tv_add_ms(&header->ts, conf->stats.timeout, &stats->quiet);
1139 return;
1140 }
1141
1142 current->timestamp = header->ts;
1143 current->data_len = header->caplen - (p - data);
1144 memcpy(¤t->data, &p, sizeof(current->data));
1145
1146 /*
1147 * Populate IP/UDP fields from PCAP data
1148 */
1149 if (ip) {
1150 current->src_ipaddr.af = AF_INET;
1151 current->src_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_src.s_addr;
1152
1153 current->dst_ipaddr.af = AF_INET;
1154 current->dst_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_dst.s_addr;
1155 } else {
1156 current->src_ipaddr.af = AF_INET6;
1157 memcpy(current->src_ipaddr.ipaddr.ip6addr.s6_addr, ip6->ip_src.s6_addr,
1158 sizeof(current->src_ipaddr.ipaddr.ip6addr.s6_addr));
1159
1160 current->dst_ipaddr.af = AF_INET6;
1161 memcpy(current->dst_ipaddr.ipaddr.ip6addr.s6_addr, ip6->ip_dst.s6_addr,
1162 sizeof(current->dst_ipaddr.ipaddr.ip6addr.s6_addr));
1163 }
1164
1165 current->src_port = ntohs(udp->src);
1166 current->dst_port = ntohs(udp->dst);
1167
1168 if (!rad_packet_ok(current, 0, &reason)) {
1169 REDEBUG("%s", fr_strerror());
1170 if (conf->event_flags & RS_ERROR) {
1171 rs_packet_print(NULL, count, RS_ERROR, event->in, current, &elapsed, NULL, false, false);
1172 }
1173 rad_free(¤t);
1174
1175 return;
1176 }
1177
1178 switch (current->code) {
1179 case PW_CODE_ACCOUNTING_RESPONSE:
1180 case PW_CODE_ACCESS_REJECT:
1181 case PW_CODE_ACCESS_ACCEPT:
1182 case PW_CODE_ACCESS_CHALLENGE:
1183 case PW_CODE_COA_NAK:
1184 case PW_CODE_COA_ACK:
1185 case PW_CODE_DISCONNECT_NAK:
1186 case PW_CODE_DISCONNECT_ACK:
1187 case PW_CODE_STATUS_CLIENT:
1188 {
1189 /* look for a matching request and use it for decoding */
1190 search.expect = current;
1191 original = rbtree_finddata(request_tree, &search);
1192
1193 /*
1194 * Verify this code is allowed
1195 */
1196 if (conf->filter_response_code && (conf->filter_response_code != current->code)) {
1197 drop_response:
1198 RDEBUG2("Response dropped by filter");
1199 rad_free(¤t);
1200
1201 /* We now need to cleanup the original request too */
1202 if (original) {
1203 RS_CLEANUP_NOW(original, true);
1204 }
1205 return;
1206 }
1207
1208 /*
1209 * Only decode attributes if we want to print them or filter on them
1210 * rad_packet_ok does checks to verify the packet is actually valid.
1211 */
1212 if (conf->decode_attrs) {
1213 int ret;
1214 FILE *log_fp = fr_log_fp;
1215
1216 fr_log_fp = NULL;
1217 ret = rad_decode(current, original ? original->expect : NULL, conf->radius_secret);
1218 fr_log_fp = log_fp;
1219 if (ret != 0) {
1220 rad_free(¤t);
1221 REDEBUG("Failed decoding");
1222 return;
1223 }
1224 }
1225
1226 /*
1227 * Check if we've managed to link it to a request
1228 */
1229 if (original) {
1230 /*
1231 * Now verify the packet passes the attribute filter
1232 */
1233 if (conf->filter_response_vps) {
1234 fr_pair_list_sort(¤t->vps, fr_pair_cmp_by_da_tag);
1235 if (!fr_pair_validate_relaxed(NULL, conf->filter_response_vps, current->vps)) {
1236 goto drop_response;
1237 }
1238 }
1239
1240 /*
1241 * Is this a retransmission?
1242 */
1243 if (original->linked) {
1244 status = RS_RTX;
1245 original->rt_rsp++;
1246
1247 rad_free(&original->linked);
1248 fr_event_delete(event->list, &original->event);
1249 /*
1250 * ...nope it's the first response to a request.
1251 */
1252 } else {
1253 original->stats_rsp = &stats->exchange[current->code];
1254 }
1255
1256 /*
1257 * Insert a callback to remove the request and response
1258 * from the tree after the timeout period.
1259 * The delay is so we can detect retransmissions.
1260 */
1261 original->linked = talloc_steal(original, current);
1262 rs_tv_add_ms(&header->ts, conf->stats.timeout, &original->when);
1263 if (!fr_event_insert(event->list, _rs_event, original, &original->when,
1264 &original->event)) {
1265 REDEBUG("Failed inserting new event");
1266 /*
1267 * Delete the original request/event, it's no longer valid
1268 * for statistics.
1269 */
1270 talloc_free(original);
1271 return;
1272 }
1273 /*
1274 * No request seen, or request was dropped by attribute filter
1275 */
1276 } else {
1277 /*
1278 * If conf->filter_request_vps are set assume the original request was dropped,
1279 * the alternative is maintaining another 'filter', but that adds
1280 * complexity, reduces max capture rate, and is generally a PITA.
1281 */
1282 if (conf->filter_request) {
1283 rad_free(¤t);
1284 RDEBUG2("Original request dropped by filter");
1285 return;
1286 }
1287
1288 status = RS_UNLINKED;
1289 stats->exchange[current->code].interval.unlinked_total++;
1290 }
1291
1292 rs_response_to_pcap(event, original, header, data);
1293 response = true;
1294 break;
1295 }
1296
1297 case PW_CODE_ACCOUNTING_REQUEST:
1298 case PW_CODE_ACCESS_REQUEST:
1299 case PW_CODE_COA_REQUEST:
1300 case PW_CODE_DISCONNECT_REQUEST:
1301 case PW_CODE_STATUS_SERVER:
1302 {
1303 /*
1304 * Verify this code is allowed
1305 */
1306 if (conf->filter_request_code && (conf->filter_request_code != current->code)) {
1307 drop_request:
1308
1309 RDEBUG2("Request dropped by filter");
1310 rad_free(¤t);
1311
1312 return;
1313 }
1314
1315 /*
1316 * Only decode attributes if we want to print them or filter on them
1317 * rad_packet_ok does checks to verify the packet is actually valid.
1318 */
1319 if (conf->decode_attrs) {
1320 int ret;
1321 FILE *log_fp = fr_log_fp;
1322
1323 fr_log_fp = NULL;
1324 ret = rad_decode(current, NULL, conf->radius_secret);
1325 fr_log_fp = log_fp;
1326
1327 if (ret != 0) {
1328 rad_free(¤t);
1329 REDEBUG("Failed decoding");
1330 return;
1331 }
1332
1333 fr_pair_list_sort(¤t->vps, fr_pair_cmp_by_da_tag);
1334 }
1335
1336 /*
1337 * Save the request for later matching
1338 */
1339 search.expect = rad_alloc_reply(current, current);
1340 if (!search.expect) {
1341 REDEBUG("Failed allocating memory to hold expected reply");
1342 rs_tv_add_ms(&header->ts, conf->stats.timeout, &stats->quiet);
1343 rad_free(¤t);
1344 return;
1345 }
1346 search.expect->code = current->code;
1347
1348 if ((conf->link_da_num > 0) && current->vps) {
1349 int ret;
1350 ret = rs_get_pairs(current, &search.link_vps, current->vps, conf->link_da,
1351 conf->link_da_num);
1352 if (ret < 0) {
1353 ERROR("Failed extracting RTX linking pairs from request");
1354 rad_free(¤t);
1355 return;
1356 }
1357 }
1358
1359 /*
1360 * If we have linking attributes set, attempt to find a request in the linking tree.
1361 */
1362 if (search.link_vps) {
1363 rs_request_t *tuple;
1364
1365 original = rbtree_finddata(link_tree, &search);
1366 tuple = rbtree_finddata(request_tree, &search);
1367
1368 /*
1369 * If the packet we matched using attributes is not the same
1370 * as the packet in the request tree, then we need to clean up
1371 * the packet in the request tree.
1372 */
1373 if (tuple && (original != tuple)) {
1374 RS_CLEANUP_NOW(tuple, true);
1375 }
1376 /*
1377 * Detect duplicates using the normal 5-tuple of src/dst ips/ports id
1378 */
1379 } else {
1380 original = rbtree_finddata(request_tree, &search);
1381 if (original && (memcmp(original->expect->vector, current->vector,
1382 sizeof(original->expect->vector)) != 0)) {
1383 /*
1384 * ID reused before the request timed out (which may be an issue)...
1385 */
1386 if (!original->linked) {
1387 status = RS_REUSED;
1388 stats->exchange[current->code].interval.reused_total++;
1389 /* Occurs regularly downstream of proxy servers (so don't complain) */
1390 RS_CLEANUP_NOW(original, true);
1391 /*
1392 * ...and before we saw a response (which may be a bigger issue).
1393 */
1394 } else {
1395 RS_CLEANUP_NOW(original, false);
1396 }
1397 /* else it's a proper RTX with the same src/dst id authenticator/nonce */
1398 }
1399 }
1400
1401 /*
1402 * Now verify the packet passes the attribute filter
1403 */
1404 if (conf->filter_request_vps) {
1405 if (!fr_pair_validate_relaxed(NULL, conf->filter_request_vps, current->vps)) {
1406 goto drop_request;
1407 }
1408 }
1409
1410 /*
1411 * Is this a retransmission?
1412 */
1413 if (original) {
1414 status = RS_RTX;
1415 original->rt_req++;
1416
1417 rad_free(&original->packet);
1418
1419 /* We may of seen the response, but it may of been lost upstream */
1420 rad_free(&original->linked);
1421
1422 original->packet = talloc_steal(original, current);
1423
1424 /* Request may need to be reinserted as the 5 tuple of the response may of changed */
1425 if (rs_packet_cmp(original, &search) != 0) {
1426 rbtree_deletebydata(request_tree, original);
1427 }
1428
1429 rad_free(&original->expect);
1430 original->expect = talloc_steal(original, search.expect);
1431
1432 /* Disarm the timer for the cleanup event for the original request */
1433 fr_event_delete(event->list, &original->event);
1434 /*
1435 * ...nope it's a new request.
1436 */
1437 } else {
1438 original = talloc_zero(conf, rs_request_t);
1439 talloc_set_destructor(original, _request_free);
1440
1441 original->id = count;
1442 original->in = event->in;
1443 original->stats_req = &stats->exchange[current->code];
1444
1445 /* Set the packet pointer to the start of the buffer*/
1446 original->capture_p = original->capture;
1447
1448 original->packet = talloc_steal(original, current);
1449 original->expect = talloc_steal(original, search.expect);
1450
1451 if (search.link_vps) {
1452 bool ret;
1453 vp_cursor_t cursor;
1454 VALUE_PAIR *vp;
1455
1456 for (vp = fr_cursor_init(&cursor, &search.link_vps);
1457 vp;
1458 vp = fr_cursor_next(&cursor)) {
1459 fr_pair_steal(original, search.link_vps);
1460 }
1461 original->link_vps = search.link_vps;
1462
1463 /* We should never have conflicts */
1464 ret = rbtree_insert(link_tree, original);
1465 RS_ASSERT(ret);
1466 original->in_link_tree = true;
1467 }
1468
1469 /*
1470 * Special case for when were filtering by response,
1471 * we never count any requests as lost, because we
1472 * don't know what the response to that request would
1473 * of been.
1474 */
1475 if (conf->filter_response_vps) {
1476 original->silent_cleanup = true;
1477 }
1478 }
1479
1480 if (!original->in_request_tree) {
1481 bool ret;
1482
1483 /* We should never have conflicts */
1484 ret = rbtree_insert(request_tree, original);
1485 RS_ASSERT(ret);
1486 original->in_request_tree = true;
1487 }
1488
1489 /*
1490 * Insert a callback to remove the request from the tree
1491 */
1492 original->packet->timestamp = header->ts;
1493 rs_tv_add_ms(&header->ts, conf->stats.timeout, &original->when);
1494 if (!fr_event_insert(event->list, _rs_event, original,
1495 &original->when, &original->event)) {
1496 REDEBUG("Failed inserting new event");
1497
1498 talloc_free(original);
1499 return;
1500 }
1501 rs_request_to_pcap(event, original, header, data);
1502 response = false;
1503 break;
1504 }
1505
1506 default:
1507 REDEBUG("Unsupported code %i", current->code);
1508 rad_free(¤t);
1509
1510 return;
1511 }
1512
1513 rs_tv_sub(&header->ts, &start_pcap, &elapsed);
1514
1515 /*
1516 * Increase received count
1517 */
1518 stats->exchange[current->code].interval.received_total++;
1519
1520 /*
1521 * It's a linked response
1522 */
1523 if (original && original->linked) {
1524 rs_tv_sub(¤t->timestamp, &original->packet->timestamp, &latency);
1525
1526 /*
1527 * Update stats for both the request and response types.
1528 *
1529 * This isn't useful for things like Access-Requests, but will be useful for
1530 * CoA and Disconnect Messages, as we get the average latency across both
1531 * response types.
1532 *
1533 * It also justifies allocating PW_CODE_MAX instances of rs_latency_t.
1534 */
1535 rs_stats_update_latency(&stats->exchange[current->code], &latency);
1536 rs_stats_update_latency(&stats->exchange[original->expect->code], &latency);
1537
1538 /*
1539 * Were filtering on response, now print out the full data from the request
1540 */
1541 if (conf->filter_response && RIDEBUG_ENABLED() && (conf->event_flags & RS_NORMAL)) {
1542 rs_time_print(timestr, sizeof(timestr), &original->packet->timestamp);
1543 rs_tv_sub(&original->packet->timestamp, &start_pcap, &elapsed);
1544 rs_packet_print(original, original->id, RS_NORMAL, original->in,
1545 original->packet, &elapsed, NULL, false, true);
1546 rs_tv_sub(&header->ts, &start_pcap, &elapsed);
1547 rs_time_print(timestr, sizeof(timestr), &header->ts);
1548 }
1549
1550 if (conf->event_flags & status) {
1551 rs_packet_print(original, count, status, event->in, current,
1552 &elapsed, &latency, response, true);
1553 }
1554 /*
1555 * It's the original request
1556 *
1557 * If were filtering on responses we can only indicate we received it on response, or timeout.
1558 */
1559 } else if (!conf->filter_response && (conf->event_flags & status)) {
1560 rs_packet_print(original, original ? original->id : count, status, event->in,
1561 current, &elapsed, NULL, response, true);
1562 }
1563
1564 fflush(fr_log_fp);
1565
1566 /*
1567 * If it's a unlinked response, we need to free it explicitly, as it will
1568 * not be done by the event queue.
1569 */
1570 if (response && !original) {
1571 rad_free(¤t);
1572 }
1573
1574 captured++;
1575 /*
1576 * We've hit our capture limit, break out of the event loop
1577 */
1578 if ((conf->limit > 0) && (captured >= conf->limit)) {
1579 INFO("Captured %" PRIu64 " packets, exiting...", captured);
1580 fr_event_loop_exit(events, 1);
1581 }
1582 }
1583
rs_got_packet(fr_event_list_t * el,int fd,void * ctx)1584 static void rs_got_packet(fr_event_list_t *el, int fd, void *ctx)
1585 {
1586 static uint64_t count = 0; /* Packets seen */
1587 rs_event_t *event = ctx;
1588 pcap_t *handle = event->in->handle;
1589
1590 int i;
1591 int ret;
1592 const uint8_t *data;
1593 struct pcap_pkthdr *header;
1594
1595 /*
1596 * Consume entire capture, interleaving not currently possible
1597 */
1598 if ((event->in->type == PCAP_FILE_IN) || (event->in->type == PCAP_STDIO_IN)) {
1599 while (!fr_event_loop_exiting(el)) {
1600 struct timeval now;
1601
1602 ret = pcap_next_ex(handle, &header, &data);
1603 if (ret == 0) {
1604 /* No more packets available at this time */
1605 return;
1606 }
1607 if (ret == -2) {
1608 DEBUG("Done reading packets (%s)", event->in->name);
1609 fr_event_fd_delete(events, 0, fd);
1610
1611 /* Signal pipe takes one slot which is why this is == 1 */
1612 if (fr_event_list_num_fds(events) == 1) {
1613 fr_event_loop_exit(events, 1);
1614 }
1615
1616 return;
1617 }
1618 if (ret < 0) {
1619 ERROR("Error requesting next packet, got (%i): %s", ret, pcap_geterr(handle));
1620 return;
1621 }
1622
1623 do {
1624 now = header->ts;
1625 } while (fr_event_run(el, &now) == 1);
1626 count++;
1627
1628 rs_packet_process(count, event, header, data);
1629 }
1630 return;
1631 }
1632
1633 /*
1634 * Consume multiple packets from the capture buffer.
1635 * We occasionally need to yield to allow events to run.
1636 */
1637 for (i = 0; i < RS_FORCE_YIELD; i++) {
1638 ret = pcap_next_ex(handle, &header, &data);
1639 if (ret == 0) {
1640 /* No more packets available at this time */
1641 return;
1642 }
1643 if (ret < 0) {
1644 ERROR("Error requesting next packet, got (%i): %s", ret, pcap_geterr(handle));
1645 return;
1646 }
1647
1648 count++;
1649 rs_packet_process(count, event, header, data);
1650 }
1651 }
1652
_rs_event_status(struct timeval * wake)1653 static void _rs_event_status(struct timeval *wake)
1654 {
1655 if (wake && ((wake->tv_sec != 0) || (wake->tv_usec >= 100000))) {
1656 DEBUG2("Waking up in %d.%01u seconds.", (int) wake->tv_sec, (unsigned int) wake->tv_usec / 100000);
1657
1658 if (RIDEBUG_ENABLED()) {
1659 rs_time_print(timestr, sizeof(timestr), wake);
1660 }
1661 }
1662 }
1663
1664 /** Compare requests using packet info and lists of attributes
1665 *
1666 */
rs_rtx_cmp(rs_request_t const * a,rs_request_t const * b)1667 static int rs_rtx_cmp(rs_request_t const *a, rs_request_t const *b)
1668 {
1669 int rcode;
1670
1671 RS_ASSERT(a->link_vps);
1672 RS_ASSERT(b->link_vps);
1673
1674 rcode = (int) a->expect->code - (int) b->expect->code;
1675 if (rcode != 0) return rcode;
1676
1677 rcode = a->expect->sockfd - b->expect->sockfd;
1678 if (rcode != 0) return rcode;
1679
1680 rcode = fr_ipaddr_cmp(&a->expect->src_ipaddr, &b->expect->src_ipaddr);
1681 if (rcode != 0) return rcode;
1682
1683 rcode = fr_ipaddr_cmp(&a->expect->dst_ipaddr, &b->expect->dst_ipaddr);
1684 if (rcode != 0) return rcode;
1685
1686 return fr_pair_list_cmp(a->link_vps, b->link_vps);
1687 }
1688
rs_build_dict_list(DICT_ATTR const ** out,size_t len,char * list)1689 static int rs_build_dict_list(DICT_ATTR const **out, size_t len, char *list)
1690 {
1691 size_t i = 0;
1692 char *p, *tok;
1693
1694 p = list;
1695 while ((tok = strsep(&p, "\t ,")) != NULL) {
1696 DICT_ATTR const *da;
1697 if ((*tok == '\t') || (*tok == ' ') || (*tok == '\0')) {
1698 continue;
1699 }
1700
1701 if (i == len) {
1702 ERROR("Too many attributes, maximum allowed is %zu", len);
1703 return -1;
1704 }
1705
1706 da = dict_attrbyname(tok);
1707 if (!da) {
1708 ERROR("Error parsing attribute name \"%s\"", tok);
1709 return -1;
1710 }
1711
1712 out[i] = da;
1713 i++;
1714 }
1715
1716 /*
1717 * This allows efficient list comparisons later
1718 */
1719 if (i > 1) fr_quick_sort((void const **)out, 0, i - 1, fr_pointer_cmp);
1720
1721 return i;
1722 }
1723
rs_build_filter(VALUE_PAIR ** out,char const * filter)1724 static int rs_build_filter(VALUE_PAIR **out, char const *filter)
1725 {
1726 vp_cursor_t cursor;
1727 VALUE_PAIR *vp;
1728 FR_TOKEN code;
1729
1730 code = fr_pair_list_afrom_str(conf, filter, out);
1731 if (code == T_INVALID) {
1732 ERROR("Invalid RADIUS filter \"%s\" (%s)", filter, fr_strerror());
1733 return -1;
1734 }
1735
1736 if (!*out) {
1737 ERROR("Empty RADIUS filter '%s'", filter);
1738 return -1;
1739 }
1740
1741 for (vp = fr_cursor_init(&cursor, out);
1742 vp;
1743 vp = fr_cursor_next(&cursor)) {
1744 /*
1745 * xlat expansion isn't supported here
1746 */
1747 if (vp->type == VT_XLAT) {
1748 vp->type = VT_DATA;
1749 vp->vp_strvalue = vp->value.xlat;
1750 vp->vp_length = talloc_array_length(vp->vp_strvalue) - 1;
1751 }
1752 }
1753
1754 /*
1755 * This allows efficient list comparisons later
1756 */
1757 fr_pair_list_sort(out, fr_pair_cmp_by_da_tag);
1758
1759 return 0;
1760 }
1761
rs_build_event_flags(int * flags,FR_NAME_NUMBER const * map,char * list)1762 static int rs_build_event_flags(int *flags, FR_NAME_NUMBER const *map, char *list)
1763 {
1764 size_t i = 0;
1765 char *p, *tok;
1766
1767 p = list;
1768 while ((tok = strsep(&p, "\t ,")) != NULL) {
1769 int flag;
1770
1771 if ((*tok == '\t') || (*tok == ' ') || (*tok == '\0')) {
1772 continue;
1773 }
1774
1775 *flags |= flag = fr_str2int(map, tok, -1);
1776 if (flag < 0) {
1777 ERROR("Invalid flag \"%s\"", tok);
1778 return -1;
1779 }
1780
1781 i++;
1782 }
1783
1784 return i;
1785 }
1786
1787 /** Callback for when the request is removed from the request tree
1788 *
1789 * @param request being removed.
1790 */
_unmark_request(void * request)1791 static void _unmark_request(void *request)
1792 {
1793 rs_request_t *this = request;
1794 this->in_request_tree = false;
1795 }
1796
1797 /** Callback for when the request is removed from the link tree
1798 *
1799 * @param request being removed.
1800 */
_unmark_link(void * request)1801 static void _unmark_link(void *request)
1802 {
1803 rs_request_t *this = request;
1804 this->in_link_tree = false;
1805 }
1806
1807 #ifdef HAVE_COLLECTDC_H
1808 /** Re-open the collectd socket
1809 *
1810 */
rs_collectd_reopen(void * ctx)1811 static void rs_collectd_reopen(void *ctx)
1812 {
1813 fr_event_list_t *list = ctx;
1814 static fr_event_t *event;
1815 struct timeval now, when;
1816
1817 if (rs_stats_collectd_open(conf) == 0) {
1818 DEBUG2("Stats output socket (re)opened");
1819 return;
1820 }
1821
1822 ERROR("Will attempt to re-establish connection in %i ms", RS_SOCKET_REOPEN_DELAY);
1823
1824 gettimeofday(&now, NULL);
1825 rs_tv_add_ms(&now, RS_SOCKET_REOPEN_DELAY, &when);
1826 if (!fr_event_insert(list, rs_collectd_reopen, list, &when, &event)) {
1827 ERROR("Failed inserting re-open event");
1828 RS_ASSERT(0);
1829 }
1830 }
1831 #endif
1832
1833 /** Write the last signal to the signal pipe
1834 *
1835 * @param sig raised
1836 */
rs_signal_self(int sig)1837 static void rs_signal_self(int sig)
1838 {
1839 if (write(self_pipe[1], &sig, sizeof(sig)) < 0) {
1840 ERROR("Failed writing signal %s to pipe: %s", strsignal(sig), fr_syserror(errno));
1841 exit(EXIT_FAILURE);
1842 }
1843 }
1844
1845 /** Read the last signal from the signal pipe
1846 *
1847 */
rs_signal_action(UNUSED fr_event_list_t * list,int fd,UNUSED void * ctx)1848 static void rs_signal_action(
1849 #ifndef HAVE_COLLECTDC_H
1850 UNUSED
1851 #endif
1852 fr_event_list_t *list, int fd, UNUSED void *ctx)
1853 {
1854 int sig;
1855 ssize_t ret;
1856
1857 ret = read(fd, &sig, sizeof(sig));
1858 if (ret < 0) {
1859 ERROR("Failed reading signal from pipe: %s", fr_syserror(errno));
1860 exit(EXIT_FAILURE);
1861 }
1862
1863 if (ret != sizeof(sig)) {
1864 ERROR("Failed reading signal from pipe: "
1865 "Expected signal to be %zu bytes but only read %zu byes", sizeof(sig), ret);
1866 exit(EXIT_FAILURE);
1867 }
1868
1869 switch (sig) {
1870 #ifdef HAVE_COLLECTDC_H
1871 case SIGPIPE:
1872 rs_collectd_reopen(list);
1873 break;
1874 #endif
1875
1876 case SIGINT:
1877 case SIGTERM:
1878 case SIGQUIT:
1879 DEBUG2("Signalling event loop to exit");
1880 fr_event_loop_exit(events, 1);
1881 break;
1882
1883 default:
1884 ERROR("Unhandled signal %s", strsignal(sig));
1885 exit(EXIT_FAILURE);
1886 }
1887 }
1888
usage(int status)1889 static void NEVER_RETURNS usage(int status)
1890 {
1891 FILE *output = status ? stderr : stdout;
1892 fprintf(output, "Usage: radsniff [options][stats options] -- [pcap files]\n");
1893 fprintf(output, "options:\n");
1894 fprintf(output, " -a List all interfaces available for capture.\n");
1895 fprintf(output, " -c <count> Number of packets to capture.\n");
1896 fprintf(output, " -C Enable UDP checksum validation.\n");
1897 fprintf(output, " -d <directory> Set dictionary directory.\n");
1898 fprintf(output, " -d <raddb> Set configuration directory (defaults to " RADDBDIR ").\n");
1899 fprintf(output, " -D <dictdir> Set main dictionary directory (defaults to " DICTDIR ").\n");
1900 fprintf(output, " -e <event>[,<event>] Only log requests with these event flags.\n");
1901 fprintf(output, " Event may be one of the following:\n");
1902 fprintf(output, " - received - a request or response.\n");
1903 fprintf(output, " - norsp - seen for a request.\n");
1904 fprintf(output, " - rtx - of a request that we've seen before.\n");
1905 fprintf(output, " - noreq - could be matched with the response.\n");
1906 fprintf(output, " - reused - ID too soon.\n");
1907 fprintf(output, " - error - decoding the packet.\n");
1908 fprintf(output, " -f <filter> PCAP filter (default is 'udp port <port> or <port + 1> or 3799')\n");
1909 fprintf(output, " -h This help message.\n");
1910 fprintf(output, " -i <interface> Capture packets from interface (defaults to all if supported).\n");
1911 fprintf(output, " -I <file> Read packets from file (overrides input of -F).\n");
1912 fprintf(output, " -l <attr>[,<attr>] Output packet sig and a list of attributes.\n");
1913 fprintf(output, " -L <attr>[,<attr>] Detect retransmissions using these attributes to link requests.\n");
1914 fprintf(output, " -m Don't put interface(s) into promiscuous mode.\n");
1915 fprintf(output, " -p <port> Filter packets by port (default is 1812).\n");
1916 fprintf(output, " -P <pidfile> Daemonize and write out <pidfile>.\n");
1917 fprintf(output, " -q Print less debugging information.\n");
1918 fprintf(output, " -r <filter> RADIUS attribute request filter.\n");
1919 fprintf(output, " -R <filter> RADIUS attribute response filter.\n");
1920 fprintf(output, " -s <secret> RADIUS secret.\n");
1921 fprintf(output, " -S Write PCAP data to stdout.\n");
1922 fprintf(output, " -v Show program version information.\n");
1923 fprintf(output, " -w <file> Write output packets to file.\n");
1924 fprintf(output, " -x Print more debugging information.\n");
1925 fprintf(output, "stats options:\n");
1926 fprintf(output, " -W <interval> Periodically write out statistics every <interval> seconds.\n");
1927 fprintf(output, " -T <timeout> How many milliseconds before the request is counted as lost "
1928 "(defaults to %i).\n", RS_DEFAULT_TIMEOUT);
1929 #ifdef HAVE_COLLECTDC_H
1930 fprintf(output, " -N <prefix> The instance name passed to the collectd plugin.\n");
1931 fprintf(output, " -O <server> Write statistics to this collectd server.\n");
1932 #endif
1933 exit(status);
1934 }
1935
main(int argc,char * argv[])1936 int main(int argc, char *argv[])
1937 {
1938 fr_pcap_t *in = NULL, *in_p;
1939 fr_pcap_t **in_head = ∈
1940 fr_pcap_t *out = NULL;
1941
1942 int ret = 1; /* Exit status */
1943
1944 char errbuf[PCAP_ERRBUF_SIZE]; /* Error buffer */
1945 int port = 1812;
1946
1947 char buffer[1024];
1948
1949 int opt;
1950 char const *radius_dir = RADDBDIR;
1951 char const *dict_dir = DICTDIR;
1952
1953 rs_stats_t stats;
1954
1955 fr_debug_lvl = 1;
1956 fr_log_fp = stdout;
1957
1958 /*
1959 * Useful if using radsniff as a long running stats daemon
1960 */
1961 #ifndef NDEBUG
1962 if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
1963 fr_perror("radsniff");
1964 exit(EXIT_FAILURE);
1965 }
1966 #endif
1967
1968 talloc_set_log_stderr();
1969
1970 conf = talloc_zero(NULL, rs_t);
1971 RS_ASSERT(conf);
1972
1973 /*
1974 * We don't really want probes taking down machines
1975 */
1976 #ifdef HAVE_TALLOC_SET_MEMLIMIT
1977 /*
1978 * @fixme causes hang in talloc steal
1979 */
1980 //talloc_set_memlimit(conf, 524288000); /* 50 MB */
1981 #endif
1982
1983 /*
1984 * Set some defaults
1985 */
1986 conf->print_packet = true;
1987 conf->limit = 0;
1988 conf->promiscuous = true;
1989 #ifdef HAVE_COLLECTDC_H
1990 conf->stats.prefix = RS_DEFAULT_PREFIX;
1991 #endif
1992 conf->radius_secret = RS_DEFAULT_SECRET;
1993 conf->logger = NULL;
1994
1995 #ifdef HAVE_COLLECTDC_H
1996 conf->stats.prefix = RS_DEFAULT_PREFIX;
1997 #endif
1998
1999 /*
2000 * Get options
2001 */
2002 while ((opt = getopt(argc, argv, "ab:c:Cd:D:e:Ff:hi:I:l:L:mp:P:qr:R:s:Svw:xXW:T:P:N:O:")) != EOF) {
2003 switch (opt) {
2004 case 'a':
2005 {
2006 pcap_if_t *all_devices = NULL;
2007 pcap_if_t *dev_p;
2008
2009 if (pcap_findalldevs(&all_devices, errbuf) < 0) {
2010 ERROR("Error getting available capture devices: %s", errbuf);
2011 goto finish;
2012 }
2013
2014 int i = 1;
2015 for (dev_p = all_devices;
2016 dev_p;
2017 dev_p = dev_p->next) {
2018 INFO("%i.%s", i++, dev_p->name);
2019 }
2020 ret = 0;
2021 pcap_freealldevs(all_devices);
2022 goto finish;
2023 }
2024
2025 /* super secret option */
2026 case 'b':
2027 conf->buffer_pkts = atoi(optarg);
2028 if (conf->buffer_pkts == 0) {
2029 ERROR("Invalid buffer length \"%s\"", optarg);
2030 usage(1);
2031 }
2032 break;
2033
2034 case 'c':
2035 conf->limit = atoi(optarg);
2036 if (conf->limit == 0) {
2037 ERROR("Invalid number of packets \"%s\"", optarg);
2038 usage(1);
2039 }
2040 break;
2041
2042 /* udp checksum */
2043 case 'C':
2044 conf->verify_udp_checksum = true;
2045 break;
2046
2047 case 'd':
2048 radius_dir = optarg;
2049 break;
2050
2051 case 'D':
2052 dict_dir = optarg;
2053 break;
2054
2055 case 'e':
2056 if (rs_build_event_flags((int *) &conf->event_flags, rs_events, optarg) < 0) {
2057 usage(64);
2058 }
2059 break;
2060
2061 case 'f':
2062 conf->pcap_filter = optarg;
2063 break;
2064
2065 case 'h':
2066 usage(0); /* never returns */
2067
2068 case 'i':
2069 *in_head = fr_pcap_init(conf, optarg, PCAP_INTERFACE_IN);
2070 if (!*in_head) goto finish;
2071 in_head = &(*in_head)->next;
2072 conf->from_dev = true;
2073 break;
2074
2075 case 'I':
2076 *in_head = fr_pcap_init(conf, optarg, PCAP_FILE_IN);
2077 if (!*in_head) {
2078 goto finish;
2079 }
2080 in_head = &(*in_head)->next;
2081 conf->from_file = true;
2082 break;
2083
2084 case 'l':
2085 conf->list_attributes = optarg;
2086 break;
2087
2088 case 'L':
2089 conf->link_attributes = optarg;
2090 break;
2091
2092 case 'm':
2093 conf->promiscuous = false;
2094 break;
2095
2096 case 'p':
2097 port = atoi(optarg);
2098 break;
2099
2100 case 'P':
2101 conf->daemonize = true;
2102 conf->pidfile = optarg;
2103 break;
2104
2105 case 'q':
2106 if (fr_debug_lvl > 0) {
2107 fr_debug_lvl--;
2108 }
2109 break;
2110
2111 case 'r':
2112 conf->filter_request = optarg;
2113 break;
2114
2115 case 'R':
2116 conf->filter_response = optarg;
2117 break;
2118
2119 case 's':
2120 conf->radius_secret = optarg;
2121 break;
2122
2123 case 'S':
2124 conf->to_stdout = true;
2125 break;
2126
2127 case 'v':
2128 #ifdef HAVE_COLLECTDC_H
2129 INFO("%s, %s, collectdclient version %s", radsniff_version, pcap_lib_version(),
2130 lcc_version_string());
2131 #else
2132 INFO("%s %s", radsniff_version, pcap_lib_version());
2133 #endif
2134 exit(EXIT_SUCCESS);
2135
2136 case 'w':
2137 out = fr_pcap_init(conf, optarg, PCAP_FILE_OUT);
2138 if (!out) {
2139 ERROR("Failed creating pcap file \"%s\"", optarg);
2140 exit(EXIT_FAILURE);
2141 }
2142 conf->to_file = true;
2143 break;
2144
2145 case 'x':
2146 case 'X':
2147 fr_debug_lvl++;
2148 break;
2149
2150 case 'W':
2151 conf->stats.interval = atoi(optarg);
2152 conf->print_packet = false;
2153 if (conf->stats.interval <= 0) {
2154 ERROR("Stats interval must be > 0");
2155 usage(64);
2156 }
2157 break;
2158
2159 case 'T':
2160 conf->stats.timeout = atoi(optarg);
2161 if (conf->stats.timeout <= 0) {
2162 ERROR("Timeout value must be > 0");
2163 usage(64);
2164 }
2165 break;
2166
2167 #ifdef HAVE_COLLECTDC_H
2168 case 'N':
2169 conf->stats.prefix = optarg;
2170 break;
2171
2172 case 'O':
2173 conf->stats.collectd = optarg;
2174 conf->stats.out = RS_STATS_OUT_COLLECTD;
2175 break;
2176 #endif
2177 default:
2178 usage(64);
2179 }
2180 }
2181
2182 /*
2183 * Mismatch between the binary and the libraries it depends on
2184 */
2185 if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
2186 fr_perror("radsniff");
2187 exit(EXIT_FAILURE);
2188 }
2189
2190 /* Useful for file globbing */
2191 while (optind < argc) {
2192 *in_head = fr_pcap_init(conf, argv[optind], PCAP_FILE_IN);
2193 if (!*in_head) {
2194 goto finish;
2195 }
2196 in_head = &(*in_head)->next;
2197 conf->from_file = true;
2198 optind++;
2199 }
2200
2201 /* Is stdin not a tty? If so it's probably a pipe */
2202 if (!isatty(fileno(stdin))) {
2203 conf->from_stdin = true;
2204 }
2205
2206 /* What's the point in specifying -F ?! */
2207 if (conf->from_stdin && conf->from_file && conf->to_file) {
2208 usage(64);
2209 }
2210
2211 /* Can't read from both... */
2212 if (conf->from_file && conf->from_dev) {
2213 usage(64);
2214 }
2215
2216 /* Reading from file overrides stdin */
2217 if (conf->from_stdin && (conf->from_file || conf->from_dev)) {
2218 conf->from_stdin = false;
2219 }
2220
2221 /* Writing to file overrides stdout */
2222 if (conf->to_file && conf->to_stdout) {
2223 conf->to_stdout = false;
2224 }
2225
2226 if (conf->to_stdout) {
2227 out = fr_pcap_init(conf, "stdout", PCAP_STDIO_OUT);
2228 if (!out) {
2229 goto finish;
2230 }
2231 }
2232
2233 if (conf->from_stdin) {
2234 *in_head = fr_pcap_init(conf, "stdin", PCAP_STDIO_IN);
2235 if (!*in_head) {
2236 goto finish;
2237 }
2238 in_head = &(*in_head)->next;
2239 }
2240
2241 if (conf->stats.interval && !conf->stats.out) {
2242 conf->stats.out = RS_STATS_OUT_STDIO;
2243 }
2244
2245 if (conf->stats.timeout == 0) {
2246 conf->stats.timeout = RS_DEFAULT_TIMEOUT;
2247 }
2248
2249 /*
2250 * If were writing pcap data, or CSV to stdout we *really* don't want to send
2251 * logging there as well.
2252 */
2253 if (conf->to_stdout || conf->list_attributes) {
2254 fr_log_fp = stderr;
2255 }
2256
2257 if (conf->list_attributes) {
2258 conf->logger = rs_packet_print_csv;
2259 } else if (fr_debug_lvl > 0) {
2260 conf->logger = rs_packet_print_fancy;
2261 }
2262
2263 #if !defined(HAVE_PCAP_FOPEN_OFFLINE) || !defined(HAVE_PCAP_DUMP_FOPEN)
2264 if (conf->from_stdin || conf->to_stdout) {
2265 ERROR("PCAP streams not supported");
2266 goto finish;
2267 }
2268 #endif
2269
2270 if (!conf->pcap_filter) {
2271 snprintf(buffer, sizeof(buffer), "udp port %d or %d or %d",
2272 port, port + 1, 3799);
2273 conf->pcap_filter = buffer;
2274 }
2275
2276 if (dict_init(dict_dir, RADIUS_DICTIONARY) < 0) {
2277 fr_perror("radsniff");
2278 ret = 64;
2279 goto finish;
2280 }
2281
2282 if (dict_read(radius_dir, RADIUS_DICTIONARY) == -1) {
2283 fr_perror("radsniff");
2284 ret = 64;
2285 goto finish;
2286 }
2287
2288 fr_strerror(); /* Clear out any non-fatal errors */
2289
2290 if (conf->list_attributes) {
2291 conf->list_da_num = rs_build_dict_list(conf->list_da, sizeof(conf->list_da) / sizeof(*conf->list_da),
2292 conf->list_attributes);
2293 if (conf->list_da_num < 0) {
2294 usage(64);
2295 }
2296 rs_packet_print_csv_header();
2297 }
2298
2299 if (conf->link_attributes) {
2300 conf->link_da_num = rs_build_dict_list(conf->link_da, sizeof(conf->link_da) / sizeof(*conf->link_da),
2301 conf->link_attributes);
2302 if (conf->link_da_num < 0) {
2303 usage(64);
2304 }
2305
2306 link_tree = rbtree_create(conf, (rbcmp) rs_rtx_cmp, _unmark_link, 0);
2307 if (!link_tree) {
2308 ERROR("Failed creating RTX tree");
2309 goto finish;
2310 }
2311 }
2312
2313 if (conf->filter_request) {
2314 vp_cursor_t cursor;
2315 VALUE_PAIR *type;
2316
2317 if (rs_build_filter(&conf->filter_request_vps, conf->filter_request) < 0) {
2318 usage(64);
2319 }
2320
2321 fr_cursor_init(&cursor, &conf->filter_request_vps);
2322 type = fr_cursor_next_by_num(&cursor, PW_PACKET_TYPE, 0, TAG_ANY);
2323 if (type) {
2324 fr_cursor_remove(&cursor);
2325 conf->filter_request_code = type->vp_integer;
2326 talloc_free(type);
2327 }
2328 }
2329
2330 if (conf->filter_response) {
2331 vp_cursor_t cursor;
2332 VALUE_PAIR *type;
2333
2334 if (rs_build_filter(&conf->filter_response_vps, conf->filter_response) < 0) {
2335 usage(64);
2336 }
2337
2338 fr_cursor_init(&cursor, &conf->filter_response_vps);
2339 type = fr_cursor_next_by_num(&cursor, PW_PACKET_TYPE, 0, TAG_ANY);
2340 if (type) {
2341 fr_cursor_remove(&cursor);
2342 conf->filter_response_code = type->vp_integer;
2343 talloc_free(type);
2344 }
2345 }
2346
2347 /*
2348 * Default to logging and capturing all events
2349 */
2350 if (conf->event_flags == 0) {
2351 DEBUG("Logging all events");
2352 memset(&conf->event_flags, 0xff, sizeof(conf->event_flags));
2353 }
2354
2355 /*
2356 * If we need to list attributes, link requests using attributes, filter attributes
2357 * or print the packet contents, we need to decode the attributes.
2358 *
2359 * But, if were just logging requests, or graphing packets, we don't need to decode
2360 * attributes.
2361 */
2362 if (conf->list_da_num || conf->link_da_num || conf->filter_response_vps || conf->filter_request_vps ||
2363 conf->print_packet) {
2364 conf->decode_attrs = true;
2365 }
2366
2367 /*
2368 * Setup the request tree
2369 */
2370 request_tree = rbtree_create(conf, (rbcmp) rs_packet_cmp, _unmark_request, 0);
2371 if (!request_tree) {
2372 ERROR("Failed creating request tree");
2373 goto finish;
2374 }
2375
2376 /*
2377 * Get the default capture device
2378 */
2379 if (!conf->from_stdin && !conf->from_file && !conf->from_dev) {
2380 pcap_if_t *all_devices; /* List of all devices libpcap can listen on */
2381 pcap_if_t *dev_p;
2382
2383 if (pcap_findalldevs(&all_devices, errbuf) < 0) {
2384 ERROR("Error getting available capture devices: %s", errbuf);
2385 goto finish;
2386 }
2387
2388 if (!all_devices) {
2389 ERROR("No capture files specified and no live interfaces available");
2390 ret = 64;
2391 goto finish;
2392 }
2393
2394 for (dev_p = all_devices;
2395 dev_p;
2396 dev_p = dev_p->next) {
2397 int link_layer;
2398
2399 /* Don't use the any devices, it's horribly broken */
2400 if (!strcmp(dev_p->name, "any")) continue;
2401
2402 link_layer = fr_pcap_if_link_layer(errbuf, dev_p);
2403 if (link_layer < 0) {
2404 DEBUG2("Skipping %s: %s", dev_p->name, errbuf);
2405 continue;
2406 }
2407
2408 if (!fr_pcap_link_layer_supported(link_layer)) {
2409 DEBUG2("Skipping %s: datalink type %s not supported",
2410 dev_p->name, pcap_datalink_val_to_name(link_layer));
2411 continue;
2412 }
2413
2414 *in_head = fr_pcap_init(conf, dev_p->name, PCAP_INTERFACE_IN);
2415 in_head = &(*in_head)->next;
2416 }
2417 conf->from_auto = true;
2418 conf->from_dev = true;
2419
2420 pcap_freealldevs(all_devices);
2421
2422 INFO("Defaulting to capture on all interfaces");
2423 }
2424
2425 /*
2426 * Print captures values which will be used
2427 */
2428 if (fr_debug_lvl > 2) {
2429 DEBUG2("Sniffing with options:");
2430 if (conf->from_dev) {
2431 char *buff = fr_pcap_device_names(conf, in, ' ');
2432 DEBUG2(" Device(s) : [%s]", buff);
2433 talloc_free(buff);
2434 }
2435 if (out) {
2436 DEBUG2(" Writing to : [%s]", out->name);
2437 }
2438 if (conf->limit > 0) {
2439 DEBUG2(" Capture limit (packets) : [%" PRIu64 "]", conf->limit);
2440 }
2441 DEBUG2(" PCAP filter : [%s]", conf->pcap_filter);
2442 DEBUG2(" RADIUS secret : [%s]", conf->radius_secret);
2443
2444 if (conf->filter_request_code) {
2445 DEBUG2(" RADIUS request code : [%s]", fr_packet_codes[conf->filter_request_code]);
2446 }
2447
2448 if (conf->filter_request_vps){
2449 DEBUG2(" RADIUS request filter :");
2450 vp_printlist(fr_log_fp, conf->filter_request_vps);
2451 }
2452
2453 if (conf->filter_response_code) {
2454 DEBUG2(" RADIUS response code : [%s]", fr_packet_codes[conf->filter_response_code]);
2455 }
2456
2457 if (conf->filter_response_vps){
2458 DEBUG2(" RADIUS response filter :");
2459 vp_printlist(fr_log_fp, conf->filter_response_vps);
2460 }
2461 }
2462
2463 /*
2464 * Setup collectd templates
2465 */
2466 #ifdef HAVE_COLLECTDC_H
2467 if (conf->stats.out == RS_STATS_OUT_COLLECTD) {
2468 size_t i;
2469 rs_stats_tmpl_t *tmpl, **next;
2470
2471 if (rs_stats_collectd_open(conf) < 0) {
2472 exit(EXIT_FAILURE);
2473 }
2474
2475 next = &conf->stats.tmpl;
2476
2477 for (i = 0; i < (sizeof(rs_useful_codes) / sizeof(*rs_useful_codes)); i++) {
2478 tmpl = rs_stats_collectd_init_latency(conf, next, conf, "exchanged",
2479 &stats.exchange[rs_useful_codes[i]],
2480 rs_useful_codes[i]);
2481 if (!tmpl) {
2482 ERROR("Error allocating memory for stats template");
2483 goto finish;
2484 }
2485 next = &(tmpl->next);
2486 }
2487 }
2488 #endif
2489
2490 /*
2491 * This actually opens the capture interfaces/files (we just allocated the memory earlier)
2492 */
2493 {
2494 fr_pcap_t *tmp;
2495 fr_pcap_t **tmp_p = &tmp;
2496
2497 for (in_p = in;
2498 in_p;
2499 in_p = in_p->next) {
2500 in_p->promiscuous = conf->promiscuous;
2501 in_p->buffer_pkts = conf->buffer_pkts;
2502 if (fr_pcap_open(in_p) < 0) {
2503 ERROR("Failed opening pcap handle (%s): %s", in_p->name, fr_strerror());
2504 if (conf->from_auto || (in_p->type == PCAP_FILE_IN)) {
2505 continue;
2506 }
2507
2508 goto finish;
2509 }
2510
2511 if (!fr_pcap_link_layer_supported(in_p->link_layer)) {
2512 ERROR("Failed opening pcap handle (%s): Datalink type %s not supported",
2513 in_p->name, pcap_datalink_val_to_name(in_p->link_layer));
2514 goto finish;
2515 }
2516
2517 if (conf->pcap_filter) {
2518 if (fr_pcap_apply_filter(in_p, conf->pcap_filter) < 0) {
2519 ERROR("Failed applying filter");
2520 goto finish;
2521 }
2522 }
2523
2524 *tmp_p = in_p;
2525 tmp_p = &(in_p->next);
2526 }
2527 *tmp_p = NULL;
2528 in = tmp;
2529
2530 if (!in) {
2531 ERROR("No PCAP sources available");
2532 exit(EXIT_FAILURE);
2533 }
2534
2535 /* Clear any irrelevant errors */
2536 fr_strerror();
2537 }
2538
2539 /*
2540 * Open our output interface (if we have one);
2541 */
2542 if (out) {
2543 out->link_layer = -1; /* Infer output link type from input */
2544
2545 for (in_p = in;
2546 in_p;
2547 in_p = in_p->next) {
2548 if (out->link_layer < 0) {
2549 out->link_layer = in_p->link_layer;
2550 continue;
2551 }
2552
2553 if (out->link_layer != in_p->link_layer) {
2554 ERROR("Asked to write to output file, but inputs do not have the same link type");
2555 ret = 64;
2556 goto finish;
2557 }
2558 }
2559
2560 RS_ASSERT(out->link_layer >= 0);
2561
2562 if (fr_pcap_open(out) < 0) {
2563 ERROR("Failed opening pcap output (%s): %s", out->name, fr_strerror());
2564 goto finish;
2565 }
2566 }
2567
2568 /*
2569 * Setup and enter the main event loop. Who needs libev when you can roll your own...
2570 */
2571 {
2572 struct timeval now;
2573 rs_update_t update;
2574
2575 char *buff;
2576
2577 memset(&stats, 0, sizeof(stats));
2578 memset(&update, 0, sizeof(update));
2579
2580 events = fr_event_list_create(conf, _rs_event_status);
2581 if (!events) {
2582 ERROR();
2583 goto finish;
2584 }
2585
2586 /*
2587 * Initialise the signal handler pipe
2588 */
2589 if (pipe(self_pipe) < 0) {
2590 ERROR("Couldn't open signal pipe: %s", fr_syserror(errno));
2591 exit(EXIT_FAILURE);
2592 }
2593
2594 if (!fr_event_fd_insert(events, 0, self_pipe[0], rs_signal_action, events)) {
2595 ERROR("Failed inserting signal pipe descriptor: %s", fr_strerror());
2596 goto finish;
2597 }
2598
2599 /*
2600 * Now add fd's for each of the pcap sessions we opened
2601 */
2602 for (in_p = in;
2603 in_p;
2604 in_p = in_p->next) {
2605 rs_event_t *event;
2606
2607 event = talloc_zero(events, rs_event_t);
2608 event->list = events;
2609 event->in = in_p;
2610 event->out = out;
2611 event->stats = &stats;
2612
2613 if (!fr_event_fd_insert(events, 0, in_p->fd, rs_got_packet, event)) {
2614 ERROR("Failed inserting file descriptor");
2615 goto finish;
2616 }
2617 }
2618
2619 buff = fr_pcap_device_names(conf, in, ' ');
2620 DEBUG("Sniffing on (%s)", buff);
2621 talloc_free(buff);
2622
2623 gettimeofday(&now, NULL);
2624
2625 /*
2626 * Insert our stats processor
2627 */
2628 if (conf->stats.interval) {
2629 static fr_event_t *event;
2630
2631 update.list = events;
2632 update.stats = &stats;
2633 update.in = in;
2634
2635 now.tv_sec += conf->stats.interval;
2636 now.tv_usec = 0;
2637 if (!fr_event_insert(events, rs_stats_process, (void *) &update, &now, &event)) {
2638 ERROR("Failed inserting stats event");
2639 }
2640
2641 INFO("Muting stats for the next %i milliseconds (warmup)", conf->stats.timeout);
2642 rs_tv_add_ms(&now, conf->stats.timeout, &stats.quiet);
2643 }
2644 }
2645
2646
2647 /*
2648 * Do this as late as possible so we can return an error code if something went wrong.
2649 */
2650 if (conf->daemonize) {
2651 rs_daemonize(conf->pidfile);
2652 }
2653
2654 /*
2655 * Setup signal handlers so we always exit gracefully, ensuring output buffers are always
2656 * flushed.
2657 */
2658 fr_set_signal(SIGPIPE, rs_signal_self);
2659 fr_set_signal(SIGINT, rs_signal_self);
2660 fr_set_signal(SIGTERM, rs_signal_self);
2661 #ifdef SIGQUIT
2662 fr_set_signal(SIGQUIT, rs_signal_self);
2663 #endif
2664
2665 fr_event_loop(events); /* Enter the main event loop */
2666
2667 DEBUG("Done sniffing");
2668
2669 finish:
2670
2671 cleanup = true;
2672
2673 /*
2674 * Free all the things! This also closes all the sockets and file descriptors
2675 */
2676 talloc_free(conf);
2677
2678 if (conf->daemonize) {
2679 unlink(conf->pidfile);
2680 }
2681
2682 return ret;
2683 }
2684