/* packet-tcp.c
* Routines for TCP packet disassembly
*
- * $Id: packet-tcp.c,v 1.198 2003/07/11 09:30:49 guy Exp $
+ * $Id: packet-tcp.c,v 1.207 2003/09/18 19:19:51 guy Exp $
*
* Ethereal - Network traffic analyzer
* By Gerald Combs <gerald@ethereal.com>
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;
&hf_tcp_segment_multiple_tails,
&hf_tcp_segment_too_long_fragment,
&hf_tcp_segment_error,
- NULL,
+ &hf_tcp_reassembled_in,
"Segments"
};
/* 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
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;
/* 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->ual2=NULL;
tcpd->base_seq2=0;
+ tcpd->win_scale2=-1;
tcpd->pdu_seq1=NULL;
tcpd->pdu_seq2=NULL;
*/
}
+/* if we saw a window scaling option, store it for future reference
+*/
static void
-tcp_get_relative_seq_ack(guint32 frame, guint32 *seq, guint32 *ack)
+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;
(*seq) -= trs->seq_base;
(*ack) -= trs->ack_base;
+ if(trs->win_scale!=-1){
+ (*win)<<=trs->win_scale;
+ }
}
static struct tcp_acked *
struct tcp_unacked *ual=NULL;
guint32 base_seq=0;
guint32 base_ack=0;
+ gint16 win_scale=-1;
struct tcp_next_pdu **tnp=NULL;
/* find(or create if needed) the conversation for this tcp session */
ual2=tcpd->ual2;
tnp=&tcpd->pdu_seq2;
base_seq=tcpd->base_seq1;
+ win_scale=tcpd->win_scale1;
base_ack=tcpd->base_seq2;
} else {
ual1=tcpd->ual2;
ual2=tcpd->ual1;
tnp=&tcpd->pdu_seq1;
base_seq=tcpd->base_seq2;
+ win_scale=tcpd->win_scale2;
base_ack=tcpd->base_seq1;
}
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->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->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
* , brilliant)
*/
if( (seglen<=1) && 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(!(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;
+ }
}
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->ts.secs=0;
ual2->ts.nsecs=0;
ual2->window=window;
+ ual2->flags=0;
}
/* update the ACK counter and check for
ual->num_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(ual->num_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=ual->num_acks-1;
+ ta->dupack_frame=ual->ack_frame;
+ }
}
}
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 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");
tcp_rel_seq_table = NULL;
}
if( tcp_pdu_tracking_table ){
- g_hash_table_foreach_remove(tcp_rel_seq_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;
}
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
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
/* 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, guint32 nxtseq)
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 */
src_port, dst_port, next_tvb, pinfo, tree))
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
port number with the higher value. This means that, for packets
if (low_port != 0 &&
dissector_try_port(subdissector_table, low_port, next_tvb, pinfo, tree))
goto end_decode_tcp_ports;
-
if (high_port != 0 &&
dissector_try_port(subdissector_table, high_port, next_tvb, pinfo, tree))
goto end_decode_tcp_ports;
- /* do lookup with the heuristic subdissector table */
- if (dissector_try_heuristic(heur_subdissector_list, next_tvb, pinfo, tree))
- 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;
+ }
/* Oh, well, we don't know this; dissect it as data. */
call_dissector(data_handle,next_tvb, pinfo, tree);
tcph_count=0;
}
tcph=&tcphstruct[tcph_count];
- /* XXX add to ipv6 so this works for that protocol as well */
- tcph->ip_header=pinfo->private_data;
+ 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");
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,
{ "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_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}},
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);