#if 0 out the stuff to set the reported length, as it'd throw an
[obnox/wireshark/wip.git] / packet-tcp.c
index 5c5c2be6fe9b9376ae6e3d4da4087ed1f681512e..da7717cf0ef49280d7794d7eb2fb33c19bdaa24a 100644 (file)
@@ -1,7 +1,7 @@
 /* packet-tcp.c
  * Routines for TCP packet disassembly
  *
- * $Id: packet-tcp.c,v 1.173 2003/02/21 00:22:45 guy Exp $
+ * $Id: packet-tcp.c,v 1.211 2003/10/28 08:50:39 sahlberg Exp $
  *
  * Ethereal - Network traffic analyzer
  * By Gerald Combs <gerald@ethereal.com>
 #include <glib.h>
 #include "in_cksum.h"
 
+#include <epan/packet.h>
 #include <epan/resolv.h>
 #include "ipproto.h"
+#include "ip_opts.h"
 #include "follow.h"
 #include "prefs.h"
 #include "packet-tcp.h"
@@ -89,13 +91,19 @@ static int hf_tcp_analysis_flags = -1;
 static int hf_tcp_analysis_acks_frame = -1;
 static int hf_tcp_analysis_ack_rtt = -1;
 static int hf_tcp_analysis_retransmission = -1;
+static int hf_tcp_analysis_fast_retransmission = -1;
+static int hf_tcp_analysis_out_of_order = -1;
 static int hf_tcp_analysis_lost_packet = -1;
 static int hf_tcp_analysis_ack_lost_packet = -1;
 static int hf_tcp_analysis_keep_alive = -1;
+static int hf_tcp_analysis_keep_alive_ack = -1;
 static int hf_tcp_analysis_duplicate_ack = -1;
+static int hf_tcp_analysis_duplicate_ack_num = -1;
+static int hf_tcp_analysis_duplicate_ack_frame = -1;
 static int hf_tcp_analysis_zero_window = -1;
 static int hf_tcp_analysis_zero_window_probe = -1;
 static int hf_tcp_analysis_zero_window_violation = -1;
+static int hf_tcp_reassembled_in = -1;
 static int hf_tcp_segments = -1;
 static int hf_tcp_segment = -1;
 static int hf_tcp_segment_overlap = -1;
@@ -103,6 +111,21 @@ static int hf_tcp_segment_overlap_conflict = -1;
 static int hf_tcp_segment_multiple_tails = -1;
 static int hf_tcp_segment_too_long_fragment = -1;
 static int hf_tcp_segment_error = -1;
+static int hf_tcp_option_mss = -1;
+static int hf_tcp_option_mss_val = -1;
+static int hf_tcp_option_wscale = -1;
+static int hf_tcp_option_wscale_val = -1;
+static int hf_tcp_option_sack_perm = -1;
+static int hf_tcp_option_sack = -1;
+static int hf_tcp_option_sack_sle = -1;
+static int hf_tcp_option_sack_sre = -1;
+static int hf_tcp_option_echo = -1;
+static int hf_tcp_option_echo_reply = -1;
+static int hf_tcp_option_time_stamp = -1;
+static int hf_tcp_option_cc = -1;
+static int hf_tcp_option_ccnew = -1;
+static int hf_tcp_option_ccecho = -1;
+static int hf_tcp_option_md5 = -1;
 
 static gint ett_tcp = -1;
 static gint ett_tcp_flags = -1;
@@ -127,6 +150,7 @@ static const fragment_items tcp_segment_items = {
        &hf_tcp_segment_multiple_tails,
        &hf_tcp_segment_too_long_fragment,
        &hf_tcp_segment_error,
+       &hf_tcp_reassembled_in,
        "Segments"
 };
 
@@ -153,13 +177,10 @@ struct tcp_unacked {
        guint32 nextseq;
        nstime_t ts;
 
-       /* these are used for detection of duplicate acks and nothing else */
-       guint32 ack_frame;
-       guint32 ack;
-       guint32 num_acks;
-
        /* this is to keep track of zero window and zero window probe */
        guint32 window;
+
+       guint32 flags;
 };
 
 /* Idea for gt: either x > y, or y is much bigger (assume wrap) */
@@ -171,18 +192,23 @@ struct tcp_unacked {
 
 static GMemChunk *tcp_acked_chunk = NULL;
 static int tcp_acked_count = 5000;     /* one for almost every other segment in the capture */
-#define TCP_A_RETRANSMISSION           0x01
-#define TCP_A_LOST_PACKET              0x02
-#define TCP_A_ACK_LOST_PACKET          0x04
-#define TCP_A_KEEP_ALIVE               0x08
-#define TCP_A_DUPLICATE_ACK            0x10
-#define TCP_A_ZERO_WINDOW              0x20
-#define TCP_A_ZERO_WINDOW_PROBE                0x40
-#define TCP_A_ZERO_WINDOW_VIOLATION    0x80
+#define TCP_A_RETRANSMISSION           0x0001
+#define TCP_A_LOST_PACKET              0x0002
+#define TCP_A_ACK_LOST_PACKET          0x0004
+#define TCP_A_KEEP_ALIVE               0x0008
+#define TCP_A_DUPLICATE_ACK            0x0010
+#define TCP_A_ZERO_WINDOW              0x0020
+#define TCP_A_ZERO_WINDOW_PROBE                0x0040
+#define TCP_A_ZERO_WINDOW_VIOLATION    0x0080
+#define TCP_A_KEEP_ALIVE_ACK           0x0100
+#define TCP_A_OUT_OF_ORDER             0x0200
+#define TCP_A_FAST_RETRANSMISSION      0x0400
 struct tcp_acked {
        guint32 frame_acked;
        nstime_t ts;
-       guint8 flags;
+       guint16 flags;
+       guint32 dupack_num;     /* dup ack number */
+       guint32 dupack_frame;   /* dup ack to frame # */
 };
 static GHashTable *tcp_analyze_acked_table = NULL;
 
@@ -191,6 +217,7 @@ static int tcp_rel_seq_count = 10000; /* one for each segment in the capture */
 struct tcp_rel_seq {
        guint32 seq_base;
        guint32 ack_base;
+       gint16  win_scale;
 };
 static GHashTable *tcp_rel_seq_table = NULL;
 
@@ -214,10 +241,243 @@ struct tcp_analysis {
        guint32 base_seq1;
        struct tcp_unacked *ual2;       /* UnAcked List 2*/
        guint32 base_seq2;
+       gint16 win_scale1;
+       gint16 win_scale2;
+       guint32 ack1, ack2;
+       guint32 ack1_frame, ack2_frame;
+       nstime_t ack1_time, ack2_time;
+       guint32 num1_acks, num2_acks;
+
+       /* these two lists are used to track when PDUs may start
+          inside a segment.
+       */
+       struct tcp_next_pdu *pdu_seq1;
+       struct tcp_next_pdu *pdu_seq2;
+};
+
+
+static GMemChunk *tcp_next_pdu_chunk = NULL;
+static int tcp_next_pdu_count = 20;
+struct tcp_next_pdu {
+       struct tcp_next_pdu *next;
+       guint32 seq;
 };
+static GHashTable *tcp_pdu_tracking_table = NULL;
+
+
+static struct tcp_analysis *
+get_tcp_conversation_data(packet_info *pinfo)
+{
+       conversation_t *conv=NULL;
+       struct tcp_analysis *tcpd=NULL;
+
+       /* Have we seen this conversation before? */
+       if( (conv=find_conversation(&pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0)) == NULL){
+               /* No this is a new conversation. */
+               conv=conversation_new(&pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+       }
+
+       /* check if we have any data for this conversation */
+       tcpd=conversation_get_proto_data(conv, proto_tcp);
+       if(!tcpd){
+               /* No no such data yet. Allocate and init it */
+               tcpd=g_mem_chunk_alloc(tcp_analysis_chunk);
+               tcpd->ual1=NULL;
+               tcpd->base_seq1=0;
+               tcpd->win_scale1=-1;
+               tcpd->ack1=0;
+               tcpd->ack1_frame=0;
+               tcpd->ack1_time.secs=0;
+               tcpd->ack1_time.nsecs=0;
+               tcpd->num1_acks=0;
+               tcpd->ual2=NULL;
+               tcpd->base_seq2=0;
+               tcpd->win_scale2=-1;
+               tcpd->ack2=0;
+               tcpd->ack2_frame=0;
+               tcpd->ack2_time.secs=0;
+               tcpd->ack2_time.nsecs=0;
+               tcpd->num2_acks=0;
+
+               tcpd->pdu_seq1=NULL;
+               tcpd->pdu_seq2=NULL;
+
+               conversation_add_proto_data(conv, proto_tcp, tcpd);
+       }
+
+       return tcpd;
+}
 
+/* This function is called from the tcp analysis code to provide
+   clues on how the seq and ack numbers are changed.
+   To prevent the next_pdu lists from growing uncontrollable in size we
+   use this function to do the following :
+   IF we see an ACK then we assume that the left edge of the window has changed
+      at least to this point and assuming it is rare with reordering and
+      trailing duplicate/retransmitted segments, we just assume that after
+      we have seen the ACK we will not see any more segments prior to the 
+      ACK value.
+      If we will not see any segments prior to the ACK value then we can just
+      delete all next_pdu entries that describe pdu's starting prior to the 
+      ACK.
+      If this heuristics is prooved to be too simplistic we can just enhance it
+      later.
+*/   
+/* XXX this function should be ehnanced to handle sequence number wrapping */
+/* XXX to handle retransmissions and reordered packets maybe we should only
+       discard entries that are more than (guesstimate) 50kb older than the
+       specified sequence number ?
+*/
 static void
-tcp_get_relative_seq_ack(guint32 frame, guint32 *seq, guint32 *ack)
+prune_next_pdu_list(struct tcp_next_pdu **tnp, guint32 seq)
+{
+       struct tcp_next_pdu *tmptnp;
+
+       if(*tnp == NULL){
+               return;
+       }
+
+       for(tmptnp=*tnp;tmptnp;tmptnp=tmptnp->next){
+               if(tmptnp->seq<=seq){
+                       struct tcp_next_pdu *oldtnp;
+                       oldtnp=tmptnp;
+
+                       if(tmptnp==*tnp){
+                               tmptnp=tmptnp->next;
+                               *tnp=tmptnp;
+                               g_mem_chunk_free(tcp_next_pdu_chunk, oldtnp);
+                               if(!tmptnp){
+                                       return;
+                               }
+                               continue;
+                       } else {
+                               for(tmptnp=*tnp;tmptnp;tmptnp=tmptnp->next){
+                                       if(tmptnp->next==oldtnp){
+                                               tmptnp->next=oldtnp->next;
+                                               g_mem_chunk_free(tcp_next_pdu_chunk, oldtnp);
+                                               break;
+                                       }
+                               }
+                               if(!tmptnp){
+                                       return;
+                               }
+                       }
+               }
+       }
+}
+               
+
+/* if we know that a PDU starts inside this segment, return the adjusted 
+   offset to where that PDU starts or just return offset back
+   and let TCP try to find out what it can about this segment
+*/
+static int
+scan_for_next_pdu(packet_info *pinfo, int offset, guint32 seq, guint32 nxtseq)
+{
+       struct tcp_analysis *tcpd=NULL;
+       struct tcp_next_pdu *tnp=NULL;
+       int direction;
+
+       if(!pinfo->fd->flags.visited){
+               /* find(or create if needed) the conversation for this tcp session */
+               tcpd=get_tcp_conversation_data(pinfo);
+               /* check direction and get pdu start lists */
+               direction=CMP_ADDRESS(&pinfo->src, &pinfo->dst);
+               /* if the addresses are equal, match the ports instead */
+               if(direction==0) {
+                       direction= (pinfo->srcport > pinfo->destport)*2-1;
+               }
+               if(direction>=0){
+                       tnp=tcpd->pdu_seq1;
+               } else {
+                       tnp=tcpd->pdu_seq2;
+               }
+
+               /* scan and see if we find any pdus starting inside this tvb */
+               for(;tnp;tnp=tnp->next){
+                       /* XXX here we should also try to handle sequence number
+                          wrapping
+                       */
+                       if(seq<tnp->seq && nxtseq>tnp->seq){
+                               g_hash_table_insert(tcp_pdu_tracking_table, 
+                                       (void *)pinfo->fd->num, (void *)tnp->seq);
+                               offset+=tnp->seq-seq;
+                               break;
+                       }
+               }
+       } else {
+               guint32 pduseq;
+
+               pduseq=(guint32)g_hash_table_lookup(tcp_pdu_tracking_table, (void *)pinfo->fd->num);
+               if(pduseq){
+                       offset+=pduseq-seq;
+               }
+       }
+
+       return offset;
+}
+
+/* if we saw a PDU that extended beyond the end of the segment,
+   use this function to remember where the next pdu starts
+*/
+static void
+pdu_store_sequencenumber_of_next_pdu(packet_info *pinfo, guint32 nxtpdu)
+{
+       struct tcp_analysis *tcpd=NULL;
+       struct tcp_next_pdu *tnp=NULL;
+       int direction;
+
+       /* find(or create if needed) the conversation for this tcp session */
+       tcpd=get_tcp_conversation_data(pinfo);
+
+       tnp=g_mem_chunk_alloc(tcp_next_pdu_chunk);
+       tnp->seq=nxtpdu;
+
+       /* check direction and get pdu start list */
+       direction=CMP_ADDRESS(&pinfo->src, &pinfo->dst);
+       /* if the addresses are equal, match the ports instead */
+       if(direction==0) {
+               direction= (pinfo->srcport > pinfo->destport)*2-1;
+       }
+       if(direction>=0){
+               tnp->next=tcpd->pdu_seq1;
+               tcpd->pdu_seq1=tnp;
+       } else {
+               tnp->next=tcpd->pdu_seq2;
+               tcpd->pdu_seq2=tnp;
+       }
+       /*QQQ 
+         Add check for ACKs and purge list of sequence numbers
+         already acked.
+       */
+}
+
+/* if we saw a window scaling option, store it for future reference 
+*/
+static void
+pdu_store_window_scale_option(packet_info *pinfo, guint8 ws)
+{
+       struct tcp_analysis *tcpd=NULL;
+       int direction;
+
+       /* find(or create if needed) the conversation for this tcp session */
+       tcpd=get_tcp_conversation_data(pinfo);
+
+       /* check direction and get pdu start list */
+       direction=CMP_ADDRESS(&pinfo->src, &pinfo->dst);
+       /* if the addresses are equal, match the ports instead */
+       if(direction==0) {
+               direction= (pinfo->srcport > pinfo->destport)*2-1;
+       }
+       if(direction>=0){
+               tcpd->win_scale1=ws;
+       } else {
+               tcpd->win_scale2=ws;
+       }
+}
+
+static void
+tcp_get_relative_seq_ack(guint32 frame, guint32 *seq, guint32 *ack, guint32 *win)
 {
        struct tcp_rel_seq *trs;
 
@@ -228,6 +488,9 @@ tcp_get_relative_seq_ack(guint32 frame, guint32 *seq, guint32 *ack)
 
        (*seq) -= trs->seq_base;
        (*ack) -= trs->ack_base;
+       if(trs->win_scale!=-1){
+               (*win)<<=trs->win_scale;
+       }
 }
 
 static struct tcp_acked *
@@ -242,6 +505,8 @@ tcp_analyze_get_acked_struct(guint32 frame, gboolean createflag)
                ta->ts.secs=0;
                ta->ts.nsecs=0;
                ta->flags=0;
+               ta->dupack_num=0;
+               ta->dupack_frame=0;
                g_hash_table_insert(tcp_analyze_acked_table, (void *)frame, ta);
        }
        return ta;
@@ -250,7 +515,6 @@ tcp_analyze_get_acked_struct(guint32 frame, gboolean createflag)
 static void
 tcp_analyze_sequence_number(packet_info *pinfo, guint32 seq, guint32 ack, guint32 seglen, guint8 flags, guint16 window)
 {
-       conversation_t *conv=NULL;
        struct tcp_analysis *tcpd=NULL;
        int direction;
        struct tcp_unacked *ual1=NULL;
@@ -258,24 +522,15 @@ tcp_analyze_sequence_number(packet_info *pinfo, guint32 seq, guint32 ack, guint3
        struct tcp_unacked *ual=NULL;
        guint32 base_seq;
        guint32 base_ack;
+       guint32 ack1, ack2;
+       guint32 ack1_frame, ack2_frame;
+       nstime_t *ack1_time, *ack2_time;
+       guint32 num1_acks, num2_acks;
+       gint16  win_scale;
+       struct tcp_next_pdu **tnp=NULL;
 
-       /* Have we seen this conversation before? */
-       if( (conv=find_conversation(&pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0)) == NULL){
-               /* No this is a new conversation. */
-               conv=conversation_new(&pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
-       }
-
-       /* check if we have any data for this conversation */
-       tcpd=conversation_get_proto_data(conv, proto_tcp);
-       if(!tcpd){
-               /* No no such data yet. Allocate and init it */
-               tcpd=g_mem_chunk_alloc(tcp_analysis_chunk);
-               tcpd->ual1=NULL;
-               tcpd->base_seq1=0;
-               tcpd->ual2=NULL;
-               tcpd->base_seq2=0;
-               conversation_add_proto_data(conv, proto_tcp, tcpd);
-       }
+       /* find(or create if needed) the conversation for this tcp session */
+       tcpd=get_tcp_conversation_data(pinfo);
 
        /* check direction and get ua lists */
        direction=CMP_ADDRESS(&pinfo->src, &pinfo->dst);
@@ -286,20 +541,75 @@ tcp_analyze_sequence_number(packet_info *pinfo, guint32 seq, guint32 ack, guint3
        if(direction>=0){
                ual1=tcpd->ual1;
                ual2=tcpd->ual2;
-               base_seq=tcpd->base_seq1;
-               base_ack=tcpd->base_seq2;
+               ack1=tcpd->ack1;
+               ack2=tcpd->ack2;
+               ack1_frame=tcpd->ack1_frame;
+               ack2_frame=tcpd->ack2_frame;
+               ack1_time=&tcpd->ack1_time;
+               ack2_time=&tcpd->ack2_time;
+               num1_acks=tcpd->num1_acks;
+               num2_acks=tcpd->num2_acks;
+               tnp=&tcpd->pdu_seq2;
+               base_seq=(tcp_relative_seq && (ual1==NULL))?seq:tcpd->base_seq1;
+               base_ack=(tcp_relative_seq && (ual2==NULL))?ack:tcpd->base_seq2;
+               win_scale=tcpd->win_scale1;
        } else {
                ual1=tcpd->ual2;
                ual2=tcpd->ual1;
-               base_seq=tcpd->base_seq2;
-               base_ack=tcpd->base_seq1;
+               ack1=tcpd->ack2;
+               ack2=tcpd->ack1;
+               ack1_frame=tcpd->ack2_frame;
+               ack2_frame=tcpd->ack1_frame;
+               ack1_time=&tcpd->ack2_time;
+               ack2_time=&tcpd->ack1_time;
+               num1_acks=tcpd->num2_acks;
+               num2_acks=tcpd->num1_acks;
+               tnp=&tcpd->pdu_seq1;
+               base_seq=(tcp_relative_seq && (ual1==NULL))?seq:tcpd->base_seq2;
+               base_ack=(tcp_relative_seq && (ual2==NULL))?ack:tcpd->base_seq1;
+               win_scale=tcpd->win_scale2;
        }
 
-       if(base_seq==0){
-               base_seq=seq;
+       if(!seglen){
+               if(!ack2_frame){
+                       ack2_frame=pinfo->fd->num;
+                       ack2=ack;
+                       ack2_time->secs=pinfo->fd->abs_secs;
+                       ack2_time->nsecs=pinfo->fd->abs_usecs*1000;
+                       num2_acks=0;
+               } else if(GT_SEQ(ack, ack2)){
+                       ack2_frame=pinfo->fd->num;
+                       ack2=ack;
+                       ack2_time->secs=pinfo->fd->abs_secs;
+                       ack2_time->nsecs=pinfo->fd->abs_usecs*1000;
+                       num2_acks=0;
+               }
        }
-       if(base_ack==0){
-               base_ack=ack;
+
+#ifdef REMOVED
+/* useful debug ouput   
+ * it prints the two lists of the sliding window emulation 
+ */
+{
+struct tcp_unacked *u=NULL;
+printf("\n");
+printf("analyze_sequence_number(frame:%d seq:%d nextseq:%d ack:%d)\n",pinfo->fd->num,seq,seq+seglen,ack);
+printf("UAL1:\n");
+for(u=ual1;u;u=u->next){
+printf("  Frame:%d seq:%d nseq:%d time:%d.%09d ack:%d:%d\n",u->frame,u->seq,u->nextseq,u->ts.secs,u->ts.nsecs,ack1,ack2);
+}
+printf("UAL2:\n");
+for(u=ual2;u;u=u->next){
+printf("  Frame:%d seq:%d nseq:%d time:%d.%09d ack:%d:%d\n",u->frame,u->seq,u->nextseq,u->ts.secs,u->ts.nsecs,ack1,ack2);
+}
+}
+#endif
+
+       /* To handle FIN, just add 1 to the length.
+          else the ACK following the FIN-ACK will look like it was
+          outside the window. */
+       if( flags&TH_FIN ){
+               seglen+=1;
        }
 
        /* handle the sequence numbers */
@@ -313,16 +623,22 @@ tcp_analyze_sequence_number(packet_info *pinfo, guint32 seq, guint32 ack, guint3
                ual1=g_mem_chunk_alloc(tcp_unacked_chunk);
                ual1->next=NULL;
                ual1->frame=pinfo->fd->num;
-               ual1->ack_frame=0;
-               ual1->ack=0;
-               ual1->num_acks=0;
+               ack1_frame=0;
+               ack2_frame=0;
+               ack1=0;
+               ack2=0;
+               num1_acks=0;
+               num2_acks=0;
                ual1->seq=seq+1;
                ual1->nextseq=seq+1;
                ual1->ts.secs=pinfo->fd->abs_secs;
                ual1->ts.nsecs=pinfo->fd->abs_usecs*1000;
                ual1->window=window;
-               base_seq=seq;
-               base_ack=ack;
+               ual1->flags=0;
+               if(tcp_relative_seq){
+                       base_seq=seq;
+                       base_ack=ack;
+               }
                goto seq_finished;
        }
 
@@ -331,26 +647,21 @@ tcp_analyze_sequence_number(packet_info *pinfo, guint32 seq, guint32 ack, guint3
                ual1=g_mem_chunk_alloc(tcp_unacked_chunk);
                ual1->next=NULL;
                ual1->frame=pinfo->fd->num;
-               ual1->ack_frame=0;
-               ual1->ack=0;
-               ual1->num_acks=0;
                ual1->seq=seq;
                ual1->nextseq=seq+seglen;
                ual1->ts.secs=pinfo->fd->abs_secs;
                ual1->ts.nsecs=pinfo->fd->abs_usecs*1000;
                ual1->window=window;
-               base_seq=seq;
+               ual1->flags=0;
+               if(tcp_relative_seq){
+                       base_seq=seq;
+                       base_ack=ack;
+               }
                goto seq_finished;
        }
 
        /* if we get past here we know that ual1 points to a segment */
 
-       /* To handle FIN, just pretend they have a length of 1.
-          else the ACK following the FIN-ACK will look like it was
-          outside the window. */
-       if( (!seglen) && (flags&TH_FIN) ){
-               seglen=1;
-       }
 
        /* if seq is beyond ual1->nextseq we have lost a segment */
        if (GT_SEQ(seq, ual1->nextseq)) {
@@ -363,27 +674,42 @@ tcp_analyze_sequence_number(packet_info *pinfo, guint32 seq, guint32 ack, guint3
                ual=g_mem_chunk_alloc(tcp_unacked_chunk);
                ual->next=ual1;
                ual->frame=pinfo->fd->num;
-               ual->ack_frame=0;
-               ual->ack=0;
-               ual->num_acks=0;
                ual->seq=seq;
                ual->nextseq=seq+seglen;
                ual->ts.secs=pinfo->fd->abs_secs;
                ual->ts.nsecs=pinfo->fd->abs_usecs*1000;
                ual->window=window;
+               ual->flags=0;
                ual1=ual;
                goto seq_finished;
        }
 
-       /* keep-alives are empty semgents with a sequence number -1 of what
+       /* keep-alives are empty segments with a sequence number -1 of what
         * we would expect.
+         *
+        * Solaris is an exception, Solaris does not really use KeepAlives
+        * according to RFC793, instead they move the left window edge one
+        * byte to the left and makes up a fake byte to fill in this position
+        * of the enlarged window.
+        * This means that Solaris will do "weird" KeepAlives that actually
+        * contains a one-byte segment with "random" junk data which the
+        * Solaris host then will try to transmit, and posisbly retransmit
+        * to the other side. Of course the other side will ignore this junk
+        * byte since it is outside (left of) the window.
+        * This is actually a brilliant trick that gives them, for free, 
+        * semi-reliable KeepAlives.
+        * (since normal retransmission will handle any lost keepalive segments
+        * , brilliant)
         */
-       if( (!seglen) && EQ_SEQ(seq, (ual1->nextseq-1)) ){
-               struct tcp_acked *ta;
-
-               ta=tcp_analyze_get_acked_struct(pinfo->fd->num, TRUE);
-               ta->flags|=TCP_A_KEEP_ALIVE;
-               goto seq_finished;
+       if( (seglen<=1) && EQ_SEQ(seq, (ual1->nextseq-1)) ){
+               if(!(flags&TH_FIN)){ /* FIN segments are not keepalives */
+                       struct tcp_acked *ta;
+       
+                       ta=tcp_analyze_get_acked_struct(pinfo->fd->num, TRUE);
+                       ta->flags|=TCP_A_KEEP_ALIVE;
+                       ual1->flags|=TCP_A_KEEP_ALIVE;
+                       goto seq_finished;
+               }
        }
 
 
@@ -392,21 +718,109 @@ tcp_analyze_sequence_number(packet_info *pinfo, guint32 seq, guint32 ack, guint3
                goto seq_finished;
        }
 
-       /* check if the sequence number is lower than expected, i.e. retransmission */
+       /* check if the sequence number is lower than expected, i.e. either a 
+        * retransmission a fast retransmission or an out of order segment
+        */
        if( LT_SEQ(seq, ual1->nextseq )){
-               struct tcp_acked *ta;
+               gboolean outoforder;
+               struct tcp_unacked *tu,*ntu;
+
+               /* assume it is a fast retransmission if
+                * 1 we have seen >=3 dupacks in the other direction for this 
+                *   segment (i.e. >=4 acks)
+                * 2 if this segment is the next unacked segment
+                * 3 this segment came within 10ms of the last dupack
+                *   (10ms is arbitrary but should be low enough not to be
+                *   confused with a retransmission timeout 
+                */
+               if( (num1_acks>=4) && (seq==ack1) ){
+                       guint32 t;
 
-               ta=tcp_analyze_get_acked_struct(pinfo->fd->num, TRUE);
-               ta->flags|=TCP_A_RETRANSMISSION;
+                       t=(pinfo->fd->abs_secs-ack1_time->secs)*1000000000;
+                       t=t+(pinfo->fd->abs_usecs*1000)-ack1_time->nsecs;
+                       if(t<10000000){
+                               /* has to be a retransmission then */
+                               struct tcp_acked *ta;
 
-               /* did this segment contain any more data we havent seen yet?
-                * if so we can just increase nextseq
+                               ta=tcp_analyze_get_acked_struct(pinfo->fd->num, TRUE);
+                               ta->flags|=TCP_A_FAST_RETRANSMISSION;
+                               goto seq_finished;
+                       }
+               }
+
+               /* check it is a suspected out of order segment.
+                * we assume it is an out of order segment if 
+                * 1 it has not been ACKed yet.
+                * 2 we have not seen the segment before
+                * 3 it arrived within (arbitrary value) 4ms of the
+                *      next semgent in the sequence.
+                *   4 there were no dupacks in the opposite direction.
+                */
+               outoforder=TRUE;
+#ifdef REMOVED
+               /* dont do this test.  For full-duplex capture devices that 
+                * capture in both directions using two NICs it is more common
+                * than one would expect for this to happen since they often
+                * lose the time integrity between the two NICs
                 */
-               if(GT_SEQ((seq+seglen), ual1->nextseq)){
-                       ual1->nextseq=seq+seglen;
-                       ual1->frame=pinfo->fd->num;
-                       ual1->ts.secs=pinfo->fd->abs_secs;
-                       ual1->ts.nsecs=pinfo->fd->abs_usecs*1000;
+               /* 1 has it already been ACKed ? */
+               if(LT_SEQ(seq,ack1)){
+                       outoforder=FALSE;
+               }
+#endif
+               /* 2 have we seen this segment before ? */
+               for(tu=ual1;tu;tu=tu->next){
+                       if((tu->frame)&&(tu->seq==seq)){
+                               outoforder=FALSE;
+                       }
+               }
+               /* 3 was it received within 4ms of the next segment ?*/
+               ntu=NULL;
+               for(tu=ual1;tu;tu=tu->next){
+                       if(LT_SEQ(seq,tu->seq)){
+                               if(tu->frame){
+                                       ntu=tu;
+                               }
+                       }
+               }
+               if(ntu){
+                       if(pinfo->fd->abs_secs>(guint32)(ntu->ts.secs+2)){
+                               outoforder=FALSE;
+                       } else if((pinfo->fd->abs_secs+2)<(guint32)ntu->ts.secs){
+                               outoforder=FALSE;
+                       } else {
+                               guint32 t;
+
+                               t=(ntu->ts.secs-pinfo->fd->abs_secs)*1000000000;
+                               t=t+ntu->ts.nsecs-(pinfo->fd->abs_usecs*1000);
+                               if(t>4000000){
+                                       outoforder=FALSE;
+                               }
+                       }
+               }
+
+               
+               if(outoforder) {
+                       struct tcp_acked *ta;
+
+                       ta=tcp_analyze_get_acked_struct(pinfo->fd->num, TRUE);
+                       ta->flags|=TCP_A_OUT_OF_ORDER;
+               } else {
+                       /* has to be a retransmission then */
+                       struct tcp_acked *ta;
+
+                       ta=tcp_analyze_get_acked_struct(pinfo->fd->num, TRUE);
+                       ta->flags|=TCP_A_RETRANSMISSION;
+
+                       /* did this segment contain any more data we havent seen yet?
+                        * if so we can just increase nextseq
+                        */
+                       if(GT_SEQ((seq+seglen), ual1->nextseq)){
+                               ual1->nextseq=seq+seglen;
+                               ual1->frame=pinfo->fd->num;
+                               ual1->ts.secs=pinfo->fd->abs_secs;
+                               ual1->ts.nsecs=pinfo->fd->abs_usecs*1000;
+                       }
                }
                goto seq_finished;
        }
@@ -415,14 +829,12 @@ tcp_analyze_sequence_number(packet_info *pinfo, guint32 seq, guint32 ack, guint3
        ual=g_mem_chunk_alloc(tcp_unacked_chunk);
        ual->next=ual1;
        ual->frame=pinfo->fd->num;
-       ual->ack_frame=0;
-       ual->ack=0;
-       ual->num_acks=0;
        ual->seq=seq;
        ual->nextseq=seq+seglen;
        ual->ts.secs=pinfo->fd->abs_secs;
        ual->ts.nsecs=pinfo->fd->abs_usecs*1000;
        ual->window=window;
+       ual->flags=0;
        ual1=ual;
 
 seq_finished:
@@ -464,6 +876,7 @@ seq_finished:
                        ual=ual2->next;
                        g_mem_chunk_free(tcp_unacked_chunk, ual2);
                }
+               prune_next_pdu_list(tnp, ack-base_ack);
                goto ack_finished;
        }
 
@@ -486,6 +899,7 @@ seq_finished:
                        ual=ual2->next;
                        g_mem_chunk_free(tcp_unacked_chunk, ual2);
                }
+               prune_next_pdu_list(tnp, ack-base_ack);
                goto ack_finished;
        }
 
@@ -521,7 +935,7 @@ seq_finished:
                        tmpual=ual->next;
                        g_mem_chunk_free(tcp_unacked_chunk, ual);
                }
-
+               prune_next_pdu_list(tnp, ack-base_ack);
        }
 
 ack_finished:
@@ -531,14 +945,12 @@ ack_finished:
                ual2=g_mem_chunk_alloc(tcp_unacked_chunk);
                ual2->next=NULL;
                ual2->frame=0;
-               ual2->ack_frame=0;
-               ual2->ack=0;
-               ual2->num_acks=0;
                ual2->seq=ack;
                ual2->nextseq=ack;
                ual2->ts.secs=0;
                ual2->ts.nsecs=0;
                ual2->window=window;
+               ual2->flags=0;
        }
 
        /* update the ACK counter and check for
@@ -557,26 +969,38 @@ ack_finished:
                if((!seglen)&&LE_SEQ(ack,ual->seq)){
                        /* if this is the first ack to keep track of, it is not
                           a duplicate */
-                       if(ual->num_acks==0){
-                               ual->ack=ack;
-                               ual->ack_frame=pinfo->fd->num;
-                               ual->num_acks=1;
+                       if(num2_acks==0){
+                               ack2=ack;
+                               ack2_frame=pinfo->fd->num;
+                               num2_acks=1;
                        /* if this ack is different, store this one 
                           instead and forget the previous one(s) */
-                       } else if(ual->ack!=ack){
-                               ual->ack=ack;
-                               ual->ack_frame=pinfo->fd->num;
-                               ual->num_acks=1;
+                       } else if(ack2!=ack){
+                               ack2=ack;
+                               ack2_frame=pinfo->fd->num;
+                               num2_acks=1;
                        /* this has to be a duplicate ack */
                        } else {
-                               ual->num_acks++;
+                               num2_acks++;
                        }       
                        
+                       /* is this an ACK to a KeepAlive? */
+                       if( (ual->flags&TCP_A_KEEP_ALIVE)
+                       && (ack==ual->seq) ){
+                               struct tcp_acked *ta;
+                               ta=tcp_analyze_get_acked_struct(pinfo->fd->num, TRUE);
+                               ta->flags|=TCP_A_KEEP_ALIVE_ACK;
+                               ual->flags^=TCP_A_KEEP_ALIVE;
+                       } else if(num2_acks>1) {
                        /* ok we have found a potential duplicate ack */
-                       if(ual->num_acks>1){
                                struct tcp_acked *ta;
                                ta=tcp_analyze_get_acked_struct(pinfo->fd->num, TRUE);
-                               ta->flags|=TCP_A_DUPLICATE_ACK;
+                               /* keepalives are not dupacks */
+                               if( (!(ta->flags&TCP_A_KEEP_ALIVE)) ){
+                                       ta->flags|=TCP_A_DUPLICATE_ACK;
+                                       ta->dupack_num=num2_acks-1;
+                                       ta->dupack_frame=ack2_frame;
+                               }
                        }
                }               
 
@@ -632,11 +1056,25 @@ ack_finished:
                 */
                tcpd->ual1=ual1;
                tcpd->ual2=ual2;
+               tcpd->ack1=ack1;
+               tcpd->ack2=ack2;
+               tcpd->ack1_frame=ack1_frame;
+               tcpd->ack2_frame=ack2_frame;
+               tcpd->num1_acks=num1_acks;
+               tcpd->num2_acks=num2_acks;
                tcpd->base_seq1=base_seq;
+               tcpd->base_seq2=base_ack;
        } else {
                tcpd->ual1=ual2;
                tcpd->ual2=ual1;
+               tcpd->ack1=ack2;
+               tcpd->ack2=ack1;
+               tcpd->ack1_frame=ack2_frame;
+               tcpd->ack2_frame=ack1_frame;
+               tcpd->num1_acks=num2_acks;
+               tcpd->num2_acks=num1_acks;
                tcpd->base_seq2=base_seq;
+               tcpd->base_seq1=base_ack;
        }
 
 
@@ -646,6 +1084,7 @@ ack_finished:
                trs=g_mem_chunk_alloc(tcp_rel_seq_chunk);
                trs->seq_base=base_seq;
                trs->ack_base=base_ack;
+               trs->win_scale=win_scale;
                g_hash_table_insert(tcp_rel_seq_table, (void *)pinfo->fd->num, trs);
        }
 }
@@ -688,6 +1127,19 @@ tcp_print_sequence_number_analysis(packet_info *pinfo, tvbuff_t *tvb, proto_tree
                                col_prepend_fstr(pinfo->cinfo, COL_INFO, "[TCP Retransmission] ");
                        }
                }
+               if( ta->flags&TCP_A_FAST_RETRANSMISSION ){
+                       proto_tree_add_none_format(flags_tree, hf_tcp_analysis_fast_retransmission, tvb, 0, 0, "This frame is a (suspected) fast retransmission");
+                       proto_tree_add_none_format(flags_tree, hf_tcp_analysis_retransmission, tvb, 0, 0, "This frame is a (suspected) retransmission");
+                       if(check_col(pinfo->cinfo, COL_INFO)){
+                               col_prepend_fstr(pinfo->cinfo, COL_INFO, "[TCP Fast Retransmission] ");
+                       }
+               }
+               if( ta->flags&TCP_A_OUT_OF_ORDER ){
+                       proto_tree_add_none_format(flags_tree, hf_tcp_analysis_out_of_order, tvb, 0, 0, "This frame is a (suspected) out-of-order segment");
+                       if(check_col(pinfo->cinfo, COL_INFO)){
+                               col_prepend_fstr(pinfo->cinfo, COL_INFO, "[TCP Out-Of-Order] ");
+                       }
+               }
                if( ta->flags&TCP_A_LOST_PACKET ){
                        proto_tree_add_none_format(flags_tree, hf_tcp_analysis_lost_packet, tvb, 0, 0, "A segment before this frame was lost");
                        if(check_col(pinfo->cinfo, COL_INFO)){
@@ -706,11 +1158,23 @@ tcp_print_sequence_number_analysis(packet_info *pinfo, tvbuff_t *tvb, proto_tree
                                col_prepend_fstr(pinfo->cinfo, COL_INFO, "[TCP Keep-Alive] ");
                        }
                }
-               if( ta->flags&TCP_A_DUPLICATE_ACK ){
-                       proto_tree_add_none_format(flags_tree, hf_tcp_analysis_duplicate_ack, tvb, 0, 0, "This is a TCP duplicate ack");
+               if( ta->flags&TCP_A_KEEP_ALIVE_ACK ){
+                       proto_tree_add_none_format(flags_tree, hf_tcp_analysis_keep_alive_ack, tvb, 0, 0, "This is an ACK to a TCP keep-alive segment");
                        if(check_col(pinfo->cinfo, COL_INFO)){
-                               col_prepend_fstr(pinfo->cinfo, COL_INFO, "[TCP Duplicate ACK] ");
+                               col_prepend_fstr(pinfo->cinfo, COL_INFO, "[TCP Keep-Alive ACK] ");
+                       }
+               }
+               if( ta->dupack_num){
+                       if( ta->flags&TCP_A_DUPLICATE_ACK ){
+                               proto_tree_add_none_format(flags_tree, hf_tcp_analysis_duplicate_ack, tvb, 0, 0, "This is a TCP duplicate ack");
+                               if(check_col(pinfo->cinfo, COL_INFO)){
+                                       col_prepend_fstr(pinfo->cinfo, COL_INFO, "[TCP Dup ACK %d#%d] ", ta->dupack_frame, ta->dupack_num);
+                               }
                        }
+                       proto_tree_add_uint(tree, hf_tcp_analysis_duplicate_ack_num,
+                               tvb, 0, 0, ta->dupack_num);
+                       proto_tree_add_uint(tree, hf_tcp_analysis_duplicate_ack_frame,
+                               tvb, 0, 0, ta->dupack_frame);
                }
                if( ta->flags&TCP_A_ZERO_WINDOW_PROBE ){
                        proto_tree_add_none_format(flags_tree, hf_tcp_analysis_zero_window_probe, tvb, 0, 0, "This is a TCP zero-window-probe");
@@ -777,11 +1241,21 @@ tcp_analyze_seq_init(void)
                g_hash_table_destroy(tcp_rel_seq_table);
                tcp_rel_seq_table = NULL;
        }
+       if( tcp_pdu_tracking_table ){
+               g_hash_table_foreach_remove(tcp_pdu_tracking_table,
+                       free_all_acked, NULL);
+               g_hash_table_destroy(tcp_pdu_tracking_table);
+               tcp_pdu_tracking_table = NULL;
+       }
 
        /*
         * Now destroy the chunk from which the conversation table
         * structures were allocated.
         */
+       if (tcp_next_pdu_chunk) {
+               g_mem_chunk_destroy(tcp_next_pdu_chunk);
+               tcp_next_pdu_chunk = NULL;
+       }
        if (tcp_analysis_chunk) {
                g_mem_chunk_destroy(tcp_analysis_chunk);
                tcp_analysis_chunk = NULL;
@@ -804,6 +1278,12 @@ tcp_analyze_seq_init(void)
                        tcp_acked_equal);
                tcp_rel_seq_table = g_hash_table_new(tcp_acked_hash,
                        tcp_acked_equal);
+               tcp_pdu_tracking_table = g_hash_table_new(tcp_acked_hash,
+                       tcp_acked_equal);
+               tcp_next_pdu_chunk = g_mem_chunk_new("tcp_next_pdu_chunk",
+                       sizeof(struct tcp_next_pdu),
+                       tcp_next_pdu_count * sizeof(struct tcp_next_pdu),
+                       G_ALLOC_ONLY);
                tcp_analysis_chunk = g_mem_chunk_new("tcp_analysis_chunk",
                        sizeof(struct tcp_analysis),
                        tcp_analysis_count * sizeof(struct tcp_analysis),
@@ -1066,7 +1546,7 @@ desegment_tcp(tvbuff_t *tvb, packet_info *pinfo, int offset,
                   Call the normal subdissector.
                */
                decode_tcp_ports(tvb, offset, pinfo, tree,
-                               sport, dport);
+                               sport, dport, 0);
                called_dissector = TRUE;
 
                /* Did the subdissector ask us to desegment some more data
@@ -1135,7 +1615,7 @@ desegment_tcp(tvbuff_t *tvb, packet_info *pinfo, int offset,
 
                        /* call subdissector */
                        decode_tcp_ports(next_tvb, 0, pinfo, tree,
-                               sport, dport);
+                               sport, dport, 0);
                        called_dissector = TRUE;
 
                        /*
@@ -1316,6 +1796,15 @@ desegment_tcp(tvbuff_t *tvb, packet_info *pinfo, int offset,
        }
 
        if (!called_dissector || pinfo->desegment_len != 0) {
+               if (ipfd_head != NULL && ipfd_head->reassembled_in != 0) {
+                       /*
+                        * We know what frame this PDU is reassembled in;
+                        * let the user know.
+                        */
+                       proto_tree_add_uint(tcp_tree, hf_tcp_reassembled_in,
+                           tvb, 0, 0, ipfd_head->reassembled_in);
+               }
+
                /*
                 * Either we didn't call the subdissector at all (i.e.,
                 * this is a segment that contains the middle of a
@@ -1521,8 +2010,10 @@ dissect_tcpopt_maxseg(const ip_tcp_opt *optp, tvbuff_t *tvb,
   guint16 mss;
 
   mss = tvb_get_ntohs(tvb, offset + 2);
-  proto_tree_add_text(opt_tree, tvb, offset,      optlen,
-                       "%s: %u bytes", optp->name, mss);
+  proto_tree_add_boolean_hidden(opt_tree, hf_tcp_option_mss, tvb, offset,
+                               optlen, TRUE);
+  proto_tree_add_uint_format(opt_tree, hf_tcp_option_mss_val, tvb, offset,
+                            optlen, mss, "%s: %u bytes", optp->name, mss);
   tcp_info_append_uint(pinfo, "MSS", mss);
 }
 
@@ -1533,9 +2024,15 @@ dissect_tcpopt_wscale(const ip_tcp_opt *optp, tvbuff_t *tvb,
   guint8 ws;
 
   ws = tvb_get_guint8(tvb, offset + 2);
-  proto_tree_add_text(opt_tree, tvb, offset,      optlen,
-                       "%s: %u (multiply by %u)", optp->name, ws, 1 << ws);
+  proto_tree_add_boolean_hidden(opt_tree, hf_tcp_option_wscale, tvb, 
+                               offset, optlen, TRUE);
+  proto_tree_add_uint_format(opt_tree, hf_tcp_option_wscale_val, tvb,
+                            offset, optlen, ws, "%s: %u (multiply by %u)", 
+                            optp->name, ws, 1 << ws);
   tcp_info_append_uint(pinfo, "WS", ws);
+  if(!pinfo->fd->flags.visited && tcp_analyze_seq && tcp_relative_seq){
+    pdu_store_window_scale_option(pinfo, ws);
+  }
 }
 
 static void
@@ -1553,6 +2050,8 @@ dissect_tcpopt_sack(const ip_tcp_opt *optp, tvbuff_t *tvb,
     if (field_tree == NULL) {
       /* Haven't yet made a subtree out of this option.  Do so. */
       field_tree = proto_item_add_subtree(tf, *optp->subtree_index);
+      proto_tree_add_boolean_hidden(field_tree, hf_tcp_option_sack, tvb, 
+                                   offset, optlen, TRUE);
     }
     if (optlen < 4) {
       proto_tree_add_text(field_tree, tvb, offset,      optlen,
@@ -1560,6 +2059,9 @@ dissect_tcpopt_sack(const ip_tcp_opt *optp, tvbuff_t *tvb,
       break;
     }
     leftedge = tvb_get_ntohl(tvb, offset);
+    proto_tree_add_uint_format(field_tree, hf_tcp_option_sack_sle, tvb, 
+                              offset, 4, leftedge, 
+                              "left edge = %u", leftedge);
     optlen -= 4;
     if (optlen < 4) {
       proto_tree_add_text(field_tree, tvb, offset,      optlen,
@@ -1569,8 +2071,9 @@ dissect_tcpopt_sack(const ip_tcp_opt *optp, tvbuff_t *tvb,
     /* XXX - check whether it goes past end of packet */
     rightedge = tvb_get_ntohl(tvb, offset + 4);
     optlen -= 4;
-    proto_tree_add_text(field_tree, tvb, offset,      8,
-        "left edge = %u, right edge = %u", leftedge, rightedge);
+    proto_tree_add_uint_format(field_tree, hf_tcp_option_sack_sre, tvb, 
+                              offset+4, 4, rightedge, 
+                              "right edge = %u", rightedge);
     tcp_info_append_uint(pinfo, "SLE", leftedge);
     tcp_info_append_uint(pinfo, "SRE", rightedge);
     offset += 8;
@@ -1584,6 +2087,8 @@ dissect_tcpopt_echo(const ip_tcp_opt *optp, tvbuff_t *tvb,
   guint32 echo;
 
   echo = tvb_get_ntohl(tvb, offset + 2);
+  proto_tree_add_boolean_hidden(opt_tree, hf_tcp_option_echo, tvb, offset,
+                               optlen, TRUE);
   proto_tree_add_text(opt_tree, tvb, offset,      optlen,
                        "%s: %u", optp->name, echo);
   tcp_info_append_uint(pinfo, "ECHO", echo);
@@ -1597,6 +2102,8 @@ dissect_tcpopt_timestamp(const ip_tcp_opt *optp, tvbuff_t *tvb,
 
   tsv = tvb_get_ntohl(tvb, offset + 2);
   tser = tvb_get_ntohl(tvb, offset + 6);
+  proto_tree_add_boolean_hidden(opt_tree, hf_tcp_option_time_stamp, tvb, 
+                               offset, optlen, TRUE);
   proto_tree_add_text(opt_tree, tvb, offset,      optlen,
     "%s: tsval %u, tsecr %u", optp->name, tsv, tser);
   tcp_info_append_uint(pinfo, "TSV", tsv);
@@ -1610,6 +2117,8 @@ dissect_tcpopt_cc(const ip_tcp_opt *optp, tvbuff_t *tvb,
   guint32 cc;
 
   cc = tvb_get_ntohl(tvb, offset + 2);
+  proto_tree_add_boolean_hidden(opt_tree, hf_tcp_option_cc, tvb, offset,
+                               optlen, TRUE);
   proto_tree_add_text(opt_tree, tvb, offset,      optlen,
                        "%s: %u", optp->name, cc);
   tcp_info_append_uint(pinfo, "CC", cc);
@@ -1728,13 +2237,22 @@ static const ip_tcp_opt tcpopts[] = {
 /* separated into a stand alone routine to other protocol dissectors */
 /* can call to it, ie. socks   */
 
+static gboolean try_heuristic_first = FALSE;
+
 void
 decode_tcp_ports(tvbuff_t *tvb, int offset, packet_info *pinfo,
-       proto_tree *tree, int src_port, int dst_port)
+       proto_tree *tree, int src_port, int dst_port, guint32 nxtseq)
 {
   tvbuff_t *next_tvb;
   int low_port, high_port;
 
+/*qqq   see if it is an unaligned PDU */
+  if(nxtseq && tcp_analyze_seq && (!tcp_desegment)){
+    guint32 seq;
+    seq=nxtseq-tvb_reported_length_remaining(tvb, offset);
+    offset=scan_for_next_pdu(pinfo, offset, seq, nxtseq);
+  }
+
   next_tvb = tvb_new_subset(tvb, offset, -1, -1);
 
 /* determine if this packet is part of a conversation and call dissector */
@@ -1742,7 +2260,13 @@ decode_tcp_ports(tvbuff_t *tvb, int offset, packet_info *pinfo,
 
   if (try_conversation_dissector(&pinfo->src, &pinfo->dst, PT_TCP,
                src_port, dst_port, next_tvb, pinfo, tree))
-    return;
+    goto end_decode_tcp_ports;
+
+  if (try_heuristic_first) {
+    /* do lookup with the heuristic subdissector table */
+    if (dissector_try_heuristic(heur_subdissector_list, next_tvb, pinfo, tree))
+       goto end_decode_tcp_ports;
+  }
 
   /* Do lookups with the subdissector table.
      We try the port number with the lower value first, followed by the
@@ -1768,17 +2292,28 @@ decode_tcp_ports(tvbuff_t *tvb, int offset, packet_info *pinfo,
   }
   if (low_port != 0 &&
       dissector_try_port(subdissector_table, low_port, next_tvb, pinfo, tree))
-    return;
+    goto end_decode_tcp_ports;
   if (high_port != 0 &&
       dissector_try_port(subdissector_table, high_port, next_tvb, pinfo, tree))
-    return;
+    goto end_decode_tcp_ports;
 
-  /* do lookup with the heuristic subdissector table */
-  if (dissector_try_heuristic(heur_subdissector_list, next_tvb, pinfo, tree))
-    return;
+  if (!try_heuristic_first) {
+    /* do lookup with the heuristic subdissector table */
+    if (dissector_try_heuristic(heur_subdissector_list, next_tvb, pinfo, tree))
+       goto end_decode_tcp_ports;
+  }
 
   /* Oh, well, we don't know this; dissect it as data. */
   call_dissector(data_handle,next_tvb, pinfo, tree);
+  return;
+
+end_decode_tcp_ports:
+  /* if !visited, check want_pdu_tracking and store it in table */
+  /* XXX fix nxtseq so that it always has valid content and skip the ==0 check */
+  if((!pinfo->fd->flags.visited) && nxtseq && tcp_analyze_seq && pinfo->want_pdu_tracking){
+    pdu_store_sequencenumber_of_next_pdu(pinfo, nxtseq+pinfo->bytes_until_next_pdu);
+  }
+
 }
 
 
@@ -1796,8 +2331,7 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
   gint       fpos = 0, i;
   guint      bpos;
   guint      optlen;
-  guint32    nxtseq;
-  guint      len;
+  guint32    nxtseq = 0;
   guint      reported_len;
   vec_t      cksum_vec[4];
   guint32    phdr[2];
@@ -1814,7 +2348,8 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
      tcph_count=0;
   }
   tcph=&tcphstruct[tcph_count];
-
+  SET_ADDRESS(&tcph->ip_src, pinfo->src.type, pinfo->src.len, pinfo->src.data);
+  SET_ADDRESS(&tcph->ip_dst, pinfo->dst.type, pinfo->dst.len, pinfo->dst.data);
 
   if (check_col(pinfo->cinfo, COL_PROTOCOL))
     col_set_str(pinfo->cinfo, COL_PROTOCOL, "TCP");
@@ -1862,31 +2397,55 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
   tcph->th_win = tvb_get_ntohs(tvb, offset + 14);
   tcph->th_hlen = hi_nibble(th_off_x2) * 4;  /* TCP header length, in bytes */
 
+  /*
+   * If we've been handed an IP fragment, we don't know how big the TCP
+   * segment is, so don't do anything that requires that we know that.
+   *
+   * The same applies if we're part of an error packet.  (XXX - if the
+   * ICMP and ICMPv6 dissectors could set a "this is how big the IP
+   * header says it is" length in the tvbuff, we could use that; such
+   * a length might also be useful for handling packets where the IP
+   * length is bigger than the actual data available in the frame; the
+   * dissectors should trust that length, and then throw a
+   * ReportedBoundsError exception when they go past the end of the frame.)
+   *
+   * We also can't determine the segment length if the reported length
+   * of the TCP packet is less than the TCP header length.
+   */
   reported_len = tvb_reported_length(tvb);
-  len = tvb_length(tvb);
 
-  /* Compute the length of data in this segment. */
-  tcph->th_seglen = reported_len - tcph->th_hlen;
-
-  if (tree) { /* Add the seglen as an invisible field */
+  if (!pinfo->fragmented && !pinfo->in_error_pkt) {
+    if (reported_len < tcph->th_hlen) {
+      proto_tree_add_text(tcp_tree, tvb, offset, 0,
+        "Short segment. Segment/fragment does not contain a full TCP header"
+        " (might be NMAP or someone else deliberately sending unusual packets)");
+      tcph->th_have_seglen = FALSE;
+    } else {
+      /* Compute the length of data in this segment. */
+      tcph->th_seglen = reported_len - tcph->th_hlen;
+      tcph->th_have_seglen = TRUE;
 
-    proto_tree_add_uint_hidden(ti, hf_tcp_len, tvb, offset, 4, tcph->th_seglen);
+      if (tree) { /* Add the seglen as an invisible field */
 
-  }
+        proto_tree_add_uint_hidden(ti, hf_tcp_len, tvb, offset, 4, tcph->th_seglen);
 
-  /* handle TCP seq# analysis parse all new segments we see */
-  if(tcp_analyze_seq){
-      if(!(pinfo->fd->flags.visited)){
-          tcp_analyze_sequence_number(pinfo, tcph->th_seq, tcph->th_ack, tcph->th_seglen, tcph->th_flags, tcph->th_win);
       }
-      if(tcp_relative_seq){
-          tcp_get_relative_seq_ack(pinfo->fd->num, &(tcph->th_seq), &(tcph->th_ack));
-      }
-  }
 
+      /* handle TCP seq# analysis parse all new segments we see */
+      if(tcp_analyze_seq){
+          if(!(pinfo->fd->flags.visited)){
+              tcp_analyze_sequence_number(pinfo, tcph->th_seq, tcph->th_ack, tcph->th_seglen, tcph->th_flags, tcph->th_win);
+          }
+          if(tcp_relative_seq){
+              tcp_get_relative_seq_ack(pinfo->fd->num, &(tcph->th_seq), &(tcph->th_ack), &(tcph->th_win));
+          }
+      }
 
-  /* Compute the sequence number of next octet after this segment. */
-  nxtseq = tcph->th_seq + tcph->th_seglen;
+      /* Compute the sequence number of next octet after this segment. */
+      nxtseq = tcph->th_seq + tcph->th_seglen;
+    }
+  } else
+    tcph->th_have_seglen = FALSE;
 
   if (check_col(pinfo->cinfo, COL_INFO) || tree) {
     for (i = 0; i < 8; i++) {
@@ -1909,8 +2468,9 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
   }
 
   if (tree) {
-    if (tcp_summary_in_tree)
+    if (tcp_summary_in_tree) {
       proto_item_append_text(ti, ", Seq: %u", tcph->th_seq);
+    }
     proto_tree_add_uint(tcp_tree, hf_tcp_seq, tvb, offset + 4, 4, tcph->th_seq);
   }
 
@@ -1931,11 +2491,17 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
   }
 
   if (tree) {
-    if (tcp_summary_in_tree)
-      proto_item_append_text(ti, ", Ack: %u, Len: %u", tcph->th_ack, tcph->th_seglen);
+    if (tcp_summary_in_tree) {
+      proto_item_append_text(ti, ", Ack: %u", tcph->th_ack);
+      if (tcph->th_have_seglen)
+        proto_item_append_text(ti, ", Len: %u", tcph->th_seglen);
+    }
     proto_item_set_len(ti, tcph->th_hlen);
-    if (nxtseq != tcph->th_seq)
-      proto_tree_add_uint(tcp_tree, hf_tcp_nxtseq, tvb, offset, 0, nxtseq);
+    if (tcph->th_have_seglen) {
+      if (nxtseq != tcph->th_seq) {
+        proto_tree_add_uint(tcp_tree, hf_tcp_nxtseq, tvb, offset, 0, nxtseq);
+      }
+    }
     if (tcph->th_flags & TH_ACK)
       proto_tree_add_uint(tcp_tree, hf_tcp_ack, tvb, offset + 8, 4, tcph->th_ack);
     proto_tree_add_uint_format(tcp_tree, hf_tcp_hdr_len, tvb, offset + 12, 1, tcph->th_hlen,
@@ -1966,9 +2532,8 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
    * Assume, initially, that we can't desegment.
    */
   pinfo->can_desegment = 0;
-
   th_sum = tvb_get_ntohs(tvb, offset + 16);
-  if (!pinfo->fragmented && len >= reported_len) {
+  if (!pinfo->fragmented && tvb_bytes_exist(tvb, 0, reported_len)) {
     /* The packet isn't part of an un-reassembled fragmented datagram
        and isn't truncated.  This means we have all the data, and thus
        can checksum it and, unless it's being returned in an error
@@ -2002,7 +2567,7 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
         g_assert_not_reached();
         break;
       }
-      cksum_vec[3].ptr = tvb_get_ptr(tvb, offset, len);
+      cksum_vec[3].ptr = tvb_get_ptr(tvb, offset, reported_len);
       cksum_vec[3].len = reported_len;
       computed_cksum = in_cksum(&cksum_vec[0], 4);
       if (computed_cksum == 0) {
@@ -2021,6 +2586,7 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
 
         /* Checksum is invalid, so we're not willing to desegment it. */
         desegment_ok = FALSE;
+        pinfo->noreassembly_reason = " (incorrect TCP checksum)";
       }
     } else {
       proto_tree_add_uint_format(tcp_tree, hf_tcp_checksum, tvb,
@@ -2065,8 +2631,10 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
   } else
     tcpinfo.urgent = FALSE;
 
-  if (check_col(pinfo->cinfo, COL_INFO))
-    col_append_fstr(pinfo->cinfo, COL_INFO, " Len=%u", tcph->th_seglen);
+  if (tcph->th_have_seglen) {
+    if (check_col(pinfo->cinfo, COL_INFO))
+      col_append_fstr(pinfo->cinfo, COL_INFO, " Len=%u", tcph->th_seglen);
+  }
 
   /* Decode TCP options, if any. */
   if (tree && tcph->th_hlen > TCPH_MIN_LEN) {
@@ -2087,18 +2655,27 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
      (it could be an ACK-only packet) */
   length_remaining = tvb_length_remaining(tvb, offset);
 
-  if( data_out_file ) {
-    reassemble_tcp( tcph->th_seq,              /* sequence number */
-        tcph->th_seglen,                       /* data length */
-        tvb_get_ptr(tvb, offset, length_remaining),    /* data */
-        length_remaining,              /* captured data length */
-        ( tcph->th_flags & TH_SYN ),           /* is syn set? */
-        &pinfo->net_src,
-       &pinfo->net_dst,
-       pinfo->srcport,
-       pinfo->destport);
+  if (tcph->th_have_seglen) {
+    if( data_out_file ) {
+      reassemble_tcp( tcph->th_seq,            /* sequence number */
+          tcph->th_seglen,                     /* data length */
+          tvb_get_ptr(tvb, offset, length_remaining),  /* data */
+          length_remaining,            /* captured data length */
+          ( tcph->th_flags & TH_SYN ),         /* is syn set? */
+          &pinfo->net_src,
+          &pinfo->net_dst,
+          pinfo->srcport,
+          pinfo->destport);
+    }
   }
 
+  /*
+   * XXX - what, if any, of this should we do if this is included in an
+   * error packet?  It might be nice to see the details of the packet
+   * that caused the ICMP error, but it might not be nice to have the
+   * dissector update state based on it.
+   * Also, we probably don't want to run TCP taps on those packets.
+   */
   if (length_remaining != 0) {
     if (tcph->th_flags & TH_RST) {
       /*
@@ -2130,7 +2707,7 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
            we don't report it as a malformed frame. */
         save_fragmented = pinfo->fragmented;
         pinfo->fragmented = TRUE;
-        decode_tcp_ports(tvb, offset, pinfo, tree, tcph->th_sport, tcph->th_dport);
+        decode_tcp_ports(tvb, offset, pinfo, tree, tcph->th_sport, tcph->th_dport, nxtseq);
         pinfo->fragmented = save_fragmented;
       }
     }
@@ -2212,8 +2789,9 @@ proto_register_tcp(void)
                { "Fin",                        "tcp.flags.fin", FT_BOOLEAN, 8, TFS(&flags_set_truth), TH_FIN,
                        "", HFILL }},
 
+               /* 32 bits so we can present some values adjusted to window scaling */
                { &hf_tcp_window_size,
-               { "Window size",                "tcp.window_size", FT_UINT16, BASE_DEC, NULL, 0x0,
+               { "Window size",                "tcp.window_size", FT_UINT32, BASE_DEC, NULL, 0x0,
                        "", HFILL }},
 
                { &hf_tcp_checksum,
@@ -2232,6 +2810,14 @@ proto_register_tcp(void)
                { "Retransmission",             "tcp.analysis.retransmission", FT_NONE, BASE_NONE, NULL, 0x0,
                        "This frame is a suspected TCP retransmission", HFILL }},
 
+               { &hf_tcp_analysis_fast_retransmission,
+               { "Fast Retransmission",                "tcp.analysis.fast_retransmission", FT_NONE, BASE_NONE, NULL, 0x0,
+                       "This frame is a suspected TCP fast retransmission", HFILL }},
+
+               { &hf_tcp_analysis_out_of_order,
+               { "Out Of Order",               "tcp.analysis.out_of_order", FT_NONE, BASE_NONE, NULL, 0x0,
+                       "This frame is a suspected Out-Of-Order segment", HFILL }},
+
                { &hf_tcp_analysis_lost_packet,
                { "Previous Segment Lost",              "tcp.analysis.lost_segment", FT_NONE, BASE_NONE, NULL, 0x0,
                        "A segment before this one was lost from the capture", HFILL }},
@@ -2244,10 +2830,22 @@ proto_register_tcp(void)
                { "Keep Alive",         "tcp.analysis.keep_alive", FT_NONE, BASE_NONE, NULL, 0x0,
                        "This is a keep-alive segment", HFILL }},
 
+               { &hf_tcp_analysis_keep_alive_ack,
+               { "Keep Alive ACK",             "tcp.analysis.keep_alive_ack", FT_NONE, BASE_NONE, NULL, 0x0,
+                       "This is an ACK to a keep-alive segment", HFILL }},
+
                { &hf_tcp_analysis_duplicate_ack,
                { "Duplicate ACK",              "tcp.analysis.duplicate_ack", FT_NONE, BASE_NONE, NULL, 0x0,
                        "This is a duplicate ACK", HFILL }},
 
+               { &hf_tcp_analysis_duplicate_ack_num,
+               { "Duplicate ACK #",            "tcp.analysis.duplicate_ack_num", FT_UINT32, BASE_DEC, NULL, 0x0,
+                       "This is duplicate ACK number #", HFILL }},
+
+               { &hf_tcp_analysis_duplicate_ack_frame,
+               { "Duplicate to the ACK in frame",              "tcp.analysis.duplicate_ack_frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0,
+                       "This is a duplicate to the ACK in frame #", HFILL }},
+
                { &hf_tcp_analysis_zero_window_violation,
                { "Zero Window Violation",              "tcp.analysis.zero_window_violation", FT_NONE, BASE_NONE, NULL, 0x0,
                        "This is a zero-window violation, an attempt to write >1 byte to a zero-window", HFILL }},
@@ -2265,7 +2863,7 @@ proto_register_tcp(void)
                    "", HFILL}},
 
                { &hf_tcp_analysis_acks_frame,
-                 { "This is an ACK to the segment in frame",            "tcp.analysis.acks_frame", FT_UINT32, BASE_DEC, NULL, 0x0,
+                 { "This is an ACK to the segment in frame",            "tcp.analysis.acks_frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0,
                    "Which previous segment is this an ACK for", HFILL}},
 
                { &hf_tcp_analysis_ack_rtt,
@@ -2303,6 +2901,75 @@ proto_register_tcp(void)
                { &hf_tcp_segments,
                { "TCP Segments", "tcp.segments", FT_NONE, BASE_NONE, NULL, 0x0,
                        "TCP Segments", HFILL }},
+
+               { &hf_tcp_reassembled_in,
+               { "Reassembled PDU in frame", "tcp.reassembled_in", FT_FRAMENUM, BASE_NONE, NULL, 0x0,
+                       "The PDU that starts but doesn't end in this segment is reassembled in this frame", HFILL }},
+
+               { &hf_tcp_option_mss,
+                 { "TCP MSS Option", "tcp.options.mss", FT_BOOLEAN, 
+                   BASE_NONE, NULL, 0x0, "TCP MSS Option", HFILL }},
+
+               { &hf_tcp_option_mss_val,
+                 { "TCP MSS Option Value", "tcp.options.mss_val", FT_UINT16,
+                   BASE_DEC, NULL, 0x0, "TCP MSS Option Value", HFILL}},
+
+               { &hf_tcp_option_wscale,
+                 { "TCP Window Scale Option", "tcp.options.wscale", 
+                   FT_BOOLEAN, 
+                   BASE_NONE, NULL, 0x0, "TCP Window Option", HFILL}},
+
+               { &hf_tcp_option_wscale_val,
+                 { "TCP Windows Scale Option Value", "tcp.options.wscale_val",
+                   FT_UINT8, BASE_DEC, NULL, 0x0, "TCP Window Scale Value",
+                   HFILL}},
+
+               { &hf_tcp_option_sack_perm, 
+                 { "TCP Sack Perm Option", "tcp.options.sack_perm", 
+                   FT_BOOLEAN,
+                   BASE_NONE, NULL, 0x0, "TCP Sack Perm Option", HFILL}},
+
+               { &hf_tcp_option_sack,
+                 { "TCP Sack Option", "tcp.options.sack", FT_BOOLEAN, 
+                   BASE_NONE, NULL, 0x0, "TCP Sack Option", HFILL}},
+
+               { &hf_tcp_option_sack_sle,
+                 {"TCP Sack Left Edge", "tcp.options.sack_le", FT_UINT32,
+                  BASE_DEC, NULL, 0x0, "TCP Sack Left Edge", HFILL}},
+
+               { &hf_tcp_option_sack_sre,
+                 {"TCP Sack Right Edge", "tcp.options.sack_re", FT_UINT32,
+                  BASE_DEC, NULL, 0x0, "TCP Sack Right Edge", HFILL}},
+
+               { &hf_tcp_option_echo,
+                 { "TCP Echo Option", "tcp.options.echo", FT_BOOLEAN, 
+                   BASE_NONE, NULL, 0x0, "TCP Sack Echo", HFILL}},
+
+               { &hf_tcp_option_echo_reply,
+                 { "TCP Echo Reply Option", "tcp.options.echo_reply", 
+                   FT_BOOLEAN,
+                   BASE_NONE, NULL, 0x0, "TCP Echo Reply Option", HFILL}},
+
+               { &hf_tcp_option_time_stamp,
+                 { "TCP Time Stamp Option", "tcp.options.time_stamp", 
+                   FT_BOOLEAN,
+                   BASE_NONE, NULL, 0x0, "TCP Time Stamp Option", HFILL}},
+
+               { &hf_tcp_option_cc,
+                 { "TCP CC Option", "tcp.options.cc", FT_BOOLEAN, BASE_NONE,
+                   NULL, 0x0, "TCP CC Option", HFILL}},
+
+               { &hf_tcp_option_ccnew,
+                 { "TCP CC New Option", "tcp.options.ccnew", FT_BOOLEAN, 
+                   BASE_NONE, NULL, 0x0, "TCP CC New Option", HFILL}},
+
+               { &hf_tcp_option_ccecho,
+                 { "TCP CC Echo Option", "tcp.options.ccecho", FT_BOOLEAN,
+                   BASE_NONE, NULL, 0x0, "TCP CC Echo Option", HFILL}},
+
+               { &hf_tcp_option_md5,
+                 { "TCP MD5 Option", "tcp.options.md5", FT_BOOLEAN, BASE_NONE,
+                   NULL, 0x0, "TCP MD5 Option", HFILL}},
        };
        static gint *ett[] = {
                &ett_tcp,
@@ -2330,24 +2997,30 @@ proto_register_tcp(void)
        tcp_module = prefs_register_protocol(proto_tcp, NULL);
        prefs_register_bool_preference(tcp_module, "summary_in_tree",
            "Show TCP summary in protocol tree",
-"Whether the TCP summary line should be shown in the protocol tree",
+           "Whether the TCP summary line should be shown in the protocol tree",
            &tcp_summary_in_tree);
        prefs_register_bool_preference(tcp_module, "check_checksum",
            "Check the validity of the TCP checksum when possible",
-"Whether to check the validity of the TCP checksum",
+           "Whether to check the validity of the TCP checksum",
            &tcp_check_checksum);
        prefs_register_bool_preference(tcp_module, "desegment_tcp_streams",
            "Allow subdissector to desegment TCP streams",
-"Whether subdissector can request TCP streams to be desegmented",
+           "Whether subdissector can request TCP streams to be desegmented",
            &tcp_desegment);
        prefs_register_bool_preference(tcp_module, "analyze_sequence_numbers",
            "Analyze TCP sequence numbers",
            "Make the TCP dissector analyze TCP sequence numbers to find and flag segment retransmissions, missing segments and RTT",
            &tcp_analyze_seq);
        prefs_register_bool_preference(tcp_module, "relative_sequence_numbers",
-           "Use relative sequence numbers",
-           "Make the TCP dissector use relative sequence numbers instead of absolute ones. To use this option you must also enable \"Analyze TCP sequence numbers\".",
+           "Relative sequence numbers and window scaling",
+           "Make the TCP dissector use relative sequence numbers instead of absolute ones. "
+           "To use this option you must also enable \"Analyze TCP sequence numbers\". "
+           "This option will also try to track and adjust the window field according to any TCP window scaling options seen.",
            &tcp_relative_seq);
+       prefs_register_bool_preference(tcp_module, "try_heuristic_first",
+           "Try heuristic sub-dissectors first",
+           "Try to decode a packet using an heuristic sub-dissector before using a sub-dissector registered to a specific port",
+           &try_heuristic_first);
 
        register_init_routine(tcp_analyze_seq_init);
        register_init_routine(tcp_desegment_init);