init
authorGeorgios Kontaxis <redacted>
Sun, 5 Apr 2015 05:13:27 +0000 (01:13 -0400)
committerGeorgios Kontaxis <redacted>
Sun, 5 Apr 2015 05:13:27 +0000 (01:13 -0400)
Makefile [new file with mode: 0644]
Readme.txt [new file with mode: 0644]
tcpstat.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..01757cd
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+.PHONY: all clean
+
+all: tcpstat tcpstat_noether
+
+debug: tcpstat_dbg tcpstat_noether_dbg
+
+tcpstat: tcpstat.c
+       gcc -Wall \
+               -D__WITH_THREADS__ \
+               tcpstat.c \
+               -lpcap -lpthread \
+               -o tcpstat
+
+tcpstat_dbg: tcpstat.c
+       gcc -Wall -ggdb -O0 -D__DEBUG__ \
+               -D__WITH_THREADS__ \
+               tcpstat.c \
+               -lpcap -lpthread \
+               -o tcpstat_dbg
+
+tcpstat_noether: tcpstat.c
+       gcc -Wall \
+               -D__WITH_THREADS__ -D__NO_ETHERNET__ \
+               tcpstat.c \
+               -lpcap -lpthread \
+               -o tcpstat_noether
+
+tcpstat_noether_dbg: tcpstat.c
+       gcc -Wall -ggdb -O0 -D__DEBUG__ \
+               -D__WITH_THREADS__ -D__NO_ETHERNET__ \
+               tcpstat.c \
+               -lpcap -lpthread \
+               -o tcpstat_noether_dbg
+
+
+clean:
+       rm -f tcpstat tcpstat_dbg tcpstat_noether tcpstat_noether_dbg
+
+
diff --git a/Readme.txt b/Readme.txt
new file mode 100644 (file)
index 0000000..54e1fef
--- /dev/null
@@ -0,0 +1,36 @@
+# This program counts the number of packets per protocol port.
+# Can be used for instance to get an idea of how much HTTP (TCP 80)
+# vs HTTPS (TCP 443) traffic is present in the network.
+#
+# For TCP it makes sense to set a BPF* to count flows instead of total packets.
+# For UDP we don't have that option. TODO implement conntrack.
+# For ICMP all packets are mapped to port 0.
+#
+# Important! A BPF MUST limit packet capture to a single protocol (e.g., TCP)
+# otherwise counts for the same port in different protocols will be aggregated.
+#
+# *: If no BPF is set the default filter captures HTTP/HTTPS flows.
+#    To run with an empty BPF (no recommended) use: -f ""
+#
+# kontaxis 2014-11-03
+
+# Build
+make
+
+# Clean
+make clean
+
+# Run on an ethernet interface.
+sudo ./tcpstat -i eth0
+
+# Run on an ethernet interface. (debug/verbose)
+# All packets processed will be written to file ./eth0.pcap
+sudo ./tcpstat_dbg -i eth0
+
+# Run on a raw IP interface. (e.g., P-t-P tunnel)
+sudo ./tcpstat_noether -i tun0
+
+# Run on a raw IP interface. (e.g., P-t-P tunnel) (debug/verbose)
+# All packets processed will be written to file ./tun0.pcap
+sudo ./tcpstat_noether_dbg -i tun0
+
diff --git a/tcpstat.c b/tcpstat.c
new file mode 100644 (file)
index 0000000..e58c970
--- /dev/null
+++ b/tcpstat.c
@@ -0,0 +1,510 @@
+/* kontaxis 2014-11-03 */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <limits.h>
+#include <unistd.h>
+#include <string.h>
+#include <pcap/pcap.h>
+
+#include <stdlib.h>
+#include <signal.h>
+
+#if __WITH_THREADS__
+#include <pthread.h>
+#endif
+
+#if !__DEBUG__
+#define NDEBUG
+#endif
+#include <assert.h>
+
+/* References:
+ *   netinet/ether.h
+ *   netinet/ip.h
+ *   netinet/tcp.h
+ *   netinet/udp.h
+ */
+
+/* Ethernet */
+
+#define ETH_ALEN 6
+
+struct ether_header
+{
+  uint8_t  ether_dhost[ETH_ALEN];
+  uint8_t  ether_shost[ETH_ALEN];
+  uint16_t ether_type;
+} __attribute__ ((__packed__));
+
+#define ETHERTYPE_IP 0x0800 /* IP */
+
+#if !__NO_ETHERNET__
+#define SIZE_ETHERNET sizeof(struct ether_header)
+#else
+#define SIZE_ETHERNET 0
+#endif
+
+/* IP */
+
+struct my_iphdr
+{
+  uint8_t  vhl;
+#define IP_HL(ip) (((ip)->vhl) & 0x0F)
+#define IP_V(ip)  (((ip)->vhl) >> 4)
+  uint8_t  tos;
+  uint16_t tot_len;
+  uint16_t id;
+  uint16_t frag_off;
+  uint8_t  ttl;
+  uint8_t  protocol;
+  uint16_t check;
+  uint32_t saddr;
+  uint32_t daddr;
+  /*The options start here. */
+} __attribute__ ((__packed__));
+
+#define MIN_SIZE_IP (sizeof(struct my_iphdr))
+#define MAX_SIZE_IP (0xF * sizeof(uint32_t))
+
+#define IPVERSION 4
+
+#define IPPROTO_TCP  6
+#define IPPROTO_UDP 17
+
+/* TCP */
+
+struct my_tcphdr
+{
+  uint16_t source;
+  uint16_t dest;
+  uint32_t seq;
+  uint32_t ack_seq;
+  uint8_t  res1doff;
+#define TCP_OFF(th)      (((th)->res1doff & 0xF0) >> 4)
+       uint8_t  flags;
+#define TCP_FIN  (0x1 << 0)
+#define TCP_SYN  (0x1 << 1)
+#define TCP_RST  (0x1 << 2)
+#define TCP_PUSH (0x1 << 3)
+#define TCP_ACK  (0x1 << 4)
+#define TCP_URG  (0x1 << 5)
+#define TCP_ECE  (0x1 << 6)
+#define TCP_CWR  (0x1 << 7)
+  uint16_t window;
+  uint16_t check;
+  uint16_t urg_ptr;
+} __attribute__ ((__packed__));
+
+#define MIN_SIZE_TCP (sizeof(struct my_tcphdr))
+#define MAX_SIZE_TCP (0xF * sizeof(uint32_t))
+
+/* UDP */
+
+struct udphdr
+{
+  uint16_t source;
+  uint16_t dest;
+  uint16_t len;
+  uint16_t check;
+} __attribute__ ((__packed__));
+
+#define MIN_SIZE_UDP (sizeof(struct udphdr))
+
+
+/* converts 16 bits in host byte order to 16 bits in network byte order */
+#if !__BIG_ENDIAN__
+#define h16ton16(n) \
+((uint16_t) (((uint16_t) n) << 8) | (uint16_t) (((uint16_t) n) >> 8))
+#else
+#define h16ton16(n) (n)
+#endif
+
+#define n16toh16(n) h16ton16(n)
+
+#define likely(x)       __builtin_expect((x),1)
+#define unlikely(x)     __builtin_expect((x),0)
+
+
+#if __WITH_THREADS__
+pthread_t my_thread;
+void *my_thread_retval;
+#endif
+
+pcap_t *pcap_handle;
+#if __DEBUG__
+pcap_dumper_t * pcap_dumper_handle;
+#endif
+
+
+/* Packet count per destination port.
+ * -  TCP: with the proper BPF (see default) this could count flots.
+ * -  UDP: no conntrack support so we just count packets.
+ * - ICMP: no notion of ports so flow_counts[0] counts all packets.
+ *
+ * Stored in network byte-order to save some shifts on little-endians.
+ */
+uint32_t flow_counts[0xFFFF + 1];
+
+void my_pcap_handler (uint8_t *user, const struct pcap_pkthdr *header,
+       const uint8_t *packet)
+{
+#if !__NO_ETHERNET__
+       struct ether_header *ether;
+#endif
+       struct my_iphdr *ip;
+       struct my_tcphdr *tcp;
+       struct udphdr *udp;
+
+#if __DEBUG__
+       uint16_t src_port;
+#endif
+       uint16_t dst_port;
+
+#if !__NO_ETHERNET__
+       /* Process ethernet header */
+       assert(header->caplen >= SIZE_ETHERNET);
+       ether = (struct ether_header *) packet;
+       if (unlikely(ether->ether_type != h16ton16(ETHERTYPE_IP))) {
+#if __DEBUG__
+               fprintf(stderr,
+                       "WARNING: ether->ether_type != ETHERTYPE_IP. Ignoring.\n");
+#endif
+               return;
+       }
+#endif
+
+       /* Process IP header */
+       assert(header->caplen >= SIZE_ETHERNET + MIN_SIZE_IP);
+       ip = (struct my_iphdr *) (packet + SIZE_ETHERNET);
+       if (unlikely(IP_V(ip) != IPVERSION)) {
+#if __DEBUG__
+               fprintf(stderr, "WARNING: IP_V(ip) != 4. Ignoring.\n");
+#endif
+               return;
+       }
+
+       switch(ip->protocol) {
+               case IPPROTO_TCP: {
+                               /* Process TCP header */
+                               assert(header->caplen >=
+                                       SIZE_ETHERNET + (IP_HL(ip) * sizeof(uint32_t)) + MIN_SIZE_TCP);
+                               tcp = (struct my_tcphdr *)
+                                       (packet + SIZE_ETHERNET + (IP_HL(ip) * sizeof(uint32_t)));
+#if __DEBUG__
+                               src_port = tcp->source;
+#endif
+                               dst_port = tcp->dest;
+                       }
+                       break;
+               case IPPROTO_UDP: {
+                               /* Process UDP header */
+                               assert(header->caplen >=
+                                       SIZE_ETHERNET + (IP_HL(ip) * sizeof(uint32_t)) + MIN_SIZE_UDP);
+                               udp = (struct udphdr *)
+                                       (packet + SIZE_ETHERNET + (IP_HL(ip) * sizeof(uint32_t)));
+#if __DEBUG__
+                               src_port = udp->source;
+#endif
+                               dst_port = udp->dest;
+                       }
+                       break;
+               default:
+#if __DEBUG__
+                               src_port = 0;
+#endif
+                               dst_port = 0;
+                       break;
+       }
+
+#if __DEBUG__
+       /* Save to dump file. We do this after we transmit to the dispatcher so
+        * that the dump reflects only packets that were sucessfuly transmitted. */
+       pcap_dump((u_char *)pcap_dumper_handle, header, packet);
+#endif
+
+#if __DEBUG__
+       fprintf(stdout, "%u.%u.%u.%u:%u -> %u.%u.%u.%u:[%u]\n",
+               *(((uint8_t *)&(ip->saddr)) + 0),
+               *(((uint8_t *)&(ip->saddr)) + 1),
+               *(((uint8_t *)&(ip->saddr)) + 2),
+               *(((uint8_t *)&(ip->saddr)) + 3),
+               n16toh16(src_port),
+               *(((uint8_t *)&(ip->daddr)) + 0),
+               *(((uint8_t *)&(ip->daddr)) + 1),
+               *(((uint8_t *)&(ip->daddr)) + 2),
+               *(((uint8_t *)&(ip->daddr)) + 3),
+               n16toh16(dst_port));
+#endif
+
+       /* If one counter is about to overflow, reset them all */
+       if (unlikely(flow_counts[dst_port] == UINT_MAX)) {
+               memset(flow_counts, 0, sizeof(flow_counts));
+       }
+
+       /* Increase flow count for this destination port */
+       flow_counts[dst_port] += 1;
+}
+
+
+void print_flow_counts(void)
+{
+       uint32_t i;
+       uint16_t j;
+
+       for (i = 0, j = 0; i <= 0xFFFF; i++) {
+               if (!flow_counts[h16ton16(i)]) continue;
+               fprintf(stdout, "%u:%u ", i, flow_counts[h16ton16(i)]);
+               j++;
+       }
+       fprintf(stdout, "\n");
+       fprintf(stdout, "[*] Ports identified: %u\n", j);
+}
+
+
+void print_flow_counts_pretty(void)
+{
+       uint32_t i;
+       uint16_t j;
+
+       fprintf(stdout, "=== BEGIN DATA ===\n");
+
+       for (i = 0, j = 0; i <= 0xFFFF; i++) {
+               if (!flow_counts[h16ton16(i)]) continue;
+               fprintf(stdout, "[\033[1;32m%5u\033[0m] = \033[1;31m%10u\033[0m",
+                       i, flow_counts[h16ton16(i)]);
+
+               if (!((j + 1) % 3)) {fprintf(stdout, "\n");}
+               else {fprintf(stdout, " ");}
+               j++;
+       }
+
+       if (j == 0) {fprintf(stdout, "NO_DATA\n");}
+       else if ((j % 3)) {     fprintf(stdout, "\n");}
+
+       fprintf(stdout, "=== END   DATA ===\n");
+}
+
+
+#if __WITH_THREADS__
+void * status_thread (void * data)
+{
+       int c;
+       while ((c = getchar())) {
+               print_flow_counts_pretty();
+       }
+
+       assert(0);
+       return NULL;
+}
+#endif
+
+
+void signal_handler (int signum)
+{
+       switch(signum) {
+               case SIGTERM:
+               case SIGINT:
+                       fprintf(stdout, "\n");
+                       print_flow_counts();
+                       pcap_breakloop(pcap_handle);
+                       break;
+               case SIGUSR1:
+                       print_flow_counts_pretty();
+                       break;
+               default:
+                       break;
+       }
+}
+
+#define MAX(a, b) ((a) < (b) ? (b) : (a))
+#define SNAPLEN_TCP (SIZE_ETHERNET + MAX_SIZE_IP + MIN_SIZE_TCP)
+#define SNAPLEN_UDP (SIZE_ETHERNET + MAX_SIZE_IP + MIN_SIZE_UDP)
+#define SNAPLEN MAX(SNAPLEN_TCP, SNAPLEN_UDP)
+#define PROMISCUOUS ((opt_flags & OPT_PROMISCUOUS) == OPT_PROMISCUOUS)
+#define PCAP_TIMEOUT 1000
+
+#define BPF_DEFAULT \
+       "ip and tcp and (tcp[tcpflags] == tcp-syn) and (dst port 80 or dst port 443)"
+#define BPF bpf_s
+#define BPF_OPTIMIZE 1
+
+int main (int argc, char *argv[])
+{
+       char * device;
+       char errbuf[PCAP_ERRBUF_SIZE];
+
+       char * bpf_s;
+       char * bpf_default = BPF_DEFAULT;
+       struct bpf_program bpf;
+
+#if __DEBUG__
+       char *       dump_fname;
+       unsigned int dump_fname_sz;
+#endif
+
+       struct sigaction act;
+
+       int i;
+#define OPT_DEVICE      (0x1 << 0)
+#define OPT_PROMISCUOUS (0x1 << 1)
+#define OPT_BPF         (0x1 << 2)
+       uint8_t opt_flags;
+
+       opt_flags = 0;
+       memset(errbuf, 0, PCAP_ERRBUF_SIZE);
+
+       while ((i = getopt(argc, argv, "hvf:pi:")) != -1) {
+               switch(i) {
+                       case 'h':
+                               fprintf(stderr,
+                                       "Use: %s [-h] [-v] [-f bpf] [-p] -i interface\n", argv[0]);
+                               return -1;
+                               break;
+                       case 'v':
+                               fprintf(stderr, "Build:\t%s\t%s\n", __DATE__, __TIME__);
+                               return -1;
+                               break;
+                       case 'f':
+                               bpf_s = optarg;
+                               opt_flags |= OPT_BPF;
+                               break;
+                       case 'p':
+                               opt_flags |= OPT_PROMISCUOUS;
+                               break;
+                       case 'i':
+                               device = optarg;
+                               opt_flags |= OPT_DEVICE;
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+       if (!(opt_flags & OPT_DEVICE)) {
+               fprintf(stderr, "[FATAL] Missing target interface. Try with -h.\n");
+               return -1;
+       }
+
+#if __DEBUG__
+#if !__BIG_ENDIAN__
+       fprintf(stderr, "LITTLE_ENDIAN\n");
+#else
+       fprintf(stderr, "BIG_ENDIAN\n");
+#endif
+#endif
+
+       fprintf(stdout, "[*] PID: %u\n", getpid());
+
+       fprintf(stdout, "[*] Device: '%s'\n", device);
+
+       /* BPF is not set. We'll use the default. */
+       if (!(opt_flags & OPT_BPF)) {
+               bpf_s = bpf_default;
+               opt_flags |= OPT_BPF;
+       }
+
+       fprintf(stdout, "[*] Promiscuous: %s%d\033[0m\n",
+               (PROMISCUOUS?"\033[1;32m":"\033[1;31m"), PROMISCUOUS);
+
+       if (!(pcap_handle =
+               pcap_open_live(device, SNAPLEN, PROMISCUOUS, PCAP_TIMEOUT, errbuf))) {
+               fprintf(stderr, "[FATAL] %s\n", errbuf);
+               return -1;
+       }
+
+       if (opt_flags & OPT_BPF) {
+               fprintf(stdout, "[*] BPF: '\033[1;32m%s\033[0m'\n", bpf_s);
+       } else {
+               fprintf(stdout, "[*] BPF: \033[1;31mNONE\033[0m\n");
+       }
+
+       if (opt_flags & OPT_BPF) {
+               if (pcap_compile(pcap_handle, &bpf, BPF, BPF_OPTIMIZE,
+                       PCAP_NETMASK_UNKNOWN) == -1) {
+                       fprintf(stderr, "[FATAL] Couldn't parse filter. %s\n",
+                               pcap_geterr(pcap_handle));
+                       pcap_close(pcap_handle);
+                       return -1;
+               }
+
+               if (pcap_setfilter(pcap_handle, &bpf) == -1) {
+                       fprintf(stderr, "[FATAL] Couldn't install filter. %s\n",
+                       pcap_geterr(pcap_handle));
+                       pcap_close(pcap_handle);
+                       return -1;
+               }
+
+               pcap_freecode(&bpf);
+       }
+
+       act.sa_handler = signal_handler;
+       sigemptyset (&act.sa_mask);
+       act.sa_flags = 0;
+
+       if (sigaction(SIGINT, &act, NULL)) {
+               perror("sigaction");
+               fprintf(stderr,
+                       "[WARNING] Failed to set signal handler for SIGINT.\n");
+       }
+
+       if (sigaction(SIGTERM, &act, NULL)) {
+               perror("sigaction");
+               fprintf(stderr,
+                       "[WARNING] Failed to set signal handler for SIGTERM.\n");
+       }
+
+       if (sigaction(SIGUSR1, &act, NULL)) {
+               perror("sigaction");
+               fprintf(stderr,
+                       "[WARNING] Failed to set signal handler for SIGUSR1.\n");
+       }
+
+#if __WITH_THREADS__
+       if (pthread_create(&my_thread, NULL, &status_thread, NULL)) {
+               fprintf(stderr, "[WARNING] Failed to create thread.\n");
+       }
+#endif
+
+#if __DEBUG__
+       dump_fname_sz = strlen(device) + strlen(".pcap") + 1;
+       if ((dump_fname = malloc(sizeof(char) * dump_fname_sz)) == NULL) {
+               perror("malloc");
+               return -1;
+       }
+       snprintf(dump_fname, dump_fname_sz, "%s%s", device, ".pcap");
+       if (!(pcap_dumper_handle = pcap_dump_open(pcap_handle, dump_fname))) {
+               pcap_geterr(pcap_handle);
+       }
+       free(dump_fname);
+#endif
+
+       fprintf(stdout, "Capturing ...\n");
+
+       if (pcap_loop(pcap_handle, -1, &my_pcap_handler, NULL) == -1) {
+               fprintf(stderr, "[FATAL] pcap_loop failed. %s\n",
+                       pcap_geterr(pcap_handle));
+       }
+
+       pcap_close(pcap_handle);
+
+#if __DEBUG__
+       pcap_dump_close(pcap_dumper_handle);
+#endif
+
+#if __WITH_THREADS__
+       if (pthread_cancel(my_thread)) {
+               fprintf(stderr, "[WARNING] Failed to cancel thread.\n");
+       }
+       if (pthread_join(my_thread, &my_thread_retval)) {
+               fprintf(stderr, "[WARNING] Failed to join thread.\n");
+       }
+       if (my_thread_retval != PTHREAD_CANCELED) {
+               fprintf(stderr, "[WARNING] Thread hasn't been canceled.\n");
+       }
+#endif
+
+       fprintf(stdout, "Goodbye\n");
+
+       return 0;
+}
git clone https://git.99rst.org/PROJECT