/* packet-tcp.c
* Routines for TCP packet disassembly
*
- * $Id: packet-tcp.c,v 1.203 2003/08/29 11:15:13 sahlberg 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>
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;
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) */
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 # */
};
struct tcp_rel_seq {
guint32 seq_base;
guint32 ack_base;
+ gint16 win_scale;
};
static GHashTable *tcp_rel_seq_table = NULL;
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.
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;
*/
}
+/* 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)
+tcp_get_relative_seq_ack(guint32 frame, guint32 *seq, guint32 *ack, guint32 *win)
{
struct tcp_rel_seq *trs;
(*seq) -= trs->seq_base;
(*ack) -= trs->ack_base;
+ if(trs->win_scale!=-1){
+ (*win)<<=trs->win_scale;
+ }
}
static struct tcp_acked *
struct tcp_unacked *ual1=NULL;
struct tcp_unacked *ual2=NULL;
struct tcp_unacked *ual=NULL;
- guint32 base_seq=0;
- guint32 base_ack=0;
+ 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;
/* find(or create if needed) the conversation for this tcp session */
if(direction>=0){
ual1=tcpd->ual1;
ual2=tcpd->ual2;
+ 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=tcpd->base_seq1;
- base_ack=tcpd->base_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;
+ 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=tcpd->base_seq2;
- base_ack=tcpd->base_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(tcp_relative_seq){
- if(base_seq==0){
- base_seq=seq;
- }
- if(base_ack==0){
- base_ack=ack;
+ 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;
}
}
+#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. */
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;
+ ual1->flags=0;
if(tcp_relative_seq){
base_seq=seq;
base_ack=ack;
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;
+ ual1->flags=0;
if(tcp_relative_seq){
base_seq=seq;
base_ack=ack;
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;
}
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;
}
}
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;
+
+ ta=tcp_analyze_get_acked_struct(pinfo->fd->num, TRUE);
+ ta->flags|=TCP_A_FAST_RETRANSMISSION;
+ goto seq_finished;
+ }
+ }
- /* did this segment contain any more data we havent seen yet?
- * if so we can just increase nextseq
+ /* 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.
*/
- 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;
+ 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
+ */
+ /* 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;
}
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:
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
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;
- ta->dupack_num=ual->num_acks-1;
- ta->dupack_frame=ual->ack_frame;
+ /* 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;
+ }
}
}
*/
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;
}
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);
}
}
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)){
col_prepend_fstr(pinfo->cinfo, COL_INFO, "[TCP Keep-Alive] ");
}
}
+ 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 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");
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
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));
+ tcp_get_relative_seq_ack(pinfo->fd->num, &(tcph->th_seq), &(tcph->th_ack), &(tcph->th_win));
}
}
{ "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,
{ "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 }},
{ "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 }},
"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",