]> mj.ucw.cz Git - netgrind.git/commitdiff
HTTP parsing. Detecting partial closes.
authorMartin Mares <mj@ucw.cz>
Sun, 8 Jun 2003 13:55:03 +0000 (13:55 +0000)
committerMartin Mares <mj@ucw.cz>
Sun, 8 Jun 2003 13:55:03 +0000 (13:55 +0000)
netgrind/http.c [new file with mode: 0644]
netgrind/netgrind.h
netgrind/save.c
netgrind/tcp.c

diff --git a/netgrind/http.c b/netgrind/http.c
new file mode 100644 (file)
index 0000000..c6acd6b
--- /dev/null
@@ -0,0 +1,400 @@
+/*
+ *     Netgrind -- HTTP Analyser
+ *
+ *     (c) 2003 Martin Mares <mj@ucw.cz>
+ *
+ *     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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+
+#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
+};
index db766a89e786af1b856f2bd5c28812c343e94f31..a524499929f53eae9d81f47f5236f798b59ee540 100644 (file)
@@ -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);
 };
 
index 4a0be0da0df6022ef7bef02c8408ddf8d36e5943..c1bfe2abc3c683ec1765bd094ec01d3ef28cb37d 100644 (file)
@@ -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;
index b90838d513c32fbe591f6cd375963c6f0653c0bc..7ca74ee4c44a9460de918279f53b0f5ace30f826 100644 (file)
@@ -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");