2 * radsniff.c Display the RADIUS traffic on the network.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2006 The FreeRADIUS server project
21 * Copyright 2006 Nicolas Baradakis <nicolas.baradakis@cegetel.net>
24 #include <freeradius-devel/ident.h>
28 #include <freeradius-devel/libradius.h>
32 #include <freeradius-devel/radpaths.h>
33 #include <freeradius-devel/conf.h>
34 #include <freeradius-devel/radsniff.h>
36 static const char *radius_secret = "testing123";
37 static VALUE_PAIR *filter_vps = NULL;
39 static int do_sort = 0;
40 static int to_stdout = 0;
43 #ifndef PCAP_NETMASK_UNKNOWN
44 # define PCAP_NETMASK_UNKNOWN 0
48 #define DEBUG1 if (fr_debug_flag > 2) fprintf
50 #define DEBUG if (fr_debug_flag > 1) fprintf
52 #define INFO if (fr_debug_flag > 0) fprintf
54 struct timeval start_pcap = {0, 0};
55 static rbtree_t *filter_tree = NULL;
56 static rbtree_t *request_tree = NULL;
57 static pcap_dumper_t *out = NULL;
58 static RADIUS_PACKET *nullpacket = NULL;
60 typedef int (*rbcmp)(const void *, const void *);
62 static const char *radsniff_version = "radsniff version " RADIUSD_VERSION_STRING
63 #ifdef RADIUSD_VERSION_COMMIT
64 " (git #" RADIUSD_VERSION_COMMIT ")"
66 ", built on " __DATE__ " at " __TIME__;
68 static int filter_packet(RADIUS_PACKET *packet)
70 VALUE_PAIR *check_item;
72 unsigned int pass, fail;
76 for (vp = packet->vps; vp != NULL; vp = vp->next) {
77 for (check_item = filter_vps;
79 check_item = check_item->next)
80 if ((check_item->da == vp->da)
81 && (check_item->op != T_OP_SET)) {
82 compare = paircmp(check_item, vp);
90 if (fail == 0 && pass != 0) {
92 * Cache authentication requests, as the replies
93 * may not match the RADIUS filter.
95 if ((packet->code == PW_AUTHENTICATION_REQUEST) ||
96 (packet->code == PW_ACCOUNTING_REQUEST)) {
97 rbtree_deletebydata(filter_tree, packet);
99 if (!rbtree_insert(filter_tree, packet)) {
101 fprintf(stderr, "radsniff: Out of memory\n");
105 return 0; /* matched */
109 * Don't create erroneous matches.
111 if ((packet->code == PW_AUTHENTICATION_REQUEST) ||
112 (packet->code == PW_ACCOUNTING_REQUEST)) {
113 rbtree_deletebydata(filter_tree, packet);
118 * Else see if a previous Access-Request
119 * matched. If so, also print out the
120 * matching accept, reject, or challenge.
122 if ((packet->code == PW_AUTHENTICATION_ACK) ||
123 (packet->code == PW_AUTHENTICATION_REJECT) ||
124 (packet->code == PW_ACCESS_CHALLENGE) ||
125 (packet->code == PW_ACCOUNTING_RESPONSE)) {
126 RADIUS_PACKET *reply;
129 * This swaps the various fields.
131 reply = rad_alloc_reply(packet);
132 if (!reply) goto oom;
135 if (rbtree_finddata(filter_tree, reply)) {
149 static void sort(RADIUS_PACKET *packet)
152 VALUE_PAIR *vp, *tmp;
153 VALUE_PAIR *array[1024]; /* way more than necessary */
156 for (vp = packet->vps; vp != NULL; vp = vp->next) {
160 if (size == 0) return;
162 for (i = 0; i < size - 1; i++) {
163 for (j = 0; j < size - 1 - i; j++) {
164 if (array[j + 1]->da->attr < array[j]->da->attr) {
166 array[j] = array[j + 1];
173 * And put them back again.
175 vp = packet->vps = array[0];
176 for (i = 1; i < size; i++) {
184 static void tv_sub(const struct timeval *end, const struct timeval *start,
185 struct timeval *elapsed)
187 elapsed->tv_sec = end->tv_sec - start->tv_sec;
188 if (elapsed->tv_sec > 0) {
190 elapsed->tv_usec = USEC;
192 elapsed->tv_usec = 0;
194 elapsed->tv_usec += end->tv_usec;
195 elapsed->tv_usec -= start->tv_usec;
197 if (elapsed->tv_usec >= USEC) {
198 elapsed->tv_usec -= USEC;
203 static void got_packet(UNUSED uint8_t *args, const struct pcap_pkthdr *header, const uint8_t *data)
206 static int count = 1; /* Packets seen */
209 * Define pointers for packet's attributes
211 const struct ethernet_header *ethernet; /* The ethernet header */
212 const struct ip_header *ip; /* The IP header */
213 const struct udp_header *udp; /* The UDP header */
214 const uint8_t *payload; /* Packet payload */
217 * And define the size of the structures we're using
219 int size_ethernet = sizeof(struct ethernet_header);
220 int size_ip = sizeof(struct ip_header);
221 int size_udp = sizeof(struct udp_header);
226 RADIUS_PACKET *packet, *original;
227 struct timeval elapsed;
230 * Define our packet's attributes
233 if ((data[0] == 2) && (data[1] == 0) &&
234 (data[2] == 0) && (data[3] == 0)) {
235 ip = (const struct ip_header*) (data + 4);
238 ethernet = (const struct ethernet_header*)(data);
239 ip = (const struct ip_header*)(data + size_ethernet);
242 udp = (const struct udp_header*)(((const uint8_t *) ip) + size_ip);
243 payload = (const uint8_t *)(((const uint8_t *) udp) + size_udp);
245 packet = malloc(sizeof(*packet));
247 fprintf(stderr, "Out of memory\n");
251 memset(packet, 0, sizeof(*packet));
252 packet->src_ipaddr.af = AF_INET;
253 packet->src_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_src.s_addr;
254 packet->src_port = ntohs(udp->udp_sport);
255 packet->dst_ipaddr.af = AF_INET;
256 packet->dst_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_dst.s_addr;
257 packet->dst_port = ntohs(udp->udp_dport);
259 memcpy(&packet->data, &payload, sizeof(packet->data));
260 packet->data_len = header->len - (payload - data);
262 if (!rad_packet_ok(packet, 0)) {
263 DEBUG(log_dst, "Packet: %s\n", fr_strerror());
265 DEBUG(log_dst, " From %s:%d\n", inet_ntoa(ip->ip_src), ntohs(udp->udp_sport));
266 DEBUG(log_dst, " To: %s:%d\n", inet_ntoa(ip->ip_dst), ntohs(udp->udp_dport));
267 DEBUG(log_dst, " Type: %s\n", fr_packet_codes[packet->code]);
273 switch (packet->code) {
275 /* we need a 16 x 0 byte vector for decrypting encrypted VSAs */
276 original = nullpacket;
278 case PW_AUTHENTICATION_ACK:
279 /* look for a matching request and use it for decoding */
280 original = rbtree_finddata(request_tree, packet);
282 case PW_AUTHENTICATION_REQUEST:
283 /* save the request for later matching */
284 original = rad_alloc_reply(packet);
285 if (original) { /* just ignore allocation failures */
286 rbtree_deletebydata(request_tree, original);
287 rbtree_insert(request_tree, original);
291 /* don't attempt to decode any encrypted attributes */
296 * Decode the data without bothering to check the signatures.
298 if (rad_decode(packet, original, radius_secret) != 0) {
305 * We've seen a successfull reply to this, so delete it now
308 rbtree_deletebydata(request_tree, original);
310 if (filter_vps && filter_packet(packet)) {
312 DEBUG(log_dst, "Packet number %d doesn't match\n", count++);
317 pcap_dump((void *) out, header, data);
321 INFO(log_dst, "%s Id %d\t", fr_packet_codes[packet->code], packet->id);
324 * Print the RADIUS packet
326 INFO(log_dst, "%s:%d -> ", inet_ntoa(ip->ip_src), ntohs(udp->udp_sport));
327 INFO(log_dst, "%s:%d", inet_ntoa(ip->ip_dst), ntohs(udp->udp_dport));
329 DEBUG1(log_dst, "\t(%d packets)", count++);
331 if (!start_pcap.tv_sec) {
332 start_pcap = header->ts;
335 tv_sub(&header->ts, &start_pcap, &elapsed);
337 INFO(log_dst, "\t+%u.%03u", (unsigned int) elapsed.tv_sec,
338 (unsigned int) elapsed.tv_usec / 1000);
340 if (fr_debug_flag > 1) {
341 DEBUG(log_dst, "\n");
343 if (do_sort) sort(packet);
345 vp_printlist(log_dst, packet->vps);
346 pairfree(&packet->vps);
352 if (!to_stdout && (fr_debug_flag > 4)) {
353 rad_print_hex(packet);
360 * If we're doing filtering, Access-Requests are cached in the
364 ((packet->code != PW_AUTHENTICATION_REQUEST) &&
365 (packet->code != PW_ACCOUNTING_REQUEST))) {
370 static void NEVER_RETURNS usage(int status)
372 FILE *output = status ? stderr : stdout;
373 fprintf(output, "Usage: radsniff [options]\n");
374 fprintf(output, "options:\n");
375 fprintf(output, " -c <count> Number of packets to capture.\n");
376 fprintf(output, " -d <directory> Set dictionary directory.\n");
377 fprintf(output, " -F Filter PCAP file from stdin to stdout.\n");
378 fprintf(output, " -f <filter> PCAP filter (default is 'udp port <port> or <port + 1> or 3799')\n");
379 fprintf(output, " -h This help message.\n");
380 fprintf(output, " -i <interface> Capture packets from interface (defaults to any if supported).\n");
381 fprintf(output, " -I <file> Read packets from file (overrides input of -F).\n");
382 fprintf(output, " -p <port> Filter packets by port (default is 1812).\n");
383 fprintf(output, " -q Print less debugging information.\n");
384 fprintf(output, " -r <filter> RADIUS attribute filter.\n");
385 fprintf(output, " -s <secret> RADIUS secret.\n");
386 fprintf(output, " -S Sort attributes in the packet (useful for diffing responses).\n");
387 fprintf(output, " -v Show program version information.\n");
388 fprintf(output, " -w <file> Write output packets to file (overrides output of -F).\n");
389 fprintf(output, " -x Print more debugging information (defaults to -xx).\n");
393 int main(int argc, char *argv[])
395 const char *from_dev = NULL; /* Capture from device */
396 const char *from_file = NULL; /* Read from pcap file */
397 int from_stdin = 0; /* Read from stdin */
399 pcap_t *in; /* PCAP input handle */
401 int limit = -1; /* How many packets to sniff */
403 char errbuf[PCAP_ERRBUF_SIZE]; /* Error buffer */
405 char *to_file = NULL; /* PCAP output file */
407 char *pcap_filter = NULL; /* PCAP filter string */
408 char *radius_filter = NULL;
411 struct bpf_program fp; /* Holds compiled filter */
412 bpf_u_int32 ip_mask = PCAP_NETMASK_UNKNOWN; /* Device Subnet mask */
413 bpf_u_int32 ip_addr = 0; /* Device IP */
419 const char *radius_dir = RADIUS_DIR;
427 while ((opt = getopt(argc, argv, "c:d:Ff:hi:I:p:qr:s:Svw:xX")) != EOF) {
431 limit = atoi(optarg);
433 fprintf(stderr, "radsniff: Invalid number of packets \"%s\"\n", optarg);
445 pcap_filter = optarg;
460 if (fr_debug_flag > 0) {
465 radius_filter = optarg;
468 radius_secret = optarg;
474 INFO(log_dst, "%s %s\n", radsniff_version, pcap_lib_version());
489 /* What's the point in specifying -F ?! */
490 if (from_stdin && from_file && to_file) {
494 /* Can't read from both... */
495 if (from_file && from_dev) {
499 /* Reading from file overrides stdin */
500 if (from_stdin && (from_file || from_dev)) {
504 /* Writing to file overrides stdout */
505 if (to_file && to_stdout) {
510 * If were writing pcap data stdout we *really* don't want to send
511 * logging there as well.
513 log_dst = to_stdout ? stderr : stdout;
515 #if !defined(HAVE_PCAP_FOPEN_OFFLINE) || !defined(HAVE_PCAP_DUMP_FOPEN)
516 if (from_stdin || to_stdout) {
517 fprintf(stderr, "radsniff: PCAP streams not supported.\n");
523 pcap_filter = buffer;
524 snprintf(buffer, sizeof(buffer), "udp port %d or %d or %d",
525 port, port + 1, 3799);
529 * There are times when we don't need the dictionaries.
532 if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
533 fr_perror("radsniff");
539 parsecode = userparse(radius_filter, &filter_vps);
540 if (parsecode == T_OP_INVALID) {
541 fprintf(stderr, "radsniff: Invalid RADIUS filter \"%s\" (%s)\n", radius_filter, fr_strerror());
546 fprintf(stderr, "radsniff: Empty RADIUS filter \"%s\"\n", radius_filter);
550 filter_tree = rbtree_create((rbcmp) fr_packet_cmp, free, 0);
552 fprintf(stderr, "radsniff: Failed creating filter tree\n");
558 * Setup the request tree
560 request_tree = rbtree_create((rbcmp) fr_packet_cmp, free, 0);
562 fprintf(stderr, "radsniff: Failed creating request tree\n");
567 * Allocate a null packet for decrypting attributes in CoA requests
569 nullpacket = rad_alloc(0);
571 fprintf(stderr, "radsniff: Out of memory\n");
576 * Get the default capture device
578 if (!from_stdin && !from_file && !from_dev) {
579 from_dev = pcap_lookupdev(errbuf);
581 fprintf(stderr, "radsniff: Failed discovering default interface (%s)\n", errbuf);
585 INFO(log_dst, "Capturing from interface \"%s\"\n", from_dev);
589 * Print captures values which will be used
591 if (fr_debug_flag > 2) {
592 DEBUG1(log_dst, "Sniffing with options:\n");
593 if (from_dev) DEBUG1(log_dst, " Device : [%s]\n", from_dev);
594 if (limit > 0) DEBUG1(log_dst, " Capture limit (packets) : [%d]\n", limit);
595 DEBUG1(log_dst, " PCAP filter : [%s]\n", pcap_filter);
596 DEBUG1(log_dst, " RADIUS secret : [%s]\n", radius_secret);
597 if (filter_vps){DEBUG1(log_dst, " RADIUS filter :\n");
598 vp_printlist(log_dst, filter_vps);
603 * Figure out whether were doing a reading from a file, doing a live
604 * capture or reading from stdin.
607 in = pcap_open_offline(from_file, errbuf);
608 #ifdef HAVE_PCAP_FOPEN_OFFLINE
609 } else if (from_stdin) {
610 in = pcap_fopen_offline(stdin, errbuf);
612 } else if (from_dev) {
613 pcap_lookupnet(from_dev, &ip_addr, &ip_mask, errbuf);
614 in = pcap_open_live(from_dev, 65536, 1, 1, errbuf);
616 fprintf(stderr, "radsniff: No capture devices available\n");
620 fprintf(stderr, "radsniff: Failed opening input (%s)\n", errbuf);
625 out = pcap_dump_open(in, to_file);
627 fprintf(stderr, "radsniff: Failed opening output file (%s)\n", pcap_geterr(in));
630 #ifdef HAVE_PCAP_DUMP_FOPEN
631 } else if (to_stdout) {
632 out = pcap_dump_fopen(in, stdout);
634 fprintf(stderr, "radsniff: Failed opening stdout (%s)\n", pcap_geterr(in));
643 if (pcap_compile(in, &fp, pcap_filter, 0, ip_mask) < 0) {
644 fprintf(stderr, "radsniff: Failed compiling PCAP filter (%s)\n", pcap_geterr(in));
648 if (pcap_setfilter(in, &fp) < 0) {
649 fprintf(stderr, "radsniff: Failed applying PCAP filter (%s)\n", pcap_geterr(in));
654 * Enter the main capture loop...
656 pcap_loop(in, limit, got_packet, NULL);
659 * ...were done capturing.
663 pcap_dump_close(out);
667 rbtree_free(filter_tree);
670 INFO(log_dst, "Done sniffing\n");