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, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief Capture, filter, and generate statistics for RADIUS traffic
21 * @copyright 2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
22 * @copyright 2006 The FreeRADIUS server project
23 * @copyright 2006 Nicolas Baradakis <nicolas.baradakis@cegetel.net>
32 #include <freeradius-devel/libradius.h>
33 #include <freeradius-devel/event.h>
35 #include <freeradius-devel/radpaths.h>
36 #include <freeradius-devel/conf.h>
37 #include <freeradius-devel/pcap.h>
38 #include <freeradius-devel/radsniff.h>
40 #ifdef HAVE_COLLECTDC_H
41 # include <collectd/client.h>
45 struct timeval start_pcap = {0, 0};
46 static char timestr[50];
48 static rbtree_t *request_tree = NULL;
49 static rbtree_t *link_tree = NULL;
50 static fr_event_list_t *events;
53 static int self_pipe[2] = {-1, -1}; //!< Signals from sig handlers
55 typedef int (*rbcmp)(void const *, void const *);
57 static char const *radsniff_version = "radsniff version " RADIUSD_VERSION_STRING
58 #ifdef RADIUSD_VERSION_COMMIT
59 " (git #" STRINGIFY(RADIUSD_VERSION_COMMIT) ")"
61 ", built on " __DATE__ " at " __TIME__;
63 static int rs_useful_codes[] = {
64 PW_CODE_ACCESS_REQUEST, //!< RFC2865 - Authentication request
65 PW_CODE_ACCESS_ACCEPT, //!< RFC2865 - Access-Accept
66 PW_CODE_ACCESS_REJECT, //!< RFC2865 - Access-Reject
67 PW_CODE_ACCOUNTING_REQUEST, //!< RFC2866 - Accounting-Request
68 PW_CODE_ACCOUNTING_RESPONSE, //!< RFC2866 - Accounting-Response
69 PW_CODE_ACCESS_CHALLENGE, //!< RFC2865 - Access-Challenge
70 PW_CODE_STATUS_SERVER, //!< RFC2865/RFC5997 - Status Server (request)
71 PW_CODE_STATUS_CLIENT, //!< RFC2865/RFC5997 - Status Server (response)
72 PW_CODE_DISCONNECT_REQUEST, //!< RFC3575/RFC5176 - Disconnect-Request
73 PW_CODE_DISCONNECT_ACK, //!< RFC3575/RFC5176 - Disconnect-Ack (positive)
74 PW_CODE_DISCONNECT_NAK, //!< RFC3575/RFC5176 - Disconnect-Nak (not willing to perform)
75 PW_CODE_COA_REQUEST, //!< RFC3575/RFC5176 - CoA-Request
76 PW_CODE_COA_ACK, //!< RFC3575/RFC5176 - CoA-Ack (positive)
77 PW_CODE_COA_NAK, //!< RFC3575/RFC5176 - CoA-Nak (not willing to perform)
80 const FR_NAME_NUMBER rs_events[] = {
81 { "received", RS_NORMAL },
84 { "noreq", RS_UNLINKED },
85 { "reused", RS_REUSED },
86 { "error", RS_ERROR },
90 static void NEVER_RETURNS usage(int status);
92 /** Fork and kill the parent process, writing out our PID
94 * @param pidfile the PID file to write our PID to
96 static void rs_daemonize(char const *pidfile)
116 * Continue as the child.
119 /* Create a new SID for the child process */
126 * Change the current working directory. This prevents the current
127 * directory from being locked; hence not being able to remove it.
129 if ((chdir("/")) < 0) {
134 * And write it AFTER we've forked, so that we write the
137 fp = fopen(pidfile, "w");
139 fprintf(fp, "%d\n", (int) sid);
142 ERROR("Failed creating PID file %s: %s", pidfile, fr_syserror(errno));
147 * Close stdout and stderr if they've not been redirected.
149 if (isatty(fileno(stdout))) {
150 if (!freopen("/dev/null", "w", stdout)) {
155 if (isatty(fileno(stderr))) {
156 if (!freopen("/dev/null", "w", stderr)) {
163 static void rs_tv_sub(struct timeval const *end, struct timeval const *start, struct timeval *elapsed)
165 elapsed->tv_sec = end->tv_sec - start->tv_sec;
166 if (elapsed->tv_sec > 0) {
168 elapsed->tv_usec = USEC;
170 elapsed->tv_usec = 0;
172 elapsed->tv_usec += end->tv_usec;
173 elapsed->tv_usec -= start->tv_usec;
175 if (elapsed->tv_usec >= USEC) {
176 elapsed->tv_usec -= USEC;
181 static void rs_tv_add_ms(struct timeval const *start, unsigned long interval, struct timeval *result) {
182 result->tv_sec = start->tv_sec + (interval / 1000);
183 result->tv_usec = start->tv_usec + ((interval % 1000) * 1000);
185 if (result->tv_usec > USEC) {
186 result->tv_usec -= USEC;
191 static void rs_time_print(char *out, size_t len, struct timeval const *t)
198 gettimeofday(&now, NULL);
202 ret = strftime(out, len, "%Y-%m-%d %H:%M:%S", localtime(&t->tv_sec));
210 while (usec < 100000) usec *= 10;
211 snprintf(out + ret, len - ret, ".%i", usec);
213 snprintf(out + ret, len - ret, ".000000");
217 static void rs_packet_print_null(UNUSED uint64_t count, UNUSED rs_status_t status, UNUSED fr_pcap_t *handle,
218 UNUSED RADIUS_PACKET *packet, UNUSED struct timeval *elapsed,
219 UNUSED struct timeval *latency, UNUSED bool response, UNUSED bool body)
224 static size_t rs_prints_csv(char *out, size_t outlen, char const *in, size_t inlen)
226 char const *start = out;
227 uint8_t const *str = (uint8_t const *) in;
241 while ((inlen > 0) && (outlen > 2)) {
243 * Escape double quotes with... MORE DOUBLE QUOTES!
251 * Safe chars which require no escaping
253 if ((*str == '\r') || (*str == '\n') || ((*str >= '\x20') && (*str <= '\x7E'))) {
262 * Everything else is dropped
272 static void rs_packet_print_csv_header(void)
278 ssize_t len, s = sizeof(buffer);
280 len = strlcpy(p, "\"Status\",\"Count\",\"Time\",\"Latency\",\"Type\",\"Interface\","
281 "\"Src IP\",\"Src Port\",\"Dst IP\",\"Dst Port\",\"ID\",", s);
287 for (i = 0; i < conf->list_da_num; i++) {
294 for (in = conf->list_da[i]->name; *in; in++) {
310 fprintf(stdout , "%s\n", buffer);
313 static void rs_packet_print_csv(uint64_t count, rs_status_t status, fr_pcap_t *handle, RADIUS_PACKET *packet,
314 UNUSED struct timeval *elapsed, struct timeval *latency, UNUSED bool response,
317 char const *status_str;
321 char src[INET6_ADDRSTRLEN];
322 char dst[INET6_ADDRSTRLEN];
324 ssize_t len, s = sizeof(buffer);
326 inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr, src, sizeof(src));
327 inet_ntop(packet->dst_ipaddr.af, &packet->dst_ipaddr.ipaddr, dst, sizeof(dst));
329 status_str = fr_int2str(rs_events, status, NULL);
332 len = snprintf(p, s, "%s,%" PRIu64 ",%s,", status_str, count, timestr);
339 len = snprintf(p, s, "%u.%03u,",
340 (unsigned int) latency->tv_sec, ((unsigned int) latency->tv_usec / 1000));
351 /* Status, Type, Interface, Src, Src port, Dst, Dst port, ID */
352 if (is_radius_code(packet->code)) {
353 len = snprintf(p, s, "%s,%s,%s,%i,%s,%i,%i,", fr_packet_codes[packet->code], handle->name,
354 src, packet->src_port, dst, packet->dst_port, packet->id);
356 len = snprintf(p, s, "%i,%s,%s,%i,%s,%i,%i,", packet->code, handle->name,
357 src, packet->src_port, dst, packet->dst_port, packet->id);
368 for (i = 0; i < conf->list_da_num; i++) {
369 vp = pairfind_da(packet->vps, conf->list_da[i], TAG_ANY);
370 if (vp && (vp->length > 0)) {
371 if (conf->list_da[i]->type == PW_TYPE_STRING) {
376 len = rs_prints_csv(p, s, vp->vp_strvalue, vp->length);
385 len = vp_prints_value(p, s, vp, 0);
397 s -= conf->list_da_num;
400 memset(p, ',', conf->list_da_num);
401 p += conf->list_da_num;
405 fprintf(stdout , "%s\n", buffer);
408 static void rs_packet_print_fancy(uint64_t count, rs_status_t status, fr_pcap_t *handle, RADIUS_PACKET *packet,
409 struct timeval *elapsed, struct timeval *latency, bool response, bool body)
414 char src[INET6_ADDRSTRLEN];
415 char dst[INET6_ADDRSTRLEN];
417 ssize_t len, s = sizeof(buffer);
419 inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr, src, sizeof(src));
420 inet_ntop(packet->dst_ipaddr.af, &packet->dst_ipaddr.ipaddr, dst, sizeof(dst));
422 /* Only print out status str if something's not right */
423 if (status != RS_NORMAL) {
424 char const *status_str;
426 status_str = fr_int2str(rs_events, status, NULL);
429 len = snprintf(p, s, "** %s ** ", status_str);
435 if (is_radius_code(packet->code)) {
436 len = snprintf(p, s, "%s Id %i %s:%s:%d %s %s:%i ",
437 fr_packet_codes[packet->code],
440 response ? dst : src,
441 response ? packet->dst_port : packet->src_port,
442 response ? "<-" : "->",
443 response ? src : dst ,
444 response ? packet->src_port : packet->dst_port);
446 len = snprintf(p, s, "%i Id %i %s:%s:%i %s %s:%i ",
450 response ? dst : src,
451 response ? packet->dst_port : packet->src_port,
452 response ? "<-" : "->",
453 response ? src : dst ,
454 response ? packet->src_port : packet->dst_port);
461 len = snprintf(p, s, "+%u.%03u ",
462 (unsigned int) elapsed->tv_sec, ((unsigned int) elapsed->tv_usec / 1000));
469 len = snprintf(p, s, "+%u.%03u ",
470 (unsigned int) latency->tv_sec, ((unsigned int) latency->tv_usec / 1000));
478 RIDEBUG("%s", buffer);
482 * Print out verbose HEX output
484 if (conf->print_packet && (fr_debug_flag > 3)) {
485 rad_print_hex(packet);
488 if (conf->print_packet && (fr_debug_flag > 1)) {
489 char vector[(AUTH_VECTOR_LEN * 2) + 1];
492 pairsort(&packet->vps, attrtagcmp);
493 vp_printlist(fr_log_fp, packet->vps);
496 fr_bin2hex(vector, packet->vector, AUTH_VECTOR_LEN);
497 INFO("\tAuthenticator-Field = 0x%s", vector);
502 static void rs_stats_print(rs_latency_t *stats, PW_CODE code)
505 bool have_rt = false;
507 for (i = 0; i <= RS_RETRANSMIT_MAX; i++) {
508 if (stats->interval.rt[i]) {
513 if (!stats->interval.received && !have_rt && !stats->interval.reused) {
517 if (stats->interval.received || stats->interval.linked) {
518 INFO("%s counters:", fr_packet_codes[code]);
519 if (stats->interval.received > 0) {
520 INFO("\tTotal : %.3lf/s" , stats->interval.received);
524 if (stats->interval.linked > 0) {
525 INFO("\tLinked : %.3lf/s", stats->interval.linked);
526 INFO("\tUnlinked : %.3lf/s", stats->interval.unlinked);
527 INFO("%s latency:", fr_packet_codes[code]);
528 INFO("\tHigh : %.3lfms", stats->interval.latency_high);
529 INFO("\tLow : %.3lfms", stats->interval.latency_low);
530 INFO("\tAverage : %.3lfms", stats->interval.latency_average);
531 INFO("\tMA : %.3lfms", stats->latency_smoothed);
534 if (have_rt || stats->interval.lost || stats->interval.reused) {
535 INFO("%s retransmits & loss:", fr_packet_codes[code]);
537 if (stats->interval.lost) {
538 INFO("\tLost : %.3lf/s", stats->interval.lost);
541 if (stats->interval.reused) {
542 INFO("\tID Reused : %.3lf/s", stats->interval.reused);
545 for (i = 0; i <= RS_RETRANSMIT_MAX; i++) {
546 if (!stats->interval.rt[i]) {
550 if (i != RS_RETRANSMIT_MAX) {
551 INFO("\tRT (%i) : %.3lf/s", i, stats->interval.rt[i]);
553 INFO("\tRT (%i+) : %.3lf/s", i, stats->interval.rt[i]);
559 /** Query libpcap to see if it dropped any packets
561 * We need to check to see if libpcap dropped any packets and if it did, we need to stop stats output for long
562 * enough for inaccurate statistics to be cleared out.
564 * @param in pcap handle to check.
565 * @param interval time between checks (used for debug output)
566 * @return 0, no drops, -1 we couldn't check, -2 dropped because of buffer exhaustion, -3 dropped because of NIC.
568 static int rs_check_pcap_drop(fr_pcap_t *in, int interval) {
570 struct pcap_stat pstats;
572 if (pcap_stats(in->handle, &pstats) != 0) {
573 ERROR("%s failed retrieving pcap stats: %s", in->name, pcap_geterr(in->handle));
577 INFO("\t%s%*s: %.3lf/s", in->name, (int) (10 - strlen(in->name)), "",
578 ((double) (pstats.ps_recv - in->pstats.ps_recv)) / interval);
580 if (pstats.ps_drop - in->pstats.ps_drop > 0) {
581 ERROR("%s dropped %i packets: Buffer exhaustion", in->name, pstats.ps_drop - in->pstats.ps_drop);
585 if (pstats.ps_ifdrop - in->pstats.ps_ifdrop > 0) {
586 ERROR("%s dropped %i packets: Interface", in->name, pstats.ps_ifdrop - in->pstats.ps_ifdrop);
595 /** Update smoothed average
598 static void rs_stats_process_latency(rs_latency_t *stats)
601 * If we didn't link any packets during this interval, we don't have a value to return.
602 * returning 0 is misleading as it would be like saying the latency had dropped to 0.
603 * We instead set NaN which libcollectd converts to a 'U' or unknown value.
605 * This will cause gaps in graphs, but is completely legitimate as we are missing data.
606 * This is unfortunately an effect of being just a passive observer.
608 if (stats->interval.linked_total == 0) {
609 double unk = strtod("NAN()", (char **) NULL);
611 stats->interval.latency_average = unk;
612 stats->interval.latency_high = unk;
613 stats->interval.latency_low = unk;
616 * We've not yet been able to determine latency, so latency_smoothed is also NaN
618 if (stats->latency_smoothed_count == 0) {
619 stats->latency_smoothed = unk;
624 if (stats->interval.linked_total && stats->interval.latency_total) {
625 stats->interval.latency_average = (stats->interval.latency_total / stats->interval.linked_total);
628 if (isnan(stats->latency_smoothed)) {
629 stats->latency_smoothed = 0;
631 if (stats->interval.latency_average > 0) {
632 stats->latency_smoothed_count++;
633 stats->latency_smoothed += ((stats->interval.latency_average - stats->latency_smoothed) /
634 ((stats->latency_smoothed_count < 100) ? stats->latency_smoothed_count : 100));
638 static void rs_stats_process_counters(rs_latency_t *stats)
642 stats->interval.received = ((long double) stats->interval.received_total) / conf->stats.interval;
643 stats->interval.linked = ((long double) stats->interval.linked_total) / conf->stats.interval;
644 stats->interval.unlinked = ((long double) stats->interval.unlinked_total) / conf->stats.interval;
645 stats->interval.reused = ((long double) stats->interval.reused_total) / conf->stats.interval;
646 stats->interval.lost = ((long double) stats->interval.lost_total) / conf->stats.interval;
648 for (i = 0; i < RS_RETRANSMIT_MAX; i++) {
649 stats->interval.rt[i] = ((long double) stats->interval.rt_total[i]) / conf->stats.interval;
653 /** Process stats for a single interval
656 static void rs_stats_process(void *ctx)
659 size_t rs_codes_len = (sizeof(rs_useful_codes) / sizeof(*rs_useful_codes));
661 rs_update_t *this = ctx;
662 rs_stats_t *stats = this->stats;
665 gettimeofday(&now, NULL);
669 INFO("######### Stats Iteration %i #########", stats->intervals);
672 * Verify that none of the pcap handles have dropped packets.
674 INFO("Interface capture rate:");
675 for (in_p = this->in;
678 if (rs_check_pcap_drop(in_p, conf->stats.interval) < 0) {
679 ERROR("Muting stats for the next %i milliseconds", conf->stats.timeout);
681 rs_tv_add_ms(&now, conf->stats.timeout, &stats->quiet);
686 if ((stats->quiet.tv_sec + (stats->quiet.tv_usec / 1000000.0)) -
687 (now.tv_sec + (now.tv_usec / 1000000.0)) > 0) {
688 INFO("Stats muted because of warmup, or previous error");
693 * Latency stats need a bit more work to calculate the SMA.
695 * No further work is required for codes.
697 for (i = 0; i < rs_codes_len; i++) {
698 rs_stats_process_latency(&stats->exchange[rs_useful_codes[i]]);
699 rs_stats_process_counters(&stats->exchange[rs_useful_codes[i]]);
700 if (fr_debug_flag > 0) {
701 rs_stats_print(&stats->exchange[rs_useful_codes[i]], rs_useful_codes[i]);
705 #ifdef HAVE_COLLECTDC_H
707 * Update stats in collectd using the complex structures we
708 * initialised earlier.
710 if ((conf->stats.out == RS_STATS_OUT_COLLECTD) && conf->stats.handle) {
711 rs_stats_collectd_do_stats(conf, conf->stats.tmpl, &now);
717 * Rinse and repeat...
719 for (i = 0; i < rs_codes_len; i++) {
720 memset(&stats->exchange[rs_useful_codes[i]].interval, 0,
721 sizeof(stats->exchange[rs_useful_codes[i]].interval));
725 static fr_event_t *event;
727 now.tv_sec += conf->stats.interval;
730 if (!fr_event_insert(this->list, rs_stats_process, ctx, &now, &event)) {
731 ERROR("Failed inserting stats interval event");
737 /** Update latency statistics for request/response and forwarded packets
740 static void rs_stats_update_latency(rs_latency_t *stats, struct timeval *latency)
744 stats->interval.linked_total++;
745 /* More useful is this in milliseconds */
746 lint = (latency->tv_sec + (latency->tv_usec / 1000000.0)) * 1000;
747 if (lint > stats->interval.latency_high) {
748 stats->interval.latency_high = lint;
750 if (!stats->interval.latency_low || (lint < stats->interval.latency_low)) {
751 stats->interval.latency_low = lint;
753 stats->interval.latency_total += lint;
757 /** Copy a subset of attributes from one list into the other
759 * Should be O(n) if all the attributes exist. List must be pre-sorted.
761 static int rs_get_pairs(TALLOC_CTX *ctx, VALUE_PAIR **out, VALUE_PAIR *vps, DICT_ATTR const *da[], int num)
763 vp_cursor_t list_cursor, out_cursor;
764 VALUE_PAIR *match, *last_match, *copy;
770 fr_cursor_init(&list_cursor, &last_match);
771 fr_cursor_init(&out_cursor, out);
772 for (i = 0; i < num; i++) {
773 match = fr_cursor_next_by_da(&list_cursor, da[i], TAG_ANY);
775 fr_cursor_init(&list_cursor, &last_match);
780 copy = paircopyvp(ctx, match);
785 fr_cursor_insert(&out_cursor, copy);
789 } while ((match = fr_cursor_next_by_da(&list_cursor, da[i], TAG_ANY)));
795 static int _request_free(rs_request_t *request)
798 * If were attempting to cleanup the request, and it's no longer in the request_tree
799 * something has gone very badly wrong.
801 if (request->in_request_tree) {
802 assert(rbtree_deletebydata(request_tree, request));
805 if (request->in_link_tree) {
806 assert(rbtree_deletebydata(link_tree, request));
809 if (request->event) {
810 assert(fr_event_delete(events, &request->event));
813 rad_free(&request->packet);
814 rad_free(&request->expect);
815 rad_free(&request->linked);
820 static void rs_packet_cleanup(rs_request_t *request)
823 RADIUS_PACKET *packet = request->packet;
824 uint64_t count = request->id;
826 assert(request->stats_req);
827 assert(!request->rt_rsp || request->stats_rsp);
831 * Don't pollute stats or print spurious messages as radsniff closes.
834 talloc_free(request);
838 if (RIDEBUG_ENABLED()) {
839 rs_time_print(timestr, sizeof(timestr), &request->when);
843 * Were at packet cleanup time which is when the packet was received + timeout
844 * and it's not been linked with a forwarded packet or a response.
846 * We now count it as lost.
848 if (!request->silent_cleanup) {
849 if (!request->linked) {
850 request->stats_req->interval.lost_total++;
852 if (conf->event_flags & RS_LOST) {
853 /* @fixme We should use flags in the request to indicate whether it's been dumped
854 * to a PCAP file or logged yet, this simplifies the body logging logic */
855 conf->logger(request->id, RS_LOST, request->in, packet, NULL, NULL, false,
856 conf->filter_response_vps || !(conf->event_flags & RS_NORMAL));
860 if (request->in->type == PCAP_INTERFACE_IN) {
861 RDEBUG("Cleaning up request packet ID %i", request->expect->id);
866 * Now the request is done, we can update the retransmission stats
868 if (request->rt_req > RS_RETRANSMIT_MAX) {
869 request->stats_req->interval.rt_total[RS_RETRANSMIT_MAX]++;
871 request->stats_req->interval.rt_total[request->rt_req]++;
874 if (request->rt_rsp) {
875 if (request->rt_rsp > RS_RETRANSMIT_MAX) {
876 request->stats_rsp->interval.rt_total[RS_RETRANSMIT_MAX]++;
878 request->stats_rsp->interval.rt_total[request->rt_rsp]++;
882 talloc_free(request);
885 static void _rs_event(void *ctx)
887 rs_request_t *request = talloc_get_type_abort(ctx, rs_request_t);
888 request->event = NULL;
889 rs_packet_cleanup(request);
892 /** Wrapper around fr_packet_cmp to strip off the outer request struct
895 static int rs_packet_cmp(rs_request_t const *a, rs_request_t const *b)
897 return fr_packet_cmp(a->expect, b->expect);
900 static inline int rs_response_to_pcap(rs_event_t *event, rs_request_t *request, struct pcap_pkthdr const *header,
903 if (!event->out) return 0;
906 * If we're filtering by response then the requests then the capture buffer
907 * associated with the request should contain buffered request packets.
909 if (conf->filter_response) {
913 * Record the current position in the header
915 start = request->capture_p;
918 * Buffer hasn't looped set capture_p to the start of the buffer
920 if (!start->header) request->capture_p = request->capture;
923 * If where capture_p points to, has a header set, write out the
924 * packet to the PCAP file, looping over the buffer until we
925 * hit our start point.
927 if (request->capture_p->header) do {
928 pcap_dump((void *)event->out->dumper, request->capture_p->header,
929 request->capture_p->data);
930 TALLOC_FREE(request->capture_p->header);
931 TALLOC_FREE(request->capture_p->data);
933 /* Reset the pointer to the start of the circular buffer */
934 if (request->capture_p++ >= (request->capture + sizeof(request->capture))) {
935 request->capture_p = request->capture;
937 } while (request->capture_p != start);
941 * Now log the response
943 pcap_dump((void *)event->out->dumper, header, data);
948 static inline int rs_request_to_pcap(rs_event_t *event, rs_request_t *request, struct pcap_pkthdr const *header,
951 if (!event->out) return 0;
954 * If we're filtering by response, then we need to wait to write out the requests
956 if (conf->filter_response) {
957 /* Free the old capture */
958 if (request->capture_p->header) {
959 talloc_free(request->capture_p->header);
960 talloc_free(request->capture_p->data);
963 if (!(request->capture_p->header = talloc(request, struct pcap_pkthdr))) return -1;
964 if (!(request->capture_p->data = talloc_array(request, uint8_t, header->caplen))) {
965 TALLOC_FREE(request->capture_p->header);
968 memcpy(request->capture_p->header, header, sizeof(struct pcap_pkthdr));
969 memcpy(request->capture_p->data, data, header->caplen);
971 /* Reset the pointer to the start of the circular buffer */
972 if (++request->capture_p >= (request->capture + sizeof(request->capture))) {
973 request->capture_p = request->capture;
978 pcap_dump((void *)event->out->dumper, header, data);
983 /* This is the same as immediately scheduling the cleanup event */
984 #define RS_CLEANUP_NOW(_x, _s)\
986 _x->silent_cleanup = _s;\
987 _x->when = header->ts;\
988 rs_packet_cleanup(_x);\
992 static void rs_packet_process(uint64_t count, rs_event_t *event, struct pcap_pkthdr const *header, uint8_t const *data)
994 rs_stats_t *stats = event->stats;
995 struct timeval elapsed = {0, 0};
996 struct timeval latency;
999 * Pointers into the packet data we just received
1002 uint8_t const *p = data;
1004 ip_header_t const *ip = NULL; /* The IP header */
1005 ip_header6_t const *ip6 = NULL; /* The IPv6 header */
1006 udp_header_t const *udp; /* The UDP header */
1007 uint8_t version; /* IP header version */
1008 bool response; /* Was it a response code */
1010 decode_fail_t reason; /* Why we failed decoding the packet */
1011 static uint64_t captured = 0;
1013 rs_status_t status = RS_NORMAL; /* Any special conditions (RTX, Unlinked, ID-Reused) */
1014 RADIUS_PACKET *current; /* Current packet were processing */
1015 rs_request_t *original = NULL;
1017 rs_request_t search;
1019 memset(&search, 0, sizeof(search));
1021 if (!start_pcap.tv_sec) {
1022 start_pcap = header->ts;
1025 if (RIDEBUG_ENABLED()) {
1026 rs_time_print(timestr, sizeof(timestr), &header->ts);
1029 len = fr_pcap_link_layer_offset(data, header->caplen, event->in->link_type);
1031 REDEBUG("Failed determining link layer header offset");
1036 version = (p[0] & 0xf0) >> 4;
1039 ip = (ip_header_t const *)p;
1040 len = (0x0f & ip->ip_vhl) * 4; /* ip_hl specifies length in 32bit words */
1045 ip6 = (ip_header6_t const *)p;
1046 p += sizeof(ip_header6_t);
1051 REDEBUG("IP version invalid %i", version);
1056 * End of variable length bits, do basic check now to see if packet looks long enough
1058 len = (p - data) + sizeof(udp_header_t) + (sizeof(radius_packet_t) - 1); /* length value */
1059 if ((size_t) len > header->caplen) {
1060 REDEBUG("Packet too small, we require at least %zu bytes, captured %i bytes",
1061 (size_t) len, header->caplen);
1066 * UDP header validation.
1068 udp = (udp_header_t const *)p;
1073 udp_len = ntohs(udp->len);
1074 diff = udp_len - (header->caplen - (p - data));
1075 /* Truncated data */
1077 REDEBUG("Packet too small by %zi bytes, UDP header + Payload should be %hu bytes",
1082 else if (diff < 0) {
1083 REDEBUG("Packet too big by %zi bytes, UDP header + Payload should be %hu bytes",
1084 diff * -1, udp_len);
1088 if ((version == 4) && conf->verify_udp_checksum) {
1091 expected = fr_udp_checksum((uint8_t const *) udp, ntohs(udp->len), udp->checksum,
1092 ip->ip_src, ip->ip_dst);
1093 if (udp->checksum != expected) {
1094 REDEBUG("UDP checksum invalid, packet: 0x%04hx calculated: 0x%04hx",
1095 ntohs(udp->checksum), ntohs(expected));
1096 /* Not a fatal error */
1099 p += sizeof(udp_header_t);
1102 * With artificial talloc memory limits there's a good chance we can
1103 * recover once some requests timeout, so make an effort to deal
1104 * with allocation failures gracefully.
1106 current = rad_alloc(conf, 0);
1108 REDEBUG("Failed allocating memory to hold decoded packet");
1109 rs_tv_add_ms(&header->ts, conf->stats.timeout, &stats->quiet);
1113 current->timestamp = header->ts;
1114 current->data_len = header->caplen - (p - data);
1115 memcpy(¤t->data, &p, sizeof(current->data));
1118 * Populate IP/UDP fields from PCAP data
1121 current->src_ipaddr.af = AF_INET;
1122 current->src_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_src.s_addr;
1124 current->dst_ipaddr.af = AF_INET;
1125 current->dst_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_dst.s_addr;
1127 current->src_ipaddr.af = AF_INET6;
1128 memcpy(¤t->src_ipaddr.ipaddr.ip6addr.s6_addr, &ip6->ip_src.s6_addr,
1129 sizeof(current->src_ipaddr.ipaddr.ip6addr.s6_addr));
1131 current->dst_ipaddr.af = AF_INET6;
1132 memcpy(¤t->dst_ipaddr.ipaddr.ip6addr.s6_addr, &ip6->ip_dst.s6_addr,
1133 sizeof(current->dst_ipaddr.ipaddr.ip6addr.s6_addr));
1136 current->src_port = ntohs(udp->src);
1137 current->dst_port = ntohs(udp->dst);
1139 if (!rad_packet_ok(current, 0, &reason)) {
1140 REDEBUG("%s", fr_strerror());
1141 if (conf->event_flags & RS_ERROR) {
1142 conf->logger(count, RS_ERROR, event->in, current, &elapsed, NULL, false, false);
1149 switch (current->code) {
1150 case PW_CODE_ACCOUNTING_RESPONSE:
1151 case PW_CODE_ACCESS_REJECT:
1152 case PW_CODE_ACCESS_ACCEPT:
1153 case PW_CODE_ACCESS_CHALLENGE:
1154 case PW_CODE_COA_NAK:
1155 case PW_CODE_COA_ACK:
1156 case PW_CODE_DISCONNECT_NAK:
1157 case PW_CODE_DISCONNECT_ACK:
1158 case PW_CODE_STATUS_CLIENT:
1160 /* look for a matching request and use it for decoding */
1161 search.expect = current;
1162 original = rbtree_finddata(request_tree, &search);
1165 * Verify this code is allowed
1167 if (conf->filter_response_code && (conf->filter_response_code != current->code)) {
1169 RDEBUG2("Response dropped by filter");
1172 /* We now need to cleanup the original request too */
1174 RS_CLEANUP_NOW(original, true);
1180 * Only decode attributes if we want to print them or filter on them
1181 * rad_packet_ok does checks to verify the packet is actually valid.
1183 if (conf->decode_attrs) {
1185 FILE *log_fp = fr_log_fp;
1188 ret = rad_decode(current, original ? original->expect : NULL, conf->radius_secret);
1192 REDEBUG("Failed decoding");
1198 * Check if we've managed to link it to a request
1202 * Now verify the packet passes the attribute filter
1204 if (conf->filter_response_vps) {
1205 pairsort(¤t->vps, attrtagcmp);
1206 if (!pairvalidate_relaxed(NULL, conf->filter_response_vps, current->vps)) {
1212 * Is this a retransmission?
1214 if (original->linked) {
1218 rad_free(&original->linked);
1219 fr_event_delete(event->list, &original->event);
1221 * ...nope it's the first response to a request.
1224 original->stats_rsp = &stats->exchange[current->code];
1228 * Insert a callback to remove the request and response
1229 * from the tree after the timeout period.
1230 * The delay is so we can detect retransmissions.
1232 original->linked = talloc_steal(original, current);
1233 rs_tv_add_ms(&header->ts, conf->stats.timeout, &original->when);
1234 if (!fr_event_insert(event->list, _rs_event, original, &original->when,
1235 &original->event)) {
1236 REDEBUG("Failed inserting new event");
1238 * Delete the original request/event, it's no longer valid
1241 talloc_free(original);
1245 * No request seen, or request was dropped by attribute filter
1249 * If conf->filter_request_vps are set assume the original request was dropped,
1250 * the alternative is maintaining another 'filter', but that adds
1251 * complexity, reduces max capture rate, and is generally a PITA.
1253 if (conf->filter_request) {
1255 RDEBUG2("Original request dropped by filter");
1259 status = RS_UNLINKED;
1260 stats->exchange[current->code].interval.unlinked_total++;
1263 rs_response_to_pcap(event, original, header, data);
1268 case PW_CODE_ACCOUNTING_REQUEST:
1269 case PW_CODE_ACCESS_REQUEST:
1270 case PW_CODE_COA_REQUEST:
1271 case PW_CODE_DISCONNECT_REQUEST:
1272 case PW_CODE_STATUS_SERVER:
1275 * Verify this code is allowed
1277 if (conf->filter_request_code && (conf->filter_request_code != current->code)) {
1280 RDEBUG2("Request dropped by filter");
1287 * Only decode attributes if we want to print them or filter on them
1288 * rad_packet_ok does checks to verify the packet is actually valid.
1290 if (conf->decode_attrs) {
1292 FILE *log_fp = fr_log_fp;
1295 ret = rad_decode(current, NULL, conf->radius_secret);
1300 REDEBUG("Failed decoding");
1304 pairsort(¤t->vps, attrtagcmp);
1308 * Save the request for later matching
1310 search.expect = rad_alloc_reply(current, current);
1311 if (!search.expect) {
1312 REDEBUG("Failed allocating memory to hold expected reply");
1313 rs_tv_add_ms(&header->ts, conf->stats.timeout, &stats->quiet);
1317 search.expect->code = current->code;
1319 if ((conf->link_da_num > 0) && current->vps) {
1321 ret = rs_get_pairs(current, &search.link_vps, current->vps, conf->link_da,
1324 ERROR("Failed extracting RTX linking pairs from request");
1331 * If we have linking attributes set, attempt to find a request in the linking tree.
1333 if (search.link_vps) {
1334 rs_request_t *tuple;
1336 original = rbtree_finddata(link_tree, &search);
1337 tuple = rbtree_finddata(request_tree, &search);
1340 * If the packet we matched using attributes is not the same
1341 * as the packet in the request tree, then we need to clean up
1342 * the packet in the request tree.
1344 if (tuple && (original != tuple)) {
1345 RS_CLEANUP_NOW(tuple, true);
1348 * Detect duplicates using the normal 5-tuple of src/dst ips/ports id
1351 original = rbtree_finddata(request_tree, &search);
1352 if (original && (memcmp(original->expect->vector, current->vector,
1353 sizeof(original->expect->vector)) != 0)) {
1355 * ID reused before the request timed out (which may be an issue)...
1357 if (!original->linked) {
1359 stats->exchange[current->code].interval.reused_total++;
1360 /* Occurs regularly downstream of proxy servers (so don't complain) */
1361 RS_CLEANUP_NOW(original, true);
1363 * ...and before we saw a response (which may be a bigger issue).
1366 RS_CLEANUP_NOW(original, false);
1368 /* else it's a proper RTX with the same src/dst id authenticator/nonce */
1373 * Now verify the packet passes the attribute filter
1375 if (conf->filter_request_vps) {
1376 if (!pairvalidate_relaxed(NULL, conf->filter_request_vps, current->vps)) {
1382 * Is this a retransmission?
1388 rad_free(&original->packet);
1390 /* We may of seen the response, but it may of been lost upstream */
1391 rad_free(&original->linked);
1393 original->packet = talloc_steal(original, current);
1395 /* Request may need to be reinserted as the 5 tuple of the response may of changed */
1396 if (rs_packet_cmp(original, &search) != 0) {
1397 rbtree_deletebydata(request_tree, original);
1400 rad_free(&original->expect);
1401 original->expect = talloc_steal(original, search.expect);
1403 /* Disarm the timer for the cleanup event for the original request */
1404 fr_event_delete(event->list, &original->event);
1406 * ...nope it's a new request.
1409 original = talloc_zero(conf, rs_request_t);
1410 talloc_set_destructor(original, _request_free);
1412 original->id = count;
1413 original->in = event->in;
1414 original->stats_req = &stats->exchange[current->code];
1416 /* Set the packet pointer to the start of the buffer*/
1417 original->capture_p = original->capture;
1419 original->packet = talloc_steal(original, current);
1420 original->expect = talloc_steal(original, search.expect);
1422 if (search.link_vps) {
1423 original->link_vps = pairsteal(original, search.link_vps);
1425 /* We should never have conflicts */
1426 assert(rbtree_insert(link_tree, original));
1427 original->in_link_tree = true;
1431 * Special case for when were filtering by response,
1432 * we never count any requests as lost, because we
1433 * don't know what the response to that request would
1436 if (conf->filter_response_vps) {
1437 original->silent_cleanup = true;
1441 if (!original->in_request_tree) {
1442 /* We should never have conflicts */
1443 assert(rbtree_insert(request_tree, original));
1444 original->in_request_tree = true;
1448 * Insert a callback to remove the request from the tree
1450 original->packet->timestamp = header->ts;
1451 rs_tv_add_ms(&header->ts, conf->stats.timeout, &original->when);
1452 if (!fr_event_insert(event->list, _rs_event, original,
1453 &original->when, &original->event)) {
1454 REDEBUG("Failed inserting new event");
1456 talloc_free(original);
1459 rs_request_to_pcap(event, original, header, data);
1465 REDEBUG("Unsupported code %i", current->code);
1471 rs_tv_sub(&header->ts, &start_pcap, &elapsed);
1474 * Increase received count
1476 stats->exchange[current->code].interval.received_total++;
1479 * It's a linked response
1481 if (original && original->linked) {
1482 rs_tv_sub(¤t->timestamp, &original->packet->timestamp, &latency);
1485 * Update stats for both the request and response types.
1487 * This isn't useful for things like Access-Requests, but will be useful for
1488 * CoA and Disconnect Messages, as we get the average latency across both
1491 * It also justifies allocating PW_CODE_MAX instances of rs_latency_t.
1493 rs_stats_update_latency(&stats->exchange[current->code], &latency);
1494 rs_stats_update_latency(&stats->exchange[original->expect->code], &latency);
1497 * Were filtering on response, now print out the full data from the request
1499 if (conf->filter_response && RIDEBUG_ENABLED() && (conf->event_flags & RS_NORMAL)) {
1500 rs_time_print(timestr, sizeof(timestr), &original->packet->timestamp);
1501 rs_tv_sub(&original->packet->timestamp, &start_pcap, &elapsed);
1502 conf->logger(original->id, RS_NORMAL, original->in, original->packet, &elapsed, NULL, false, true);
1503 rs_tv_sub(&header->ts, &start_pcap, &elapsed);
1504 rs_time_print(timestr, sizeof(timestr), &header->ts);
1507 if (conf->event_flags & status) {
1508 conf->logger(count, status, event->in, current, &elapsed, &latency, response, true);
1511 * It's the original request
1513 * If were filtering on responses we can only indicate we received it on response, or timeout.
1515 } else if (!conf->filter_response && (conf->event_flags & status)) {
1516 conf->logger(original ? original->id : count, status, event->in,
1517 current, &elapsed, NULL, response, true);
1523 * If it's a unlinked response, we need to free it explicitly, as it will
1524 * not be done by the event queue.
1526 if (response && !original) {
1532 * We've hit our capture limit, break out of the event loop
1534 if ((conf->limit > 0) && (captured >= conf->limit)) {
1535 INFO("Captured %" PRIu64 " packets, exiting...", captured);
1536 fr_event_loop_exit(events, 1);
1540 static void rs_got_packet(UNUSED fr_event_list_t *el, int fd, void *ctx)
1542 static uint64_t count = 0; /* Packets seen */
1543 rs_event_t *event = ctx;
1544 pcap_t *handle = event->in->handle;
1548 const uint8_t *data;
1549 struct pcap_pkthdr *header;
1552 * Consume entire capture, interleaving not currently possible
1554 if ((event->in->type == PCAP_FILE_IN) || (event->in->type == PCAP_STDIO_IN)) {
1555 while (!fr_event_loop_exiting(el)) {
1558 ret = pcap_next_ex(handle, &header, &data);
1560 /* No more packets available at this time */
1564 DEBUG("Done reading packets (%s)", event->in->name);
1565 fr_event_fd_delete(events, 0, fd);
1567 /* Signal pipe takes one slot which is why this is == 1 */
1568 if (fr_event_list_num_fds(events) == 1) {
1569 fr_event_loop_exit(events, 1);
1575 ERROR("Error requesting next packet, got (%i): %s", ret, pcap_geterr(handle));
1581 } while (fr_event_run(el, &now) == 1);
1584 rs_packet_process(count, event, header, data);
1590 * Consume multiple packets from the capture buffer.
1591 * We occasionally need to yield to allow events to run.
1593 for (i = 0; i < RS_FORCE_YIELD; i++) {
1594 ret = pcap_next_ex(handle, &header, &data);
1596 /* No more packets available at this time */
1600 ERROR("Error requesting next packet, got (%i): %s", ret, pcap_geterr(handle));
1605 rs_packet_process(count, event, header, data);
1609 static void _rs_event_status(struct timeval *wake)
1611 if (wake && ((wake->tv_sec != 0) || (wake->tv_usec >= 100000))) {
1612 DEBUG2("Waking up in %d.%01u seconds.", (int) wake->tv_sec, (unsigned int) wake->tv_usec / 100000);
1614 if (RIDEBUG_ENABLED()) {
1615 rs_time_print(timestr, sizeof(timestr), wake);
1620 /** Compare requests using packet info and lists of attributes
1623 static int rs_rtx_cmp(rs_request_t const *a, rs_request_t const *b)
1627 assert(a->link_vps);
1628 assert(b->link_vps);
1630 rcode = (int) a->expect->code - (int) b->expect->code;
1631 if (rcode != 0) return rcode;
1633 rcode = a->expect->sockfd - b->expect->sockfd;
1634 if (rcode != 0) return rcode;
1636 rcode = fr_ipaddr_cmp(&a->expect->src_ipaddr, &b->expect->src_ipaddr);
1637 if (rcode != 0) return rcode;
1639 rcode = fr_ipaddr_cmp(&a->expect->dst_ipaddr, &b->expect->dst_ipaddr);
1640 if (rcode != 0) return rcode;
1642 return pairlistcmp(a->link_vps, b->link_vps);
1645 static int rs_build_dict_list(DICT_ATTR const **out, size_t len, char *list)
1651 while ((tok = strsep(&p, "\t ,")) != NULL) {
1652 DICT_ATTR const *da;
1653 if ((*tok == '\t') || (*tok == ' ') || (*tok == '\0')) {
1658 ERROR("Too many attributes, maximum allowed is %zu", len);
1662 da = dict_attrbyname(tok);
1664 ERROR("Error parsing attribute name \"%s\"", tok);
1673 * This allows efficient list comparisons later
1675 if (i > 1) fr_quick_sort((void const **)out, 0, i - 1, fr_pointer_cmp);
1680 static int rs_build_filter(VALUE_PAIR **out, char const *filter)
1686 code = userparse(conf, filter, out);
1687 if (code == T_OP_INVALID) {
1688 ERROR("Invalid RADIUS filter \"%s\" (%s)", filter, fr_strerror());
1693 ERROR("Empty RADIUS filter '%s'", filter);
1697 for (vp = fr_cursor_init(&cursor, out);
1699 vp = fr_cursor_next(&cursor)) {
1701 * xlat expansion isn't support here
1703 if (vp->type == VT_XLAT) {
1705 vp->vp_strvalue = vp->value.xlat;
1706 vp->length = talloc_array_length(vp->vp_strvalue) - 1;
1711 * This allows efficient list comparisons later
1713 pairsort(out, attrtagcmp);
1718 static int rs_build_event_flags(int *flags, FR_NAME_NUMBER const *map, char *list)
1724 while ((tok = strsep(&p, "\t ,")) != NULL) {
1727 if ((*tok == '\t') || (*tok == ' ') || (*tok == '\0')) {
1731 *flags |= flag = fr_str2int(map, tok, -1);
1733 ERROR("Invalid flag \"%s\"", tok);
1743 /** Callback for when the request is removed from the request tree
1745 * @param request being removed.
1747 static void _unmark_request(void *request)
1749 rs_request_t *this = request;
1750 this->in_request_tree = false;
1753 /** Callback for when the request is removed from the link tree
1755 * @param request being removed.
1757 static void _unmark_link(void *request)
1759 rs_request_t *this = request;
1760 this->in_link_tree = false;
1763 #ifdef HAVE_COLLECTDC_H
1764 /** Re-open the collectd socket
1767 static void rs_collectd_reopen(void *ctx)
1769 fr_event_list_t *list = ctx;
1770 static fr_event_t *event;
1771 struct timeval now, when;
1773 if (rs_stats_collectd_open(conf) == 0) {
1774 DEBUG2("Stats output socket (re)opened");
1778 ERROR("Will attempt to re-establish connection in %i ms", RS_SOCKET_REOPEN_DELAY);
1780 gettimeofday(&now, NULL);
1781 rs_tv_add_ms(&now, RS_SOCKET_REOPEN_DELAY, &when);
1782 if (!fr_event_insert(list, rs_collectd_reopen, list, &when, &event)) {
1783 ERROR("Failed inserting re-open event");
1789 /** Write the last signal to the signal pipe
1793 static void rs_signal_self(int sig)
1795 if (write(self_pipe[1], &sig, sizeof(sig)) < 0) {
1796 ERROR("Failed writing signal %s to pipe: %s", strsignal(sig), fr_syserror(errno));
1801 /** Read the last signal from the signal pipe
1804 static void rs_signal_action(UNUSED fr_event_list_t *list, int fd, UNUSED void *ctx)
1809 ret = read(fd, &sig, sizeof(sig));
1811 ERROR("Failed reading signal from pipe: %s", fr_syserror(errno));
1815 if (ret != sizeof(sig)) {
1816 ERROR("Failed reading signal from pipe: "
1817 "Expected signal to be %zu bytes but only read %zu byes", sizeof(sig), ret);
1822 #ifdef HAVE_COLLECTDC_H
1824 rs_collectd_reopen(list);
1831 DEBUG2("Signalling event loop to exit");
1832 fr_event_loop_exit(events, 1);
1836 ERROR("Unhandled signal %s", strsignal(sig));
1841 static void NEVER_RETURNS usage(int status)
1843 FILE *output = status ? stderr : stdout;
1844 fprintf(output, "Usage: radsniff [options][stats options] -- [pcap files]\n");
1845 fprintf(output, "options:\n");
1846 fprintf(output, " -a List all interfaces available for capture.\n");
1847 fprintf(output, " -c <count> Number of packets to capture.\n");
1848 fprintf(output, " -C Enable UDP checksum validation.\n");
1849 fprintf(output, " -d <directory> Set dictionary directory.\n");
1850 fprintf(output, " -d <raddb> Set configuration directory (defaults to " RADDBDIR ").\n");
1851 fprintf(output, " -D <dictdir> Set main dictionary directory (defaults to " DICTDIR ").\n");
1852 fprintf(output, " -e <event>[,<event>] Only log requests with these event flags.\n");
1853 fprintf(output, " Event may be one of the following:\n");
1854 fprintf(output, " - received - a request or response.\n");
1855 fprintf(output, " - norsp - seen for a request.\n");
1856 fprintf(output, " - rtx - of a request that we've seen before.\n");
1857 fprintf(output, " - noreq - could be matched with the response.\n");
1858 fprintf(output, " - reused - ID too soon.\n");
1859 fprintf(output, " - error - decoding the packet.\n");
1860 fprintf(output, " -f <filter> PCAP filter (default is 'udp port <port> or <port + 1> or 3799')\n");
1861 fprintf(output, " -h This help message.\n");
1862 fprintf(output, " -i <interface> Capture packets from interface (defaults to all if supported).\n");
1863 fprintf(output, " -I <file> Read packets from file (overrides input of -F).\n");
1864 fprintf(output, " -l <attr>[,<attr>] Output packet sig and a list of attributes.\n");
1865 fprintf(output, " -L <attr>[,<attr>] Detect retransmissions using these attributes to link requests.\n");
1866 fprintf(output, " -m Don't put interface(s) into promiscuous mode.\n");
1867 fprintf(output, " -p <port> Filter packets by port (default is 1812).\n");
1868 fprintf(output, " -P <pidfile> Daemonize and write out <pidfile>.\n");
1869 fprintf(output, " -q Print less debugging information.\n");
1870 fprintf(output, " -r <filter> RADIUS attribute request filter.\n");
1871 fprintf(output, " -R <filter> RADIUS attribute response filter.\n");
1872 fprintf(output, " -s <secret> RADIUS secret.\n");
1873 fprintf(output, " -S Write PCAP data to stdout.\n");
1874 fprintf(output, " -v Show program version information.\n");
1875 fprintf(output, " -w <file> Write output packets to file.\n");
1876 fprintf(output, " -x Print more debugging information.\n");
1877 fprintf(output, "stats options:\n");
1878 fprintf(output, " -W <interval> Periodically write out statistics every <interval> seconds.\n");
1879 fprintf(output, " -T <timeout> How many milliseconds before the request is counted as lost "
1880 "(defaults to %i).\n", RS_DEFAULT_TIMEOUT);
1881 #ifdef HAVE_COLLECTDC_H
1882 fprintf(output, " -N <prefix> The instance name passed to the collectd plugin.\n");
1883 fprintf(output, " -O <server> Write statistics to this collectd server.\n");
1888 int main(int argc, char *argv[])
1890 fr_pcap_t *in = NULL, *in_p;
1891 fr_pcap_t **in_head = ∈
1892 fr_pcap_t *out = NULL;
1894 int ret = 1; /* Exit status */
1896 char errbuf[PCAP_ERRBUF_SIZE]; /* Error buffer */
1902 char const *radius_dir = RADDBDIR;
1903 char const *dict_dir = DICTDIR;
1911 * Useful if using radsniff as a long running stats daemon
1914 if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
1915 fr_perror("radsniff");
1920 talloc_set_log_stderr();
1922 conf = talloc_zero(NULL, rs_t);
1923 if (!fr_assert(conf)) {
1928 * We don't really want probes taking down machines
1930 #ifdef HAVE_TALLOC_SET_MEMLIMIT
1932 * @fixme causes hang in talloc steal
1934 //talloc_set_memlimit(conf, 524288000); /* 50 MB */
1940 conf->print_packet = true;
1942 conf->promiscuous = true;
1943 #ifdef HAVE_COLLECTDC_H
1944 conf->stats.prefix = RS_DEFAULT_PREFIX;
1946 conf->radius_secret = RS_DEFAULT_SECRET;
1947 conf->logger = rs_packet_print_null;
1949 #ifdef HAVE_COLLECTDC_H
1950 conf->stats.prefix = RS_DEFAULT_PREFIX;
1956 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) {
1960 pcap_if_t *all_devices = NULL;
1963 if (pcap_findalldevs(&all_devices, errbuf) < 0) {
1964 ERROR("Error getting available capture devices: %s", errbuf);
1969 for (dev_p = all_devices;
1971 dev_p = dev_p->next) {
1972 INFO("%i.%s", i++, dev_p->name);
1978 /* super secret option */
1980 conf->buffer_pkts = atoi(optarg);
1981 if (conf->buffer_pkts == 0) {
1982 ERROR("Invalid buffer length \"%s\"", optarg);
1988 conf->limit = atoi(optarg);
1989 if (conf->limit == 0) {
1990 ERROR("Invalid number of packets \"%s\"", optarg);
1997 conf->verify_udp_checksum = true;
2001 radius_dir = optarg;
2009 if (rs_build_event_flags((int *) &conf->event_flags, rs_events, optarg) < 0) {
2015 conf->pcap_filter = optarg;
2023 *in_head = fr_pcap_init(conf, optarg, PCAP_INTERFACE_IN);
2027 in_head = &(*in_head)->next;
2028 conf->from_dev = true;
2032 *in_head = fr_pcap_init(conf, optarg, PCAP_FILE_IN);
2036 in_head = &(*in_head)->next;
2037 conf->from_file = true;
2041 conf->list_attributes = optarg;
2045 conf->link_attributes = optarg;
2049 conf->promiscuous = false;
2053 port = atoi(optarg);
2057 conf->daemonize = true;
2058 conf->pidfile = optarg;
2062 if (fr_debug_flag > 0) {
2068 conf->filter_request = optarg;
2072 conf->filter_response = optarg;
2076 conf->radius_secret = optarg;
2080 conf->to_stdout = true;
2084 #ifdef HAVE_COLLECTDC_H
2085 INFO("%s, %s, collectdclient version %s", radsniff_version, pcap_lib_version(),
2086 lcc_version_string());
2088 INFO("%s %s", radsniff_version, pcap_lib_version());
2094 out = fr_pcap_init(conf, optarg, PCAP_FILE_OUT);
2096 ERROR("Failed creating pcap file \"%s\"", optarg);
2099 conf->to_file = true;
2108 conf->stats.interval = atoi(optarg);
2109 conf->print_packet = false;
2110 if (conf->stats.interval <= 0) {
2111 ERROR("Stats interval must be > 0");
2117 conf->stats.timeout = atoi(optarg);
2118 if (conf->stats.timeout <= 0) {
2119 ERROR("Timeout value must be > 0");
2124 #ifdef HAVE_COLLECTDC_H
2126 conf->stats.prefix = optarg;
2130 conf->stats.collectd = optarg;
2131 conf->stats.out = RS_STATS_OUT_COLLECTD;
2140 * Mismatch between the binary and the libraries it depends on
2142 if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
2143 fr_perror("radsniff");
2147 /* Useful for file globbing */
2148 while (optind < argc) {
2149 *in_head = fr_pcap_init(conf, argv[optind], PCAP_FILE_IN);
2153 in_head = &(*in_head)->next;
2154 conf->from_file = true;
2158 /* Is stdin not a tty? If so it's probably a pipe */
2159 if (!isatty(fileno(stdin))) {
2160 conf->from_stdin = true;
2163 /* What's the point in specifying -F ?! */
2164 if (conf->from_stdin && conf->from_file && conf->to_file) {
2168 /* Can't read from both... */
2169 if (conf->from_file && conf->from_dev) {
2173 /* Reading from file overrides stdin */
2174 if (conf->from_stdin && (conf->from_file || conf->from_dev)) {
2175 conf->from_stdin = false;
2178 /* Writing to file overrides stdout */
2179 if (conf->to_file && conf->to_stdout) {
2180 conf->to_stdout = false;
2183 if (conf->to_stdout) {
2184 out = fr_pcap_init(conf, "stdout", PCAP_STDIO_OUT);
2190 if (conf->from_stdin) {
2191 *in_head = fr_pcap_init(conf, "stdin", PCAP_STDIO_IN);
2195 in_head = &(*in_head)->next;
2198 if (conf->stats.interval && !conf->stats.out) {
2199 conf->stats.out = RS_STATS_OUT_STDIO;
2202 if (conf->stats.timeout == 0) {
2203 conf->stats.timeout = RS_DEFAULT_TIMEOUT;
2207 * If were writing pcap data, or CSV to stdout we *really* don't want to send
2208 * logging there as well.
2210 if (conf->to_stdout || conf->list_attributes) {
2214 if (conf->list_attributes) {
2215 conf->logger = rs_packet_print_csv;
2216 } else if (fr_debug_flag > 0) {
2217 conf->logger = rs_packet_print_fancy;
2220 #if !defined(HAVE_PCAP_FOPEN_OFFLINE) || !defined(HAVE_PCAP_DUMP_FOPEN)
2221 if (conf->from_stdin || conf->to_stdout) {
2222 ERROR("PCAP streams not supported");
2227 if (!conf->pcap_filter) {
2228 snprintf(buffer, sizeof(buffer), "udp port %d or %d or %d",
2229 port, port + 1, 3799);
2230 conf->pcap_filter = buffer;
2233 if (dict_init(dict_dir, RADIUS_DICTIONARY) < 0) {
2234 fr_perror("radsniff");
2239 if (dict_read(radius_dir, RADIUS_DICTIONARY) == -1) {
2240 fr_perror("radsniff");
2245 fr_strerror(); /* Clear out any non-fatal errors */
2247 if (conf->list_attributes) {
2248 conf->list_da_num = rs_build_dict_list(conf->list_da, sizeof(conf->list_da) / sizeof(*conf->list_da),
2249 conf->list_attributes);
2250 if (conf->list_da_num < 0) {
2253 rs_packet_print_csv_header();
2256 if (conf->link_attributes) {
2257 conf->link_da_num = rs_build_dict_list(conf->link_da, sizeof(conf->link_da) / sizeof(*conf->link_da),
2258 conf->link_attributes);
2259 if (conf->link_da_num < 0) {
2263 link_tree = rbtree_create(conf, (rbcmp) rs_rtx_cmp, _unmark_link, 0);
2265 ERROR("Failed creating RTX tree");
2270 if (conf->filter_request) {
2274 if (rs_build_filter(&conf->filter_request_vps, conf->filter_request) < 0) {
2278 fr_cursor_init(&cursor, &conf->filter_request_vps);
2279 type = fr_cursor_next_by_num(&cursor, PW_PACKET_TYPE, 0, TAG_ANY);
2281 fr_cursor_remove(&cursor);
2282 conf->filter_request_code = type->vp_integer;
2287 if (conf->filter_response) {
2291 if (rs_build_filter(&conf->filter_response_vps, conf->filter_response) < 0) {
2295 fr_cursor_init(&cursor, &conf->filter_response_vps);
2296 type = fr_cursor_next_by_num(&cursor, PW_PACKET_TYPE, 0, TAG_ANY);
2298 fr_cursor_remove(&cursor);
2299 conf->filter_response_code = type->vp_integer;
2305 * Default to logging and capturing all events
2307 if (conf->event_flags == 0) {
2308 DEBUG("Logging all events");
2309 memset(&conf->event_flags, 0xff, sizeof(conf->event_flags));
2313 * If we need to list attributes, link requests using attributes, filter attributes
2314 * or print the packet contents, we need to decode the attributes.
2316 * But, if were just logging requests, or graphing packets, we don't need to decode
2319 if (conf->list_da_num || conf->link_da_num || conf->filter_response_vps || conf->filter_request_vps ||
2320 conf->print_packet) {
2321 conf->decode_attrs = true;
2325 * Setup the request tree
2327 request_tree = rbtree_create(conf, (rbcmp) rs_packet_cmp, _unmark_request, 0);
2328 if (!request_tree) {
2329 ERROR("Failed creating request tree");
2334 * Get the default capture device
2336 if (!conf->from_stdin && !conf->from_file && !conf->from_dev) {
2337 pcap_if_t *all_devices; /* List of all devices libpcap can listen on */
2340 if (pcap_findalldevs(&all_devices, errbuf) < 0) {
2341 ERROR("Error getting available capture devices: %s", errbuf);
2346 ERROR("No capture files specified and no live interfaces available");
2351 for (dev_p = all_devices;
2353 dev_p = dev_p->next) {
2354 /* Don't use the any devices, it's horribly broken */
2355 if (!strcmp(dev_p->name, "any")) continue;
2356 *in_head = fr_pcap_init(conf, dev_p->name, PCAP_INTERFACE_IN);
2357 in_head = &(*in_head)->next;
2359 conf->from_auto = true;
2360 conf->from_dev = true;
2361 INFO("Defaulting to capture on all interfaces");
2365 * Print captures values which will be used
2367 if (fr_debug_flag > 2) {
2368 DEBUG2("Sniffing with options:");
2369 if (conf->from_dev) {
2370 char *buff = fr_pcap_device_names(conf, in, ' ');
2371 DEBUG2(" Device(s) : [%s]", buff);
2375 DEBUG2(" Writing to : [%s]", out->name);
2377 if (conf->limit > 0) {
2378 DEBUG2(" Capture limit (packets) : [%" PRIu64 "]", conf->limit);
2380 DEBUG2(" PCAP filter : [%s]", conf->pcap_filter);
2381 DEBUG2(" RADIUS secret : [%s]", conf->radius_secret);
2383 if (conf->filter_request_code) {
2384 DEBUG2(" RADIUS request code : [%s]", fr_packet_codes[conf->filter_request_code]);
2387 if (conf->filter_request_vps){
2388 DEBUG2(" RADIUS request filter :");
2389 vp_printlist(fr_log_fp, conf->filter_request_vps);
2392 if (conf->filter_response_code) {
2393 DEBUG2(" RADIUS response code : [%s]", fr_packet_codes[conf->filter_response_code]);
2396 if (conf->filter_response_vps){
2397 DEBUG2(" RADIUS response filter :");
2398 vp_printlist(fr_log_fp, conf->filter_response_vps);
2403 * Setup collectd templates
2405 #ifdef HAVE_COLLECTDC_H
2406 if (conf->stats.out == RS_STATS_OUT_COLLECTD) {
2408 rs_stats_tmpl_t *tmpl, **next;
2410 if (rs_stats_collectd_open(conf) < 0) {
2414 next = &conf->stats.tmpl;
2416 for (i = 0; i < (sizeof(rs_useful_codes) / sizeof(*rs_useful_codes)); i++) {
2417 tmpl = rs_stats_collectd_init_latency(conf, next, conf, "exchanged",
2418 &stats.exchange[rs_useful_codes[i]],
2419 rs_useful_codes[i]);
2421 ERROR("Error allocating memory for stats template");
2424 next = &(tmpl->next);
2430 * This actually opens the capture interfaces/files (we just allocated the memory earlier)
2434 fr_pcap_t **tmp_p = &tmp;
2438 in_p = in_p->next) {
2439 in_p->promiscuous = conf->promiscuous;
2440 in_p->buffer_pkts = conf->buffer_pkts;
2441 if (fr_pcap_open(in_p) < 0) {
2442 ERROR("Failed opening pcap handle (%s): %s", in_p->name, fr_strerror());
2443 if (conf->from_auto || (in_p->type == PCAP_FILE_IN)) {
2450 if (conf->pcap_filter) {
2451 if (fr_pcap_apply_filter(in_p, conf->pcap_filter) < 0) {
2452 ERROR("Failed applying filter");
2458 tmp_p = &(in_p->next);
2464 ERROR("No PCAP sources available");
2468 /* Clear any irrelevant errors */
2473 * Open our output interface (if we have one);
2476 out->link_type = -1; /* Infer output link type from input */
2480 in_p = in_p->next) {
2481 if (out->link_type < 0) {
2482 out->link_type = in_p->link_type;
2486 if (out->link_type != in_p->link_type) {
2487 ERROR("Asked to write to output file, but inputs do not have the same link type");
2493 assert(out->link_type >= 0);
2495 if (fr_pcap_open(out) < 0) {
2496 ERROR("Failed opening pcap output (%s): %s", out->name, fr_strerror());
2502 * Setup and enter the main event loop. Who needs libev when you can roll your own...
2510 memset(&stats, 0, sizeof(stats));
2511 memset(&update, 0, sizeof(update));
2513 events = fr_event_list_create(conf, _rs_event_status);
2520 * Initialise the signal handler pipe
2522 if (pipe(self_pipe) < 0) {
2523 ERROR("Couldn't open signal pipe: %s", fr_syserror(errno));
2527 if (!fr_event_fd_insert(events, 0, self_pipe[0], rs_signal_action, events)) {
2528 ERROR("Failed inserting signal pipe descriptor: %s", fr_strerror());
2533 * Now add fd's for each of the pcap sessions we opened
2537 in_p = in_p->next) {
2540 event = talloc_zero(events, rs_event_t);
2541 event->list = events;
2544 event->stats = &stats;
2546 if (!fr_event_fd_insert(events, 0, in_p->fd, rs_got_packet, event)) {
2547 ERROR("Failed inserting file descriptor");
2552 buff = fr_pcap_device_names(conf, in, ' ');
2553 DEBUG("Sniffing on (%s)", buff);
2556 gettimeofday(&now, NULL);
2559 * Insert our stats processor
2561 if (conf->stats.interval) {
2562 static fr_event_t *event;
2564 update.list = events;
2565 update.stats = &stats;
2568 now.tv_sec += conf->stats.interval;
2570 if (!fr_event_insert(events, rs_stats_process, (void *) &update, &now, &event)) {
2571 ERROR("Failed inserting stats event");
2574 INFO("Muting stats for the next %i milliseconds (warmup)", conf->stats.timeout);
2575 rs_tv_add_ms(&now, conf->stats.timeout, &stats.quiet);
2581 * Do this as late as possible so we can return an error code if something went wrong.
2583 if (conf->daemonize) {
2584 rs_daemonize(conf->pidfile);
2588 * Setup signal handlers so we always exit gracefully, ensuring output buffers are always
2591 fr_set_signal(SIGPIPE, rs_signal_self);
2592 fr_set_signal(SIGINT, rs_signal_self);
2593 fr_set_signal(SIGTERM, rs_signal_self);
2595 fr_set_signal(SIGQUIT, rs_signal_self);
2598 fr_event_loop(events); /* Enter the main event loop */
2600 DEBUG("Done sniffing");
2607 * Free all the things! This also closes all the sockets and file descriptors
2611 if (conf->daemonize) {
2612 unlink(conf->pidfile);