tcpprep.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. /* $Id$ */
  2. /*
  3. * Copyright (c) 2001-2010 Aaron Turner <aturner at synfin dot net>
  4. * Copyright (c) 2013-2018 Fred Klassen <tcpreplay at appneta dot com> - AppNeta
  5. *
  6. * The Tcpreplay Suite of tools is free software: you can redistribute it
  7. * and/or modify it under the terms of the GNU General Public License as
  8. * published by the Free Software Foundation, either version 3 of the
  9. * License, or with the authors permission any later version.
  10. *
  11. * The Tcpreplay Suite is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with the Tcpreplay Suite. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. /*
  20. * Purpose:
  21. * 1) Remove the performance bottleneck in tcpreplay for choosing an NIC
  22. * 2) Separate code to make it more manageable
  23. * 3) Add additional features which require multiple passes of a pcap
  24. *
  25. * Support:
  26. * Right now we support matching source IP based upon on of the following:
  27. * - Regular expression
  28. * - IP address is contained in one of a list of CIDR blocks
  29. * - Auto learning of CIDR block for servers (clients all other)
  30. */
  31. #include "config.h"
  32. #include "defines.h"
  33. #include "common.h"
  34. #include <stdio.h>
  35. #include <stdlib.h>
  36. #include <string.h>
  37. #include <regex.h>
  38. #include <string.h>
  39. #include <unistd.h>
  40. #include <errno.h>
  41. #include "tcpprep.h"
  42. #include "tcpprep_api.h"
  43. #include "tcpprep_opts.h"
  44. #include "lib/tree.h"
  45. #include "tree.h"
  46. #include "lib/sll.h"
  47. #ifndef HAVE_STRLCPY
  48. #include "lib/strlcpy.h"
  49. #endif
  50. /*
  51. * global variables
  52. */
  53. #ifdef DEBUG
  54. int debug = 0;
  55. #endif
  56. tcpprep_t *tcpprep;
  57. int info = 0;
  58. char *ourregex = NULL;
  59. char *cidr = NULL;
  60. tcpr_data_tree_t treeroot;
  61. void print_comment(const char *);
  62. void print_info(const char *);
  63. void print_stats(const char *);
  64. static int check_ipv4_regex(const unsigned long ip);
  65. static int check_ipv6_regex(const struct tcpr_in6_addr *addr);
  66. static COUNTER process_raw_packets(pcap_t * pcap);
  67. static int check_dst_port(ipv4_hdr_t *ip_hdr, ipv6_hdr_t *ip6_hdr, int len);
  68. /*
  69. * main()
  70. */
  71. int
  72. main(int argc, char *argv[])
  73. {
  74. int out_file;
  75. COUNTER totpackets = 0;
  76. char errbuf[PCAP_ERRBUF_SIZE];
  77. tcpprep_opt_t *options;
  78. tcpprep = tcpprep_init();
  79. options = tcpprep->options;
  80. optionProcess(&tcpprepOptions, argc, argv);
  81. tcpprep_post_args(tcpprep, argc, argv);
  82. /* open the cache file */
  83. if ((out_file = open(OPT_ARG(CACHEFILE), O_WRONLY | O_CREAT | O_TRUNC,
  84. S_IREAD | S_IWRITE | S_IRGRP | S_IWGRP | S_IROTH)) == -1)
  85. errx(-1, "Unable to open cache file %s for writing: %s",
  86. OPT_ARG(CACHEFILE), strerror(errno));
  87. readpcap:
  88. /* open the pcap file */
  89. if ((options->pcap = pcap_open_offline(OPT_ARG(PCAP), errbuf)) == NULL)
  90. errx(-1, "Error opening file: %s", errbuf);
  91. #ifdef HAVE_PCAP_SNAPSHOT
  92. if (pcap_snapshot(options->pcap) < 65535)
  93. warnx("%s was captured using a snaplen of %d bytes. This may mean you have truncated packets.",
  94. OPT_ARG(PCAP), pcap_snapshot(options->pcap));
  95. #endif
  96. /* make sure we support the DLT type */
  97. switch(pcap_datalink(options->pcap)) {
  98. case DLT_EN10MB:
  99. case DLT_LINUX_SLL:
  100. case DLT_RAW:
  101. case DLT_C_HDLC:
  102. case DLT_JUNIPER_ETHER:
  103. case DLT_PPP_SERIAL:
  104. break; /* do nothing because all is good */
  105. default:
  106. errx(-1, "Unsupported pcap DLT type: 0x%x", pcap_datalink(options->pcap));
  107. }
  108. /* Can only split based on MAC address for ethernet */
  109. if ((pcap_datalink(options->pcap) != DLT_EN10MB) &&
  110. (options->mode == MAC_MODE)) {
  111. err(-1, "MAC mode splitting is only supported by DLT_EN10MB packet captures.");
  112. }
  113. #ifdef ENABLE_VERBOSE
  114. if (HAVE_OPT(VERBOSE)) {
  115. tcpdump_open(&tcpprep->tcpdump, options->pcap);
  116. }
  117. #endif
  118. /* do we apply a bpf filter? */
  119. if (options->bpf.filter != NULL) {
  120. if (pcap_compile(options->pcap, &options->bpf.program, options->bpf.filter,
  121. options->bpf.optimize, 0) != 0) {
  122. errx(-1, "Error compiling BPF filter: %s", pcap_geterr(options->pcap));
  123. }
  124. pcap_setfilter(options->pcap, &options->bpf.program);
  125. }
  126. if ((totpackets = process_raw_packets(options->pcap)) == 0) {
  127. pcap_close(options->pcap);
  128. err(-1, "No packets were processed. Filter too limiting?");
  129. }
  130. pcap_close(options->pcap);
  131. #ifdef ENABLE_VERBOSE
  132. tcpdump_close(&tcpprep->tcpdump);
  133. #endif
  134. /* we need to process the pcap file twice in HASH/AUTO mode */
  135. if (options->mode == AUTO_MODE) {
  136. options->mode = options->automode;
  137. if (options->mode == ROUTER_MODE) { /* do we need to convert TREE->CIDR? */
  138. if (info)
  139. notice("Building network list from pre-cache...\n");
  140. if (!process_tree()) {
  141. err(-1, "Error: unable to build a valid list of servers. Aborting.");
  142. }
  143. }
  144. else {
  145. /*
  146. * in bridge mode we need to calculate client/sever
  147. * manually since this is done automatically in
  148. * process_tree()
  149. */
  150. tree_calculate(&treeroot);
  151. }
  152. if (info)
  153. notice("Building cache file...\n");
  154. /*
  155. * re-process files, but this time generate
  156. * cache
  157. */
  158. goto readpcap;
  159. }
  160. #ifdef DEBUG
  161. if (debug && (options->cidrdata != NULL))
  162. print_cidr(options->cidrdata);
  163. #endif
  164. /* write cache data */
  165. totpackets = write_cache(options->cachedata, out_file, totpackets,
  166. options->comment);
  167. if (info)
  168. notice("Done.\nCached " COUNTER_SPEC " packets.\n", totpackets);
  169. /* close cache file */
  170. close(out_file);
  171. restore_stdin();
  172. return 0;
  173. }
  174. /**
  175. * checks the dst port to see if this is destined for a server port.
  176. * returns 1 for true, 0 for false
  177. */
  178. static int
  179. check_dst_port(ipv4_hdr_t *ip_hdr, ipv6_hdr_t *ip6_hdr, int len)
  180. {
  181. tcp_hdr_t *tcp_hdr = NULL;
  182. udp_hdr_t *udp_hdr = NULL;
  183. tcpprep_opt_t *options = tcpprep->options;
  184. uint8_t proto;
  185. u_char *l4;
  186. if (ip_hdr) {
  187. if (len < ((ip_hdr->ip_hl * 4) + 4))
  188. return 0; /* not enough data in the packet to know */
  189. proto = ip_hdr->ip_p;
  190. l4 = get_layer4_v4(ip_hdr, len);
  191. } else if (ip6_hdr) {
  192. if (len < (TCPR_IPV6_H + 4))
  193. return 0; /* not enough data in the packet to know */
  194. proto = get_ipv6_l4proto(ip6_hdr, len);
  195. dbgx(3, "Our layer4 proto is 0x%hhu", proto);
  196. if ((l4 = get_layer4_v6(ip6_hdr, len)) == NULL)
  197. return 0;
  198. dbgx(3, "Found proto %u at offset %p. base %p (%p)", proto, (void *)l4, (void *)ip6_hdr, (void*)(l4 - (u_char *)ip6_hdr));
  199. } else {
  200. assert(0);
  201. }
  202. dbg(3, "Checking the destination port...");
  203. switch(proto) {
  204. case IPPROTO_TCP:
  205. tcp_hdr = (tcp_hdr_t *)l4;
  206. /* is a service? */
  207. if (options->services.tcp[ntohs(tcp_hdr->th_dport)]) {
  208. dbgx(1, "TCP packet is destined for a server port: %d", ntohs(tcp_hdr->th_dport));
  209. return 1;
  210. }
  211. /* nope */
  212. dbgx(1, "TCP packet is NOT destined for a server port: %d", ntohs(tcp_hdr->th_dport));
  213. return 0;
  214. break;
  215. case IPPROTO_UDP:
  216. udp_hdr = (udp_hdr_t *)l4;
  217. /* is a service? */
  218. if (options->services.udp[ntohs(udp_hdr->uh_dport)]) {
  219. dbgx(1, "UDP packet is destined for a server port: %d", ntohs(udp_hdr->uh_dport));
  220. return 1;
  221. }
  222. /* nope */
  223. dbgx(1, "UDP packet is NOT destined for a server port: %d", ntohs(udp_hdr->uh_dport));
  224. return 0;
  225. break;
  226. default:
  227. /* not a TCP or UDP packet... return as non_ip */
  228. dbg(1, "Packet isn't a UDP or TCP packet... no port to process.");
  229. return options->nonip;
  230. }
  231. }
  232. /**
  233. * checks to see if an ip address matches a regex. Returns 1 for true
  234. * 0 for false
  235. */
  236. static int
  237. check_ipv4_regex(const unsigned long ip)
  238. {
  239. int eflags = 0;
  240. u_char src_ip[16];
  241. size_t nmatch = 0;
  242. tcpprep_opt_t *options = tcpprep->options;
  243. memset(src_ip, '\0', sizeof(src_ip));
  244. strlcpy((char *)src_ip, (char *)get_addr2name4(ip, RESOLVE),
  245. sizeof(src_ip));
  246. if (regexec(&options->preg, (char *)src_ip, nmatch, NULL, eflags) == 0) {
  247. return 1;
  248. } else {
  249. return 0;
  250. }
  251. }
  252. static int
  253. check_ipv6_regex(const struct tcpr_in6_addr *addr)
  254. {
  255. int eflags = 0;
  256. u_char src_ip[INET6_ADDRSTRLEN];
  257. size_t nmatch = 0;
  258. tcpprep_opt_t *options = tcpprep->options;
  259. memset(src_ip, '\0', sizeof(src_ip));
  260. strlcpy((char *)src_ip, (char *)get_addr2name6(addr, RESOLVE),
  261. sizeof(src_ip));
  262. if (regexec(&options->preg, (char *)src_ip, nmatch, NULL, eflags) == 0) {
  263. return 1;
  264. } else {
  265. return 0;
  266. }
  267. }
  268. /**
  269. * uses libpcap library to parse the packets and build
  270. * the cache file.
  271. */
  272. static COUNTER
  273. process_raw_packets(pcap_t * pcap)
  274. {
  275. ipv4_hdr_t *ip_hdr = NULL;
  276. ipv6_hdr_t *ip6_hdr = NULL;
  277. eth_hdr_t *eth_hdr = NULL;
  278. struct pcap_pkthdr pkthdr;
  279. const u_char *pktdata = NULL;
  280. COUNTER packetnum = 0;
  281. int l2len;
  282. u_char ipbuff[MAXPACKET], *buffptr;
  283. tcpr_dir_t direction = TCPR_DIR_ERROR;
  284. tcpprep_opt_t *options = tcpprep->options;
  285. assert(pcap);
  286. while ((pktdata = safe_pcap_next(pcap, &pkthdr)) != NULL) {
  287. packetnum++;
  288. dbgx(1, "Packet " COUNTER_SPEC, packetnum);
  289. /* look for include or exclude LIST match */
  290. if (options->xX.list != NULL) {
  291. if (options->xX.mode < xXExclude) {
  292. /* include list */
  293. if (!check_list(options->xX.list, packetnum)) {
  294. add_cache(&(options->cachedata), DONT_SEND, 0);
  295. continue;
  296. }
  297. }
  298. /* exclude list */
  299. else if (check_list(options->xX.list, packetnum)) {
  300. add_cache(&(options->cachedata), DONT_SEND, 0);
  301. continue;
  302. }
  303. }
  304. /*
  305. * If the packet doesn't include an IPv4 header we should just treat
  306. * it as a non-IP packet, UNLESS we're in MAC mode, in which case
  307. * we should let the MAC matcher below handle it
  308. */
  309. if (options->mode != MAC_MODE) {
  310. dbg(3, "Looking for IPv4/v6 header in non-MAC mode");
  311. /* get the IP header (if any) */
  312. buffptr = ipbuff;
  313. /* first look for IPv4 */
  314. if ((ip_hdr = (ipv4_hdr_t *)get_ipv4(pktdata, pkthdr.caplen,
  315. pcap_datalink(pcap), &buffptr))) {
  316. dbg(2, "Packet is IPv4");
  317. } else if ((ip6_hdr = (ipv6_hdr_t *)get_ipv6(pktdata, pkthdr.caplen,
  318. pcap_datalink(pcap), &buffptr))) {
  319. /* IPv6 */
  320. dbg(2, "Packet is IPv6");
  321. } else {
  322. /* we're something else... */
  323. dbg(2, "Packet isn't IPv4/v6");
  324. /* we don't want to cache these packets twice */
  325. if (options->mode != AUTO_MODE) {
  326. dbg(3, "Adding to cache using options for Non-IP packets");
  327. add_cache(&options->cachedata, SEND, options->nonip);
  328. }
  329. /* go to next packet */
  330. continue;
  331. }
  332. l2len = get_l2len(pktdata, pkthdr.caplen, pcap_datalink(pcap));
  333. if (l2len < 0) {
  334. /* go to next packet */
  335. continue;
  336. }
  337. /* look for include or exclude CIDR match */
  338. if (options->xX.cidr != NULL) {
  339. if (ip_hdr) {
  340. if (!process_xX_by_cidr_ipv4(options->xX.mode, options->xX.cidr, ip_hdr)) {
  341. add_cache(&options->cachedata, DONT_SEND, 0);
  342. continue;
  343. }
  344. } else if (ip6_hdr) {
  345. if (!process_xX_by_cidr_ipv6(options->xX.mode, options->xX.cidr, ip6_hdr)) {
  346. add_cache(&options->cachedata, DONT_SEND, 0);
  347. continue;
  348. }
  349. }
  350. }
  351. }
  352. switch (options->mode) {
  353. case REGEX_MODE:
  354. dbg(2, "processing regex mode...");
  355. if (ip_hdr) {
  356. direction = check_ipv4_regex(ip_hdr->ip_src.s_addr);
  357. } else if (ip6_hdr) {
  358. direction = check_ipv6_regex(&ip6_hdr->ip_src);
  359. }
  360. /* reverse direction? */
  361. if (HAVE_OPT(REVERSE) && (direction == TCPR_DIR_C2S || direction == TCPR_DIR_S2C))
  362. direction = direction == TCPR_DIR_C2S ? TCPR_DIR_S2C : TCPR_DIR_C2S;
  363. add_cache(&options->cachedata, SEND, direction);
  364. break;
  365. case CIDR_MODE:
  366. dbg(2, "processing cidr mode...");
  367. if (ip_hdr) {
  368. direction = check_ip_cidr(options->cidrdata, ip_hdr->ip_src.s_addr) ? TCPR_DIR_C2S : TCPR_DIR_S2C;
  369. } else if (ip6_hdr) {
  370. direction = check_ip6_cidr(options->cidrdata, &ip6_hdr->ip_src) ? TCPR_DIR_C2S : TCPR_DIR_S2C;
  371. }
  372. /* reverse direction? */
  373. if (HAVE_OPT(REVERSE) && (direction == TCPR_DIR_C2S || direction == TCPR_DIR_S2C))
  374. direction = direction == TCPR_DIR_C2S ? TCPR_DIR_S2C : TCPR_DIR_C2S;
  375. add_cache(&options->cachedata, SEND, direction);
  376. break;
  377. case MAC_MODE:
  378. dbg(2, "processing mac mode...");
  379. if (pkthdr.caplen < sizeof(*eth_hdr)) {
  380. dbg(2, "capture length too short for mac mode processing");
  381. break;
  382. }
  383. eth_hdr = (eth_hdr_t *)pktdata;
  384. direction = macinstring(options->maclist, (u_char *)eth_hdr->ether_shost);
  385. /* reverse direction? */
  386. if (HAVE_OPT(REVERSE) && (direction == TCPR_DIR_C2S || direction == TCPR_DIR_S2C))
  387. direction = direction == TCPR_DIR_C2S ? TCPR_DIR_S2C : TCPR_DIR_C2S;
  388. add_cache(&options->cachedata, SEND, direction);
  389. break;
  390. case AUTO_MODE:
  391. dbg(2, "processing first pass of auto mode...");
  392. /* first run through in auto mode: create tree */
  393. if (options->automode != FIRST_MODE) {
  394. if (ip_hdr) {
  395. add_tree_ipv4(ip_hdr->ip_src.s_addr, pktdata, pkthdr.caplen);
  396. } else if (ip6_hdr) {
  397. add_tree_ipv6(&ip6_hdr->ip_src, pktdata, pkthdr.caplen);
  398. }
  399. } else {
  400. if (ip_hdr) {
  401. add_tree_first_ipv4(pktdata, pkthdr.caplen);
  402. } else if (ip6_hdr) {
  403. add_tree_first_ipv6(pktdata, pkthdr.caplen);
  404. }
  405. }
  406. break;
  407. case ROUTER_MODE:
  408. /*
  409. * second run through in auto mode: create route
  410. * based cache
  411. */
  412. dbg(2, "processing second pass of auto: router mode...");
  413. if (ip_hdr) {
  414. add_cache(&options->cachedata, SEND,
  415. check_ip_tree(options->nonip, ip_hdr->ip_src.s_addr));
  416. } else {
  417. add_cache(&options->cachedata, SEND,
  418. check_ip6_tree(options->nonip, &ip6_hdr->ip_src));
  419. }
  420. break;
  421. case BRIDGE_MODE:
  422. /*
  423. * second run through in auto mode: create bridge
  424. * based cache
  425. */
  426. dbg(2, "processing second pass of auto: bridge mode...");
  427. if (ip_hdr) {
  428. add_cache(&options->cachedata, SEND,
  429. check_ip_tree(DIR_UNKNOWN, ip_hdr->ip_src.s_addr));
  430. } else {
  431. add_cache(&options->cachedata, SEND,
  432. check_ip6_tree(DIR_UNKNOWN, &ip6_hdr->ip_src));
  433. }
  434. break;
  435. case SERVER_MODE:
  436. /*
  437. * second run through in auto mode: create bridge
  438. * where unknowns are servers
  439. */
  440. dbg(2, "processing second pass of auto: server mode...");
  441. if (ip_hdr) {
  442. add_cache(&options->cachedata, SEND,
  443. check_ip_tree(DIR_SERVER, ip_hdr->ip_src.s_addr));
  444. } else {
  445. add_cache(&options->cachedata, SEND,
  446. check_ip6_tree(DIR_SERVER, &ip6_hdr->ip_src));
  447. }
  448. break;
  449. case CLIENT_MODE:
  450. /*
  451. * second run through in auto mode: create bridge
  452. * where unknowns are clients
  453. */
  454. dbg(2, "processing second pass of auto: client mode...");
  455. if (ip_hdr) {
  456. add_cache(&options->cachedata, SEND,
  457. check_ip_tree(DIR_CLIENT, ip_hdr->ip_src.s_addr));
  458. } else {
  459. add_cache(&options->cachedata, SEND,
  460. check_ip6_tree(DIR_CLIENT, &ip6_hdr->ip_src));
  461. }
  462. break;
  463. case PORT_MODE:
  464. /*
  465. * process ports based on their destination port
  466. */
  467. dbg(2, "processing port mode...");
  468. add_cache(&options->cachedata, SEND,
  469. check_dst_port(ip_hdr, ip6_hdr, (pkthdr.caplen - l2len)));
  470. break;
  471. case FIRST_MODE:
  472. /*
  473. * First packet mode, looks at each host and picks clients
  474. * by the ones which send the first packet in a session
  475. */
  476. dbg(2, "processing second pass of auto: first packet mode...");
  477. if (ip_hdr) {
  478. add_cache(&options->cachedata, SEND,
  479. check_ip_tree(DIR_UNKNOWN, ip_hdr->ip_src.s_addr));
  480. } else {
  481. add_cache(&options->cachedata, SEND,
  482. check_ip6_tree(DIR_UNKNOWN, &ip6_hdr->ip_src));
  483. }
  484. break;
  485. default:
  486. errx(-1, "Whoops! What mode are we in anyways? %d", options->mode);
  487. }
  488. #ifdef ENABLE_VERBOSE
  489. if (options->verbose)
  490. tcpdump_print(&tcpprep->tcpdump, &pkthdr, pktdata);
  491. #endif
  492. }
  493. return packetnum;
  494. }
  495. /**
  496. * print the tcpprep cache file comment
  497. */
  498. void
  499. print_comment(const char *file)
  500. {
  501. char *cachedata = NULL;
  502. char *comment = NULL;
  503. COUNTER count = 0;
  504. count = read_cache(&cachedata, file, &comment);
  505. printf("tcpprep args: %s\n", comment);
  506. printf("Cache contains data for " COUNTER_SPEC " packets\n", count);
  507. exit(0);
  508. }
  509. /**
  510. * prints out the cache file details
  511. */
  512. void
  513. print_info(const char *file)
  514. {
  515. char *cachedata = NULL;
  516. char *comment = NULL;
  517. COUNTER count = 0, i;
  518. count = read_cache(&cachedata, file, &comment);
  519. if (count > 65535)
  520. exit(-1);
  521. for (i = 1; i <= count; i ++) {
  522. switch (check_cache(cachedata, i)) {
  523. case TCPR_DIR_C2S:
  524. printf("Packet " COUNTER_SPEC " -> Primary\n", i);
  525. break;
  526. case TCPR_DIR_S2C:
  527. printf("Packet " COUNTER_SPEC " -> Secondary\n", i);
  528. break;
  529. case TCPR_DIR_NOSEND:
  530. printf("Packet " COUNTER_SPEC " -> Don't Send\n", i);
  531. break;
  532. default:
  533. err(-1, "Invalid cachedata value!");
  534. break;
  535. }
  536. }
  537. exit(0);
  538. }
  539. /**
  540. * Print the per-packet statistics
  541. */
  542. void
  543. print_stats(const char *file)
  544. {
  545. char *cachedata = NULL;
  546. char *comment = NULL;
  547. COUNTER i, count = 0;
  548. COUNTER pri = 0, sec = 0, nosend = 0;
  549. count = read_cache(&cachedata, file, &comment);
  550. for (i = 1; i <= count; i ++) {
  551. int cacheval = check_cache(cachedata, i);
  552. switch (cacheval) {
  553. case TCPR_DIR_C2S:
  554. pri ++;
  555. break;
  556. case TCPR_DIR_S2C:
  557. sec ++;
  558. break;
  559. case TCPR_DIR_NOSEND:
  560. nosend ++;
  561. break;
  562. default:
  563. errx(-1, "Unknown cache value: %d", cacheval);
  564. }
  565. }
  566. printf("Primary packets:\t" COUNTER_SPEC "\n", pri);
  567. printf("Secondary packets:\t" COUNTER_SPEC "\n", sec);
  568. printf("Skipped packets:\t" COUNTER_SPEC "\n", nosend);
  569. printf("------------------------------\n");
  570. printf("Total packets:\t\t" COUNTER_SPEC "\n", count);
  571. exit(0);
  572. }