]> mj.ucw.cz Git - netgrind.git/commitdiff
Working version of TCP analyser.
authorMartin Mares <mj@ucw.cz>
Sat, 7 Jun 2003 21:58:51 +0000 (21:58 +0000)
committerMartin Mares <mj@ucw.cz>
Sat, 7 Jun 2003 21:58:51 +0000 (21:58 +0000)
netgrind/ip.c
netgrind/netgrind.c
netgrind/netgrind.h
netgrind/save.c
netgrind/tcp.c

index 0eff8e2d2935893fa86d3f6ab290f29149a40012..2368eada9a7d05db2f320ee9fa115434944b08ea 100644 (file)
@@ -32,7 +32,7 @@ uns tcpip_calc_checksum(void *data, uns len, uns csum)
     }
   if (len)
     {
-      csum += x[0];
+      csum += x[0] << 8;
       if (csum & 0xffff0000)
        {
          csum &= 0x0000ffff;
@@ -44,8 +44,6 @@ uns tcpip_calc_checksum(void *data, uns len, uns csum)
 
 uns tcpip_verify_checksum(uns csum)
 {
-  return 1;
-  /* FIXME: Fix checksum calculation! */
   return (csum == 0xffff);
 }
 
index a54797a74ea4341b6925a07459752cbc147d84bc..ca657465c162d2a1a0a56820df933cf03bbb922b 100644 (file)
@@ -7,10 +7,6 @@
  *     of the GNU General Public License.
  */
 
-/*
- *  FIXME: TCP stats
- */
-
 #include "lib/lib.h"
 #include "netgrind/pkt.h"
 #include "netgrind/netgrind.h"
@@ -19,6 +15,7 @@
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
+#include <getopt.h>
 
 #include <pcap.h>
 
@@ -61,31 +58,75 @@ static void got_pcap_packet(u_char *userdata UNUSED, const struct pcap_pkthdr *h
   link_handler(p);
 }
 
+static void usage(void)
+{
+  fprintf(stderr, "Usage: netgrind [<switches>] <capture-file>\n\
+\n\
+-a             TCP: Record arrival times instead of processing times\n\
+-c <count>     Stop after processing <count> packets\n\
+-d <dir>       Dump connections to a given directory\n\
+-D <dir>       Dump connections with more details\n\
+-f <filter>    Apply filter expression\n\
+-w             TCP: Wait for ACK before processing packets\n\
+");
+  exit(1);
+}
+
 int main(int argc, char **argv)
 {
   char errbuf[PCAP_ERRBUF_SIZE];
   pcap_t *pcap;
-  int dlt;
+  int c, dlt;
+  int max_packets = -1;
+  byte *filter = NULL;
+  struct bpf_program filter_prog;
 
-  if (argc != 2)
-    die("Usage: netgrind <capture-file>");
+  tcp_default_appl = &appl_sink;
+  while ((c = getopt(argc, argv, "ac:d:D:f:w")) >= 0)
+    switch (c)
+      {
+      case 'a':
+       tcp_arrival_times = 1;
+       break;
+      case 'c':
+       max_packets = atol(optarg);
+       break;
+      case 'd':
+       tcp_default_appl = &appl_save;
+       save_dir = optarg;
+       break;
+      case 'D':
+       tcp_default_appl = &appl_asave;
+       save_dir = optarg;
+       break;
+      case 'f':
+       filter = optarg;
+       break;
+      case 'w':
+       tcp_wait_for_ack = 1;
+       break;
+      default:
+       usage();
+      }
+  if (optind != argc - 1)
+    usage();
 
   tcp_init();
 
-  if (!(pcap = pcap_open_offline(argv[1], errbuf)))
-    die("Unable to open %s: %s", argv[1], errbuf);
+  if (!(pcap = pcap_open_offline(argv[optind], errbuf)))
+    die("Unable to open %s", errbuf);
   dlt = pcap_datalink(pcap);
   if (!link_setup_handler(dlt))
     die("Don't know how to handle data link type %d", dlt);
-  if (pcap_loop(pcap, -1, got_pcap_packet, NULL) < 0)
+  if (filter)
+    {
+      if (pcap_compile(pcap, &filter_prog, filter, 1, 0) < 0)
+       die("Error compiling filter: %s", pcap_geterr(pcap));
+      pcap_setfilter(pcap, &filter_prog);
+    }
+  if (pcap_loop(pcap, max_packets, got_pcap_packet, NULL) < 0)
     die("Capture failed: %s", pcap_geterr(pcap));
   tcp_cleanup();
-#if 0
-  struct pcap_stat stats;
-  if (pcap_stats(pcap, &stats))
-    die("pcap_stats: %s", pcap_geterr(pcap));
-  printf("libpcap stats: %d packets received, %d dropped\n", stats.ps_recv, stats.ps_drop);
-#endif
   printf("Pcap: %Ld(%Ld) incomplete\n",
         stat_pcap_incomplete.packets, stat_pcap_incomplete.bytes);
   printf("Link: %Ld(%Ld) in, %Ld(%Ld) dwarves, %Ld(%Ld) strangers, %Ld(%Ld) ARPs\n",
@@ -93,16 +134,20 @@ int main(int argc, char **argv)
         stat_link_dwarf.packets, stat_link_dwarf.bytes,
         stat_link_unknown.packets, stat_link_unknown.bytes,
         stat_link_arp.packets, stat_link_arp.bytes);
-  printf("IP: %Ld(%Ld) in, %Ld(%Ld) invalid, %Ld(%Ld) boring, %Ld(%Ld) fragmented, %Ld(%Ld) bad checksum\n",
+  printf("IP: %Ld(%Ld) in, %Ld(%Ld) invalid, %Ld(%Ld) boring, %Ld(%Ld) fragmented, %Ld(%Ld) bad checksum; %d flows\n",
         stat_ip_in.packets, stat_ip_in.bytes,
         stat_ip_invalid.packets, stat_ip_invalid.bytes,
         stat_ip_uninteresting.packets, stat_ip_uninteresting.bytes,
         stat_ip_fragmented.packets, stat_ip_fragmented.bytes,
-        stat_ip_badsum.packets, stat_ip_badsum.bytes);
-  printf("TCP: %Ld(%Ld) in, %Ld(%Ld) invalid, %Ld(%Ld) bad checksum\n",
+        stat_ip_badsum.packets, stat_ip_badsum.bytes,
+        tcp_total_flows);
+  printf("TCP: %Ld(%Ld) in, %Ld(%Ld) invalid, %Ld(%Ld) bad checksum, %Ld(%Ld) unmatched, %Ld(%Ld) on closed connections, %Ld(%Ld) in unexpected state\n",
         stat_tcp_in.packets, stat_tcp_in.bytes,
         stat_tcp_invalid.packets, stat_tcp_invalid.bytes,
-        stat_tcp_badsum.packets, stat_tcp_badsum.bytes);
+        stat_tcp_badsum.packets, stat_tcp_badsum.bytes,
+        stat_tcp_unmatched.packets, stat_tcp_unmatched.bytes,
+        stat_tcp_on_closed.packets, stat_tcp_on_closed.bytes,
+        stat_tcp_bad_state.packets, stat_tcp_bad_state.bytes);
   pcap_close(pcap);
   return 0;
 }
index ab899ec865741303df6447912f33326b6d189a5c..d31526070a382b38db55c875fe1622cadc5054ac 100644 (file)
@@ -7,6 +7,8 @@
  *     of the GNU General Public License.
  */
 
+#define IPQUAD(x) ((byte*)&(x))[0], ((byte*)&(x))[1], ((byte*)&(x))[2], ((byte*)&(x))[3]
+
 /* link.c */
 
 extern struct pkt_stats stat_link_dwarf, stat_link_in, stat_link_unknown, stat_link_arp;
@@ -24,12 +26,18 @@ void ip_got_packet(struct pkt *p);
 /* tcp.c */
 
 extern struct pkt_stats stat_tcp_in, stat_tcp_invalid, stat_tcp_badsum, stat_tcp_unmatched,
-  stat_tcp_on_closed;
+  stat_tcp_on_closed, stat_tcp_bad_state;
+extern uns tcp_total_flows;
+
+/* config switches */
+extern uns tcp_arrival_times;
+extern uns tcp_wait_for_ack;
 
 struct pipe {
   list queue;                          /* incoming packets */
   u32 last_acked_seq;                  /* last sequence number for which I sent ACK */
   u32 syn_or_fin_seq;                  /* sequence number of SYN/FIN I sent */
+  u32 queue_start_seq;                 /* sequence number expected at the start of the queue */
   enum {                               /* very simplified TCP state machine */
     FLOW_IDLE,
     FLOW_SYN_SENT,                     /* sent SYN, waiting for SYN ACK */
@@ -38,7 +46,7 @@ struct pipe {
     FLOW_FIN_SENT,                     /* sent FIN, waiting for its ACK */
     FLOW_FINISHED                      /* closed, ignoring further packets */
   } state;
-  struct pkt_stats stat_in;
+  struct pkt_stats stat;
 };
 
 struct flow {
@@ -49,6 +57,7 @@ struct flow {
   struct appl_hooks *appl;
   void *appl_data;
   struct pipe pipe[2];
+  struct pkt_stats stat_raw;
 };
 
 enum close_cause {
@@ -70,7 +79,10 @@ void tcp_init(void);
 void tcp_cleanup(void);
 void tcp_got_packet(struct iphdr *iph, struct pkt *p);
 
+extern struct appl_hooks *tcp_default_appl;
+
 /* save.c */
 
-extern struct appl_hooks appl_save, appl_asave;
+extern struct appl_hooks appl_sink, appl_save, appl_asave;
 extern uns asave_width;
+extern byte *save_dir;
index 80754bcdb3b68d91f6821f2fa4cf8d2207b0bf0c..69c31dae0d2fccc620208d981cb7996a7b413f45 100644 (file)
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
+#include <netinet/in.h>
+
+byte *save_dir;
+
+static void sink_open(struct flow *f UNUSED, u64 when UNUSED)
+{
+}
+
+static void sink_close(struct flow *f UNUSED, int cause UNUSED, u64 when UNUSED)
+{
+}
+
+static void sink_input(struct flow *f UNUSED, int dir UNUSED, struct pkt *p)
+{
+  pkt_free(p);
+}
+
+struct appl_hooks appl_sink = {
+  .open = sink_open,
+  .input = sink_input,
+  .close = sink_close
+};
 
 struct save_state {
   FILE *file[2];
@@ -26,7 +48,7 @@ static void save_open(struct flow *f, u64 when UNUSED)
   for (uns dir=0; dir<2; dir++)
     {
       byte name[256];
-      sprintf(name, "flows/%06u-%04x:%d-%04x:%d-%c", save_counter, f->saddr, f->sport, f->daddr, f->dport, 'A' + dir);
+      sprintf(name, "%s/%06u-%04x:%d-%04x:%d-%c", save_dir, save_counter, f->saddr, f->sport, f->daddr, f->dport, 'A' + dir);
       if (!(s->file[dir] = fopen(name, "w")))
        die("Unable to create %s: %m", name);
     }
@@ -67,8 +89,13 @@ static void asave_event(struct asave_state *s, u64 time, byte *msg, ...)
   va_list args;
 
   va_start(args, msg);
-  time -= s->start_time;
-  fprintf(s->file, "%04d.%03d ", (int)(time/1000000), (int)(time%1000000)%1000);
+  if (time == ~(u64)0)
+    fprintf(s->file, "????.??? ");
+  else
+    {
+      time -= s->start_time;
+      fprintf(s->file, "%04d.%03d ", (int)(time/1000000), (int)(time%1000000)/1000);
+    }
   vfprintf(s->file, msg, args);
   va_end(args);
 }
@@ -78,22 +105,26 @@ static void asave_open(struct flow *f, u64 when)
   struct asave_state *s = xmalloc(sizeof(*s));
   byte name[256];
   static uns asave_counter;
-  sprintf(name, "flows/%06u-%04x:%d-%04x:%d", asave_counter++, f->saddr, f->sport, f->daddr, f->dport);
+  sprintf(name, "%s/%06u-%04x:%d-%04x:%d", save_dir, asave_counter++, f->saddr, f->sport, f->daddr, f->dport);
   if (!(s->file = fopen(name, "w")))
     die("Unable to create %s: %m", name);
   f->appl_data = s;
   s->start_time = when;
-  asave_event(s, when, "Established\n");
+  asave_event(s, when, "Initiated: %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d\n",
+             IPQUAD(f->saddr), ntohs(f->sport), IPQUAD(f->daddr), ntohs(f->dport));
 }
 
 static void asave_close(struct flow *f, int cause, u64 when)
 {
   struct asave_state *s = f->appl_data;
-  static byte *close_reasons[] = { "Closed", "Connection reset", "Timed out", "Doomsday" };
-  asave_event(s, when, "%s\n", close_reasons[cause]);
+  static byte *close_reasons[] = { "Close", "Connection reset", "Timeout", "Doomsday" };
+  asave_event(s, when, "Terminated: %s\n", close_reasons[cause]);
   asave_event(s, when, "TX %Ld bytes in %Ld packets, RX %Ld bytes in %Ld packets\n",
-             f->pipe[0].
-XXXXXXXXXXXXXXXXXXXXX
+             f->pipe[1].stat.bytes, f->pipe[1].stat.packets,
+             f->pipe[0].stat.bytes, f->pipe[0].stat.packets);
+  asave_event(s, when, "Transferred %Ld bytes, TCP overhead %Ld bytes\n",
+             f->pipe[0].stat.bytes + f->pipe[1].stat.bytes,
+             f->stat_raw.bytes - (f->pipe[0].stat.bytes + f->pipe[1].stat.bytes));
   fclose(s->file);
   xfree(s);
 }
index ab912d521cd0221317d34ac160e1fc050a88b8d2..82e4e700ceb766b8062d44e579469eb2b0dfa272 100644 (file)
@@ -7,7 +7,7 @@
  *     of the GNU General Public License.
  */
 
-#define LOCAL_DEBUG
+#undef LOCAL_DEBUG
 
 #include "lib/lib.h"
 #include "lib/heap.h"
 /*** TCP LAYER ***/
 
 struct pkt_stats stat_tcp_in, stat_tcp_invalid, stat_tcp_badsum, stat_tcp_unmatched,
-  stat_tcp_on_closed;
+  stat_tcp_on_closed, stat_tcp_bad_state;
+uns tcp_total_flows;
+uns tcp_arrival_times, tcp_wait_for_ack;
 
+struct appl_hooks *tcp_default_appl;
+
+#ifdef LOCAL_DEBUG
 static byte *pipe_state_names[] = { "IDLE", "SYNSENT", "SYNACK", "ESTAB", "FINSENT", "FINISH" };
+#endif
 
 static uns num_flows, max_flows;
 static struct flow **flow_hash;
@@ -55,7 +61,7 @@ static void flow_rehash(void)
     max_flows = nextprime(2*max_flows);
   else
     max_flows = 3;
-  DBG("Rehashing to %d buckets\n", max_flows);
+  // DBG("Rehashing to %d buckets\n", max_flows);
   flow_hash = xmalloc_zero(sizeof(struct flow *) * max_flows);
   flow_heap = xmalloc_zero(sizeof(struct flow *) * (max_flows+1));
   num_flows = 0;
@@ -103,6 +109,7 @@ static struct flow *flow_create(u32 saddr, u32 daddr, u32 sport, u32 dport)
   flow_hash[h] = f;
   flow_heap[++num_flows] = f;
   f->heap_pos = num_flows;
+  tcp_total_flows++;
   return f;
 }
 
@@ -119,7 +126,10 @@ static uns flow_now(struct pkt *p)
 
 static u64 flow_now_to_time(uns now)
 {
-  return (u64)now << 20;
+  if (now == ~0U)
+    return ~(u64)0;
+  else
+    return (u64)now << 20;
 }
 
 static inline int tcp_seq_le(u32 a, u32 b)
@@ -177,7 +187,7 @@ static void tcp_enqueue_data(struct pipe *b, struct pkt *p)
        {
          if (tcp_seq_le(p->seq + pkt_len(p), last_seq))
            {
-             DBG(" have\n");
+             DBG(" have");
              pkt_free(p);
              return;
            }
@@ -212,7 +222,6 @@ static void tcp_enqueue_data(struct pipe *b, struct pkt *p)
          p = new;
        }
     }
-  DBG("\n");
 }
 
 void tcp_got_packet(struct iphdr *iph, struct pkt *p)
@@ -225,6 +234,7 @@ void tcp_got_packet(struct iphdr *iph, struct pkt *p)
     byte proto;
     u16 len;
   } fakehdr;
+  struct pkt *q;
   uns now = flow_now(p);
 
   tcp_time_step(now);
@@ -248,12 +258,12 @@ void tcp_got_packet(struct iphdr *iph, struct pkt *p)
       goto drop;
     }
   /* XXX: Check TCP options? */
-  pkt_pop(p, hdrlen);
 
   u32 seq = ntohl(tcph->seq);
   u32 ack = ntohl(tcph->ack_seq);
-  DBG("TCP %08x %08x %04x %04x seq=%u+%u ack=%u%s%s%s%s%s%s\n",
-      ntohl(iph->saddr), ntohl(iph->daddr), ntohs(tcph->source), ntohs(tcph->dest), seq, pkt_len(p), ack,
+  DBG("TCP %08x %08x %04x %04x seq=%u len=%u end=%u ack=%u%s%s%s%s%s%s\n",
+      ntohl(iph->saddr), ntohl(iph->daddr), ntohs(tcph->source), ntohs(tcph->dest),
+      seq, pkt_len(p) - hdrlen, seq + pkt_len(p) - hdrlen, ack,
       (tcph->fin ? " FIN" : ""),
       (tcph->syn ? " SYN" : ""),
       (tcph->rst ? " RST" : ""),
@@ -279,7 +289,7 @@ void tcp_got_packet(struct iphdr *iph, struct pkt *p)
       if (tcph->syn && !tcph->ack && !tcph->rst && !tcph->fin)
        {
          f = flow_create(iph->saddr, iph->daddr, tcph->source, tcph->dest);
-         f->appl = &appl_asave;
+         f->appl = tcp_default_appl;
          f->appl->open(f, p->timestamp);
          a = &f->pipe[0];
          b = &f->pipe[1];
@@ -289,12 +299,15 @@ void tcp_got_packet(struct iphdr *iph, struct pkt *p)
          list_init(&b->queue);
          b->state = FLOW_IDLE;
          DBG("\t%p NEW\n", f);
+         pkt_account(&f->stat_raw, p);
          goto drop;
        }
       DBG("\tUnmatched\n");
       pkt_account(&stat_tcp_unmatched, p);
       goto drop;
     }
+  pkt_account(&f->stat_raw, p);
+  pkt_pop(p, hdrlen);
 
   DBG("\t%p %s (%s/%s) ", f, (a == &f->pipe[0] ? "A->B" : "B->A"), pipe_state_names[f->pipe[0].state], pipe_state_names[f->pipe[1].state]);
   if (a->state == FLOW_FINISHED && b->state == FLOW_FINISHED)
@@ -309,11 +322,11 @@ void tcp_got_packet(struct iphdr *iph, struct pkt *p)
       DBG("RESET\n");
       f->appl->close(f, CAUSE_RESET, p->timestamp);
       a->state = b->state = FLOW_FINISHED;
-      flow_set_timeout(f, now + 300); /* FIXME */
+      flow_set_timeout(f, now + 120);
       goto drop;
     }
 
-  flow_set_timeout(f, now + 600); /* FIXME */
+  flow_set_timeout(f, now + 3000);     /* Somewhat arbitrary timeout */
 
   if (tcph->syn)
     {
@@ -326,6 +339,7 @@ void tcp_got_packet(struct iphdr *iph, struct pkt *p)
              DBG("SYN ACK\n");
              a->last_acked_seq = ack;
              a->syn_or_fin_seq = seq;
+             a->queue_start_seq = ack;
              a->state = FLOW_SYN_SENT_ACK;
              b->last_acked_seq = seq;
              goto drop;
@@ -345,19 +359,24 @@ void tcp_got_packet(struct iphdr *iph, struct pkt *p)
        DBG("DUP ACK, ");
       else
        {
-         struct pkt *q;
          a->last_acked_seq = ack;
-         while ((q = list_head(&a->queue)) && tcp_seq_le(q->seq+pkt_len(q), ack))
+         if (tcp_wait_for_ack)
            {
-             list_remove(&q->n);
-             q->timestamp = p->timestamp;
-             DBG("data(%Ld-%Ld), ", a->stat_in.bytes, a->stat_in.bytes+pkt_len(q)-1);
-             pkt_account(&a->stat_in, q);
-             f->appl->input(f, (a == &f->pipe[0]), q);
+             while ((q = list_head(&a->queue)) && tcp_seq_le(q->seq+pkt_len(q), ack))
+               {
+                 list_remove(&q->n);
+                 a->queue_start_seq = q->seq + pkt_len(q);
+                 if (!tcp_arrival_times)
+                   q->timestamp = p->timestamp;
+                 DBG("data(%Ld-%Ld), ", a->stat.bytes, a->stat.bytes+pkt_len(q)-1);
+                 pkt_account(&a->stat, q);
+                 f->appl->input(f, (a == &f->pipe[0]), q);
+               }
            }
          if (b->state == FLOW_SYN_SENT_ACK && b->syn_or_fin_seq+1 == ack)
            {
              a->state = b->state = FLOW_ESTABLISHED;
+             a->queue_start_seq = ack;
              DBG("ACKED SYN, ");
            }
          else if (b->state == FLOW_FIN_SENT && b->syn_or_fin_seq+1 == ack)
@@ -373,7 +392,7 @@ void tcp_got_packet(struct iphdr *iph, struct pkt *p)
              else
                DBG("CLOSED ONE-WAY, ");
            }
-         else if ((q = list_head(&a->queue)) && tcp_seq_lt(ack, q->seq))
+         else if (tcp_seq_lt(a->queue_start_seq, ack))
            {
              DBG("DAMNED, ACK FOR UNCAUGHT DATA!\n");
              goto invalid;
@@ -388,7 +407,7 @@ void tcp_got_packet(struct iphdr *iph, struct pkt *p)
       if (a->state == FLOW_ESTABLISHED)
        {
          a->state = FLOW_FIN_SENT;
-         a->syn_or_fin_seq = seq;
+         a->syn_or_fin_seq = seq + pkt_len(p);
          DBG("FIN SENT, waiting for FIN ACK, ");
        }
       else if (a->state == FLOW_FIN_SENT)
@@ -405,8 +424,23 @@ void tcp_got_packet(struct iphdr *iph, struct pkt *p)
 
   if (b->state == FLOW_ESTABLISHED || b->state == FLOW_FIN_SENT || b->state == FLOW_FINISHED)
     {
+      u64 arrival = p->timestamp;
       p->seq = seq;
       tcp_enqueue_data(b, p);
+      if (!tcp_wait_for_ack)
+       {
+         while ((q = list_head(&b->queue)) && q->seq == b->queue_start_seq)
+           {
+             list_remove(&q->n);
+             if (!tcp_arrival_times)
+               q->timestamp = arrival;
+             DBG(", data(%Ld-%Ld)", b->stat.bytes, b->stat.bytes+pkt_len(q)-1);
+             pkt_account(&b->stat, q);
+             b->queue_start_seq += pkt_len(q);
+             f->appl->input(f, (b == &f->pipe[1]), q);
+           }
+       }
+      DBG("\n");
       return;
     }
   else
@@ -422,6 +456,7 @@ void tcp_got_packet(struct iphdr *iph, struct pkt *p)
 
  unex:
   DBG("UNEXPECTED\n");
+  pkt_account(&stat_tcp_bad_state, p);
   goto drop;
 
  inval: