/* * Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, * 2002, 2003, 2004 * Ohio University. * * --- * * Starting with the release of tcptrace version 6 in 2001, tcptrace * is licensed under the GNU General Public License (GPL). We believe * that, among the available licenses, the GPL will do the best job of * allowing tcptrace to continue to be a valuable, freely-available * and well-maintained tool for the networking community. * * Previous versions of tcptrace were released under a license that * was much less restrictive with respect to how tcptrace could be * used in commercial products. Because of this, I am willing to * consider alternate license arrangements as allowed in Section 10 of * the GNU GPL. Before I would consider licensing tcptrace under an * alternate agreement with a particular individual or company, * however, I would have to be convinced that such an alternative * would be to the greater benefit of the networking community. * * --- * * This file is part of Tcptrace. * * Tcptrace was originally written and continues to be maintained by * Shawn Ostermann with the help of a group of devoted students and * users (see the file 'THANKS'). The work on tcptrace has been made * possible over the years through the generous support of NASA GRC, * the National Science Foundation, and Sun Microsystems. * * Tcptrace is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Tcptrace is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tcptrace (in the file 'COPYING'); if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * Author: Shawn Ostermann * School of Electrical Engineering and Computer Science * Ohio University * Athens, OH * ostermann@cs.ohiou.edu * http://www.tcptrace.org/ */ #include "tcptrace.h" static char const GCC_UNUSED rcsid[] = "$Header: /usr/local/cvs/tcptrace/mod_http.c,v 5.13 2003/11/19 14:38:02 sdo Exp $"; #ifdef LOAD_MODULE_HTTP #include #include "mod_http.h" #define DEFAULT_SERVER_PORT 80 /* Revised HTTP module with a new HTTP parser provided by Bruce Mah */ /* codes for different message types */ typedef enum { MethodCodeOptions, MethodCodeGet, MethodCodeHead, MethodCodePost, MethodCodePut, MethodCodeDelete, MethodCodeTrace, MethodCodeUnknown } MethodCode; char *MethodCodeString[] = { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE" }; /* info gathered for each GET */ struct get_info { timeval get_time; /* when CLIENT sent GET */ timeval send_time; /* when SERVER sent CONTENT */ timeval lastbyte_time; /* when SERVER sent last byte of CONTENT */ timeval ack_time; /* when CLIENT acked CONTENT */ unsigned request_position; /* byte offset for this request */ unsigned reply_position; /* byte offset for this reply */ MethodCode method; /* HTTP method code */ unsigned response_code; /* HTTP response code */ unsigned content_length; /* as reported by server */ char *get_string; /* content of GET string */ char *content_type; /* MIME type */ struct get_info *next; }; /* linked list of times */ struct time_stamp { timeval thetime; u_long position; struct time_stamp *next; struct time_stamp *prev; }; /* info kept for each client */ static struct client_info { PLOTTER plotter; char *clientname; struct client_info *next; } *client_head = NULL; /* info kept for each connection */ static struct http_info { timeval c_syn_time; /* when CLIENT sent SYN */ timeval s_syn_time; /* when SERVER sent SYN */ timeval c_fin_time; /* when CLIENT sent FIN */ timeval s_fin_time; /* when SERVER sent FIN */ /* client record */ struct client_info *pclient; /* info about the TCP connection */ tcp_pair *ptp; tcb *tcb_client; tcb *tcb_server; /* aggregate statistics for HTTP requests on this connection*/ /* some of this info is available in *tcb_client and *tcb_server */ /* but we keep a copy of it here to keep all useful info in one place */ unsigned total_request_length; unsigned total_reply_length; unsigned total_request_count; unsigned total_reply_count; /* when querries (GETs) were sent by client */ struct time_stamp get_head; struct time_stamp get_tail; /* when answers (CONTENT) were sent by server */ struct time_stamp data_head; struct time_stamp data_tail; /* when answers (CONTENT) were acked by client */ struct time_stamp ack_head; struct time_stamp ack_tail; /* linked list of requests */ struct get_info *gets_head; struct get_info *gets_tail; struct http_info *next; } *httphead = NULL, *httptail = NULL; /* which port are we monitoring?? */ static unsigned httpd_port; /* local routines */ static timeval WhenSent(struct time_stamp *phead, struct time_stamp *ptail, u_long position); static timeval WhenAcked(struct time_stamp *phead, struct time_stamp *ptail, u_long position); static void MFUnMap(MFILE *mf, char *firstbyte); static void MFMap(MFILE *mf, char **firstbyte, char **lastbyte); static void FindGets(struct http_info *ph); static void FindContent(struct http_info *ph); static void HttpGather(struct http_info *ph); static struct http_info *MakeHttpRec(void); static struct get_info *MakeGetRec(struct http_info *ph); static u_long DataOffset(tcb *tcb, seqnum seq); static void AddGetTS(struct http_info *ph, u_long position); static void AddDataTS(struct http_info *ph, u_long position); static void AddAckTS(struct http_info *ph, u_long position); static void AddTS(struct time_stamp *phead, struct time_stamp *ptail, u_long position); static double ts2d(timeval *pt); static void HttpPrintone(MFILE *pmf, struct http_info *ph); static void HttpDoPlot(void); static struct client_info *FindClient(char *clientname); /* useful macros */ #define IS_CLIENT(ptcp) (ntohs((ptcp)->th_dport) == httpd_port) #define IS_SERVER(ptcp) (ntohs((ptcp)->th_dport) != httpd_port) /* Mostly as a module example, here's a plug in that records HTTP info */ int http_init( int argc, char *argv[]) { int i; int enable=0; /* look for "-xhttp[N]" */ for (i=1; i < argc; ++i) { if (!argv[i]) continue; /* argument already taken by another module... */ if (strncmp(argv[i],"-x",2) == 0) { if (strncasecmp(argv[i]+2,"http",4) == 0) { /* I want to be called */ enable = 1; if (isdigit((int)(argv[i][6]))) { httpd_port = atoi(argv[i]+6); } else { httpd_port = DEFAULT_SERVER_PORT; } printf("mod_http: Capturing HTTP traffic (port %d)\n", httpd_port); argv[i] = NULL; } } } if (!enable) return(0); /* don't call me again */ /* init stuff */ /* We need to save the contents for accurate reconstruction of questions */ /* and answers */ save_tcp_data = TRUE; return(1); /* TRUE means call http_read and http_done later */ } /* N.B. first byte is position _1_ */ static u_long DataOffset( tcb *tcb, seqnum seq) { u_long off; /* we're going to be a little lazy and assume that a http connection */ /* can't be longer than 2**32 */ if (seq > tcb->syn) off = seq-tcb->syn; else off = tcb->syn-seq; if (debug>1) fprintf(stderr,"DataOffset: seq is %lu, syn is %lu, offset is %ld\n", seq, tcb->syn, off); return(off); } static struct get_info * MakeGetRec( struct http_info *ph) { struct get_info *pg; pg = MallocZ(sizeof(struct get_info)); /* initialize some fields */ pg->get_string = "- -"; pg->content_type = "-/-"; /* put at the end of the chain */ if (ph->gets_head == NULL) { ph->gets_head = pg; ph->gets_tail = pg; } else { ph->gets_tail->next = pg; ph->gets_tail = pg; } return(pg); } static struct http_info * MakeHttpRec() { struct http_info *ph; ph = MallocZ(sizeof(struct http_info)); ph->get_head.next = &ph->get_tail; ph->get_tail.prev = &ph->get_head; ph->get_tail.position = 0xffffffff; ph->data_head.next = &ph->data_tail; ph->data_tail.prev = &ph->data_head; ph->data_tail.position = 0xffffffff; ph->ack_head.next = &ph->ack_tail; ph->ack_tail.prev = &ph->ack_head; ph->ack_tail.position = 0xffffffff; /* chain it in (at the tail of the list) */ if (httphead == NULL) { httphead = ph; httptail = ph; } else { httptail->next = ph; httptail = ph; } return(ph); } static void AddGetTS( struct http_info *ph, u_long position) { AddTS(&ph->get_head,&ph->get_tail,position); } static void AddDataTS( struct http_info *ph, u_long position) { AddTS(&ph->data_head,&ph->data_tail,position); } static void AddAckTS( struct http_info *ph, u_long position) { AddTS(&ph->ack_head,&ph->ack_tail,position); } /* add a timestamp to the record */ /* HEAD points to the smallest position numbers */ /* TAIL points to the largest position numbers */ static void AddTS( struct time_stamp *phead, struct time_stamp *ptail, u_long position) { struct time_stamp *pts; struct time_stamp *pts_new; pts_new = MallocZ(sizeof(struct time_stamp)); pts_new->thetime = current_time; pts_new->position = position; for (pts = ptail->prev; pts != NULL; pts = pts->prev) { if (position == pts->position) return; /* ignore duplicates */ if (position > pts->position) { /* it goes AFTER this one (pts) */ pts_new->next = pts->next; pts_new->prev = pts; pts->next = pts_new; pts_new->next->prev = pts_new; return; } } /* can't fail, the tail has timestamp 0 */ } static struct client_info * FindClient( char *clientname) { struct client_info *p; for (p=client_head; p; p = p->next) { if (strcmp(clientname,p->clientname)==0) { return(p); } } /* else, make one up */ p = MallocZ(sizeof(struct client_info)); p->next = client_head; client_head = p; p->clientname = strdup(clientname); p->plotter = NO_PLOTTER; return(p); } void http_read( struct ip *pip, /* the packet */ tcp_pair *ptp, /* info I have about this connection */ void *plast, /* past byte in the packet */ void *mod_data) /* module specific info for this connection */ { struct tcphdr *ptcp; unsigned tcp_length; unsigned tcp_data_length; char *pdata; struct http_info *ph = mod_data; /* find the start of the TCP header */ ptcp = (struct tcphdr *) ((char *)pip + 4*IP_HL(pip)); tcp_length = ntohs(pip->ip_len) - (4 * IP_HL(pip)); tcp_data_length = tcp_length - (4 * TH_OFF(ptcp)); /* verify port */ if ((ntohs(ptcp->th_sport) != httpd_port) && (ntohs(ptcp->th_dport) != httpd_port)) return; /* find the data */ pdata = (char *)ptcp + (unsigned)TH_OFF(ptcp)*4; /* for client, record both ACKs and DATA time stamps */ if (ph && IS_CLIENT(ptcp)) { if (tcp_data_length > 0) { AddGetTS(ph,DataOffset(ph->tcb_client,ntohl(ptcp->th_seq))); } if (ACK_SET(ptcp)) { if (debug > 4) printf("Client acks %ld\n", DataOffset(ph->tcb_server,ntohl(ptcp->th_ack))); AddAckTS(ph,DataOffset(ph->tcb_server,ntohl(ptcp->th_ack))); } } /* for server, record DATA time stamps */ if (ph && IS_SERVER(ptcp)) { if (tcp_data_length > 0) { AddDataTS(ph,DataOffset(ph->tcb_server,ntohl(ptcp->th_seq))); if (debug > 5) { printf("Server sends %ld thru %ld\n", DataOffset(ph->tcb_server,ntohl(ptcp->th_seq)), DataOffset(ph->tcb_server,ntohl(ptcp->th_seq))+tcp_data_length-1); } } } /* we also want the time that the FINs were sent */ if (ph && FIN_SET(ptcp)) { if (IS_SERVER(ptcp)) { /* server */ if (ZERO_TIME(&(ph->s_fin_time))) ph->s_fin_time = current_time; } else { /* client */ if (ZERO_TIME(&ph->c_fin_time)) ph->c_fin_time = current_time; } } /* we also want the time that the SYNs were sent */ if (ph && SYN_SET(ptcp)) { if (IS_SERVER(ptcp)) { /* server */ if (ZERO_TIME(&ph->s_syn_time)) ph->s_syn_time = current_time; } else { /* client */ if (ZERO_TIME(&ph->c_syn_time)) ph->c_syn_time = current_time; } } } static double ts2d(timeval *pt) { double d; d = pt->tv_sec; d += (double)pt->tv_usec/1000000; return(d); } static void MFMap( MFILE *mf, char **firstbyte, char **lastbyte) { int fd; char *vaddr; int len; /* find length of file */ if (Mfseek(mf,0,SEEK_END) != 0) { perror("fseek"); exit(-1); } len = Mftell(mf); if (len == 0) { *firstbyte = NULL; *lastbyte = NULL; return; } /* Memory map the entire file */ fd = Mfileno(mf); vaddr = mmap((caddr_t) 0, /* put it anywhere */ len, /* fixed size */ PROT_READ, /* read only */ MAP_PRIVATE, /* won't be sharing... */ fd, /* attach to 'fd' */ (off_t) 0); /* ... offset 0 in 'fd' */ if (vaddr == (char *) -1) { perror("mmap"); exit(-1); } *firstbyte = vaddr; *lastbyte = vaddr+len-1; return; } static void MFUnMap( MFILE *mf, char *firstbyte) { int fd; int len; /* find length of file */ if (Mfseek(mf,0,SEEK_END) != 0) { perror("fseek"); exit(-1); } len = Mftell(mf); /* unmap it */ fd = Mfileno(mf); if (munmap(firstbyte,len) != 0) { perror("munmap"); exit(-1); } return; } static void HttpGather( struct http_info *ph) { while (ph) { if (ph->tcb_client->extr_contents_file && ph->tcb_server->extr_contents_file) { FindGets(ph); FindContent(ph); } ph = ph->next; } } static void PrintTSChain( struct time_stamp *phead, struct time_stamp *ptail) { struct time_stamp *pts; for (pts = phead->next; pts != ptail; pts = pts->next) { printf("Pos: %ld time: %s\n", pts->position, ts2ascii(&pts->thetime)); } } /* when was the byte at offset "position" acked?? */ /* return the timeval for the record of the smallest position >= "position" */ static timeval WhenAcked( struct time_stamp *phead, struct time_stamp *ptail, u_long position) { struct time_stamp *pts; timeval epoch = {0,0}; if (debug > 10) { printf("pos:%ld, Chain:\n", position); PrintTSChain(phead,ptail); } for (pts = phead->next; pts != NULL; pts = pts->next) { /* fprintf(stderr,"Checking pos %ld against %ld\n", */ /* position, pts->position); */ if (pts->position >= position) { /* sent after this one */ return(pts->thetime); } } /* fails if we can't find it */ return(epoch); } /* when was the byte at offset "position" sent?? */ /* return the timeval for the record of the largest position <= "position" */ static timeval WhenSent( struct time_stamp *phead, struct time_stamp *ptail, u_long position) { struct time_stamp *pts; timeval epoch = {0,0}; if (debug > 10) { printf("pos:%ld, Chain:\n", position); PrintTSChain(phead,ptail); } for (pts = ptail->prev; pts != phead; pts = pts->prev) { /* fprintf(stderr,"Checking pos %ld against %ld\n", */ /* position, pts->position); */ if (pts->position <= position) { /* sent after this one */ return(pts->thetime); } } /* fails if we can't find it */ return(epoch); } static void FindContent( struct http_info *ph) { tcb *tcb = ph->tcb_server; MFILE *mf = tcb->extr_contents_file; char *pdata; char *plast; char *pch; char *pch2; struct get_info *pget; char getbuf[1024]; u_long position = 0; unsigned last_position = 0; int done; int i; typedef enum { ContentStateStartHttp, ContentStateFinishHttp, ContentStateFindResponse, ContentStateFindContentLength, ContentStateFinishHeader} StateType; StateType state; pget = ph->gets_head; if ((mf) && (pget)) { state = ContentStateStartHttp; done = 0; /* Memory map the entire file (I hope it's short!) */ MFMap(mf,&pdata,&plast); if (pdata == NULL) { return; } pch = pdata; ph->total_reply_length = (unsigned) (plast - pdata); ph->total_reply_count= 0; while ((!done) && (pch <= (char *) plast)) { switch (state) { /* Start state: Find "HTTP/" that begins a response */ case (ContentStateStartHttp): { if (strncasecmp(pch, "HTTP/", 5) == 0) { /* Found start of a response */ state = ContentStateFinishHttp; position = pch - pdata + 1; pget->reply_position = position; pch += 5; } else { pch++; } } break; /* Finish off HTTP string (version number) by looking for whitespace */ case (ContentStateFinishHttp): { if (*(pch) == ' ') { state = ContentStateFindResponse; } else { pch++; } } break; /* Look for response code by finding non-whitespace. */ case (ContentStateFindResponse): { if (*(pch) != ' ') { pget->response_code = atoi(pch); pch += 3; state = ContentStateFindContentLength; } else { pch++; } } break; /* this state is now misnamed since we pull out other */ /* headers than just content-length now. */ case (ContentStateFindContentLength): { if (strncasecmp(pch, "\r\nContent-Length:", 17) == 0) { /* Got content-length field, ignore rest of header */ pget->content_length = atoi(&(pch[17])); pch += 18; } else if (strncasecmp(pch, "\r\nContent-Type:", 15) == 0) { /* Get content-type field, skipping leading spaces */ pch += 15; while (*pch == ' ') { pch++; } for (i=0,pch2 = pch; ; ++i, ++pch2) { if ((*pch2 == '\n') || (*pch2 == '\r') || (i >= sizeof(getbuf)-1)) { getbuf[i] = '\00'; pch = pch2; /* skip forward */ break; } getbuf[i] = *pch2; } /* If there are any spaces in the Content-Type */ /* field, we need to truncate at that point */ { char *sp; sp = (char *)index(getbuf, ' '); if (sp) { *sp = '\00'; } } pget->content_type = strdup(getbuf); } else if (strncmp(pch, "\r\n\r\n", 4) == 0) { /* No content-length header detected */ /* No increment for pch here, effectively fall through */ /* pget->content_length = 0; */ state = ContentStateFinishHeader; } else { pch++; } } break; /* Skip over the rest of the header */ case (ContentStateFinishHeader): { if (strncmp(pch, "\r\n\r\n", 4) == 0) { /* Found end of header */ pch += 4; /* * At this point, we need to find the end of the * response body. There's a variety of ways to * do this, but in any case, we need to make sure * that pget->content_length, pch, and last_postiion * are all set appropriately. * * See if we can ignore the body. We can do this * for the reply to HEAD, for a 204 (no content), * 205 (reset content), or 304 (not modified). */ if ((pget->method == MethodCodeHead) || (pget->response_code == 204) || (pget->response_code == 205) || (pget->response_code == 304)) { pget->content_length = 0; } /* * Use content-length header if one was present. * XXX is content_length > 0 the right test? */ else if (pget->content_length > 0) { pch += pget->content_length; } /* * No content-length header, so delimit response * by end of file. * * But, make sure we do not have a "\r\n\r\n" string * in the response, because that might indicate the * beginning of a following response. * (Patch from Yufei Wang) */ else { char *start = pch; while (pch <= (char *)plast) { if (strncmp(pch, "\r\n\r\n", 4) == 0) { pch += 4; state = ContentStateStartHttp; break; } else { pch++; } } if (state == ContentStateStartHttp) { pget->content_length = pch - start; } else { /* calculate the content length */ pget->content_length = plast - start + 1; pch = plast + 1; } } /* Set next state and do original tcptrace * processing based on what we learned above. */ state = ContentStateStartHttp; last_position = pch - pdata + 1; /* when was the first byte sent? */ pget->send_time = WhenSent(&ph->data_head,&ph->data_tail,position); /* when was the LAST byte sent? */ pget->lastbyte_time = WhenSent(&ph->data_head,&ph->data_tail,last_position); /* when was the last byte ACKed? */ if (debug > 4) printf("Content length: %d\n", pget->content_length); pget->ack_time = WhenAcked(&ph->ack_head,&ph->ack_tail,last_position); /* increment our counts */ ph->total_reply_count++; /* skip to the next request */ pget = pget->next; if (!pget) { /* no more questions, quit */ done = 1; break; } } else { pch++; } } break; } } MFUnMap(mf,pdata); } else { if (debug > 4) { printf("FindContent() with null server contents"); } } } static char * formatGetString(char * s) { int len = strlen(s); int i = 0; int j = 0; char *buf = (char *)malloc(len); char ascii[2]; while (i < len) { if (s[i] == '%') { ascii[0] = s[i+1]; ascii[1] = s[i+2]; buf[j++] = atoi(ascii); i = i+3; } else { buf[j++] = s[i]; i++; } } buf[j] = 0; return buf; } static void FindGets( struct http_info *ph) { tcb *tcb = ph->tcb_client; MFILE *mf = tcb->extr_contents_file; char *pdata; char *plast; char *pch; char *pch2; struct get_info *pget = NULL; char getbuf[1024]; u_long position = 0; int j; int methodlen; unsigned long long contentLength = 0; typedef enum { GetStateStartMethod, GetStateFinishMethod, GetStateFindContentLength, GetStateFinishHeader } StateType; StateType state; if (mf) { /* Memory map the entire file (I hope it's short!) */ MFMap(mf,&pdata,&plast); if (pdata == NULL) { return; } ph->total_request_length = (unsigned) (plast - pdata); ph->total_request_count = 0; state = GetStateStartMethod; /* search for method string*/ pch = pdata; while (pch <= (char *)plast) { switch (state) { /* Start state: Find access method keyword */ case (GetStateStartMethod): { /* Try to find a word describing a method. These * are all the methods defined in * draft-ietf-http-v11-spec-rev-06 */ MethodCode method = MethodCodeUnknown; methodlen = 0; if (strncasecmp(pch, "options ", 8) == 0) { methodlen = 8; method = MethodCodeOptions; } else if (strncasecmp(pch, "get ", 4) == 0) { methodlen = 4; method = MethodCodeGet; } else if (strncasecmp(pch, "head ", 5) == 0) { methodlen = 5; method = MethodCodeHead; } else if (strncasecmp(pch, "post ", 5) == 0) { methodlen = 5; method = MethodCodePost; } else if (strncasecmp(pch, "put ", 4) == 0) { methodlen = 4; method = MethodCodePut; } else if (strncasecmp(pch, "delete ", 7) == 0) { methodlen = 7; method = MethodCodeDelete; } else if (strncasecmp(pch, "trace ", 6) == 0) { methodlen = 6; method = MethodCodeTrace; } if (methodlen > 0) { /* make a new record for this entry */ pget = MakeGetRec(ph); /* remember where it started */ position = pch - pdata + 1; pget->request_position = position; pget->reply_position = 0; pget->method = method; contentLength = 0; pch += methodlen; state = GetStateFinishMethod; } else { /* Couldn't find a valid method, so increment */ /* and attempt to resynchronize. This shouldn't */ /* happen often. */ pch++; } }; break; case (GetStateFinishMethod): { /* grab the GET string */ for (j=0,pch2 = pch; ; ++j,++pch2) { if ((*pch2 == '\n') || (*pch2 == '\r') || (j >= sizeof(getbuf)-1)) { getbuf[j] = '\00'; pch = pch2; /* skip forward */ state = GetStateFindContentLength; break; } getbuf[j] = *pch2; } pget->get_string = formatGetString(getbuf); /* grab the time stamps */ pget->get_time = WhenSent(&ph->get_head,&ph->get_tail,position); ph->total_request_count++; } break; /* Locate content-length field, if any */ case (GetStateFindContentLength): { if (strncasecmp(pch, "\r\nContent-Length:", 17) == 0) { /* Get content-length field */ contentLength = atoi(&pch[17]); pch += 17; } else if (strncmp(pch, "\r\n\r\n", 4) == 0) { /* No content-length header detected, assume */ /* zero. Fall through (effective). */ /* contentLength = 0; */ state = GetStateFinishHeader; } else { pch++; } } break; case (GetStateFinishHeader): { if (strncmp(pch, "\r\n\r\n", 4) == 0) { /* Found end of header */ pch += 4; /* Find end of response body. */ if (contentLength > 0) { pch += contentLength; } else { /* XXX What if a POST with no content-length? */ } state = GetStateStartMethod; } else { pch++; } } break; } } MFUnMap(mf,pdata); } else { if (debug > 4) { printf("FindGets() with null client contents"); } } } static void HttpDoPlot() { struct http_info *ph; struct get_info *pget; int y_axis = 1000; int ix_color = 0; char buf[100]; struct time_stamp *pts; /* sort by increasing order of TCP connection startup */ /* (makes the graphs look better) */ for (ph=httphead; ph; ph=ph->next) { PLOTTER p = ph->pclient->plotter; tcp_pair *ptp = ph->ptp; tcb a2b, b2a; if (ptp == NULL) continue; a2b = ptp->a2b; b2a = ptp->b2a; ix_color = (ix_color + 1) % NCOLORS; /* find the plotter for this client */ if (p==NO_PLOTTER) { char title[256]; snprintf(title, sizeof(title), "Client %s HTTP trace\n", ph->pclient->clientname); p = ph->pclient->plotter = new_plotter(&ptp->a2b, ph->pclient->clientname, /* file name prefix */ title, /* plot title */ "time", /* X axis */ "URL", /* Y axis */ "_http.xpl"); /* file suffix */ } y_axis += 2; /* plot the TCP connection lifetime */ plotter_perm_color(p,ColorNames[ix_color]); plotter_larrow(p, ph->ptp->first_time, y_axis); plotter_rarrow(p, ph->ptp->last_time, y_axis); plotter_line(p, ph->ptp->first_time, y_axis, ph->ptp->last_time, y_axis); /* label the connection */ plotter_text(p,ph->ptp->first_time,y_axis,"b", (snprintf(buf, sizeof(buf), "%s ==> %s", ph->ptp->a_endpoint, ph->ptp->b_endpoint), buf)); /* mark the data packets */ for (pts=ph->data_head.next; pts->next; pts=pts->next) { plotter_tick(p,pts->thetime,y_axis,'d'); } /* plot the SYN's */ if (!ZERO_TIME(&ph->c_syn_time)) { plotter_tick(p,ph->c_syn_time,y_axis,'u'); plotter_text(p,ph->c_syn_time,y_axis,"a","Clnt SYN"); } if (!ZERO_TIME(&ph->s_syn_time)) { plotter_tick(p,ph->s_syn_time,y_axis,'u'); plotter_text(p,ph->s_syn_time,y_axis,"a","Serv SYN"); } /* plot the FINs */ if (!ZERO_TIME(&ph->c_fin_time)) { plotter_tick(p,ph->c_fin_time,y_axis,'u'); plotter_text(p,ph->c_fin_time,y_axis,"a","Clnt FIN"); } if (!ZERO_TIME(&ph->s_fin_time)) { plotter_tick(p,ph->s_fin_time,y_axis,'u'); plotter_text(p,ph->s_fin_time,y_axis,"a","Serv FIN"); } y_axis += 4; for (pget = ph->gets_head; pget; pget = pget->next) { if (ZERO_TIME(&pget->send_time) || ZERO_TIME(&pget->get_time) || ZERO_TIME(&pget->ack_time)) continue; plotter_temp_color(p,"white"); plotter_text(p, pget->get_time, y_axis, "l", pget->get_string); plotter_diamond(p, pget->get_time, y_axis); plotter_larrow(p, pget->send_time, y_axis); plotter_rarrow(p, pget->lastbyte_time, y_axis); plotter_line(p, pget->send_time, y_axis, pget->lastbyte_time, y_axis); plotter_temp_color(p,"white"); plotter_text(p, pget->lastbyte_time, y_axis, "r", (snprintf(buf, sizeof(buf), "%d",pget->content_length),buf)); plotter_diamond(p, pget->ack_time, y_axis); #ifdef CLUTTERED plotter_temp_color(p,"white"); plotter_text(p, pget->ack_time, y_axis, "b", "ACK"); #endif /* CLUTTERED */ y_axis += 2; } } } static void HttpPrintone( MFILE *pmf, struct http_info *ph) { tcp_pair *ptp = ph->ptp; tcb *pab = &ptp->a2b; tcb *pba = &ptp->b2a; struct get_info *pget = NULL; u_long missing; double etime; if (!ptp) return; printf("%s ==> %s (%s2%s)\n", ptp->a_endpoint, ptp->b_endpoint, ptp->a2b.host_letter, ptp->b2a.host_letter); printf(" Server Syn Time: %s (%.3f)\n", ts2ascii(&ph->s_syn_time), ts2d(&ph->s_syn_time)); printf(" Client Syn Time: %s (%.3f)\n", ts2ascii(&ph->c_syn_time), ts2d(&ph->c_syn_time)); /* From the patch by Yufei Wang * Print "Server Rst Time" if the last segment we saw was a RST * "Server Fin Time" if we saw a FIN * "Server Last Time" if neither FIN/RST was received to indicate * the time the last segment was seen from the * server. */ if ((pba->fin_count == 0) && (pba->reset_count > 0)) { printf(" Server Rst Time: %s (%.3f)\n", ts2ascii(&pba->last_time), ts2d(&pba->last_time)); } else if (pba->fin_count == 0) { printf(" Server Last Time: %s (%.3f)\n", ts2ascii(&pba->last_time), ts2d(&pba->last_time)); } else { printf(" Server Fin Time: %s (%.3f)\n", ts2ascii(&ph->s_fin_time), ts2d(&ph->s_fin_time)); } /* Similarly information is printed out for the Client end-point.*/ if ((pab->fin_count == 0) && (pab->reset_count > 0)) { printf(" Client Rst Time: %s (%.3f)\n", ts2ascii(&pab->last_time), ts2d(&pab->last_time)); } else if (pab->fin_count == 0) { printf(" Client Last Time: %s (%.3f)\n", ts2ascii(&pab->last_time), ts2d(&pab->last_time)); } else { printf(" Client Fin Time: %s (%.3f)\n", ts2ascii(&ph->c_fin_time), ts2d(&ph->c_fin_time)); } #ifdef HTTP_SAFE /* check the SYNs */ if ((pab->syn_count == 0) || (pba->syn_count == 0)) { printf("\ No additional information available, beginning of \ connection (SYNs) were not found in trace file.\n"); return; } /* check if we had RSTs (Patch from Yufei Wang)*/ if ((pab->reset_count > 0) || (pba->reset_count > 0)) { /* Do nothing */ } /* check the FINs */ /* Note from Yufei Wang : If we see a FIN from only one direction, * we shall not panic, but print out information that we have under the * assumption that either a RST was sent in the missing direction or the * FIN segment was lost from packet capture. The information we have * is still worth printing out. Hence we have a "&&" instead of a "||" * in the following condition. */ else if ((pab->fin_count == 0) && (pba->fin_count == 0)) { printf("\ No additional information available, end of \ connection (FINs) were not found in trace file.\n"); return; } #endif /* HTTP_SAFE */ /* see if we got all the bytes */ missing = pab->trunc_bytes + pba->trunc_bytes; /* Patch from Yufei Wang : * Adding in a check to see if the connection were closed with RST/FIN * and calculating the "missing" field appropriately */ if (pab->fin_count > 0) missing += ( (pab->fin - pab->syn -1)- pab->unique_bytes); else if (pab->reset_count > 0) { /* Check to make sure if no segments were observed between SYN and RST * The following check does not work if file is huge and seq space rolled * over - To be fixed - Mani */ if (pab->latest_seq != pab->syn) missing += ( (pab->latest_seq - pab->syn -1) - pab->unique_bytes); } if (pba->fin_count > 0) missing += ( (pba->fin - pba->syn -1)- pba->unique_bytes); else if (pba->reset_count > 0) { /* Check to make sure if no segments were observed between SYN and RST * The following check does not work if file is huge and seq space rolled * over - To be fixed - Mani */ if (pba->latest_seq != pba->syn) missing += ( (pba->latest_seq - pba->syn -1) - pba->unique_bytes); } if (missing != 0) printf("WARNING!!!! Information may be invalid, %ld bytes were not captured\n", missing); #ifdef HTTP_DUMP_TIMES Mfprintf(pmf, "conn %s %s %s2%s %u %u %u %u\n", ptp->a_endpoint, ptp->b_endpoint, ptp->a2b.host_letter, ptp->b2a.host_letter, ph->total_request_length, ph->total_request_count, ph->total_reply_length, ph->total_reply_count); #endif /* HTTP_DUMP_TIMES */ for (pget = ph->gets_head; pget; pget = pget->next) { unsigned request_length = 0; unsigned reply_length = 0; /* Compute request lengths */ if (pget->next) { /* Retrieval following ours, use its position to compute our length */ request_length = pget->next->request_position - pget->request_position; } else { /* Last one in this file, so use the EOF as a delimiter */ request_length = ph->total_request_length - pget->request_position; } /* Compute reply lengths */ if (pget->reply_position == 0) { /* No reply, so length is 0 by definition */ request_length = 0; } else { if ((pget->next) && (pget->next->reply_position > 0)) { /* Retrieval following ours with valid position, so use that to compute length */ reply_length = pget->next->reply_position - pget->reply_position; } else { /* No record following ours, or it didn't have a valid position, so use EOF as delimiter */ reply_length = ph->total_reply_length - pget->reply_position; } } printf(" %s %s\n", MethodCodeString[pget->method], pget->get_string); /* Interpretation of response codes as per RFC 2616 - HTTP/1.1 */ switch (pget->response_code) { /* Informational 1xx */ case 100 : printf("\tResponse Code: %d (Continue)\n", pget->response_code); break; case 101 : printf("\tResponse Code: %d (Switching Protocols)\n", pget->response_code); break; /* Successful 2xx */ case 200 : printf("\tResponse Code: %d (OK)\n", pget->response_code); break; case 201 : printf("\tResponse Code: %d (Created)\n", pget->response_code); break; case 202 : printf("\tResponse Code: %d (Accepted)\n", pget->response_code); break; case 203 : printf("\tResponse Code: %d (Non-Authoritative Information)\n", pget->response_code); break; case 204 : printf("\tResponse Code: %d (No Content)\n", pget->response_code); break; case 205 : printf("\tResponse Code: %d (Reset Content)\n", pget->response_code); break; case 206 : printf("\tResponse Code: %d (Partial Content)\n", pget->response_code); break; /* Redirection 3xx */ case 300 : printf("\tResponse Code: %d (Multiple Choices)\n", pget->response_code); break; case 301 : printf("\tResponse Code: %d (Moved Permanently)\n", pget->response_code); break; case 302 : printf("\tResponse Code: %d (Found)\n", pget->response_code); break; case 303 : printf("\tResponse Code: %d (See Other)\n", pget->response_code); break; case 304 : printf("\tResponse Code: %d (Not Modified)\n", pget->response_code); break; case 305 : printf("\tResponse Code: %d (Use Proxy)\n", pget->response_code); break; case 306 : printf("\tResponse Code: %d (Unused)\n", pget->response_code); break; case 307 : printf("\tResponse Code: %d (Temporary Redirect)\n", pget->response_code); break; /* Client Error 4xx */ case 400 : printf("\tResponse Code: %d (Bad Request)\n", pget->response_code); break; case 401 : printf("\tResponse Code: %d (Unauthorized)\n", pget->response_code); break; case 402 : printf("\tResponse Code: %d (Payment Required)\n", pget->response_code); break; case 403 : printf("\tResponse Code: %d (Forbidden)\n", pget->response_code); break; case 404 : printf("\tResponse Code: %d (Not Found)\n", pget->response_code); break; case 405 : printf("\tResponse Code: %d (Method Not Allowed)\n", pget->response_code); break; case 406 : printf("\tResponse Code: %d (Not Acceptable)\n", pget->response_code); break; case 407 : printf("\tResponse Code: %d (Proxy Authentication Required)\n", pget->response_code); break; case 408 : printf("\tResponse Code: %d (Request Timeout)\n", pget->response_code); break; case 409 : printf("\tResponse Code: %d (Conflict)\n", pget->response_code); break; case 410 : printf("\tResponse Code: %d (Gone)\n", pget->response_code); break; case 411 : printf("\tResponse Code: %d (Length Required)\n", pget->response_code); break; case 412 : printf("\tResponse Code: %d (Precondition Failed)\n", pget->response_code); break; case 413 : printf("\tResponse Code: %d (Request Entity Too Large)\n", pget->response_code); break; case 414 : printf("\tResponse Code: %d (Request-URI Too Long)\n", pget->response_code); break; case 415 : printf("\tResponse Code: %d (Unsupported Media Type)\n", pget->response_code); break; case 416 : printf("\tResponse Code: %d (Requested Range Not Satisfiable)\n", pget->response_code); break; case 417: printf("\tResponse Code: %d (Expectation Failed)\n", pget->response_code); break; /* Server Error 5xx */ case 500 : printf("\tResponse Code: %d (Internal Server Error)\n", pget->response_code); break; case 501 : printf("\tResponse Code: %d (Not Implemented)\n", pget->response_code); break; case 502 : printf("\tResponse Code: %d (Bad Gateway)\n", pget->response_code); break; case 503 : printf("\tResponse Code: %d (Service Unavailable)\n", pget->response_code); break; case 504 : printf("\tResponse Code: %d (Gateway Timeout)\n", pget->response_code); break; case 505 : printf("\tResponse Code: %d (HTTP Version Not Supported)\n", pget->response_code); break; default : printf("\tResponse Code: %d (unknown response code)\n", pget->response_code); } printf("\tRequest Length: %u\n", request_length); printf("\tReply Length: %u\n", reply_length); printf("\tContent Length: %d\n", pget->content_length); printf("\tContent Type : %s\n", pget->content_type); printf("\tTime request sent: %s (%.3f)\n", ts2ascii(&pget->get_time), ts2d(&pget->get_time)); printf("\tTime reply started: %s (%.3f)\n", ts2ascii(&pget->send_time), ts2d(&pget->send_time)); printf("\tTime reply ACKed: %s (%.3f)\n", ts2ascii(&pget->ack_time), ts2d(&pget->ack_time)); /* elapsed time, request started to answer started */ etime = elapsed(pget->get_time,pget->send_time); etime /= 1000; /* us to msecs */ printf("\tElapsed time: %.0f ms (request to first byte sent)\n", etime); /* elapsed time, request started to answer ACKed */ etime = elapsed(pget->get_time,pget->ack_time); etime /= 1000; /* us to msecs */ printf("\tElapsed time: %.0f ms (request to content ACKed)\n", etime); #ifdef HTTP_DUMP_TIMES Mfprintf(pmf,"reqrep %s %s %s2%s %.3f %.3f %.3f %u %u %3d %s %s %s\n", ptp->a_endpoint, ptp->b_endpoint, ptp->a2b.host_letter, ptp->b2a.host_letter, ts2d(&pget->get_time), ts2d(&pget->send_time), ts2d(&pget->ack_time), request_length, reply_length, pget->response_code, MethodCodeString[pget->method], pget->get_string, pget->content_type); #endif /* HTTP_DUMP_TIMES */ } } void http_done(void) { MFILE *pmf = NULL; struct http_info *ph; /* just return if we didn't grab anything */ if (!httphead) return; /* gather up the information */ HttpGather(httphead); #ifdef HTTP_DUMP_TIMES pmf = Mfopen("http.times","w"); #endif /* HTTP_DUMP_TIMES */ printf("Http module output:\n"); for (ph=httphead; ph; ph=ph->next) { HttpPrintone(pmf,ph); } HttpDoPlot(); #ifdef HTTP_DUMP_TIMES Mfclose(pmf); #endif /* HTTP_DUMP_TIMES */ } void http_usage(void) { printf("\t-xHTTP[P]\tprint info about http traffic (on port P, default %d)\n", DEFAULT_SERVER_PORT); } void http_newfile( char *newfile, u_long filesize, Bool fcompressed) { /* just an example, really */ } void * http_newconn( tcp_pair *ptp) { struct http_info *ph; ph = MakeHttpRec(); /* attach tcptrace's info */ ph->ptp = ptp; /* determine the server and client tcb's */ if (ptp->addr_pair.a_port == httpd_port) { ph->tcb_client = &ptp->b2a; ph->tcb_server = &ptp->a2b; } else { ph->tcb_client = &ptp->a2b; ph->tcb_server = &ptp->b2a; } /* attach the client info */ ph->pclient = FindClient(HostName(ptp->addr_pair.a_address)); return(ph); } #endif /* LOAD_MODULE_HTTP */