From 90a89b12681db316c9beda36fe29dec9631d8de1 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sat, 7 Jun 2003 21:58:51 +0000 Subject: [PATCH] Working version of TCP analyser. --- netgrind/ip.c | 4 +-- netgrind/netgrind.c | 85 ++++++++++++++++++++++++++++++++++----------- netgrind/netgrind.h | 18 ++++++++-- netgrind/save.c | 49 +++++++++++++++++++++----- netgrind/tcp.c | 77 +++++++++++++++++++++++++++++----------- 5 files changed, 177 insertions(+), 56 deletions(-) diff --git a/netgrind/ip.c b/netgrind/ip.c index 0eff8e2..2368ead 100644 --- a/netgrind/ip.c +++ b/netgrind/ip.c @@ -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); } diff --git a/netgrind/netgrind.c b/netgrind/netgrind.c index a54797a..ca65746 100644 --- a/netgrind/netgrind.c +++ b/netgrind/netgrind.c @@ -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 #include #include +#include #include @@ -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 [] \n\ +\n\ +-a TCP: Record arrival times instead of processing times\n\ +-c Stop after processing packets\n\ +-d Dump connections to a given directory\n\ +-D Dump connections with more details\n\ +-f 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 "); + 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; } diff --git a/netgrind/netgrind.h b/netgrind/netgrind.h index ab899ec..d315260 100644 --- a/netgrind/netgrind.h +++ b/netgrind/netgrind.h @@ -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; diff --git a/netgrind/save.c b/netgrind/save.c index 80754bc..69c31da 100644 --- a/netgrind/save.c +++ b/netgrind/save.c @@ -14,6 +14,28 @@ #include #include #include +#include + +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); } diff --git a/netgrind/tcp.c b/netgrind/tcp.c index ab912d5..82e4e70 100644 --- a/netgrind/tcp.c +++ b/netgrind/tcp.c @@ -7,7 +7,7 @@ * of the GNU General Public License. */ -#define LOCAL_DEBUG +#undef LOCAL_DEBUG #include "lib/lib.h" #include "lib/heap.h" @@ -24,9 +24,15 @@ /*** 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: -- 2.39.5