}
if (len)
{
- csum += x[0];
+ csum += x[0] << 8;
if (csum & 0xffff0000)
{
csum &= 0x0000ffff;
uns tcpip_verify_checksum(uns csum)
{
- return 1;
- /* FIXME: Fix checksum calculation! */
return (csum == 0xffff);
}
* of the GNU General Public License.
*/
-/*
- * FIXME: TCP stats
- */
-
#include "lib/lib.h"
#include "netgrind/pkt.h"
#include "netgrind/netgrind.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
+#include <getopt.h>
#include <pcap.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",
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;
}
* 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;
/* 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 */
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 {
struct appl_hooks *appl;
void *appl_data;
struct pipe pipe[2];
+ struct pkt_stats stat_raw;
};
enum close_cause {
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;
#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];
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);
}
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);
}
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);
}
* 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;
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;
flow_hash[h] = f;
flow_heap[++num_flows] = f;
f->heap_pos = num_flows;
+ tcp_total_flows++;
return f;
}
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)
{
if (tcp_seq_le(p->seq + pkt_len(p), last_seq))
{
- DBG(" have\n");
+ DBG(" have");
pkt_free(p);
return;
}
p = new;
}
}
- DBG("\n");
}
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);
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" : ""),
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];
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)
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)
{
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;
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)
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;
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)
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
unex:
DBG("UNEXPECTED\n");
+ pkt_account(&stat_tcp_bad_state, p);
goto drop;
inval: