From: Martin Mares Date: Sun, 8 Jun 2003 13:55:03 +0000 (+0000) Subject: HTTP parsing. Detecting partial closes. X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=fc551e50d9445824c5ccaa0e58c701152444f62b;p=netgrind.git HTTP parsing. Detecting partial closes. --- diff --git a/netgrind/http.c b/netgrind/http.c new file mode 100644 index 0000000..c6acd6b --- /dev/null +++ b/netgrind/http.c @@ -0,0 +1,400 @@ +/* + * Netgrind -- HTTP Analyser + * + * (c) 2003 Martin Mares + * + * This software may be freely distributed and used according to the terms + * of the GNU General Public License. + */ + +#define LOCAL_DEBUG + +#include "lib/lib.h" +#include "lib/pools.h" +#include "netgrind/pkt.h" +#include "netgrind/netgrind.h" + +#include +#include +#include +#include + +#define MAXLINE 4096 + +struct http_header { + node n; + byte *name, *value; + byte buf[1]; +}; + +struct http_state { + enum { + HTTP_INIT, /* initialized, waiting for request */ + HTTP_ERROR, /* protocol error, ignoring everything else */ + HTTP_CUT, /* unexpected EOF in one direction, ignoring everything else */ + HTTP_REQUEST, /* parsing request */ + HTTP_BODY_CHUNKED, /* receiving body: chunked encoding */ + HTTP_BODY_LENGTH, /* receiving body: length given */ + HTTP_BODY_INF, /* receiving body: till EOF */ + HTTP_RESPONSE, /* parsing response */ + HTTP_DONE, /* transaction finished, logging it */ + HTTP_CONNECT, /* inside CONNECT transaction */ + } state; + byte *error; + u64 init_time; + uns id; + struct mempool *pool; + list tx_queue, rx_queue; + byte *req_line, *resp_line; + list req_headers, resp_headers; + byte line[MAXLINE]; + uns line_len; + uns body_len; + uns body_trailer; + list *body_queue; + uns body_end_state; +}; + +static void http_open(struct flow *f, u64 when) +{ + static int http_counter; + struct http_state *s = xmalloc_zero(sizeof(*s)); + f->appl_data = s; + s->init_time = when; + s->id = http_counter++; + DBG("HTTP: %d NEW %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d\n", s->id, + IPQUAD(f->saddr), ntohs(f->sport), IPQUAD(f->daddr), ntohs(f->dport)); + list_init(&s->tx_queue); + list_init(&s->rx_queue); +} + +static void http_close(struct flow *f, int cause, u64 when) +{ + struct http_state *s = f->appl_data; + DBG("HTTP: %d CLOSE in state %d (cause %d)\n", s->id, s->state, cause); + pkt_flush_queue(&s->rx_queue); + pkt_flush_queue(&s->tx_queue); + if (s->pool) + mp_delete(s->pool); + xfree(s); +} + +static struct http_header *http_get_line(struct http_state *s, list *l) +{ + for(;;) + { + struct pkt *p = list_head(l); + if (!p) + return NULL; + while (p->data < p->stop) + { + uns c = *p->data++; + if (c == '\r') + ; + else if (c == '\n') + { + struct http_header *h = mp_alloc(s->pool, sizeof(*h) + s->line_len); + memcpy(h->buf, s->line, s->line_len); + h->buf[s->line_len] = 0; + h->name = h->value = NULL; + s->line_len = 0; + return h; + } + else if (s->line_len >= MAXLINE-1) + { + DBG("HTTP: Line too long!\n"); + s->state = HTTP_ERROR; + return NULL; + } + else + s->line[s->line_len++] = c; + } + list_remove(&p->n); + pkt_free(p); + } +} + +static int http_skip_bytes(list *l, uns *pcnt) +{ + for(;;) + { + struct pkt *p = list_head(l); + if (!p) + return 0; + uns avail = pkt_len(p); + uns want = *pcnt; + uns go = MIN(avail, want); + p->data += go; + *pcnt -= go; + if (!*pcnt) + return 1; + if (!pkt_len(p)) + { + list_remove(&p->n); + pkt_free(p); + } + } +} + +static int http_have_input(list *l) +{ + for(;;) + { + struct pkt *p = list_head(l); + if (!p) + return 0; + if (pkt_len(p)) + return 1; + list_remove(&p->n); + pkt_free(p); + } +} + +static void http_init_xact(struct http_state *s) +{ + list_init(&s->req_headers); + list_init(&s->resp_headers); + if (s->pool) + mp_flush(s->pool); + else + s->pool = mp_new(4096); + s->req_line = s->resp_line = NULL; + s->line_len = 0; +} + +static void http_parse_hdr(list *l, struct http_header *h) +{ + byte *x = h->buf; + h->name = x; + while (*x && *x != ' ' && *x != '\t') + x++; + while (*x == ' ' || *x == '\t') + *x++ = 0; + h->value = x; + list_add_tail(l, &h->n); +} + +static byte *http_lookup_hdr(list *l, byte *name) +{ + struct http_header *h; + WALK_LIST(h, *l) + if (!strcasecmp(h->name, name)) + return h->value; + return NULL; +} + +static int http_ask_body(struct http_state *s, list *hdr) +{ + byte *x; + if (x = http_lookup_hdr(hdr, "Transfer-Encoding:")) + { + DBG("\tBody encoding: %s\n", x); + if (!strcasecmp(x, "chunked")) + { + s->state = HTTP_BODY_CHUNKED; + s->body_len = 0; + s->body_trailer = 0; + } + else + s->state = HTTP_ERROR; + } + else if (x = http_lookup_hdr(hdr, "Content-Length:")) + { + s->body_len = atol(x); + DBG("\tBody length: %d\n", s->body_len); + s->state = HTTP_BODY_LENGTH; + } + else + return 0; + return 1; +} + +static void http_parse_req(struct http_state *s) +{ + if (!strstr(s->req_line, " HTTP/1")) + { + DBG("\tNot a HTTP/1.x request!\n"); + s->state = HTTP_ERROR; + } + else if (http_ask_body(s, &s->req_headers)) + ; + else if (!strncasecmp(s->req_line, "POST ", 4)) + { + DBG("\tPOST with no request body, that smells!\n"); + s->state = HTTP_BODY_INF; + } + else + { + DBG("\tNo request body, awaiting reply\n"); + s->state = HTTP_RESPONSE; + } + s->body_queue = &s->tx_queue; + s->body_end_state = HTTP_RESPONSE; +} + +static void http_parse_resp(struct http_state *s) +{ + if (http_ask_body(s, &s->resp_headers)) + ; + else if (!strncasecmp(s->req_line, "GET ", 4) && strstr(s->resp_line, " 200 ")) + { + DBG("\tGET with no response body, that smells!\n"); + s->state = HTTP_BODY_INF; + } + else + { + DBG("\tNo response body\n"); + s->state = HTTP_DONE; + } + s->body_queue = &s->rx_queue; + s->body_end_state = HTTP_DONE; +} + +static void http_input(struct flow *f, int dir, struct pkt *p) +{ + struct http_state *s = f->appl_data; + struct http_header *h; + int fin_tx = (f->pipe[0].state == FLOW_FINISHED); + int fin_rx = (f->pipe[1].state == FLOW_FINISHED); + + // DBG("dir=%d txf=%d rxf=%d len=%d\n", dir, fin_tx, fin_rx, pkt_len(p)); + if (s->state == HTTP_ERROR || s->state == HTTP_CUT) + { + DBG("HTTP: %d DROPPING INPUT\n", s->id); + pkt_free(p); + return; + } + if (pkt_len(p)) + list_add_tail((dir ? &s->tx_queue : &s->rx_queue), &p->n); + for(;;) + { + DBG("HTTP: %d STATE %d\n", s->id, s->state); + switch (s->state) + { + case HTTP_INIT: + if (fin_tx || !http_have_input(&s->tx_queue)) + return; + s->state = HTTP_REQUEST; + http_init_xact(s); + break; + case HTTP_REQUEST: + if (fin_tx || fin_rx) + goto cut; + if (!(h = http_get_line(s, &s->tx_queue))) + return; + DBG("\t>> %s\n", h->buf); + if (!s->req_line) + { + if (!h->buf[0]) + goto err; + s->req_line = h->buf; + } + else if (h->buf[0]) + http_parse_hdr(&s->req_headers, h); + else + http_parse_req(s); + break; + case HTTP_BODY_LENGTH: + if (fin_rx) + goto cut; + if (!http_skip_bytes(s->body_queue, &s->body_len)) + return; + DBG("\tEnd of body\n"); + s->state = s->body_end_state; + break; + case HTTP_BODY_CHUNKED: + if (fin_rx) + goto cut; + if (s->body_len) + { + if (!http_skip_bytes(s->body_queue, &s->body_len)) + return; + } + else if (s->body_trailer) + { + if (!(h = http_get_line(s, s->body_queue))) + return; + if (!h->buf[0]) + { + DBG("\tEnd of chunk-encoded body\n"); + s->state = s->body_end_state; + } + } + else + { + if (!(h = http_get_line(s, s->body_queue))) + return; + if (sscanf(h->buf, "%x", &s->body_len) != 1) + goto err; + if (s->body_len) + s->body_len += 2; /* extra CRLF */ + else /* last chunk */ + s->body_trailer = 1; + } + break; + case HTTP_BODY_INF: + s->body_len = ~0U; + http_skip_bytes(s->body_queue, &s->body_len); + if (fin_rx) + { + DBG("\tEnd of FIN-delimited body\n"); + s->state = s->body_end_state; + } + else + return; + break; + case HTTP_RESPONSE: + if (fin_rx) + goto cut; + if (!(h = http_get_line(s, &s->rx_queue))) + return; + DBG("\t<< %s\n", h->buf); + if (!s->resp_line) + { + if (!h->buf[0]) + goto err; + s->resp_line = h->buf; + } + else if (h->buf[0]) + http_parse_hdr(&s->resp_headers, h); + else + http_parse_resp(s); + break; + case HTTP_DONE: + DBG("\tTransaction finished.\n"); + if (!strncasecmp(s->req_line, "CONNECT ", 8)) + { + s->state = HTTP_CONNECT; + return; + } + s->state = HTTP_INIT; + break; + case HTTP_CONNECT: + s->body_len = ~0U; + http_skip_bytes(&s->rx_queue, &s->body_len); + s->body_len = ~0U; + http_skip_bytes(&s->tx_queue, &s->body_len); + return; + case HTTP_ERROR: + case HTTP_CUT: + return; + default: + ASSERT(0); + } + } + + err: + DBG("HTTP: %d ERROR: PROTOCOL VIOLATION\n", s->id); + s->state = HTTP_ERROR; + return; + + cut: + DBG("HTTP: %d ERROR: UNEXPECTED EOF\n", s->id); + s->state = HTTP_CUT; +} + +struct appl_hooks appl_http = { + .open = http_open, + .input = http_input, + .close = http_close +}; diff --git a/netgrind/netgrind.h b/netgrind/netgrind.h index db766a8..a524499 100644 --- a/netgrind/netgrind.h +++ b/netgrind/netgrind.h @@ -72,7 +72,7 @@ enum close_cause { struct appl_hooks { void (*open)(struct flow *f, u64 when); - void (*input)(struct flow *f, int dir, struct pkt *p); /* dir0 = sent by initiator */ + void (*input)(struct flow *f, int dir, struct pkt *p); /* dir0 = sent by initiator, pkt_len(p)==0 for close */ void (*close)(struct flow *f, int cause, u64 when); }; diff --git a/netgrind/save.c b/netgrind/save.c index 4a0be0d..c1bfe2a 100644 --- a/netgrind/save.c +++ b/netgrind/save.c @@ -145,6 +145,13 @@ static void asave_close(struct flow *f, int cause, u64 when) static void asave_input(struct flow *f, int dir, struct pkt *p) { struct asave_state *s = f->appl_data; + + if (!pkt_len(p)) + { + asave_event(s, p->timestamp, "%s FIN\n", (dir ? ">>" : "<<")); + return; + } + asave_event(s, p->timestamp, (dir ? ">>> " : "<<< ")); uns len = pkt_len(p); uns cnt = 0; diff --git a/netgrind/tcp.c b/netgrind/tcp.c index b90838d..7ca74ee 100644 --- a/netgrind/tcp.c +++ b/netgrind/tcp.c @@ -393,6 +393,9 @@ void tcp_got_packet(struct iphdr *iph, struct pkt *p) else if (b->state == FLOW_FIN_SENT && b->syn_or_fin_seq+1 == ack) { b->state = FLOW_FINISHED; + q = pkt_new(0, 0); + q->timestamp = p->timestamp; + f->appl->input(f, (b == &f->pipe[0]), q); if (a->state == FLOW_FINISHED) { DBG("CLOSED BOTH WAYS\n");