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