Huge thanks to our Platinum Members Endace and LiveAction,
and our Silver Member Veeam, for supporting the Wireshark Foundation and project.

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 */