Wireshark-dev: [Wireshark-dev] defragmentation over RTP
From: Richard van der Hoff <richardv@xxxxxxxxxxxxx>
Date: Fri, 16 Feb 2007 15:19:43 +0000
Hi,Here's a patch which adds an option enabling subdissectors to request defragmentation of packets over RTP streams, using the pinfo->desegment_{len,offset} API.
Please be aware that it depends on my patches yesterday to make fragment_set_partial_reassembly() work for datagrams
assembled with fragment_add_seq(). Cheers, Richard
Index: epan/dissectors/packet-rtp.c =================================================================== --- epan/dissectors/packet-rtp.c (.../svn+ssh://svn/svn-ethereal/mirror/ethereal/trunk/epan/dissectors/packet-rtp.c) (revision 11911) +++ epan/dissectors/packet-rtp.c (.../epan/dissectors/packet-rtp.c) (working copy) @@ -70,11 +70,17 @@ #include <epan/rtp_pt.h> #include "packet-ntp.h" #include <epan/conversation.h> +#include <epan/reassemble.h> #include <epan/tap.h> #include <epan/prefs.h> #include <epan/emem.h> +#include "log.h" + +/* uncomment this to enable debugging of fragment reassembly */ +/* #define DEBUG_FRAGMENTS 1 */ + typedef struct _rfc2198_hdr { guint8 pt; int offset; @@ -82,6 +88,52 @@ struct _rfc2198_hdr *next; } rfc2198_hdr; +/* we have one of these for each pdu which spans more than one segment + */ +typedef struct _rtp_multisegment_pdu { + /* the seqno of the segment where the pdu starts */ + guint32 startseq; + + /* the seqno of the segment where the pdu ends */ + guint32 endseq; +} rtp_multisegment_pdu; + +typedef struct _rtp_private_conv_info { + /* This tree is indexed by sequence number and keeps track of all + * all pdus spanning multiple segments for this flow. + */ + emem_tree_t *multisegment_pdus; +} rtp_private_conv_info; + +static GHashTable *fragment_table = NULL; +static GHashTable * fid_table = NULL; + +static int hf_rtp_fragments = -1; +static int hf_rtp_fragment = -1; +static int hf_rtp_fragment_overlap = -1; +static int hf_rtp_fragment_overlap_conflict = -1; +static int hf_rtp_fragment_multiple_tails = -1; +static int hf_rtp_fragment_too_long_fragment = -1; +static int hf_rtp_fragment_error = -1; +static int hf_rtp_reassembled_in = -1; + +static gint ett_rtp_fragment = -1; +static gint ett_rtp_fragments = -1; + +static const fragment_items rtp_fragment_items = { + &ett_rtp_fragment, + &ett_rtp_fragments, + &hf_rtp_fragments, + &hf_rtp_fragment, + &hf_rtp_fragment_overlap, + &hf_rtp_fragment_overlap_conflict, + &hf_rtp_fragment_multiple_tails, + &hf_rtp_fragment_too_long_fragment, + &hf_rtp_fragment_error, + &hf_rtp_reassembled_in, + "RTP fragments" +}; + static dissector_handle_t rtp_handle; static dissector_handle_t rtp_rfc2198_handle; static dissector_handle_t stun_handle; @@ -103,6 +155,7 @@ static int hf_rtp_marker = -1; static int hf_rtp_payload_type = -1; static int hf_rtp_seq_nr = -1; +static int hf_rtp_ext_seq_nr = -1; static int hf_rtp_timestamp = -1; static int hf_rtp_ssrc = -1; static int hf_rtp_csrc_item = -1; @@ -171,6 +224,9 @@ /* Try heuristic RTP decode */ static gboolean global_rtp_heur = FALSE; +/* desegmnent RTP streams */ +static gboolean desegment_rtp = TRUE; + /* RFC2198 Redundat Audio Data */ static guint rtp_rfc2198_pt = 99; static guint rtp_saved_rfc2198_pt = 0; @@ -274,6 +330,14 @@ { 0, NULL }, }; + +/* initialisation routine */ +static void rtp_fragment_init(void) +{ + fragment_table_init(&fragment_table); + fid_table = g_hash_table_new(g_direct_hash, g_direct_equal); +} + void rtp_free_hash_dyn_payload(GHashTable *rtp_dyn_payload) { @@ -336,6 +400,12 @@ p_conv_data = se_alloc(sizeof(struct _rtp_conversation_info)); p_conv_data->rtp_dyn_payload = NULL; + /* start this at 0x10000 so that we cope gracefully with the + * first few packets being out of order (hence 0,65535,1,2,...) + */ + p_conv_data->extended_seqno = 0x10000; + p_conv_data->rtp_conv_info = se_alloc(sizeof(rtp_private_conv_info)); + p_conv_data->rtp_conv_info->multisegment_pdus = se_tree_create(EMEM_TREE_TYPE_RED_BLACK,"rtp_ms_pdus"); conversation_add_proto_data(p_conv, proto_rtp, p_conv_data); } @@ -406,17 +476,17 @@ } } +/* + * Process the payload of the RTP packet, hand it to the subdissector + */ static void -dissect_rtp_data( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, - proto_tree *rtp_tree, int offset, unsigned int data_len, - unsigned int data_reported_len, unsigned int payload_type ) +process_rtp_payload(tvbuff_t *newtvb, packet_info *pinfo, proto_tree *tree, + proto_tree *rtp_tree, + unsigned int payload_type) { - tvbuff_t *newtvb; struct _rtp_conversation_info *p_conv_data = NULL; gboolean found_match = FALSE; - newtvb = tvb_new_subset( tvb, offset, data_len, data_reported_len ); - /* if the payload type is dynamic (96 to 127), we check if the conv is set and we look for the pt definition */ if ( (payload_type >=96) && (payload_type <=127) ) { p_conv_data = p_get_proto_data(pinfo->fd, proto_rtp); @@ -445,7 +515,236 @@ } +/* Rtp payload reassembly + * + * This handles the reassembly of PDUs for higher-level protocols. + * + * We're a bit limited on how we can cope with out-of-order packets, because + * we don't have any idea of where the datagram boundaries are. So if we see + * packets A, C, B (all of which comprise a single datagram), we cannot know + * that C should be added to the same datagram as A, until we come to B (which + * may or may not actually be present...). + * + * What we end up doing in this case is passing A+B to the subdissector as one + * datagram, and make out that a new one starts on C. + */ static void +dissect_rtp_data( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, + proto_tree *rtp_tree, int offset, unsigned int data_len, + unsigned int data_reported_len, + unsigned int payload_type ) +{ + tvbuff_t *newtvb; + struct _rtp_conversation_info *p_conv_data= NULL; + gboolean must_desegment = FALSE; + rtp_private_conv_info *finfo = NULL; + rtp_multisegment_pdu *msp = NULL; + guint32 seqno; + + /* Retrieve RTPs idea of a converation */ + p_conv_data = p_get_proto_data(pinfo->fd, proto_rtp); + + if(p_conv_data != NULL) + finfo = p_conv_data->rtp_conv_info; + + if(finfo == NULL || !desegment_rtp) { + /* Hand the whole lot off to the subdissector */ + newtvb=tvb_new_subset(tvb,offset,data_len,data_reported_len); + process_rtp_payload(tvb, pinfo, tree, rtp_tree, payload_type); + return; + } + + seqno = p_conv_data->extended_seqno; + + pinfo->can_desegment = 2; + pinfo->desegment_offset = 0; + pinfo->desegment_len = 0; + +#ifdef DEBUG_FRAGMENTS + g_debug("%d: RTP Part of convo %d(%p); seqno %d", + pinfo->fd->num, + p_conv_data->frame_number, p_conv_data, + seqno + ); +#endif + + /* look for a pdu which we might be extending */ + msp = (rtp_multisegment_pdu *)se_tree_lookup32_le(finfo->multisegment_pdus,seqno-1); + + if(msp && msp->startseq < seqno && msp->endseq >= seqno) { + guint32 fid = msp->startseq; + fragment_data *fd_head; + +#ifdef DEBUG_FRAGMENTS + g_debug("\tContinues fragment %d", fid); +#endif + + /* we always assume the datagram is complete; if this is the + * first pass, that's our best guess, and if it's not, what we + * say gets ignored anyway. + */ + fd_head = fragment_add_seq(tvb, offset, pinfo, fid, fragment_table, + seqno-msp->startseq, data_len, FALSE); + + newtvb = process_reassembled_data(tvb,offset, pinfo, "Reassembled RTP", fd_head, + &rtp_fragment_items, NULL, tree); + +#ifdef DEBUG_FRAGMENTS + g_debug("\tFragment Coalesced; fd_head=%p, newtvb=%p (len %d)",fd_head, newtvb, + newtvb?tvb_reported_length(newtvb):0); +#endif + + if(newtvb != NULL) { + /* Hand off to the subdissector */ + process_rtp_payload(newtvb, pinfo, tree, rtp_tree, payload_type); + + /* + * Check to see if there were any complete fragments within the chunk + */ + if( pinfo->desegment_len && pinfo->desegment_offset == 0 ) + { +#ifdef DEBUG_FRAGMENTS + g_debug("\tNo complete pdus in payload" ); +#endif + /* Mark the fragments and not complete yet */ + fragment_set_partial_reassembly(pinfo, fid, fragment_table); + + /* we must need another segment */ + msp->endseq = MIN(msp->endseq,seqno) + 1; + } + else + { + /* + * Data was dissected so add the protocol tree to the display + */ + proto_item *rtp_tree_item, *frag_tree_item; + /* this nargery is to insert the fragment tree into the main tree + * between the RTP protocol entry and the subdissector entry */ + show_fragment_tree(fd_head, &rtp_fragment_items, tree, pinfo, newtvb, &frag_tree_item); + rtp_tree_item = proto_item_get_parent( proto_tree_get_parent( rtp_tree )); + if( frag_tree_item && rtp_tree_item ) + proto_tree_move_item( tree, rtp_tree_item, frag_tree_item ); + + + if(pinfo->desegment_len) + { + /* the higher-level dissector has asked for some more data - ie, + the end of this segment does not coincide with the end of a + higher-level PDU. */ + must_desegment = TRUE; + } + } + + } + + } + else + { + /* + * The segment is not the continuation of a fragmented segment + * so process it as normal + */ +#ifdef DEBUG_FRAGMENTS + g_debug("\tRTP non-fragment payload"); +#endif + newtvb = tvb_new_subset( tvb, offset, data_len, data_reported_len ); + + /* Hand off to the subdissector */ + process_rtp_payload(newtvb, pinfo, tree, rtp_tree, payload_type); + + if(pinfo->desegment_len) { + /* the higher-level dissector has asked for some more data - ie, + the end of this segment does not coincide with the end of a + higher-level PDU. */ + must_desegment = TRUE; + } + } + + /* + * There were bytes left over that the higher protocol couldn't dissect so save them + */ + if(must_desegment) + { + guint32 deseg_offset = pinfo->desegment_offset; + guint32 frag_len = tvb_reported_length_remaining(newtvb, deseg_offset); + fragment_data *fd_head = NULL; + +#ifdef DEBUG_FRAGMENTS + g_debug("\tRTP Must Desegment: tvb_len=%d ds_len=%d %d frag_len=%d ds_off=%d", + tvb_reported_length(newtvb), + pinfo->desegment_len, + pinfo->fd->flags.visited, + frag_len, + deseg_offset); +#endif + /* allocate a new msp for this pdu */ + msp = se_alloc(sizeof(rtp_multisegment_pdu)); + msp->startseq = seqno; + msp->endseq = seqno+1; + se_tree_insert32(finfo->multisegment_pdus,seqno,msp); + + /* + * Add the fragment to the fragment table + */ + fd_head = fragment_add_seq(newtvb,deseg_offset, pinfo, seqno, fragment_table, 0, frag_len, + TRUE ); + + if(fd_head != NULL) + { + if( fd_head->reassembled_in != 0 && !(fd_head->flags & FD_PARTIAL_REASSEMBLY) ) + { + proto_item *rtp_tree_item; + rtp_tree_item = proto_tree_add_uint( tree, hf_rtp_reassembled_in, + newtvb, deseg_offset, tvb_reported_length_remaining(newtvb,deseg_offset), + fd_head->reassembled_in); + PROTO_ITEM_SET_GENERATED(rtp_tree_item); +#ifdef DEBUG_FRAGMENTS + g_debug("\tReassembled in %d", fd_head->reassembled_in); +#endif + } + else + { +#ifdef DEBUG_FRAGMENTS + g_debug("\tUnfinished fragment"); +#endif + /* this fragment is never reassembled */ + proto_tree_add_text( tree, tvb, deseg_offset, -1,"RTP fragment, unfinished"); + } + } + else + { + /* + * This fragment was the first fragment in a new entry in the + * frag_table; we don't yet know where it is reassembled + */ +#ifdef DEBUG_FRAGMENTS + g_debug("\tnew pdu"); +#endif + } + + if( pinfo->desegment_offset == 0 ) + { + if (check_col(pinfo->cinfo, COL_PROTOCOL)) + { + col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTP"); + } + if (check_col(pinfo->cinfo, COL_INFO)) + { + col_set_str(pinfo->cinfo, COL_INFO, "[RTP segment of a reassembled PDU]"); + } + } + } + + + + pinfo->can_desegment = 0; + pinfo->desegment_offset = 0; + pinfo->desegment_len = 0; +} + + + +static void dissect_rtp_rfc2198(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree ) { int offset = 0; @@ -651,6 +950,7 @@ /* Look for conv and add to the frame if found */ get_conv_info(pinfo, rtp_info); + p_conv_data = p_get_proto_data(pinfo->fd, proto_rtp); if ( check_col( pinfo->cinfo, COL_PROTOCOL ) ) { col_set_str( pinfo->cinfo, COL_PROTOCOL, "RTP" ); @@ -658,8 +958,6 @@ /* if it is dynamic payload, let use the conv data to see if it is defined */ if ( (payload_type>95) && (payload_type<128) ) { - /* Use existing packet info if available */ - p_conv_data = p_get_proto_data(pinfo->fd, proto_rtp); if (p_conv_data && p_conv_data->rtp_dyn_payload){ payload_type_str = g_hash_table_lookup(p_conv_data->rtp_dyn_payload, &payload_type); rtp_info->info_payload_type_str = payload_type_str; @@ -711,6 +1009,10 @@ /* Sequence number 16 bits (2 octets) */ proto_tree_add_uint( rtp_tree, hf_rtp_seq_nr, tvb, offset, 2, seq_num ); + if(p_conv_data != NULL) { + item = proto_tree_add_uint( rtp_tree, hf_rtp_ext_seq_nr, tvb, offset, 2, p_conv_data->extended_seqno ); + PROTO_ITEM_SET_GENERATED(item); + } offset += 2; /* Timestamp 32 bits (4 octets) */ @@ -855,6 +1157,22 @@ tap_queue_packet(rtp_tap, pinfo, rtp_info); } + +/* calculate the extended sequence number - top 16 bits of the previous sequence number, + * plus our own; then correct for wrapping */ +static guint32 calculate_extended_seqno(guint32 previous_seqno, guint16 raw_seqno) +{ + guint32 seqno = (previous_seqno & 0xffff0000) | raw_seqno; + if(seqno + 0x8000 < previous_seqno) { + seqno += 0x10000; + } else if(previous_seqno + 0x8000 < seqno) { + /* we got an out-of-order packet which happened to go backwards over the + * wrap boundary */ + seqno -= 0x10000; + } + return seqno; +} + /* Look for conversation info */ static void get_conv_info(packet_info *pinfo, struct _rtp_info *rtp_info) { @@ -878,13 +1196,23 @@ p_conv_data = conversation_get_proto_data(p_conv, proto_rtp); if (p_conv_data) { + guint32 seqno; + /* Save this conversation info into packet info */ p_conv_packet_data = se_alloc(sizeof(struct _rtp_conversation_info)); g_snprintf(p_conv_packet_data->method, MAX_RTP_SETUP_METHOD_SIZE, "%s", p_conv_data->method); p_conv_packet_data->method[MAX_RTP_SETUP_METHOD_SIZE]=0; p_conv_packet_data->frame_number = p_conv_data->frame_number; p_conv_packet_data->rtp_dyn_payload = p_conv_data->rtp_dyn_payload; + p_conv_packet_data->rtp_conv_info = p_conv_data->rtp_conv_info; p_add_proto_data(pinfo->fd, proto_rtp, p_conv_packet_data); + + /* calculate extended sequence number */ + seqno = calculate_extended_seqno(p_conv_data->extended_seqno, + rtp_info->info_seq_num); + + p_conv_packet_data->extended_seqno = seqno; + p_conv_data->extended_seqno = seqno; } } } @@ -1107,6 +1435,18 @@ } }, { + &hf_rtp_ext_seq_nr, + { + "Extended sequence number", + "rtp.extseq", + FT_UINT32, + BASE_DEC, + NULL, + 0x0, + "", HFILL + } + }, + { &hf_rtp_timestamp, { "Timestamp", @@ -1285,6 +1625,52 @@ 0x03FF, "Block Length", HFILL } + }, + + /* reassembly stuff */ + {&hf_rtp_fragments, + {"RTP Fragments", "rtp.fragments", FT_NONE, BASE_NONE, NULL, 0x0, + "RTP Fragments", HFILL } + }, + + {&hf_rtp_fragment, + {"RTP Fragment data", "rtp.fragment", FT_FRAMENUM, BASE_NONE, NULL, 0x0, + "RTP Fragment data", HFILL } + }, + + {&hf_rtp_fragment_overlap, + {"Fragment overlap", "rtp.fragment.overlap", FT_BOOLEAN, BASE_NONE, + NULL, 0x0, "Fragment overlaps with other fragments", HFILL } + }, + + {&hf_rtp_fragment_overlap_conflict, + {"Conflicting data in fragment overlap", "rtp.fragment.overlap.conflict", + FT_BOOLEAN, BASE_NONE, NULL, 0x0, + "Overlapping fragments contained conflicting data", HFILL } + }, + + {&hf_rtp_fragment_multiple_tails, + {"Multiple tail fragments found", "rtp.fragment.multipletails", + FT_BOOLEAN, BASE_NONE, NULL, 0x0, + "Several tails were found when defragmenting the packet", HFILL } + }, + + {&hf_rtp_fragment_too_long_fragment, + {"Fragment too long", "rtp.fragment.toolongfragment", + FT_BOOLEAN, BASE_NONE, NULL, 0x0, + "Fragment contained data past end of packet", HFILL } + }, + + {&hf_rtp_fragment_error, + {"Defragmentation error", "rtp.fragment.error", + FT_FRAMENUM, BASE_NONE, NULL, 0x0, + "Defragmentation error due to illegal fragments", HFILL } + }, + + {&hf_rtp_reassembled_in, + {"RTP fragment, reassembled in frame", "rtp.reassembled_in", + FT_FRAMENUM, BASE_NONE, NULL, 0x0, + "This RTP packet is reassembled in this frame", HFILL } } }; @@ -1296,7 +1682,9 @@ &ett_hdr_ext, &ett_rtp_setup, &ett_rtp_rfc2198, - &ett_rtp_rfc2198_hdr + &ett_rtp_rfc2198_hdr, + &ett_rtp_fragment, + &ett_rtp_fragments }; module_t *rtp_module; @@ -1332,6 +1720,11 @@ "RTP isn't decoded without this", &global_rtp_heur); + prefs_register_bool_preference(rtp_module, "desegment_rtp_streams", + "Allow subdissector to reassemble RTP streams", + "Whether subdissector can request RTP streams to be reassembled", + &desegment_rtp); + prefs_register_enum_preference(rtp_module, "version0_type", "Treat RTP version 0 packets as", "If an RTP version 0 packet is encountered, it can be treated as an invalid packet, a STUN packet, or a T.38 packet", @@ -1342,6 +1735,8 @@ "Payload Type for RFC2198 Redundant Audio Data", 10, &rtp_rfc2198_pt); + + register_init_routine(rtp_fragment_init); } void @@ -1371,3 +1766,11 @@ heur_dissector_add( "udp", dissect_rtp_heur, proto_rtp); } + +/* + * Local Variables: + * c-basic-offset: 8 + * indent-tabs-mode: t + * tab-width: 8 + * End: + */ Index: epan/dissectors/packet-rtp.h =================================================================== --- epan/dissectors/packet-rtp.h (.../svn+ssh://svn/svn-ethereal/mirror/ethereal/trunk/epan/dissectors/packet-rtp.h) (revision 11911) +++ epan/dissectors/packet-rtp.h (.../epan/dissectors/packet-rtp.h) (working copy) @@ -58,8 +58,15 @@ struct _rtp_conversation_info { gchar method[MAX_RTP_SETUP_METHOD_SIZE + 1]; - guint32 frame_number; + guint32 frame_number; /* the frame where this conversation is started */ GHashTable *rtp_dyn_payload; /* a hash table with the dynamic RTP payload */ + + guint32 extended_seqno; /* the sequence number, extended to a 32-bit + * int to guarantee it increasing monotonically + */ + + struct _rtp_private_conv_info *rtp_conv_info; /* conversation info private + * to the rtp dissector */ }; /* Add an RTP conversation with the given details */
- Follow-Ups:
- Re: [Wireshark-dev] defragmentation over RTP
- From: Anders Broman
- Re: [Wireshark-dev] defragmentation over RTP
- Prev by Date: Re: [Wireshark-dev] adding dissector, automake fails
- Next by Date: [Wireshark-dev] Building RPM with Lua support
- Previous by thread: Re: [Wireshark-dev] adding dissector, automake fails
- Next by thread: Re: [Wireshark-dev] defragmentation over RTP
- Index(es):