From f36a4fca258fc0a725352cfc744037634a08b076 Mon Sep 17 00:00:00 2001 From: lijia Date: Wed, 10 Jul 2019 17:54:02 +0800 Subject: copy from intranet. --- src/tcpprep.c | 787 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 787 insertions(+) create mode 100644 src/tcpprep.c (limited to 'src/tcpprep.c') diff --git a/src/tcpprep.c b/src/tcpprep.c new file mode 100644 index 0000000..0834312 --- /dev/null +++ b/src/tcpprep.c @@ -0,0 +1,787 @@ +/* $Id: tcpprep.c 2427 2010-03-25 00:38:13Z aturner $ */ + +/* + * Copyright (c) 2001-2010 Aaron Turner. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Purpose: + * 1) Remove the performance bottleneck in tcpreplay for choosing an NIC + * 2) Seperate code to make it more manageable + * 3) Add addtional features which require multiple passes of a pcap + * + * Support: + * Right now we support matching source IP based upon on of the following: + * - Regular expression + * - IP address is contained in one of a list of CIDR blocks + * - Auto learning of CIDR block for servers (clients all other) + */ + +#include "config.h" +#include "defines.h" +#include "common.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "tcpprep.h" +#include "tcpedit/tcpedit.h" +#include "tcpprep_opts.h" +#include "lib/tree.h" +#include "tree.h" +#include "lib/sll.h" +#include "lib/strlcpy.h" + +/* + * global variables + */ +#ifdef DEBUG +int debug = 0; +#endif + +#ifdef ENABLE_VERBOSE +tcpdump_t tcpdump; +#endif + +tcpprep_opt_t options; +int info = 0; +char *ourregex = NULL; +char *cidr = NULL; +tcpr_data_tree_t treeroot; + +static void init(void); +static void post_args(int, char *[]); +static void print_comment(const char *); +static void print_info(const char *); +static void print_stats(const char *); +static int check_ipv4_regex(const unsigned long ip); +static int check_ipv6_regex(const struct tcpr_in6_addr *addr); +static COUNTER process_raw_packets(pcap_t * pcap); +static int check_dst_port(ipv4_hdr_t *ip_hdr, ipv6_hdr_t *ip6_hdr, int len); + + +/* + * main() + */ +int +main(int argc, char *argv[]) +{ + int out_file; + COUNTER totpackets = 0; + char errbuf[PCAP_ERRBUF_SIZE]; + int optct = 0; + + init(); /* init our globals */ + + optct = optionProcess(&tcpprepOptions, argc, argv); + post_args(argc, argv); + + argc -= optct; + argv += optct; + + /* open the cache file */ + if ((out_file = open(OPT_ARG(CACHEFILE), O_WRONLY | O_CREAT | O_TRUNC, + S_IREAD | S_IWRITE | S_IRGRP | S_IWGRP | S_IROTH)) == -1) + errx(-1, "Unable to open cache file %s for writing: %s", + OPT_ARG(CACHEFILE), strerror(errno)); + + readpcap: + /* open the pcap file */ + if ((options.pcap = pcap_open_offline(OPT_ARG(PCAP), errbuf)) == NULL) + errx(-1, "Error opening file: %s", errbuf); + +#ifdef HAVE_PCAP_SNAPSHOT + if (pcap_snapshot(options.pcap) < 65535) + warnx("%s was captured using a snaplen of %d bytes. This may mean you have truncated packets.", + OPT_ARG(PCAP), pcap_snapshot(options.pcap)); +#endif + + /* make sure we support the DLT type */ + switch(pcap_datalink(options.pcap)) { + case DLT_EN10MB: + case DLT_LINUX_SLL: + case DLT_RAW: + case DLT_C_HDLC: + break; /* do nothing because all is good */ + default: + errx(-1, "Unsupported pcap DLT type: 0x%x", pcap_datalink(options.pcap)); + } + + /* Can only split based on MAC address for ethernet */ + if ((pcap_datalink(options.pcap) != DLT_EN10MB) && + (options.mode == MAC_MODE)) { + err(-1, "MAC mode splitting is only supported by DLT_EN10MB packet captures."); + } + +#ifdef ENABLE_VERBOSE + if (HAVE_OPT(VERBOSE)) { + tcpdump_open(&tcpdump, options.pcap); + } +#endif + + /* do we apply a bpf filter? */ + if (options.bpf.filter != NULL) { + if (pcap_compile(options.pcap, &options.bpf.program, options.bpf.filter, + options.bpf.optimize, 0) != 0) { + errx(-1, "Error compiling BPF filter: %s", pcap_geterr(options.pcap)); + } + pcap_setfilter(options.pcap, &options.bpf.program); + } + + if ((totpackets = process_raw_packets(options.pcap)) == 0) { + pcap_close(options.pcap); + err(-1, "No packets were processed. Filter too limiting?"); + } + pcap_close(options.pcap); + +#ifdef ENABLE_VERBOSE + tcpdump_close(&tcpdump); +#endif + + /* we need to process the pcap file twice in HASH/AUTO mode */ + if (options.mode == AUTO_MODE) { + options.mode = options.automode; + if (options.mode == ROUTER_MODE) { /* do we need to convert TREE->CIDR? */ + if (info) + notice("Building network list from pre-cache...\n"); + if (!process_tree()) { + err(-1, "Error: unable to build a valid list of servers. Aborting."); + } + } + else { + /* + * in bridge mode we need to calculate client/sever + * manually since this is done automatically in + * process_tree() + */ + tree_calculate(&treeroot); + } + + if (info) + notice("Buliding cache file...\n"); + /* + * re-process files, but this time generate + * cache + */ + goto readpcap; + } +#ifdef DEBUG + if (debug && (options.cidrdata != NULL)) + print_cidr(options.cidrdata); +#endif + + /* write cache data */ + totpackets = write_cache(options.cachedata, out_file, totpackets, + options.comment); + if (info) + notice("Done.\nCached " COUNTER_SPEC " packets.\n", totpackets); + + /* close cache file */ + close(out_file); + return 0; + +} + + +/** + * checks the dst port to see if this is destined for a server port. + * returns 1 for true, 0 for false + */ +static int +check_dst_port(ipv4_hdr_t *ip_hdr, ipv6_hdr_t *ip6_hdr, int len) +{ + tcp_hdr_t *tcp_hdr = NULL; + udp_hdr_t *udp_hdr = NULL; + u_char proto; + u_char *l4; + + if (ip_hdr) { + if (len < ((ip_hdr->ip_hl * 4) + 4)) + return 0; /* not enough data in the packet to know */ + + proto = ip_hdr->ip_p; + l4 = get_layer4_v4(ip_hdr); + } else if (ip6_hdr) { + if (len < (TCPR_IPV6_H + 4)) + return 0; /* not enough data in the packet to know */ + + proto = get_ipv6_l4proto(ip6_hdr); + dbgx(3, "Our layer4 proto is 0x%hhu", proto); + l4 = get_layer4_v6(ip6_hdr); + + dbgx(3, "Found proto %u at offset %p. base %p (%ld)", proto, (void *)l4, (void *)ip6_hdr, (l4 - (u_char *)ip6_hdr)); + } else { + assert(0); + } + + dbg(3, "Checking the destination port..."); + + if (proto == IPPROTO_TCP) { + tcp_hdr = (tcp_hdr_t *)l4; + + /* is a service? */ + if (options.services.tcp[ntohs(tcp_hdr->th_dport)]) { + dbgx(1, "TCP packet is destined for a server port: %d", ntohs(tcp_hdr->th_dport)); + return 1; + } + + /* nope */ + dbgx(1, "TCP packet is NOT destined for a server port: %d", ntohs(tcp_hdr->th_dport)); + return 0; + } else if (proto == IPPROTO_UDP) { + udp_hdr = (udp_hdr_t *)l4; + + /* is a service? */ + if (options.services.udp[ntohs(udp_hdr->uh_dport)]) { + dbgx(1, "UDP packet is destined for a server port: %d", ntohs(udp_hdr->uh_dport)); + return 1; + } + + /* nope */ + dbgx(1, "UDP packet is NOT destined for a server port: %d", ntohs(udp_hdr->uh_dport)); + return 0; + } + + + /* not a TCP or UDP packet... return as non_ip */ + dbg(1, "Packet isn't a UDP or TCP packet... no port to process."); + return options.nonip; +} + + +/** + * checks to see if an ip address matches a regex. Returns 1 for true + * 0 for false + */ +static int +check_ipv4_regex(const unsigned long ip) +{ + int eflags = 0; + u_char src_ip[16]; + size_t nmatch = 0; + regmatch_t *pmatch = NULL; + + memset(src_ip, '\0', 16); + strlcpy((char *)src_ip, (char *)get_addr2name4(ip, RESOLVE), + sizeof(src_ip)); + if (regexec(&options.preg, (char *)src_ip, nmatch, pmatch, eflags) == 0) { + return 1; + } else { + return 0; + } + +} + +static int +check_ipv6_regex(const struct tcpr_in6_addr *addr) +{ + int eflags = 0; + u_char src_ip[INET6_ADDRSTRLEN]; + size_t nmatch = 0; + regmatch_t *pmatch = NULL; + + memset(src_ip, '\0', 16); + strlcpy((char *)src_ip, (char *)get_addr2name6(addr, RESOLVE), sizeof(src_ip)); + if (regexec(&options.preg, (char *)src_ip, nmatch, pmatch, eflags) == 0) { + return 1; + } else { + return 0; + } +} + +/** + * uses libpcap library to parse the packets and build + * the cache file. + */ +static COUNTER +process_raw_packets(pcap_t * pcap) +{ + ipv4_hdr_t *ip_hdr = NULL; + ipv6_hdr_t *ip6_hdr = NULL; + eth_hdr_t *eth_hdr = NULL; + struct pcap_pkthdr pkthdr; + const u_char *pktdata = NULL; + COUNTER packetnum = 0; + int l2len, cache_result = 0; + u_char ipbuff[MAXPACKET], *buffptr; + tcpr_dir_t direction; + +#ifdef ENABLE_VERBOSE + struct pollfd poller[1]; + + poller[0].fd = tcpdump.outfd; + poller[0].events = POLLIN; + poller[0].revents = 0; +#endif + + assert(pcap); + + while ((pktdata = pcap_next(pcap, &pkthdr)) != NULL) { + packetnum++; + + dbgx(1, "Packet " COUNTER_SPEC, packetnum); + + /* look for include or exclude LIST match */ + if (options.xX.list != NULL) { + if (options.xX.mode < xXExclude) { + if (!check_list(options.xX.list, packetnum)) { + add_cache(&(options.cachedata), DONT_SEND, 0); + continue; + } + } + else if (check_list(options.xX.list, packetnum)) { + add_cache(&(options.cachedata), DONT_SEND, 0); + continue; + } + } + + /* + * If the packet doesn't include an IPv4 header we should just treat + * it as a non-IP packet, UNLESS we're in MAC mode, in which case + * we should let the MAC matcher below handle it + */ + + eth_hdr = (eth_hdr_t *)pktdata; + + if (options.mode != MAC_MODE) { + dbg(3, "Looking for IPv4/v6 header in non-MAC mode"); + + /* get the IP header (if any) */ + buffptr = ipbuff; + + /* first look for IPv4 */ + if ((ip_hdr = (ipv4_hdr_t *)get_ipv4(pktdata, pkthdr.caplen, + pcap_datalink(pcap), &buffptr))) { + dbg(2, "Packet is IPv4"); + + } + + /* then look for IPv6 */ + else if ((ip6_hdr = (ipv6_hdr_t *)get_ipv6(pktdata, pkthdr.caplen, + pcap_datalink(pcap), &buffptr))) { + dbg(2, "Packet is IPv6"); + } + + /* we're something else... */ + else { + dbg(2, "Packet isn't IPv4/v6"); + + /* we don't want to cache these packets twice */ + if (options.mode != AUTO_MODE) { + dbg(3, "Adding to cache using options for Non-IP packets"); + add_cache(&options.cachedata, SEND, options.nonip); + } + + /* go to next packet */ + continue; + } + + l2len = get_l2len(pktdata, pkthdr.caplen, pcap_datalink(pcap)); + + /* look for include or exclude CIDR match */ + if (options.xX.cidr != NULL) { + if (ip_hdr) { + if (!process_xX_by_cidr_ipv4(options.xX.mode, options.xX.cidr, ip_hdr)) { + add_cache(&options.cachedata, DONT_SEND, 0); + continue; + } + } else if (ip6_hdr) { + if (!process_xX_by_cidr_ipv6(options.xX.mode, options.xX.cidr, ip6_hdr)) { + add_cache(&options.cachedata, DONT_SEND, 0); + continue; + } + } + } + } + + switch (options.mode) { + case REGEX_MODE: + dbg(2, "processing regex mode..."); + if (ip_hdr) { + direction = check_ipv4_regex(ip_hdr->ip_src.s_addr); + } else if (ip6_hdr) { + direction = check_ipv6_regex(&ip6_hdr->ip_src); + } + + /* reverse direction? */ + if (HAVE_OPT(REVERSE) && (direction == TCPR_DIR_C2S || direction == TCPR_DIR_S2C)) + direction = direction == TCPR_DIR_C2S ? TCPR_DIR_S2C : TCPR_DIR_C2S; + + cache_result = add_cache(&options.cachedata, SEND, direction); + break; + + case CIDR_MODE: + dbg(2, "processing cidr mode..."); + if (ip_hdr) { + direction = check_ip_cidr(options.cidrdata, ip_hdr->ip_src.s_addr) ? TCPR_DIR_C2S : TCPR_DIR_S2C; + } else if (ip6_hdr) { + direction = check_ip6_cidr(options.cidrdata, &ip6_hdr->ip_src) ? TCPR_DIR_C2S : TCPR_DIR_S2C; + } + + /* reverse direction? */ + if (HAVE_OPT(REVERSE) && (direction == TCPR_DIR_C2S || direction == TCPR_DIR_S2C)) + direction = direction == TCPR_DIR_C2S ? TCPR_DIR_S2C : TCPR_DIR_C2S; + + cache_result = add_cache(&options.cachedata, SEND, direction); + break; + + case MAC_MODE: + dbg(2, "processing mac mode..."); + direction = macinstring(options.maclist, (u_char *)eth_hdr->ether_shost); + + /* reverse direction? */ + if (HAVE_OPT(REVERSE) && (direction == TCPR_DIR_C2S || direction == TCPR_DIR_S2C)) + direction = direction == TCPR_DIR_C2S ? TCPR_DIR_S2C : TCPR_DIR_C2S; + + cache_result = add_cache(&options.cachedata, SEND, direction); + break; + + case AUTO_MODE: + dbg(2, "processing first pass of auto mode..."); + /* first run through in auto mode: create tree */ + if (options.automode != FIRST_MODE) { + if (ip_hdr) { + add_tree_ipv4(ip_hdr->ip_src.s_addr, pktdata); + } else if (ip6_hdr) { + add_tree_ipv6(&ip6_hdr->ip_src, pktdata); + } + } else { + if (ip_hdr) { + add_tree_first_ipv4(pktdata); + } else if (ip6_hdr) { + add_tree_first_ipv6(pktdata); + } + } + break; + + case ROUTER_MODE: + /* + * second run through in auto mode: create route + * based cache + */ + dbg(2, "processing second pass of auto: router mode..."); + if (ip_hdr) { + cache_result = add_cache(&options.cachedata, SEND, + check_ip_tree(options.nonip, ip_hdr->ip_src.s_addr)); + } else { + cache_result = add_cache(&options.cachedata, SEND, + check_ip6_tree(options.nonip, &ip6_hdr->ip_src)); + } + break; + + case BRIDGE_MODE: + /* + * second run through in auto mode: create bridge + * based cache + */ + dbg(2, "processing second pass of auto: bridge mode..."); + if (ip_hdr) { + cache_result = add_cache(&options.cachedata, SEND, + check_ip_tree(DIR_UNKNOWN, ip_hdr->ip_src.s_addr)); + } else { + cache_result = add_cache(&options.cachedata, SEND, + check_ip6_tree(DIR_UNKNOWN, &ip6_hdr->ip_src)); + } + break; + + case SERVER_MODE: + /* + * second run through in auto mode: create bridge + * where unknowns are servers + */ + dbg(2, "processing second pass of auto: server mode..."); + if (ip_hdr) { + cache_result = add_cache(&options.cachedata, SEND, + check_ip_tree(DIR_SERVER, ip_hdr->ip_src.s_addr)); + } else { + cache_result = add_cache(&options.cachedata, SEND, + check_ip6_tree(DIR_SERVER, &ip6_hdr->ip_src)); + } + break; + + case CLIENT_MODE: + /* + * second run through in auto mode: create bridge + * where unknowns are clients + */ + dbg(2, "processing second pass of auto: client mode..."); + if (ip_hdr) { + cache_result = add_cache(&options.cachedata, SEND, + check_ip_tree(DIR_CLIENT, ip_hdr->ip_src.s_addr)); + } else { + cache_result = add_cache(&options.cachedata, SEND, + check_ip6_tree(DIR_CLIENT, &ip6_hdr->ip_src)); + } + break; + + case PORT_MODE: + /* + * process ports based on their destination port + */ + dbg(2, "processing port mode..."); + cache_result = add_cache(&options.cachedata, SEND, + check_dst_port(ip_hdr, ip6_hdr, (pkthdr.caplen - l2len))); + break; + + case FIRST_MODE: + /* + * First packet mode, looks at each host and picks clients + * by the ones which send the first packet in a session + */ + dbg(2, "processing second pass of auto: first packet mode..."); + if (ip_hdr) { + cache_result = add_cache(&options.cachedata, SEND, + check_ip_tree(DIR_UNKNOWN, ip_hdr->ip_src.s_addr)); + } else { + cache_result = add_cache(&options.cachedata, SEND, + check_ip6_tree(DIR_UNKNOWN, &ip6_hdr->ip_src)); + } + break; + + default: + errx(-1, "Whops! What mode are we in anyways? %d", options.mode); + } +#ifdef ENABLE_VERBOSE + if (options.verbose) + tcpdump_print(&tcpdump, &pkthdr, pktdata); +#endif + } + + return packetnum; +} + +/** + * init our options + */ +void +init(void) +{ + int i; + + memset(&options, '\0', sizeof(options)); + options.bpf.optimize = BPF_OPTIMIZE; + + for (i = DEFAULT_LOW_SERVER_PORT; i <= DEFAULT_HIGH_SERVER_PORT; i++) { + options.services.tcp[i] = 1; + options.services.udp[i] = 1; + } + +} + +/** + * post process args + */ +static void +post_args(int argc, char *argv[]) +{ + char myargs[MYARGS_LEN]; + int i, bufsize; + char *tempstr; + + memset(myargs, 0, MYARGS_LEN); + + /* print_comment and print_info don't return */ + if (HAVE_OPT(PRINT_COMMENT)) + print_comment(OPT_ARG(PRINT_COMMENT)); + + if (HAVE_OPT(PRINT_INFO)) + print_info(OPT_ARG(PRINT_INFO)); + + if (HAVE_OPT(PRINT_STATS)) + print_stats(OPT_ARG(PRINT_STATS)); + + if (! HAVE_OPT(CACHEFILE) && ! HAVE_OPT(PCAP)) + err(-1, "Must specify an output cachefile (-o) and input pcap (-i)"); + + if (! options.mode) + err(-1, "Must specify a processing mode: -a, -c, -r, -p"); + +#ifdef DEBUG + if (HAVE_OPT(DBUG)) + debug = OPT_VALUE_DBUG; +#endif + +#ifdef ENABLE_VERBOSE + if (HAVE_OPT(VERBOSE)) { + options.verbose = 1; + } + + if (HAVE_OPT(DECODE)) + tcpdump.args = safe_strdup(OPT_ARG(DECODE)); + + /* + * put the open after decode options so they are passed to tcpdump + */ +#endif + + + /* + * if we are to include the cli args, then prep it for the + * cache file header + */ + if (! options.nocomment) { + /* copy all of our args to myargs */ + for (i = 1; i < argc; i ++) { + /* skip the -C */ + if (strcmp(argv[i], "-C") == 0) { + i += 2; + continue; + } + + strlcat(myargs, argv[i], MYARGS_LEN); + strlcat(myargs, " ", MYARGS_LEN); + } + + /* remove trailing space */ + myargs[strlen(myargs) - 1] = 0; + + dbgx(1, "Comment args length: %zu", strlen(myargs)); + } + + /* setup or options.comment buffer so that that we get args\ncomment */ + if (options.comment != NULL) { + strlcat(myargs, "\n", MYARGS_LEN); + bufsize = strlen(options.comment) + strlen(myargs) + 1; + options.comment = (char *)safe_realloc(options.comment, + bufsize); + + tempstr = strdup(options.comment); + strlcpy(options.comment, myargs, bufsize); + strlcat(options.comment, tempstr, bufsize); + } else { + bufsize = strlen(myargs) + 1; + options.comment = (char *)safe_malloc(bufsize); + strlcpy(options.comment, myargs, bufsize); + } + + dbgx(1, "Final comment length: %zu", strlen(options.comment)); + + /* copy over our min/max mask */ + options.min_mask = OPT_VALUE_MINMASK; + + options.max_mask = OPT_VALUE_MAXMASK; + + if (! options.min_mask > options.max_mask) + errx(-1, "Min network mask len (%d) must be less then max network mask len (%d)", + options.min_mask, options.max_mask); + + options.ratio = atof(OPT_ARG(RATIO)); + if (options.ratio < 0) + err(-1, "Ratio must be a non-negative number."); +} + +/** + * print the tcpprep cache file comment + */ +static void +print_comment(const char *file) +{ + char *cachedata = NULL; + char *comment = NULL; + COUNTER count = 0; + + count = read_cache(&cachedata, file, &comment); + printf("tcpprep args: %s\n", comment); + printf("Cache contains data for " COUNTER_SPEC " packets\n", count); + + exit(0); +} + +/** + * prints out the cache file details + */ +static void +print_info(const char *file) +{ + char *cachedata = NULL; + char *comment = NULL; + COUNTER count = 0, i; + + count = read_cache(&cachedata, file, &comment); + for (i = 1; i <= count; i ++) { + + switch (check_cache(cachedata, i)) { + case TCPR_DIR_C2S: + printf("Packet " COUNTER_SPEC " -> Primary\n", i); + break; + case TCPR_DIR_S2C: + printf("Packet " COUNTER_SPEC " -> Secondary\n", i); + break; + case TCPR_DIR_NOSEND: + printf("Packet " COUNTER_SPEC " -> Don't Send\n", i); + break; + default: + err(-1, "Invalid cachedata value!"); + break; + } + + } + exit(0); +} + +/** + * Print the per-packet statistics + */ +static void +print_stats(const char *file) +{ + char *cachedata = NULL; + char *comment = NULL; + COUNTER count = 0; + COUNTER pri = 0, sec = 0, nosend = 0; + + count = read_cache(&cachedata, file, &comment); + for (COUNTER i = 1; i <= count; i ++) { + int cacheval = check_cache(cachedata, i); + switch (cacheval) { + case TCPR_DIR_C2S: + pri ++; + break; + case TCPR_DIR_S2C: + sec ++; + break; + case TCPR_DIR_NOSEND: + nosend ++; + break; + default: + errx(-1, "Unknown cache value: %d", cacheval); + } + } + printf("Primary packets:\t" COUNTER_SPEC "\n", pri); + printf("Secondary packets:\t" COUNTER_SPEC "\n", sec); + printf("Skipped packets:\t" COUNTER_SPEC "\n", nosend); + printf("------------------------------\n"); + printf("Total packets:\t\t" COUNTER_SPEC "\n", count); + exit(0); +} -- cgit v1.2.3