tcpprep.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. /* $Id$ */
  2. /*
  3. * Copyright (c) 2001-2010 Aaron Turner <aturner at synfin dot net>
  4. * Copyright (c) 2013-2017 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) Seperate code to make it more manageable
  23. * 3) Add addtional 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. return 0;
  172. }
  173. /**
  174. * checks the dst port to see if this is destined for a server port.
  175. * returns 1 for true, 0 for false
  176. */
  177. static int
  178. check_dst_port(ipv4_hdr_t *ip_hdr, ipv6_hdr_t *ip6_hdr, int len)
  179. {
  180. tcp_hdr_t *tcp_hdr = NULL;
  181. udp_hdr_t *udp_hdr = NULL;
  182. tcpprep_opt_t *options = tcpprep->options;
  183. uint8_t proto;
  184. u_char *l4;
  185. if (ip_hdr) {
  186. if (len < ((ip_hdr->ip_hl * 4) + 4))
  187. return 0; /* not enough data in the packet to know */
  188. proto = ip_hdr->ip_p;
  189. l4 = get_layer4_v4(ip_hdr, len);
  190. } else if (ip6_hdr) {
  191. if (len < (TCPR_IPV6_H + 4))
  192. return 0; /* not enough data in the packet to know */
  193. proto = get_ipv6_l4proto(ip6_hdr, len);
  194. dbgx(3, "Our layer4 proto is 0x%hhu", proto);
  195. if ((l4 = get_layer4_v6(ip6_hdr, len)) == NULL)
  196. return 0;
  197. dbgx(3, "Found proto %u at offset %p. base %p (%p)", proto, (void *)l4, (void *)ip6_hdr, (void*)(l4 - (u_char *)ip6_hdr));
  198. } else {
  199. assert(0);
  200. }
  201. dbg(3, "Checking the destination port...");
  202. switch(proto) {
  203. case IPPROTO_TCP:
  204. tcp_hdr = (tcp_hdr_t *)l4;
  205. /* is a service? */
  206. if (options->services.tcp[ntohs(tcp_hdr->th_dport)]) {
  207. dbgx(1, "TCP packet is destined for a server port: %d", ntohs(tcp_hdr->th_dport));
  208. return 1;
  209. }
  210. /* nope */
  211. dbgx(1, "TCP packet is NOT destined for a server port: %d", ntohs(tcp_hdr->th_dport));
  212. return 0;
  213. break;
  214. case IPPROTO_UDP:
  215. udp_hdr = (udp_hdr_t *)l4;
  216. /* is a service? */
  217. if (options->services.udp[ntohs(udp_hdr->uh_dport)]) {
  218. dbgx(1, "UDP packet is destined for a server port: %d", ntohs(udp_hdr->uh_dport));
  219. return 1;
  220. }
  221. /* nope */
  222. dbgx(1, "UDP packet is NOT destined for a server port: %d", ntohs(udp_hdr->uh_dport));
  223. return 0;
  224. break;
  225. default:
  226. /* not a TCP or UDP packet... return as non_ip */
  227. dbg(1, "Packet isn't a UDP or TCP packet... no port to process.");
  228. return options->nonip;
  229. }
  230. }
  231. /**
  232. * checks to see if an ip address matches a regex. Returns 1 for true
  233. * 0 for false
  234. */
  235. static int
  236. check_ipv4_regex(const unsigned long ip)
  237. {
  238. int eflags = 0;
  239. u_char src_ip[16];
  240. size_t nmatch = 0;
  241. tcpprep_opt_t *options = tcpprep->options;
  242. memset(src_ip, '\0', sizeof(src_ip));
  243. strlcpy((char *)src_ip, (char *)get_addr2name4(ip, RESOLVE),
  244. sizeof(src_ip));
  245. if (regexec(&options->preg, (char *)src_ip, nmatch, NULL, eflags) == 0) {
  246. return 1;
  247. } else {
  248. return 0;
  249. }
  250. }
  251. static int
  252. check_ipv6_regex(const struct tcpr_in6_addr *addr)
  253. {
  254. int eflags = 0;
  255. u_char src_ip[INET6_ADDRSTRLEN];
  256. size_t nmatch = 0;
  257. tcpprep_opt_t *options = tcpprep->options;
  258. memset(src_ip, '\0', sizeof(src_ip));
  259. strlcpy((char *)src_ip, (char *)get_addr2name6(addr, RESOLVE),
  260. sizeof(src_ip));
  261. if (regexec(&options->preg, (char *)src_ip, nmatch, NULL, eflags) == 0) {
  262. return 1;
  263. } else {
  264. return 0;
  265. }
  266. }
  267. /**
  268. * uses libpcap library to parse the packets and build
  269. * the cache file.
  270. */
  271. static COUNTER
  272. process_raw_packets(pcap_t * pcap)
  273. {
  274. ipv4_hdr_t *ip_hdr = NULL;
  275. ipv6_hdr_t *ip6_hdr = NULL;
  276. eth_hdr_t *eth_hdr = NULL;
  277. struct pcap_pkthdr pkthdr;
  278. const u_char *pktdata = NULL;
  279. COUNTER packetnum = 0;
  280. int l2len;
  281. u_char ipbuff[MAXPACKET], *buffptr;
  282. tcpr_dir_t direction = TCPR_DIR_ERROR;
  283. tcpprep_opt_t *options = tcpprep->options;
  284. assert(pcap);
  285. while ((pktdata = pcap_next(pcap, &pkthdr)) != NULL) {
  286. packetnum++;
  287. dbgx(1, "Packet " COUNTER_SPEC, packetnum);
  288. /* look for include or exclude LIST match */
  289. if (options->xX.list != NULL) {
  290. if (options->xX.mode < xXExclude) {
  291. if (!check_list(options->xX.list, packetnum)) {
  292. add_cache(&(options->cachedata), DONT_SEND, 0);
  293. continue;
  294. }
  295. }
  296. else if (check_list(options->xX.list, packetnum)) {
  297. add_cache(&(options->cachedata), DONT_SEND, 0);
  298. continue;
  299. }
  300. }
  301. /*
  302. * If the packet doesn't include an IPv4 header we should just treat
  303. * it as a non-IP packet, UNLESS we're in MAC mode, in which case
  304. * we should let the MAC matcher below handle it
  305. */
  306. eth_hdr = (eth_hdr_t *)pktdata;
  307. if (options->mode != MAC_MODE) {
  308. dbg(3, "Looking for IPv4/v6 header in non-MAC mode");
  309. /* get the IP header (if any) */
  310. buffptr = ipbuff;
  311. /* first look for IPv4 */
  312. if ((ip_hdr = (ipv4_hdr_t *)get_ipv4(pktdata, pkthdr.caplen,
  313. pcap_datalink(pcap), &buffptr))) {
  314. dbg(2, "Packet is IPv4");
  315. }
  316. /* then look for IPv6 */
  317. else if ((ip6_hdr = (ipv6_hdr_t *)get_ipv6(pktdata, pkthdr.caplen,
  318. pcap_datalink(pcap), &buffptr))) {
  319. dbg(2, "Packet is IPv6");
  320. }
  321. /* we're something else... */
  322. 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. /* look for include or exclude CIDR match */
  334. if (options->xX.cidr != NULL) {
  335. if (ip_hdr) {
  336. if (!process_xX_by_cidr_ipv4(options->xX.mode, options->xX.cidr, ip_hdr)) {
  337. add_cache(&options->cachedata, DONT_SEND, 0);
  338. continue;
  339. }
  340. } else if (ip6_hdr) {
  341. if (!process_xX_by_cidr_ipv6(options->xX.mode, options->xX.cidr, ip6_hdr)) {
  342. add_cache(&options->cachedata, DONT_SEND, 0);
  343. continue;
  344. }
  345. }
  346. }
  347. }
  348. switch (options->mode) {
  349. case REGEX_MODE:
  350. dbg(2, "processing regex mode...");
  351. if (ip_hdr) {
  352. direction = check_ipv4_regex(ip_hdr->ip_src.s_addr);
  353. } else if (ip6_hdr) {
  354. direction = check_ipv6_regex(&ip6_hdr->ip_src);
  355. }
  356. /* reverse direction? */
  357. if (HAVE_OPT(REVERSE) && (direction == TCPR_DIR_C2S || direction == TCPR_DIR_S2C))
  358. direction = direction == TCPR_DIR_C2S ? TCPR_DIR_S2C : TCPR_DIR_C2S;
  359. add_cache(&options->cachedata, SEND, direction);
  360. break;
  361. case CIDR_MODE:
  362. dbg(2, "processing cidr mode...");
  363. if (ip_hdr) {
  364. direction = check_ip_cidr(options->cidrdata, ip_hdr->ip_src.s_addr) ? TCPR_DIR_C2S : TCPR_DIR_S2C;
  365. } else if (ip6_hdr) {
  366. direction = check_ip6_cidr(options->cidrdata, &ip6_hdr->ip_src) ? TCPR_DIR_C2S : TCPR_DIR_S2C;
  367. }
  368. /* reverse direction? */
  369. if (HAVE_OPT(REVERSE) && (direction == TCPR_DIR_C2S || direction == TCPR_DIR_S2C))
  370. direction = direction == TCPR_DIR_C2S ? TCPR_DIR_S2C : TCPR_DIR_C2S;
  371. add_cache(&options->cachedata, SEND, direction);
  372. break;
  373. case MAC_MODE:
  374. dbg(2, "processing mac mode...");
  375. direction = macinstring(options->maclist, (u_char *)eth_hdr->ether_shost);
  376. /* reverse direction? */
  377. if (HAVE_OPT(REVERSE) && (direction == TCPR_DIR_C2S || direction == TCPR_DIR_S2C))
  378. direction = direction == TCPR_DIR_C2S ? TCPR_DIR_S2C : TCPR_DIR_C2S;
  379. add_cache(&options->cachedata, SEND, direction);
  380. break;
  381. case AUTO_MODE:
  382. dbg(2, "processing first pass of auto mode...");
  383. /* first run through in auto mode: create tree */
  384. if (options->automode != FIRST_MODE) {
  385. if (ip_hdr) {
  386. add_tree_ipv4(ip_hdr->ip_src.s_addr, pktdata);
  387. } else if (ip6_hdr) {
  388. add_tree_ipv6(&ip6_hdr->ip_src, pktdata);
  389. }
  390. } else {
  391. if (ip_hdr) {
  392. add_tree_first_ipv4(pktdata);
  393. } else if (ip6_hdr) {
  394. add_tree_first_ipv6(pktdata);
  395. }
  396. }
  397. break;
  398. case ROUTER_MODE:
  399. /*
  400. * second run through in auto mode: create route
  401. * based cache
  402. */
  403. dbg(2, "processing second pass of auto: router mode...");
  404. if (ip_hdr) {
  405. add_cache(&options->cachedata, SEND,
  406. check_ip_tree(options->nonip, ip_hdr->ip_src.s_addr));
  407. } else {
  408. add_cache(&options->cachedata, SEND,
  409. check_ip6_tree(options->nonip, &ip6_hdr->ip_src));
  410. }
  411. break;
  412. case BRIDGE_MODE:
  413. /*
  414. * second run through in auto mode: create bridge
  415. * based cache
  416. */
  417. dbg(2, "processing second pass of auto: bridge mode...");
  418. if (ip_hdr) {
  419. add_cache(&options->cachedata, SEND,
  420. check_ip_tree(DIR_UNKNOWN, ip_hdr->ip_src.s_addr));
  421. } else {
  422. add_cache(&options->cachedata, SEND,
  423. check_ip6_tree(DIR_UNKNOWN, &ip6_hdr->ip_src));
  424. }
  425. break;
  426. case SERVER_MODE:
  427. /*
  428. * second run through in auto mode: create bridge
  429. * where unknowns are servers
  430. */
  431. dbg(2, "processing second pass of auto: server mode...");
  432. if (ip_hdr) {
  433. add_cache(&options->cachedata, SEND,
  434. check_ip_tree(DIR_SERVER, ip_hdr->ip_src.s_addr));
  435. } else {
  436. add_cache(&options->cachedata, SEND,
  437. check_ip6_tree(DIR_SERVER, &ip6_hdr->ip_src));
  438. }
  439. break;
  440. case CLIENT_MODE:
  441. /*
  442. * second run through in auto mode: create bridge
  443. * where unknowns are clients
  444. */
  445. dbg(2, "processing second pass of auto: client mode...");
  446. if (ip_hdr) {
  447. add_cache(&options->cachedata, SEND,
  448. check_ip_tree(DIR_CLIENT, ip_hdr->ip_src.s_addr));
  449. } else {
  450. add_cache(&options->cachedata, SEND,
  451. check_ip6_tree(DIR_CLIENT, &ip6_hdr->ip_src));
  452. }
  453. break;
  454. case PORT_MODE:
  455. /*
  456. * process ports based on their destination port
  457. */
  458. dbg(2, "processing port mode...");
  459. add_cache(&options->cachedata, SEND,
  460. check_dst_port(ip_hdr, ip6_hdr, (pkthdr.caplen - l2len)));
  461. break;
  462. case FIRST_MODE:
  463. /*
  464. * First packet mode, looks at each host and picks clients
  465. * by the ones which send the first packet in a session
  466. */
  467. dbg(2, "processing second pass of auto: first packet mode...");
  468. if (ip_hdr) {
  469. add_cache(&options->cachedata, SEND,
  470. check_ip_tree(DIR_UNKNOWN, ip_hdr->ip_src.s_addr));
  471. } else {
  472. add_cache(&options->cachedata, SEND,
  473. check_ip6_tree(DIR_UNKNOWN, &ip6_hdr->ip_src));
  474. }
  475. break;
  476. default:
  477. errx(-1, "Whoops! What mode are we in anyways? %d", options->mode);
  478. }
  479. #ifdef ENABLE_VERBOSE
  480. if (options->verbose)
  481. tcpdump_print(&tcpprep->tcpdump, &pkthdr, pktdata);
  482. #endif
  483. }
  484. return packetnum;
  485. }
  486. /**
  487. * print the tcpprep cache file comment
  488. */
  489. void
  490. print_comment(const char *file)
  491. {
  492. char *cachedata = NULL;
  493. char *comment = NULL;
  494. COUNTER count = 0;
  495. count = read_cache(&cachedata, file, &comment);
  496. printf("tcpprep args: %s\n", comment);
  497. printf("Cache contains data for " COUNTER_SPEC " packets\n", count);
  498. exit(0);
  499. }
  500. /**
  501. * prints out the cache file details
  502. */
  503. void
  504. print_info(const char *file)
  505. {
  506. char *cachedata = NULL;
  507. char *comment = NULL;
  508. COUNTER count = 0, i;
  509. count = read_cache(&cachedata, file, &comment);
  510. if (count > 65535)
  511. exit(-1);
  512. for (i = 1; i <= count; i ++) {
  513. switch (check_cache(cachedata, i)) {
  514. case TCPR_DIR_C2S:
  515. printf("Packet " COUNTER_SPEC " -> Primary\n", i);
  516. break;
  517. case TCPR_DIR_S2C:
  518. printf("Packet " COUNTER_SPEC " -> Secondary\n", i);
  519. break;
  520. case TCPR_DIR_NOSEND:
  521. printf("Packet " COUNTER_SPEC " -> Don't Send\n", i);
  522. break;
  523. default:
  524. err(-1, "Invalid cachedata value!");
  525. break;
  526. }
  527. }
  528. exit(0);
  529. }
  530. /**
  531. * Print the per-packet statistics
  532. */
  533. void
  534. print_stats(const char *file)
  535. {
  536. char *cachedata = NULL;
  537. char *comment = NULL;
  538. COUNTER count = 0;
  539. COUNTER pri = 0, sec = 0, nosend = 0;
  540. count = read_cache(&cachedata, file, &comment);
  541. if (count > 65535)
  542. exit(-1);
  543. for (COUNTER i = 1; i <= count; i ++) {
  544. int cacheval = check_cache(cachedata, i);
  545. switch (cacheval) {
  546. case TCPR_DIR_C2S:
  547. pri ++;
  548. break;
  549. case TCPR_DIR_S2C:
  550. sec ++;
  551. break;
  552. case TCPR_DIR_NOSEND:
  553. nosend ++;
  554. break;
  555. default:
  556. errx(-1, "Unknown cache value: %d", cacheval);
  557. }
  558. }
  559. printf("Primary packets:\t" COUNTER_SPEC "\n", pri);
  560. printf("Secondary packets:\t" COUNTER_SPEC "\n", sec);
  561. printf("Skipped packets:\t" COUNTER_SPEC "\n", nosend);
  562. printf("------------------------------\n");
  563. printf("Total packets:\t\t" COUNTER_SPEC "\n", count);
  564. exit(0);
  565. }