/*
* 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 <sys/mman.h>
#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 */