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