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