commit all the code!
[detour.git] / pulserecord.c
diff --git a/pulserecord.c b/pulserecord.c
new file mode 100644 (file)
index 0000000..f7bec56
--- /dev/null
@@ -0,0 +1,224 @@
+// most of this copied from http://www.tcpdump.org/pcap.html and other places
+
+#include <pcap.h>
+#include "common.c"
+#include "uthash.h"
+
+
+/* Ethernet addresses are 6 bytes */
+#define ETHER_ADDR_LEN 6
+
+/* ethernet headers are always exactly 14 bytes */
+#define SIZE_ETHERNET 14
+
+/* Ethernet header */
+struct sniff_ethernet {
+  u_char ether_dhost[ETHER_ADDR_LEN]; /* Destination host address */
+  u_char ether_shost[ETHER_ADDR_LEN]; /* Source host address */
+  u_short ether_type; /* IP? ARP? RARP? etc */
+};
+
+/* IP header */
+struct sniff_ip {
+  u_char ip_vhl;               /* version << 4 | header length >> 2 */
+  u_char ip_tos;               /* type of service */
+  u_short ip_len;              /* total length */
+  u_short ip_id;               /* identification */
+  u_short ip_off;              /* fragment offset field */
+#define IP_RF 0x8000           /* reserved fragment flag */
+#define IP_DF 0x4000           /* dont fragment flag */
+#define IP_MF 0x2000           /* more fragments flag */
+#define IP_OFFMASK 0x1fff      /* mask for fragmenting bits */
+  u_char ip_ttl;               /* time to live */
+  u_char ip_p;                 /* protocol */
+  u_short ip_sum;              /* checksum */
+  struct in_addr ip_src,ip_dst; /* source and dest address */
+};
+#define IP_HL(ip)              (((ip)->ip_vhl) & 0x0f)
+#define IP_V(ip)               (((ip)->ip_vhl) >> 4)
+
+/* TCP header */
+typedef u_int tcp_seq;
+
+struct sniff_tcp {
+  u_short th_sport;            /* source port */
+  u_short th_dport;            /* destination port */
+  tcp_seq th_seq;              /* sequence number */
+  tcp_seq th_ack;              /* acknowledgement number */
+  u_char th_offx2;             /* data offset, rsvd */
+#define TH_OFF(th)     (((th)->th_offx2 & 0xf0) >> 4)
+  u_char th_flags;
+#define TH_FIN 0x01
+#define TH_SYN 0x02
+#define TH_RST 0x04
+#define TH_PUSH 0x08
+#define TH_ACK 0x10
+#define TH_URG 0x20
+#define TH_ECE 0x40
+#define TH_CWR 0x80
+#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
+  u_short th_win;              /* window */
+  u_short th_sum;              /* checksum */
+  u_short th_urp;              /* urgent pointer */
+};
+
+
+
+struct con {
+  UT_hash_handle hh;
+  char name[47];
+  unsigned int bytesA, bytesB;
+  unsigned int idle;
+};
+struct con *cons = NULL;
+
+time_t t;
+bool even;
+
+
+void packet_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *packet) {
+  unsigned int len = h->len;
+
+  /* The ethernet header */
+  //const struct sniff_ethernet *ethernet = (struct sniff_ethernet*)(packet);
+  /* The IP header */
+  const struct sniff_ip *ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);
+  u_int size_ip = IP_HL(ip)*4;
+  if (size_ip < 20) {
+    printf("   * Invalid IP header length: %u bytes\n", size_ip);
+    return;
+  }
+  /* The TCP header */
+  const struct sniff_tcp *tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);
+  u_int size_tcp = TH_OFF(tcp)*4;
+  if (size_tcp < 20) {
+    printf("   * Invalid TCP header length: %u bytes\n", size_tcp);
+    return;
+  }
+
+  // construct name string
+  // length of IP: 3+1+3+1+3+1+3=15
+  // length of port: 5
+  // total: 2*(15+1+5)+4+1=47 including '\0'
+  char name[47];
+  uint32_t ipA = ntohl(ip->ip_src.s_addr);
+  uint32_t ipB = ntohl(ip->ip_dst.s_addr);
+  sprintf(name, "%u.%u.%u.%u:%u -> %u.%u.%u.%u:%u",
+      ipA>>24, (ipA>>16)&0xff, (ipA>>8)&0xff, ipA&0xff, ntohs(tcp->th_sport),
+      ipB>>24, (ipB>>16)&0xff, (ipB>>8)&0xff, ipB&0xff, ntohs(tcp->th_dport));
+
+  //printf("%s: %u\n", name, len);
+
+  struct con *c;
+  HASH_FIND_STR(cons, name, c);
+  if (!c) {
+    c = calloc(1, sizeof(struct con));
+    if (!c) printf("calloc fail"), exit(1);
+    strcpy(c->name, name);
+    HASH_ADD_STR(cons, name, c);
+  }
+
+  if (even)
+    c->bytesB += len;
+  else
+    c->bytesA += len;
+  c->idle = 0;
+}
+
+int main(int argc, char **argv) {
+  if (argc != 2) puts("invocation: ./pulserecord <interface>"), exit(1);
+  char *dev = argv[1];
+
+  setbuf(stdout, NULL);
+  char errbuf[PCAP_ERRBUF_SIZE];
+  printf("Device: %s\n", dev);
+
+  mkdir("out", 0700);
+  if (chdir("out")) perror("unable to enter directory 'out'"), exit(1);
+
+  // We use a zero-timeout. The pcap manual says:
+  // "to_ms is the read time out in milliseconds (a value of 0 means
+  //  no time out; on at least some platforms, this means that you may
+  //  wait until a sufficient number of packets arrive before seeing
+  //  any packets, so you should use a non-zero timeout)."
+  // That's simply not acceptable for us, so we can use a zero timeout
+  // just as well and tell everyone to use a sensible OS. :D
+  pcap_t *handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
+  if (!handle) printf("can't open device %s: %s\n", dev, errbuf), exit(1);
+  if (pcap_datalink(handle) != DLT_EN10MB)
+    printf("Device %s doesn't provide Ethernet headers - not supported\n", dev), exit(1);
+  if (pcap_setnonblock(handle, 1, errbuf) == -1) printf("unable to go nonblocking\n"), exit(1);
+  int pcap_fd = pcap_get_selectable_fd(handle);
+  if (pcap_fd == -1) printf("unable to get a pcap fd\n"), exit(1);
+
+  struct bpf_program fp; /* The compiled filter */
+  if (pcap_compile(handle, &fp, "tcp", 1, PCAP_NETMASK_UNKNOWN) == -1) {
+    printf("Couldn't parse filter: %s\n", pcap_geterr(handle));
+    exit(1);
+  }
+  if (pcap_setfilter(handle, &fp) == -1) {
+    printf("Couldn't install filter: %s\n", pcap_geterr(handle));
+    exit(1);
+  }
+
+  t = round_up(real_seconds(), 2);
+  even = (t&3) == 0; /* are we processing part 2/2 of one encoded bit? */
+  while (1) {
+    struct timespec dst, delta;
+    dst.tv_sec = t;
+    dst.tv_nsec = 0;
+
+    // this inner loop runs until we hit a 2s-boundary
+    while (1) {
+      fd_set rfds;
+      FD_ZERO(&rfds);
+      FD_SET(pcap_fd, &rfds);
+      struct timespec cur;
+      int r = clock_gettime(CLOCK_REALTIME, &cur);
+      assert(r==0);
+      if (timespec_subtract(&delta, &dst, &cur)) break;
+      r = pselect(pcap_fd+1, &rfds, NULL, NULL, &delta, NULL);
+      if (r == -1) perror("select failed"), exit(1);
+      if (r == 0) {
+        // timeout
+        break;
+      } else {
+        r = pcap_dispatch(handle, -1, packet_handler, NULL);
+        if (r < 0) printf("pcap_dispatch failed\n"), exit(1);
+      }
+    }
+
+    if (even) { // cycle complete
+      struct con *c, *tmp;
+      HASH_ITER(hh, cons, c, tmp) {
+        if (c->idle >= 10) {
+          HASH_DEL(cons, c);
+          free(c);
+          c = NULL; // just to be sure
+          continue;
+        }
+        char bit = '_';
+        if (!c->idle)
+          bit = (c->bytesA < c->bytesB) ? '1' : '0';
+        int fd = open(c->name, O_WRONLY|O_APPEND|O_CREAT, 0666);
+        if (fd == -1) {
+          perror("unable to open confile");
+        } else {
+w:;       ssize_t r = write(fd, &bit, 1);
+          if (r == -1 && errno == EINTR) goto w;
+          if (r == -1) perror("confile write failed");
+          if (r == 0) puts("confile write failed");
+          close(fd);
+        }
+        c->bytesA = 0;
+        c->bytesB = 0;
+        c->idle++;
+      }
+    }
+
+    t += 2;
+    even = !even;
+  }
+
+  return 0;
+}