]> mj.ucw.cz Git - netgrind.git/blob - netgrind/http.c
Fixed mixup of HTTP connection/transaction numbering
[netgrind.git] / netgrind / http.c
1 /*
2  *      Netgrind -- HTTP Analyser
3  *
4  *      (c) 2003--2013 Martin Mares <mj@ucw.cz>
5  *
6  *      This software may be freely distributed and used according to the terms
7  *      of the GNU General Public License.
8  */
9
10 #undef LOCAL_DEBUG
11
12 #include "lib/lib.h"
13 #include "lib/pools.h"
14 #include "netgrind/pkt.h"
15 #include "netgrind/netgrind.h"
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <alloca.h>
21 #include <netinet/in.h>
22
23 #define MAXLINE 4096
24
25 struct http_header {
26   node n;
27   byte *name, *value;
28   byte buf[1];
29 };
30
31 struct http_state {
32   struct flow *flow;
33   enum {
34     HTTP_IDLE,                  /* initialized, waiting for request */
35     HTTP_ERROR,                 /* protocol error, ignoring everything else */
36     HTTP_CUT,                   /* unexpected EOF in one direction, ignoring everything else */
37     HTTP_REQUEST,               /* parsing request */
38     HTTP_BODY_CHUNKED,          /* receiving body: chunked encoding */
39     HTTP_BODY_LENGTH,           /* receiving body: length given */
40     HTTP_BODY_INF,              /* receiving body: till EOF */
41     HTTP_RESPONSE,              /* parsing response */
42     HTTP_DONE,                  /* transaction finished, logging it */
43     HTTP_CONNECT,               /* inside CONNECT transaction */
44   } state;
45   byte *error;
46   u64 req_start_time, resp_start_time;
47   uns conn_id;
48   struct mempool *pool;
49   list tx_queue, rx_queue;
50   uns xact_id;
51   byte *req_line, *resp_line;
52   list req_headers, resp_headers;
53   byte line[MAXLINE];
54   uns line_len;
55   uns body_len;
56   uns body_trailer;
57   list *body_queue;
58   uns body_end_state;
59   uns body_total_size;
60   uns req_counter;
61   FILE *log_file;
62 };
63
64 char *http_log_dir;
65
66 static uns http_conn_counter;
67 static uns http_xact_counter;
68
69 static void http_open(struct flow *f, u64 when)
70 {
71   struct http_state *s = xmalloc_zero(sizeof(*s));
72   s->flow = f;
73   f->appl_data = s;
74   s->conn_id = http_conn_counter++;
75   DBG("HTTP: %d NEW %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d\n", s->conn_id,
76       IPQUAD(f->saddr), ntohs(f->sport), IPQUAD(f->daddr), ntohs(f->dport));
77   list_init(&s->tx_queue);
78   list_init(&s->rx_queue);
79   s->req_start_time = when;
80 }
81
82 static byte *http_lookup_hdr(list *l, byte *name)
83 {
84   struct http_header *h;
85   WALK_LIST(h, *l)
86     if (!strcasecmp(h->name, name))
87       return h->value;
88   return NULL;
89 }
90
91 static uns find_token(byte *hay, byte *needle)
92 {
93   if (!hay)
94     return 0;
95   while (*hay)
96     {
97       if (*hay == ' ' || *hay == '\t' || *hay == ',')
98         hay++;
99       else
100         {
101           byte *h = hay;
102           while (*hay && *hay != ',' && *hay != ' ' && *hay != '\t')
103             hay++;
104           uns old = *hay;
105           *hay = 0;
106           uns found = !strcasecmp(h, needle);
107           *hay = old;
108           if (found)
109             return 1;
110         }
111     }
112   return 0;
113 }
114
115 static void http_log_start(struct http_state *s)
116 {
117   if (!http_log_dir)
118     return;
119
120   char name[256], stamp[TIMESTAMP_LEN];
121   struct flow *f = s->flow;
122
123   sprintf(name, "%s/%06u-%d.%d.%d.%d:%d-%d.%d.%d.%d:%d", http_log_dir, s->xact_id,
124     IPQUAD(f->saddr), ntohs(f->sport), IPQUAD(f->daddr), ntohs(f->dport));
125   if (!(s->log_file = fopen(name, "w")))
126     die("Unable to create %s: %m", name);
127
128   format_timestamp(stamp, s->req_start_time);
129   fprintf(s->log_file, "; [%s] From %d.%d.%d.%d:%d to %d.%d.%d.%d:%d (req %u)\n",
130     stamp, IPQUAD(f->saddr), ntohs(f->sport), IPQUAD(f->daddr), ntohs(f->dport),
131     s->req_counter + 1);
132 }
133
134 static void http_log_end(struct http_state *s)
135 {
136   if (!s->log_file)
137     return;
138   fclose(s->log_file);
139   s->log_file = NULL;
140 }
141
142 static void http_log_req_line(struct http_state *s, byte *line)
143 {
144   if (s->log_file)
145     fprintf(s->log_file, "> %s\n", line);
146 }
147
148 static void http_log_resp_line(struct http_state *s, byte *line)
149 {
150   if (s->log_file)
151     fprintf(s->log_file, "< %s\n", line);
152 }
153
154 static void http_log_body(struct http_state *s, byte *data, uns len)
155 {
156   if (s->log_file)
157     fwrite(data, len, 1, s->log_file);
158 }
159
160 static void http_report(struct flow *f, struct http_state *s, u64 when, byte *reason)
161 {
162   byte *method, *url, *x, *y, *stat;
163
164   if (!(method = s->req_line))
165     {
166       http_log_end(s);
167       return;
168     }
169
170   /* Analyse request line */
171   url = method;
172   while (*url && *url != ' ')
173     url++;
174   while (*url == ' ')
175     *url++ = 0;
176   x = url;
177   while (*x != ' ')
178     x++;
179   *x = 0;
180
181   /* Analyse response line */
182   if (stat = s->resp_line)
183     {
184       while (*stat && *stat != ' ')
185         stat++;
186       while (*stat == ' ')
187         stat++;
188       x = stat;
189       while (*x && *x != ' ')
190         x++;
191       *x = 0;
192     }
193   else
194     stat = "";
195   if (!reason)
196     reason = stat[0] ? stat : (byte*)"???";
197
198   /* Reconstruct full URL */
199   if (!strstr(url, "://") && strcasecmp(method, "CONNECT"))
200     {
201       if (!(x = http_lookup_hdr(&s->req_headers, "Host:")))
202         x = "???";
203       y = url;
204       url = alloca(7 + strlen(x) + strlen(y) + 1);
205       sprintf(url, "http://%s%s", x, y);
206     }
207   char *ffor = http_lookup_hdr(&s->req_headers, "X-Forwarded-For:");
208
209   /* Find out cacheability */
210   byte *rq_pragma = http_lookup_hdr(&s->req_headers, "Pragma:");
211   byte *rp_pragma = http_lookup_hdr(&s->resp_headers, "Pragma:");
212   byte *rq_cc = http_lookup_hdr(&s->req_headers, "Cache-control:");
213   byte *rp_cc = http_lookup_hdr(&s->resp_headers, "Cache-control:");
214   byte *rp_cache = http_lookup_hdr(&s->resp_headers, "X-Cache:");
215   uns rq_cflag, rp_cflag, rp_hit;
216   if (find_token(rq_pragma, "no-cache") || find_token(rq_cc, "no-cache"))
217     rq_cflag = 'N';
218   else if (find_token(rq_cc, "max-age=0") || find_token(rq_cc, "must-revalidate"))
219     rq_cflag = 'R';
220   else
221     rq_cflag = '.';
222   if (find_token(rp_pragma, "no-cache") || find_token(rp_cc, "no-cache"))
223     rp_cflag = 'N';
224   else if (find_token(rp_cc, "private"))
225     rp_cflag = 'P';
226   else if (find_token(rp_cc, "no-store"))
227     rp_cflag = 'S';
228   else if (find_token(rp_cc, "must-revalidate"))
229     rp_cflag = 'R';
230   else
231     rp_cflag = '.';
232   if (!rp_cache)
233     rp_hit = '.';
234   else if (!strncmp(rp_cache, "HIT ", 4))
235     rp_hit = '+';
236   else if (!strncmp(rp_cache, "MISS ", 5))
237     rp_hit = '-';
238   else
239     rp_hit = '?';
240
241   byte stamp[TIMESTAMP_LEN], src[22], dst[22];
242   sprintf(src, "%d.%d.%d.%d:%d", IPQUAD(f->saddr), ntohs(f->sport));
243   sprintf(dst, "%d.%d.%d.%d:%d", IPQUAD(f->daddr), ntohs(f->dport));
244   format_timestamp(stamp, s->req_start_time);
245   u64 ttotal = when - s->req_start_time;
246   u64 tresp = (s->resp_line ? (s->resp_start_time - s->req_start_time) : 0);
247   byte *ctype = (http_lookup_hdr(&s->resp_headers, "Content-type:") ? : http_lookup_hdr(&s->req_headers, "Content-type:")) ? : (byte*)"-";
248   byte *sep;
249   if (sep = strchr(ctype, ';'))
250     *sep = 0;
251   if (!s->xact_id)
252     printf("# id   timestamp               source                destination           forwarded-for   res cac que   length total time  wait time ctype      method URL\n");
253          /* 000000 2003-06-06 22:53:38.642 81.27.194.19:1175     205.217.153.53:80     123.123.123.123 200 ...   0    14030      0.957      0.444 text/plain GET http://... */
254   printf("%06u %s %-21s %-21s %-15s %-3s %c%c%c %3d %8d %6d.%03d %6d.%03d %-12s %s %s\n",
255          s->xact_id, stamp, src, dst, (ffor ? : "-"), reason,
256          rq_cflag, rp_cflag, rp_hit,
257          s->req_counter,
258          s->body_total_size,
259          (uns)(ttotal/1000000), (uns)(ttotal%1000000)/1000,
260          (uns)(tresp/1000000), (uns)(tresp%1000000)/1000,
261          ctype, method, url);
262
263   http_log_end(s);
264   s->req_counter++;
265 }
266
267 static void http_close(struct flow *f, int cause, u64 when)
268 {
269   struct http_state *s = f->appl_data;
270   DBG("HTTP: %d CLOSE in state %d (cause %d)\n", s->conn_id, s->state, cause);
271   if (cause != CAUSE_CLOSE)
272     {
273       if (s->state != HTTP_IDLE)
274         {
275           byte buf[16];
276           sprintf(buf, "T%s", flow_cause_names_short[cause]);
277           http_report(f, s, when, buf);
278         }
279     }
280   else
281     switch (s->state)
282       {
283       case HTTP_ERROR:
284         http_report(f, s, when, "ERR");
285         break;
286       case HTTP_CUT:
287         http_report(f, s, when, "CUT");
288         break;
289       case HTTP_CONNECT:
290         http_report(f, s, when, "FIN");
291         break;
292       default: ;
293       }
294   http_log_end(s);
295   pkt_flush_queue(&s->rx_queue);
296   pkt_flush_queue(&s->tx_queue);
297   if (s->pool)
298     mp_delete(s->pool);
299   xfree(s);
300 }
301
302 static struct http_header *http_get_line(struct http_state *s, list *l)
303 {
304   for(;;)
305     {
306       struct pkt *p = list_head(l);
307       if (!p)
308         return NULL;
309       while (p->data < p->stop)
310         {
311           uns c = *p->data++;
312           if (c == '\r')
313             ;
314           else if (c == '\n')
315             {
316               struct http_header *h = mp_alloc(s->pool, sizeof(*h) + s->line_len);
317               memcpy(h->buf, s->line, s->line_len);
318               h->buf[s->line_len] = 0;
319               h->name = h->value = NULL;
320               s->line_len = 0;
321               return h;
322             }
323           else if (s->line_len >= MAXLINE-1)
324             {
325               DBG("HTTP: Line too long!\n");
326               s->state = HTTP_ERROR;
327               return NULL;
328             }
329           else
330             s->line[s->line_len++] = c;
331         }
332       list_remove(&p->n);
333       pkt_free(p);
334     }
335 }
336
337 static int http_skip_body_bytes(struct http_state *s)
338 {
339   for(;;)
340     {
341       struct pkt *p = list_head(s->body_queue);
342       if (!p)
343         return 0;
344       uns avail = pkt_len(p);
345       uns want = s->body_len;
346       uns go = MIN(avail, want);
347       http_log_body(s, p->data, go);
348       p->data += go;
349       s->body_len -= go;
350       s->body_total_size += go;
351       if (!s->body_len)
352         return 1;
353       if (!pkt_len(p))
354         {
355           list_remove(&p->n);
356           pkt_free(p);
357         }
358     }
359 }
360
361 static int http_have_input(list *l)
362 {
363   for(;;)
364     {
365       struct pkt *p = list_head(l);
366       if (!p)
367         return 0;
368       if (pkt_len(p))
369         return 1;
370       list_remove(&p->n);
371       pkt_free(p);
372     }
373 }
374
375 static void http_init_xact(struct http_state *s)
376 {
377   list_init(&s->req_headers);
378   list_init(&s->resp_headers);
379   if (s->pool)
380     mp_flush(s->pool);
381   else
382     s->pool = mp_new(4096);
383   s->req_line = s->resp_line = NULL;
384   s->line_len = 0;
385   s->body_total_size = 0;
386   s->xact_id = http_xact_counter++;
387
388   http_log_start(s);
389 }
390
391 static void http_parse_hdr(list *l, struct http_header *h)
392 {
393   byte *x = h->buf;
394   h->name = x;
395   while (*x && *x != ' ' && *x != '\t')
396     x++;
397   while (*x == ' ' || *x == '\t')
398     *x++ = 0;
399   h->value = x;
400   list_add_tail(l, &h->n);
401 }
402
403 static int http_ask_body(struct http_state *s, list *hdr)
404 {
405   byte *x;
406   if (x = http_lookup_hdr(hdr, "Transfer-Encoding:"))
407     {
408       DBG("\tBody encoding: %s\n", x);
409       if (!strcasecmp(x, "chunked"))
410         {
411           s->state = HTTP_BODY_CHUNKED;
412           s->body_len = 0;
413           s->body_trailer = 0;
414         }
415       else
416         s->state = HTTP_ERROR;
417     }
418   else if (x = http_lookup_hdr(hdr, "Content-Length:"))
419     {
420       s->body_len = atol(x);
421       DBG("\tBody length: %d\n", s->body_len);
422       s->state = HTTP_BODY_LENGTH;
423     }
424   else
425     return 0;
426   return 1;
427 }
428
429 static void http_parse_req(struct http_state *s)
430 {
431   if (!strstr(s->req_line, " HTTP/1"))
432     {
433       DBG("\tNot a HTTP/1.x request!\n");
434       s->state = HTTP_ERROR;
435     }
436   else if (http_ask_body(s, &s->req_headers))
437     ;
438   else if (!strncasecmp(s->req_line, "POST ", 4))
439     {
440       DBG("\tPOST with no request body, that smells!\n");
441       s->state = HTTP_BODY_INF;
442     }
443   else
444     {
445       DBG("\tNo request body, awaiting reply\n");
446       s->state = HTTP_RESPONSE;
447     }
448   s->body_queue = &s->tx_queue;
449   s->body_end_state = HTTP_RESPONSE;
450 }
451
452 static void http_parse_resp(struct http_state *s)
453 {
454   if (!strncasecmp(s->req_line, "HEAD ", 5))
455     {
456       DBG("\tHEAD has no body :)\n");
457       s->state = HTTP_DONE;
458     }
459   else if (http_ask_body(s, &s->resp_headers))
460     ;
461   else if (!strncasecmp(s->req_line, "GET ", 4) && strstr(s->resp_line, " 200 "))
462     {
463       DBG("\tGET with no response body, that smells!\n");
464       s->state = HTTP_BODY_INF;
465     }
466   else
467     {
468       DBG("\tNo response body\n");
469       s->state = HTTP_DONE;
470     }
471   s->body_queue = &s->rx_queue;
472   s->body_end_state = HTTP_DONE;
473 }
474
475 static void http_input(struct flow *f, int dir, struct pkt *p)
476 {
477   struct http_state *s = f->appl_data;
478   struct http_header *h;
479   int fin_tx = (f->pipe[0].state == FLOW_FINISHED);
480   int fin_rx = (f->pipe[1].state == FLOW_FINISHED);
481
482   // DBG("dir=%d txf=%d rxf=%d len=%d\n", dir, fin_tx, fin_rx, pkt_len(p));
483   if (s->state == HTTP_ERROR || s->state == HTTP_CUT)
484     {
485       DBG("HTTP: %d DROPPING INPUT\n", s->conn_id);
486       pkt_free(p);
487       return;
488     }
489   if (pkt_len(p))
490     list_add_tail((dir ? &s->tx_queue : &s->rx_queue), &p->n);
491   for(;;)
492     {
493       DBG("HTTP: %d STATE %d\n", s->conn_id, s->state);
494       switch (s->state)
495         {
496         case HTTP_IDLE:
497           if (fin_tx || !http_have_input(&s->tx_queue))
498             return;
499           s->state = HTTP_REQUEST;
500           if (!s->req_start_time)
501             s->req_start_time = p->timestamp;
502           http_init_xact(s);
503           break;
504         case HTTP_REQUEST:
505           if (fin_tx || fin_rx)
506             goto cut;
507           if (!(h = http_get_line(s, &s->tx_queue)))
508             return;
509           DBG("\t>> %s\n", h->buf);
510           http_log_req_line(s, h->buf);
511           if (!s->req_line)
512             {
513               if (!h->buf[0])
514                 goto err;
515               s->req_line = h->buf;
516             }
517           else if (h->buf[0])
518             http_parse_hdr(&s->req_headers, h);
519           else
520             http_parse_req(s);
521           break;
522         case HTTP_BODY_LENGTH:
523           if (fin_rx)
524             goto cut;
525           if (!http_skip_body_bytes(s))
526             return;
527           DBG("\tEnd of body\n");
528           s->state = s->body_end_state;
529           break;
530         case HTTP_BODY_CHUNKED:
531           if (fin_rx)
532             goto cut;
533           if (s->body_len)
534             {
535               if (!http_skip_body_bytes(s))
536                 return;
537             }
538           else if (s->body_trailer)
539             {
540               if (!(h = http_get_line(s, s->body_queue)))
541                 return;
542               if (!h->buf[0])
543                 {
544                   DBG("\tEnd of chunk-encoded body\n");
545                   s->state = s->body_end_state;
546                 }
547             }
548           else
549             {
550               if (!(h = http_get_line(s, s->body_queue)))
551                 return;
552               if (sscanf(h->buf, "%x", &s->body_len) != 1)
553                 goto err;
554               if (s->body_len)
555                 s->body_len += 2; /* extra CRLF */
556               else                /* last chunk */
557                 s->body_trailer = 1;
558             }
559           break;
560         case HTTP_BODY_INF:
561           s->body_len = ~0U;
562           http_skip_body_bytes(s);
563           if (fin_rx)
564             {
565               DBG("\tEnd of FIN-delimited body\n");
566               s->state = s->body_end_state;
567             }
568           else
569             return;
570           break;
571         case HTTP_RESPONSE:
572           if (fin_rx)
573             goto cut;
574           if (!(h = http_get_line(s, &s->rx_queue)))
575             return;
576           DBG("\t<< %s\n", h->buf);
577           http_log_resp_line(s, h->buf);
578           if (!s->resp_line)
579             {
580               if (!h->buf[0])
581                 goto err;
582               s->resp_line = h->buf;
583               s->resp_start_time = p->timestamp;
584             }
585           else if (h->buf[0])
586             http_parse_hdr(&s->resp_headers, h);
587           else
588             http_parse_resp(s);
589           break;
590         case HTTP_DONE:
591           DBG("\tTransaction finished.\n");
592           if (!strncasecmp(s->req_line, "CONNECT ", 8))
593             {
594               s->state = HTTP_CONNECT;
595               return;
596             }
597           http_report(f, s, p->timestamp, NULL);
598           s->state = HTTP_IDLE;
599           s->req_start_time = 0;
600           break;
601         case HTTP_CONNECT:
602           s->body_len = ~0U;
603           s->body_queue = &s->rx_queue;
604           http_skip_body_bytes(s);
605           s->body_len = ~0U;
606           s->body_queue = &s->tx_queue;
607           http_skip_body_bytes(s);
608           return;
609         case HTTP_ERROR:
610         case HTTP_CUT:
611           return;
612         default:
613           ASSERT(0);
614         }
615     }
616
617  err:
618   DBG("HTTP: %d ERROR: PROTOCOL VIOLATION\n", s->conn_id);
619   s->state = HTTP_ERROR;
620   return;
621
622  cut:
623   DBG("HTTP: %d ERROR: UNEXPECTED EOF\n", s->conn_id);
624   s->state = HTTP_CUT;
625 }
626
627 struct appl_hooks appl_http = {
628   .open = http_open,
629   .input = http_input,
630   .close = http_close
631 };