From 2a148564d67964bef7eb5a2e8511fa431060850f Mon Sep 17 00:00:00 2001 From: Guy Harris Date: Thu, 13 Sep 2001 07:56:53 +0000 Subject: TCP desegmentation support, and changes to the ONC RPC and NBSS dissectors to use it, from Ronnie Sahlberg, with additional changes to handle the case where a frame contains messages that don't run past the end followed by one that does and where a reassembled chunk has, at the end, a message that runs past the end of that chunk (because the reassembly was for an earlier message). svn path=/trunk/; revision=3923 --- AUTHORS | 2 + epan/packet_info.h | 5 +- packet-nbns.c | 33 +++- packet-rpc.c | 170 +++++++++++++++--- packet-tcp.c | 507 +++++++++++++++++++++++++++++++++++++++++++++++++---- reassemble.c | 19 +- 6 files changed, 673 insertions(+), 63 deletions(-) diff --git a/AUTHORS b/AUTHORS index 2bb26db326..3641760615 100644 --- a/AUTHORS +++ b/AUTHORS @@ -549,6 +549,8 @@ Ronnie Sahlberg { Tvbuffified ISIS dissector Tvbuffified SMB NETLOGON dissector Tvbuffified SMB BROWSER dissector + TCP segment reassembly and support for it in ONC RPC and NBSS + dissectors } Borosa Tomislav { diff --git a/epan/packet_info.h b/epan/packet_info.h index f236b7559a..a80bb6b4bb 100644 --- a/epan/packet_info.h +++ b/epan/packet_info.h @@ -1,7 +1,7 @@ /* packet_info.h * Definitions for packet info structures and routines * - * $Id: packet_info.h,v 1.5 2001/08/04 04:04:35 guy Exp $ + * $Id: packet_info.h,v 1.6 2001/09/13 07:53:53 guy Exp $ * * Ethereal - Network traffic analyzer * By Gerald Combs @@ -108,6 +108,9 @@ typedef struct _packet_info { guint32 srcport; /* source port */ guint32 destport; /* destination port */ guint32 match_port; + gboolean can_desegment; /* TRUE if this segment could be desegmented */ + int desegment_offset; /* offset of stuff needing desegmentation */ + guint32 desegment_len; /* requested desegmentation additional length */ int iplen; int iphdrlen; int p2p_dir; diff --git a/packet-nbns.c b/packet-nbns.c index 42e268d08d..48cf6d54f5 100644 --- a/packet-nbns.c +++ b/packet-nbns.c @@ -4,7 +4,7 @@ * Gilbert Ramirez * Much stuff added by Guy Harris * - * $Id: packet-nbns.c,v 1.54 2001/08/05 10:00:35 guy Exp $ + * $Id: packet-nbns.c,v 1.55 2001/09/13 07:53:51 guy Exp $ * * Ethereal - Network traffic analyzer * By Gerald Combs @@ -41,6 +41,7 @@ #include "packet-dns.h" #include "packet-netbios.h" #include "packet-smb.h" +#include "prefs.h" static int proto_nbns = -1; static int hf_nbns_response = -1; @@ -78,6 +79,10 @@ static int hf_nbss_flags = -1; static gint ett_nbss = -1; static gint ett_nbss_flags = -1; +/* desegmentation of NBSS over TCP */ +static gboolean nbss_desegment = FALSE; + + /* See RFC 1001 and 1002 for information on the first three, and see http://www.cifs.com/specs/draft-leach-cifs-v1-spec-01.txt @@ -1393,6 +1398,25 @@ dissect_nbss_packet(tvbuff_t *tvb, int offset, packet_info *pinfo, length += 65536; } + /*Desegmentation */ + if (nbss_desegment) { + if (pinfo->can_desegment + && length > tvb_length_remaining(tvb, offset+4)) { + /* + * This frame doesn't have all of the data for + * this message, but we can do reassembly on it. + * + * Tell the TCP dissector where the data for this + * message starts in the data it handed us, and + * how many more bytes we need, and return. + */ + pinfo->desegment_offset = offset; + pinfo->desegment_len = + length - tvb_length_remaining(tvb, offset+4); + return max_data; + } + } + if (tree) { ti = proto_tree_add_item(tree, proto_nbss, tvb, offset, length + 4, FALSE); nbss_tree = proto_item_add_subtree(ti, ett_nbss); @@ -1661,6 +1685,7 @@ proto_register_nbt(void) &ett_nbss, &ett_nbss_flags, }; + module_t *nbss_module; proto_nbns = proto_register_protocol("NetBIOS Name Service", "NBNS", "nbns"); proto_register_field_array(proto_nbns, hf_nbns, array_length(hf_nbns)); @@ -1674,6 +1699,12 @@ proto_register_nbt(void) proto_register_field_array(proto_nbss, hf_nbss, array_length(hf_nbss)); proto_register_subtree_array(ett, array_length(ett)); + + nbss_module = prefs_register_protocol(proto_nbss, NULL); + prefs_register_bool_preference(nbss_module, "desegment_nbss_commands", + "Desegment all NBSS commands spanning multiple TCP segments", + "Whether NBSS dissector should desegment all commands spanning multiple TCP segments", + &nbss_desegment); } void diff --git a/packet-rpc.c b/packet-rpc.c index 1637de7ad2..dcd5412669 100644 --- a/packet-rpc.c +++ b/packet-rpc.c @@ -2,7 +2,7 @@ * Routines for rpc dissection * Copyright 1999, Uwe Girlich * - * $Id: packet-rpc.c,v 1.70 2001/09/12 08:46:39 guy Exp $ + * $Id: packet-rpc.c,v 1.71 2001/09/13 07:53:51 guy Exp $ * * Ethereal - Network traffic analyzer * By Gerald Combs @@ -40,6 +40,7 @@ #include "packet.h" #include "conversation.h" #include "packet-rpc.h" +#include "prefs.h" /* * See: @@ -60,6 +61,9 @@ #define RPC_RM_FRAGLEN 0x7fffffffL +/* desegmentation of RPC over TCP */ +static gboolean rpc_desegment = FALSE; + static struct true_false_string yesno = { "Yes", "No" }; @@ -218,6 +222,7 @@ typedef struct _rpc_prog_info_value { } rpc_prog_info_value; static void dissect_rpc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree); +static void dissect_rpc_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree); /***********************************/ /* Hash array with procedure names */ @@ -1175,7 +1180,8 @@ dissect_rpc_indir_call(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, /* Make the dissector for this conversation the non-heuristic RPC dissector. */ - conversation_set_dissector(conversation, dissect_rpc); + conversation_set_dissector(conversation, + (pinfo->ptype == PT_TCP) ? dissect_rpc_tcp : dissect_rpc); /* Prepare the key data. @@ -1384,9 +1390,9 @@ dissect_rpc_continuation(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) } static gboolean -dissect_rpc_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +dissect_rpc_message(tvbuff_t *tvb, int offset, packet_info *pinfo, + proto_tree *tree, gboolean use_rm, guint32 rpc_rm) { - int offset = 0; guint32 msg_type; rpc_call_info_key rpc_call_key; rpc_call_info_value *rpc_call = NULL; @@ -1425,9 +1431,6 @@ dissect_rpc_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) proto_tree *ptree = NULL; int offset_old = offset; - int use_rm = 0; - guint32 rpc_rm = 0; - rpc_call_info_key *new_rpc_call_key; rpc_proc_info_key key; rpc_proc_info_value *value = NULL; @@ -1436,17 +1439,6 @@ dissect_rpc_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) dissect_function_t *dissect_function = NULL; - /* TCP uses record marking */ - use_rm = (pinfo->ptype == PT_TCP); - - /* the first 4 bytes are special in "record marking mode" */ - if (use_rm) { - if (!tvb_bytes_exist(tvb, offset, 4)) - return FALSE; - rpc_rm = tvb_get_ntohl(tvb, offset); - offset += 4; - } - /* * Check to see whether this looks like an RPC call or reply. */ @@ -1723,7 +1715,8 @@ dissect_rpc_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) /* Make the dissector for this conversation the non-heuristic RPC dissector. */ - conversation_set_dissector(conversation, dissect_rpc); + conversation_set_dissector(conversation, + (pinfo->ptype == PT_TCP) ? dissect_rpc_tcp : dissect_rpc); /* prepare the key data */ rpc_call_key.xid = xid; @@ -2068,13 +2061,140 @@ dissect_rpc_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) static gboolean dissect_rpc_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { - return dissect_rpc_common(tvb, pinfo, tree); + return dissect_rpc_message(tvb, 0, pinfo, tree, FALSE, 0); } static void dissect_rpc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { - if (!dissect_rpc_common(tvb, pinfo, tree)) + if (!dissect_rpc_message(tvb, 0, pinfo, tree, FALSE, 0)) + dissect_rpc_continuation(tvb, pinfo, tree); +} + +/* + * Can return: + * + * NEED_MORE_DATA, if we don't have enough data to dissect anything; + * + * IS_RPC, if we dissected at least one message in its entirety + * as RPC; + * + * IS_NOT_RPC, if we found no RPC message. + */ +typedef enum { + NEED_MORE_DATA, + IS_RPC, + IS_NOT_RPC +} rpc_tcp_return_t; + +static rpc_tcp_return_t +dissect_rpc_tcp_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, + gboolean is_heur) +{ + int offset = 0; + guint32 rpc_rm; + gboolean saw_rpc = FALSE; + gint32 len, seglen; + gint tvb_len, tvb_reported_len; + tvbuff_t *msg_tvb; + + while (tvb_reported_length_remaining(tvb, offset) != 0) { + /* + * XXX - we need to handle records that don't have the "last + * fragment" bit set, and reassemble fragments. + */ + + /* the first 4 bytes are special in "record marking mode" */ + if (!tvb_bytes_exist(tvb, offset, 4)) { + /* + * XXX - we should somehow arrange to handle + * a record mark split across TCP segments. + */ + return saw_rpc ? IS_RPC : IS_NOT_RPC; + } + rpc_rm = tvb_get_ntohl(tvb, offset); + + len = rpc_rm&RPC_RM_FRAGLEN; + + /* + * XXX - reject fragments bigger than 2 megabytes. + * This is arbitrary, but should at least prevent + * some crashes from either packets with really + * large RPC-over-TCP fragments or from stuff that's + * not really RPC. + */ + if (len > 2*1024*1024) + return saw_rpc ? IS_RPC : IS_NOT_RPC; + if (rpc_desegment) { + seglen = tvb_length_remaining(tvb, offset + 4); + + if (len > seglen && pinfo->can_desegment) { + /* + * This frame doesn't have all of the + * data for this message, but we can do + * reassembly on it. + * + * If this is a heuristic dissector, just + * return IS_NOT_RPC - we don't want to try + * to get more data, as that's too likely + * to cause us to misidentify this as + * RPC. + * + * If this isn't a heuristic dissector, + * we've already identified this conversation + * as containing RPC data, as we saw RPC + * data in previous frames. Try to get + * more data. + */ + if (is_heur) + return IS_NOT_RPC; + else { + pinfo->desegment_offset = offset; + pinfo->desegment_len = len - seglen; + return NEED_MORE_DATA; + } + } + } + len += 4; /* include record mark */ + tvb_len = tvb_length_remaining(tvb, offset); + tvb_reported_len = tvb_reported_length_remaining(tvb, offset); + if (tvb_len > len) + tvb_len = len; + if (tvb_reported_len > len) + tvb_reported_len = len; + msg_tvb = tvb_new_subset(tvb, offset, tvb_len, + tvb_reported_len); + if (!dissect_rpc_message(msg_tvb, 4, pinfo, tree, + TRUE, rpc_rm)) + break; + offset += len; + saw_rpc = TRUE; + } + return saw_rpc ? IS_RPC : IS_NOT_RPC; +} + +static gboolean +dissect_rpc_tcp_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +{ + switch (dissect_rpc_tcp_common(tvb, pinfo, tree, TRUE)) { + + case IS_RPC: + return TRUE; + + case IS_NOT_RPC: + return FALSE; + + default: + /* "Can't happen" */ + g_assert_not_reached(); + return FALSE; + } +} + +static void +dissect_rpc_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +{ + if (dissect_rpc_tcp_common(tvb, pinfo, tree, FALSE) == IS_NOT_RPC) dissect_rpc_continuation(tvb, pinfo, tree); } @@ -2259,12 +2379,18 @@ proto_register_rpc(void) &ett_rpc_gss_data, &ett_rpc_array, }; + module_t *rpc_module; proto_rpc = proto_register_protocol("Remote Procedure Call", "RPC", "rpc"); proto_register_field_array(proto_rpc, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); register_init_routine(&rpc_init_protocol); + rpc_module = prefs_register_protocol(proto_rpc, NULL); + prefs_register_bool_preference(rpc_module, "desegment_rpc_over_tcp", + "Desegment all RPC over TCP commands", + "Whether the RPC dissector should desegment all RPC over TCP commands", + &rpc_desegment); /* * Init the hash tables. Dissectors for RPC protocols must @@ -2284,6 +2410,6 @@ proto_register_rpc(void) void proto_reg_handoff_rpc(void) { - heur_dissector_add("tcp", dissect_rpc_heur, proto_rpc); + heur_dissector_add("tcp", dissect_rpc_tcp_heur, proto_rpc); heur_dissector_add("udp", dissect_rpc_heur, proto_rpc); } diff --git a/packet-tcp.c b/packet-tcp.c index 547876df75..35cb968a89 100644 --- a/packet-tcp.c +++ b/packet-tcp.c @@ -1,7 +1,7 @@ /* packet-tcp.c * Routines for TCP packet disassembly * - * $Id: packet-tcp.c,v 1.106 2001/09/03 17:57:17 guy Exp $ + * $Id: packet-tcp.c,v 1.107 2001/09/13 07:53:51 guy Exp $ * * Ethereal - Network traffic analyzer * By Gerald Combs @@ -51,6 +51,7 @@ #include "packet-ip.h" #include "conversation.h" #include "strutil.h" +#include "reassemble.h" /* Place TCP summary in proto tree */ static gboolean tcp_summary_in_tree = TRUE; @@ -85,6 +86,7 @@ static gint ett_tcp = -1; static gint ett_tcp_flags = -1; static gint ett_tcp_options = -1; static gint ett_tcp_option_sack = -1; +static gint ett_tcp_segments = -1; static dissector_table_t subdissector_table; static heur_dissector_list_t heur_subdissector_list; @@ -149,6 +151,410 @@ typedef struct _e_tcphdr { #define TCPOLEN_CCECHO 6 #define TCPOLEN_MD5 18 + + +/* Desegmentation of TCP streams */ +/* table to hold defragmented TCP streams */ +static GHashTable *tcp_fragment_table = NULL; +static void +tcp_fragment_init(void) +{ + fragment_table_init(&tcp_fragment_table); +} + +/* functions to trace tcp segments */ +/* Enable desegmenting of TCP streams */ +static gboolean tcp_desegment = FALSE; + +static GHashTable *tcp_segment_table = NULL; +static GMemChunk *tcp_segment_key_chunk = NULL; +static int tcp_segment_init_count = 200; + +typedef struct _tcp_segment_key { + /* for ouwn bookkeeping inside packet-tcp.c */ + address *src; + address *dst; + guint32 seq; + /* xxx */ + guint32 start_seq; + guint32 tot_len; + guint32 first_frame; +} tcp_segment_key; + +static gboolean +free_all_segments(gpointer key_arg, gpointer value, gpointer user_data) +{ + tcp_segment_key *key = key_arg; + + if((key->src)&&(key->src->data)){ + g_free((gpointer)key->src->data); + key->src->data=NULL; + g_free((gpointer)key->src); + key->src=NULL; + } + if((key->dst)&&(key->dst->data)){ + g_free((gpointer)key->dst->data); + key->dst->data=NULL; + g_free((gpointer)key->dst); + key->dst=NULL; + } + + return TRUE; +} + +static guint +tcp_segment_hash(gconstpointer k) +{ + tcp_segment_key *key = (tcp_segment_key *)k; + + return key->seq; +} + +static gint +tcp_segment_equal(gconstpointer k1, gconstpointer k2) +{ + tcp_segment_key *key1 = (tcp_segment_key *)k1; + tcp_segment_key *key2 = (tcp_segment_key *)k2; + + return ( ( (key1->seq==key2->seq) + &&(ADDRESSES_EQUAL(key1->src, key2->src)) + &&(ADDRESSES_EQUAL(key1->dst, key2->dst)) + ) ? TRUE:FALSE); +} + +static void +tcp_desegment_init(void) +{ + + /* dont allocate any memory chunks unless the user really + uses this option + */ + if(!tcp_desegment){ + return; + } + + if(tcp_segment_table){ + g_hash_table_foreach_remove(tcp_segment_table, + free_all_segments, NULL); + } else { + tcp_segment_table = g_hash_table_new(tcp_segment_hash, + tcp_segment_equal); + } + + if(tcp_segment_key_chunk){ + g_mem_chunk_destroy(tcp_segment_key_chunk); + } + tcp_segment_key_chunk = g_mem_chunk_new("tcp_segment_key_chunk", + sizeof(tcp_segment_key), + tcp_segment_init_count*sizeof(tcp_segment_key), + G_ALLOC_ONLY); +} + +static void +desegment_tcp(tvbuff_t *tvb, packet_info *pinfo, int offset, + guint32 seq, guint32 nxtseq, + guint32 sport, guint32 dport, + proto_tree *tree, proto_tree *tcp_tree) +{ + fragment_data *ipfd_head; + tcp_segment_key old_tsk, *tsk; + gboolean must_desegment = FALSE; + gboolean called_dissector = FALSE; + int deseg_offset; + + /* + * Initialize these to assume no desegmentation. + * If that's not the case, these will be set appropriately + * by the subdissector. + */ + pinfo->desegment_offset = 0; + pinfo->desegment_len = 0; + + /* + * Initialize this to assume that this segment will just be + * added to the middle of a desegmented chunk of data, so + * that we should show it all as data. + * If that's not the case, it will be set appropriately. + */ + deseg_offset = offset; + + /* First we must check if this TCP segment should be desegmented. + This is only to check if we should desegment this packet, + so we dont spend time doing COPY_ADDRESS/g_free. + We just "borrow" some address structures from pinfo instead. Cheaper. + */ + old_tsk.src = &pinfo->src; + old_tsk.dst = &pinfo->dst; + old_tsk.seq = seq; + tsk = g_hash_table_lookup(tcp_segment_table, &old_tsk); + + if(tsk){ + /* OK, this segment was found, which means it continues + a higher-level PDU. This means we must desegment it. + Add it to the defragmentation lists. + */ + ipfd_head = fragment_add(tvb, offset, pinfo, tsk->start_seq, + tcp_fragment_table, + seq - tsk->start_seq, + nxtseq - seq, + (nxtseq < (tsk->start_seq + tsk->tot_len)) ); + + if(!ipfd_head){ + /* fragment_add() returned NULL, This means that + desegmentation is not completed yet. + (its like defragmentation but we know we will + always add the segments in order). + XXX - no, we don't; there is no guarantee that + TCP segments are in order on the wire. + + we must add next segment to our table so we will + find it later. + */ + tcp_segment_key *new_tsk; + + new_tsk = g_mem_chunk_alloc(tcp_segment_key_chunk); + memcpy(new_tsk, tsk, sizeof(tcp_segment_key)); + new_tsk->seq=nxtseq; + g_hash_table_insert(tcp_segment_table,new_tsk,new_tsk); + } + } else { + /* This segment was not found in our table, so it doesn't + contain a continuation of a higher-level PDU. + Call the normal subdissector. + */ + decode_tcp_ports(tvb, offset, pinfo, tree, + sport, dport); + called_dissector = TRUE; + + /* + * Advance the offset to the first byte that the + * subdissector didn't process. + */ + offset += pinfo->desegment_offset; + + /* Did the subdissector ask us to desegment some more data + before it could handle the packet? + If so we have to create some structures in our table but + this is something we only do the first time we see this + packet. + */ + if(pinfo->desegment_len) { + if (!pinfo->fd->flags.visited) + must_desegment = TRUE; + + /* + * Set "deseg_offset" to the offset in "tvb" + * of the first byte of data that the + * subdissector didn't process. + */ + deseg_offset = offset; + } + + /* Either no desegmentation is necessary, or this is + segment contains the beginning but not the end of + a higher-level PDU and thus isn't completely + desegmented. + */ + ipfd_head = NULL; + } + + /* is it completely desegmented? */ + if(ipfd_head){ + fragment_data *ipfd; + proto_tree *st = NULL; + proto_item *si = NULL; + + /* first we show a tree with all segments */ + si = proto_tree_add_text(tcp_tree, tvb, 0, 0, + "Segments"); + st = proto_item_add_subtree(si, ett_tcp_segments); + for(ipfd=ipfd_head->next; ipfd; ipfd=ipfd->next){ + proto_tree_add_text(st, tvb, 0, 0, + "Frame:%d seq#:%d-%d [%d-%d]", + ipfd->frame, + tsk->start_seq + ipfd->offset, + tsk->start_seq + ipfd->offset + ipfd->len - 1, + ipfd->offset, + ipfd->offset + ipfd->len - 1); + } + + /* + * We only call subdissector for the last segment. + * Note that the last segment may include more than what + * we needed. + */ + if(nxtseq >= (tsk->start_seq + tsk->tot_len)){ + /* ok, lest call subdissector with desegmented data */ + packet_info save_pi; + tvbuff_t *next_tvb; + + /* create a new TVB structure for desegmented data */ + next_tvb = tvb_new_real_data(ipfd_head->data, + ipfd_head->datalen, ipfd_head->datalen, + "Desegmented"); + + /* add this tvb as a child to the original one */ + tvb_set_child_real_data_tvbuff(tvb, next_tvb); + + /* add desegmented data to the data source list */ + pinfo->fd->data_src = g_slist_append(pinfo->fd->data_src, next_tvb); + + /* save current value of *pinfo across call to + dissector */ + save_pi = *pinfo; + pinfo->compat_top_tvb = next_tvb; + pinfo->len = tvb_reported_length(next_tvb); + pinfo->captured_len = tvb_length(next_tvb); + + /* call subdissector */ + decode_tcp_ports(next_tvb, 0, pinfo, tree, + sport, dport); + called_dissector = TRUE; + + /* + * Don't trash the new values of "desegment_offset" + * and "desegment_len". + */ + save_pi.desegment_offset = pinfo->desegment_offset; + save_pi.desegment_len = pinfo->desegment_len; + *pinfo = save_pi; + + /* Did the subdissector ask us to desegment some more + data? This means that the data at the beginning + of this segment completed a higher-level PDU, + but the data at the end of this segment started + a higher-level PDU but didn't complete it. + + If so we have to create some structures in our + table but this is something we only do the first + time we see this packet. + */ + if(pinfo->desegment_len) { + if (!pinfo->fd->flags.visited) + must_desegment = TRUE; + + /* + * The stuff we couldn't dissect must have + * come from this segment, so it's all in + * "tvb". + * + * "pinfo->desegment_offset" is relative + * to the beginning of "next_tvb"; + * we want an offset relative to the + * beginning of "tvb". + * + * First, compute the offset relative to + * the *end* of "next_tvb" - i.e., the number + * of bytes before the end of "next_tvb" + * at which the subdissector stopped. + * That's the length of "next_tvb" minus + * the offset, relative to the beginning + * of "next_tvb, at which the subdissector + * stopped. + */ + deseg_offset = + ipfd_head->datalen - pinfo->desegment_offset; + + /* + * "tvb" and "next_tvb" end at the same byte + * of data, so the offset relative to the + * end of "next_tvb" of the byte at which + * we stopped is also the offset relative + * to the end of "tvb" of the byte at which + * we stopped. + * + * Convert that back into an offset relative + * to the beginninng of "tvb", by taking + * the length of "tvb" and subtracting the + * offset relative to the end. + */ + deseg_offset = tvb_length(tvb) - deseg_offset; + } + } + } + + if (must_desegment) { + tcp_segment_key *tsk, *new_tsk; + + /* + * XXX - how do we detect out-of-order transmissions? + * We can't just check for "nxtseq" being greater than + * "tsk->start_seq"; for now, we check for the difference + * being less than a megabyte, but this is a really + * gross hack - we really need to handle out-of-order + * transmissions correctly. + */ + if ((nxtseq - (seq + pinfo->desegment_offset)) <= 1024*1024) { + /* OK, subdissector wants us to desegment + some data before it can process it. Add + what remains of this packet and set + up next packet/sequence number as well. + + We must remember this segment + */ + tsk = g_mem_chunk_alloc(tcp_segment_key_chunk); + tsk->src = g_malloc(sizeof(address)); + COPY_ADDRESS(tsk->src, &pinfo->src); + tsk->dst = g_malloc(sizeof(address)); + COPY_ADDRESS(tsk->dst, &pinfo->dst); + tsk->seq = seq + pinfo->desegment_offset; + tsk->start_seq = tsk->seq; + tsk->tot_len = nxtseq - tsk->start_seq + pinfo->desegment_len; + tsk->first_frame = pinfo->fd->num; + g_hash_table_insert(tcp_segment_table, tsk, tsk); + + /* Add portion of segment unprocessed by the subdissector + to defragmentation lists */ + fragment_add(tvb, deseg_offset, pinfo, tsk->start_seq, + tcp_fragment_table, + tsk->seq - tsk->start_seq, + nxtseq - tsk->start_seq, + (nxtseq < tsk->start_seq + tsk->tot_len)); + + /* this is the next segment in the sequence we want */ + new_tsk = g_mem_chunk_alloc(tcp_segment_key_chunk); + memcpy(new_tsk, tsk, sizeof(tcp_segment_key)); + new_tsk->seq = nxtseq; + g_hash_table_insert(tcp_segment_table,new_tsk,new_tsk); + } + } + + if (!called_dissector || pinfo->desegment_len != 0) { + /* + * Either we didn't call the subdissector at all (i.e., + * this is a segment that contains the middle of a + * higher-level PDU, but contains neither the beginning + * nor the end), or the subdissector couldn't dissect it + * all, as some data was missing (i.e., it set + * "pinfo->desegment_len" to the amount of additional + * data it needs). + */ + if (pinfo->desegment_offset == 0) { + /* + * It couldn't, in fact, dissect any of it (the + * first byte it couldn't dissect is at an offset + * of "pinfo->desegment_offset" from the beginning + * of the payload, and that's 0). + * Just mark this as TCP. + */ + if (check_col(pinfo->fd, COL_PROTOCOL)){ + col_set_str(pinfo->fd, COL_PROTOCOL, "TCP"); + } + if (check_col(pinfo->fd, COL_INFO)){ + col_set_str(pinfo->fd, COL_INFO, "[Desegmented TCP]"); + } + } + + /* + * Show what's left in the packet as data. + */ + dissect_data(tvb, deseg_offset, pinfo, tree); + } +} + + + + static void tcp_info_append_uint(frame_data *fd, const char *abbrev, guint32 val) { @@ -537,57 +943,72 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) proto_tree_add_boolean(field_tree, hf_tcp_flags_syn, tvb, offset + 13, 1, th.th_flags); proto_tree_add_boolean(field_tree, hf_tcp_flags_fin, tvb, offset + 13, 1, th.th_flags); proto_tree_add_uint(tcp_tree, hf_tcp_window_size, tvb, offset + 14, 2, th.th_win); - if (!pinfo->fragmented && len >= reported_len) { - /* The packet isn't part of a fragmented datagram and isn't - truncated, so we can checksum it. - XXX - make a bigger scatter-gather list once we do fragment - reassembly? */ - - /* Set up the fields of the pseudo-header. */ - cksum_vec[0].ptr = pinfo->src.data; - cksum_vec[0].len = pinfo->src.len; - cksum_vec[1].ptr = pinfo->dst.data; - cksum_vec[1].len = pinfo->dst.len; - cksum_vec[2].ptr = (const guint8 *)&phdr; - switch (pinfo->src.type) { - - case AT_IPv4: + } + + /* + * Assume, initially, that we can't desegment. + */ + pinfo->can_desegment = FALSE; + + if (!pinfo->fragmented && len >= reported_len) { + /* The packet isn't part of a fragmented datagram and isn't + truncated, so we can checksum it. + XXX - make a bigger scatter-gather list once we do fragment + reassembly? */ + + /* Set up the fields of the pseudo-header. */ + cksum_vec[0].ptr = pinfo->src.data; + cksum_vec[0].len = pinfo->src.len; + cksum_vec[1].ptr = pinfo->dst.data; + cksum_vec[1].len = pinfo->dst.len; + cksum_vec[2].ptr = (const guint8 *)&phdr; + switch (pinfo->src.type) { + + case AT_IPv4: phdr[0] = htonl((IP_PROTO_TCP<<16) + reported_len); cksum_vec[2].len = 4; break; - case AT_IPv6: + case AT_IPv6: phdr[0] = htonl(reported_len); phdr[1] = htonl(IP_PROTO_TCP); cksum_vec[2].len = 8; break; - default: + default: /* TCP runs only atop IPv4 and IPv6.... */ g_assert_not_reached(); break; + } + cksum_vec[3].ptr = tvb_get_ptr(tvb, offset, len); + cksum_vec[3].len = reported_len; + computed_cksum = in_cksum(&cksum_vec[0], 4); + if (computed_cksum == 0) { + /* + * We have all the data for this TCP segment, and the checksum of + * the header and the data is good, so we can desegment it. + * Is desegmentation enabled? + */ + if (tcp_desegment) { + /* Yes - indicate that we will desegment. */ + pinfo->can_desegment = TRUE; } - cksum_vec[3].ptr = tvb_get_ptr(tvb, offset, len); - cksum_vec[3].len = reported_len; - computed_cksum = in_cksum(&cksum_vec[0], 4); - if (computed_cksum == 0) { - proto_tree_add_uint_format(tcp_tree, hf_tcp_checksum, tvb, - offset + 16, 2, th.th_sum, "Checksum: 0x%04x (correct)", th.th_sum); - } else { - proto_tree_add_boolean_hidden(tcp_tree, hf_tcp_checksum_bad, tvb, + proto_tree_add_uint_format(tcp_tree, hf_tcp_checksum, tvb, + offset + 16, 2, th.th_sum, "Checksum: 0x%04x (correct)", th.th_sum); + } else { + proto_tree_add_boolean_hidden(tcp_tree, hf_tcp_checksum_bad, tvb, offset + 16, 2, TRUE); - proto_tree_add_uint_format(tcp_tree, hf_tcp_checksum, tvb, + proto_tree_add_uint_format(tcp_tree, hf_tcp_checksum, tvb, offset + 16, 2, th.th_sum, "Checksum: 0x%04x (incorrect, should be 0x%04x)", th.th_sum, in_cksum_shouldbe(th.th_sum, computed_cksum)); - } - } else { - proto_tree_add_uint_format(tcp_tree, hf_tcp_checksum, tvb, - offset + 16, 2, th.th_sum, "Checksum: 0x%04x", th.th_sum); } - if (th.th_flags & TH_URG) - proto_tree_add_uint(tcp_tree, hf_tcp_urgent_pointer, tvb, offset + 18, 2, th.th_urp); + } else { + proto_tree_add_uint_format(tcp_tree, hf_tcp_checksum, tvb, + offset + 16, 2, th.th_sum, "Checksum: 0x%04x", th.th_sum); } + if (th.th_flags & TH_URG) + proto_tree_add_uint(tcp_tree, hf_tcp_urgent_pointer, tvb, offset + 18, 2, th.th_urp); /* Decode TCP options, if any. */ if (tree && hlen > sizeof (e_tcphdr)) { @@ -631,8 +1052,16 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) proto_tree_add_text(tcp_tree, tvb, offset, length_remaining, "Reset cause: %s", tvb_format_text(tvb, offset, length_remaining)); - } else - decode_tcp_ports( tvb, offset, pinfo, tree, th.th_sport, th.th_dport); + } else { + /* Can we desegment this segment? */ + if (pinfo->can_desegment) { + /* Yes. */ + desegment_tcp(tvb, pinfo, offset, th.th_seq, nxtseq, th.th_sport, th.th_dport, tree, tcp_tree); + } else { + /* No - just call the subdissector. */ + decode_tcp_ports(tvb, offset, pinfo, tree, th.th_sport, th.th_dport); + } + } } if( data_out_file ) { @@ -738,6 +1167,7 @@ proto_register_tcp(void) &ett_tcp_flags, &ett_tcp_options, &ett_tcp_option_sack, + &ett_tcp_segments, }; module_t *tcp_module; @@ -757,6 +1187,13 @@ proto_register_tcp(void) "Show TCP summary in protocol tree", "Whether the TCP summary line should be shown in the protocol tree", &tcp_summary_in_tree); + prefs_register_bool_preference(tcp_module, "desegment_tcp_streams", + "Allow subdissector to desegment TCP streams", +"Whether subdissector can request TCP streams to be desegmented", + &tcp_desegment); + + register_init_routine(tcp_desegment_init); + register_init_routine(tcp_fragment_init); } void diff --git a/reassemble.c b/reassemble.c index 82ff5fa98b..42301572e7 100644 --- a/reassemble.c +++ b/reassemble.c @@ -1,7 +1,7 @@ /* reassemble.c * Routines for {fragment,segment} reassembly * - * $Id: reassemble.c,v 1.2 2001/06/28 19:15:11 guy Exp $ + * $Id: reassemble.c,v 1.3 2001/09/13 07:53:52 guy Exp $ * * Ethereal - Network traffic analyzer * By Gerald Combs @@ -60,9 +60,13 @@ fragment_equal(gconstpointer k1, gconstpointer k2) fragment_key* key1 = (fragment_key*) k1; fragment_key* key2 = (fragment_key*) k2; - return ( ( (ADDRESSES_EQUAL(&key1->src, &key2->src)) && - (ADDRESSES_EQUAL(&key1->dst, &key2->dst)) && - (key1->id == key2->id) + /*key.id is the first item to compare since item is most + likely to differ between sessions, thus shortcircuiting + the comparasion of addresses. + */ + return ( ( (key1->id == key2->id) && + (ADDRESSES_EQUAL(&key1->src, &key2->src)) && + (ADDRESSES_EQUAL(&key1->dst, &key2->dst)) ) ? TRUE : FALSE); } @@ -75,10 +79,17 @@ fragment_hash(gconstpointer k) int i; hash_val = 0; + +/* More than likely: in most captures src and dst addresses are the + same, and would hash the same. + We only use id as the hash as an optimization. + for (i = 0; i < key->src.len; i++) hash_val += key->src.data[i]; for (i = 0; i < key->dst.len; i++) hash_val += key->dst.data[i]; +*/ + hash_val += key->id; return hash_val; -- cgit v1.2.3