Ethereal-dev: Re: [Ethereal-dev] ip defragment, virtual packets

Note: This archive is from the project's previous web site, ethereal.com. This list is no longer active.

From: Guy Harris <gharris@xxxxxxxxxxxx>
Date: Tue, 10 Apr 2001 01:20:57 -0700
On Mon, Apr 09, 2001 at 11:50:56AM -0700, Guy Harris wrote:
> > I think that the named data sources would be a cleaner method to 
> > handle the ip defrag.
> 
> Precisely what I was thinking this morning.
> 
> > You can use the current code to build the
> > ip data list. Then when all the data is received create a named
> > view with the defrag'ed data. This way you don't have to create
> > the virtual packet. And the data storage doesn't have to include
> > an IP header, just save the actual packet data after the IP
> > header.
> 
> ...and, in addition, you'd get to see the link-layer header on the last
> fragment even if you've enabled IP reassembly.

Here's a patch, which applies to the current CVS version of Ethereal.

Unfortunately, the named data sources stuff works right only if all the
dissectors handed the named data are tvbuffified.

This may be a failure in the way we handle calling old-style dissectors
from tvbuffified dissectors...

...but the right way to fix it is, I think, to get rid of the remaining
old-style dissectors or, at least, get rid of the ones for protocols
that run atop UDP (you're unlikely to get TCP segments inside fragmented
IP datagrams, although it's not impossible to get that).
Index: packet-ip.c
===================================================================
RCS file: /usr/local/cvsroot/ethereal/packet-ip.c,v
retrieving revision 1.129
diff -c -r1.129 packet-ip.c
*** packet-ip.c	2001/03/28 21:33:31	1.129
--- packet-ip.c	2001/04/10 08:17:38
***************
*** 62,67 ****
--- 62,70 ----
  /* Decode the old IPv4 TOS field as the DiffServ DS Field */
  static gboolean g_ip_dscp_actif = TRUE;
  
+ /* Defragment fragmented IP datagrams */
+ static gboolean ip_defragment = FALSE;
+ 
  static int proto_ip = -1;
  static int hf_ip_version = -1;
  static int hf_ip_hdr_len = -1;
***************
*** 97,102 ****
--- 100,106 ----
  static gint ett_ip_option_sec = -1;
  static gint ett_ip_option_route = -1;
  static gint ett_ip_option_timestamp = -1;
+ static gint ett_ip_fragments = -1;
  
  /* Used by IPv6 as well, so not static */
  dissector_table_t ip_dissector_table;
***************
*** 309,315 ****
--- 313,540 ----
  #define	IPOPT_TS_TSANDADDR	1		/* timestamps and addresses */
  #define	IPOPT_TS_PRESPEC	3		/* specified modules only */
  
+ static void
+ dissect_ip(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
+ 
+ /*
+  * defragmentation of IPv4
+  */
+ static GHashTable *ip_fragment_table=NULL;
+ 
+ typedef struct _ip_fragment_key {
+ 	guint32	srcip;
+ 	guint32	dstip;
+ 	guint32	id;
+ } ip_fragment_key;
+ 
+ #define IPD_DEFRAGMENTED	0x0001
+ typedef struct _ip_fragment_data {
+ 	struct _ip_fragment_data *next;
+ 	guint32 frame;
+ 	guint32	offset;
+ 	guint32	len;
+ 	guint32 datalen; /*Only valid in first item of list */
+ 	guint32 flags;
+ 	unsigned char *data;
+ } ip_fragment_data;
+ 
+ static gint
+ ip_fragment_equal(gconstpointer k1, gconstpointer k2)
+ {
+ 	ip_fragment_key* key1 = (ip_fragment_key*) k1;
+ 	ip_fragment_key* key2 = (ip_fragment_key*) k2;
+ 
+ 	return ( ( (key1->srcip == key2->srcip) &&
+ 		   (key1->dstip == key2->dstip) &&
+ 		   (key1->id    == key2->id) 
+ 		 ) ?
+ 		 TRUE : FALSE);
+ }
+ 
+ static guint
+ ip_fragment_hash(gconstpointer k)
+ {
+ 	ip_fragment_key* key = (ip_fragment_key*) k;
+ 
+ 	return (key->srcip ^ key->dstip ^ key->id);
+ }
+ 
+ static gboolean
+ free_all_fragments(gpointer key, gpointer value, gpointer user_data)
+ {
+ 	ip_fragment_data *ipfd_head;
+ 	ip_fragment_data *ipfd_i;
+ 
+ 	ipfd_head=value;
+ 	while(ipfd_head){
+ 		ipfd_i=ipfd_head->next;
+ 		if(ipfd_head->data){
+ 			g_free(ipfd_head->data);
+ 		}
+ 		g_free(ipfd_head);
+ 		ipfd_head=ipfd_i;
+ 	}
+ 
+ 	g_free(key);
+ 	return TRUE;
+ }
+ 
+ static void
+ ip_defragment_init(void)
+ {
+ 	if( ip_fragment_table != NULL ){
+ 		/* The fragment hash table exists.
+ 		 * 
+ 		 * Remove all entries and free all memory.
+ 		 */
+ 		g_hash_table_foreach_remove(ip_fragment_table,free_all_fragments,NULL);
+ 	} else {
+ 		/* The fragment table does not exist. Create it */
+ 		ip_fragment_table = g_hash_table_new(ip_fragment_hash,
+ 				ip_fragment_equal);
+ 	}
+ }
+ 
+ /* This function adds a new fragment to the fragment hash table
+  * If this is the first fragment seen in for this ip packet,
+  * a new entry is created in the hash table, otherwise
+  * This fragment is just added to the linked list of 
+  * fragments for this packet.
+  * The list of fragments for a specific ip-packet is kept sorted for
+  * easier handling.
+  * tvb had better contain an IPv4 packet, or BAD things will probably happen.
+  *
+  * Returns a pointer to the head of the fragment data list if we have all the
+  * fragments, NULL otherwise.
+  */
+ static ip_fragment_data *
+ ip_fragment_add(tvbuff_t *tvb, int offset, packet_info *pinfo, guint32 id,
+ 		guint32 floff)
+ {
+ 	ip_fragment_key *ipfk;
+ 	ip_fragment_data *ipfd_head;
+ 	ip_fragment_data *ipfd;
+ 	ip_fragment_data *ipfd_i;
+ 	guint32 frag_offset;
+ 	guint32 max;
+ 
+ 	ipfk=g_malloc(sizeof(ip_fragment_key);
+ 	ipfk->srcip = *((guint32 *)pinfo->src.data);
+ 	ipfk->dstip = *((guint32 *)pinfo->dst.data);
+ 	ipfk->id    = id;
+ 
+ 	ipfd_head=g_hash_table_lookup(ip_fragment_table, ipfk);
+ 
+ 	if( pinfo->fd->flags.visited ){
+ 		if (ipfd_head != NULL && ipfd_head->flags & IPD_DEFRAGMENTED)
+ 			return ipfd_head;
+ 		else
+ 			return NULL;
+ 	}
+ 
+ 	frag_offset = (floff & IP_OFFSET)*8;
+ 
+ 	if (ipfd_head==NULL){
+ 		/* not found, this must be the first snooped fragment for this
+                  * ip packet. Create list-head.
+ 		 */
+ 		ipfd_head=g_malloc(sizeof(ip_fragment_data);
+ 		/* head/first structure in list only holds no other data than
+                  * 'datalen' then we dont have to change the head of the list
+                  * even if we want to keep it sorted
+                  */
+ 		ipfd_head->next=NULL;
+ 		ipfd_head->datalen=0;
+ 		ipfd_head->offset=0;
+ 		ipfd_head->len=0;
+ 		ipfd_head->flags=0;
+ 		ipfd_head->data=NULL;
+ 		g_hash_table_insert(ip_fragment_table, ipfk, ipfd_head);
+ 	}
+ 
+ 	/* if this is a fragment for a packet we have already defragmented,
+          * just ignore this packet.
+ 	 * XXX - return NULL?
+ 	 */
+ 	if (ipfd_head->flags & IPD_DEFRAGMENTED) {
+ 		return ipfd_head;
+ 	}
+ 
+ 
+ 	/* create new ipfd describing this fragment */
+ 	ipfd = g_malloc(sizeof(ip_fragment_data);
+ 	ipfd->next = NULL;
+ 	ipfd->frame = pinfo->fd->num;
+ 	ipfd->offset = frag_offset;
+ 	ipfd->len  = pinfo->iplen;
+ 	ipfd->len  -= (pinfo->iphdrlen*4);
+ 	ipfd->data = g_malloc(ipfd->len);
+ 	tvb_memcpy(tvb, ipfd->data, offset, ipfd->len);
+ 
+ 	if( !(floff&IP_MF) ){
+ 		/* this was the last fragment, we now know the total length
+ 		   of the payload data */
+ 		ipfd_head->datalen = ipfd->offset + ipfd->len;
+ 	}
+ 
+ 
+ 	/* add fragment to list, keep list sorted */
+ 	for(ipfd_i=ipfd_head;ipfd_i->next;ipfd_i=ipfd_i->next){
+ 		if( (ipfd->offset) < (ipfd_i->next->offset) )
+ 			break;
+ 	}
+ 	ipfd->next=ipfd_i->next;
+ 	ipfd_i->next=ipfd;	
+ 
+ 
+ 	if( !(ipfd_head->datalen) ){
+ 		/* if we dont know the datalen, there are still missing
+ 		 * packets. Cheaper than the check below.
+ 		 */
+ 		return NULL;
+ 	}
+ 
+ 
+ 	/* check if we have received the entire fragment
+ 	 * this is easy since the list is sorted and the head is faked.
+ 	 */
+ 	max = 0;
+ 	for(ipfd_i=ipfd_head->next;ipfd_i;ipfd_i=ipfd_i->next){
+ 		if( ((ipfd_i->offset)<=max) && 
+ 		    ((ipfd_i->offset+ipfd_i->len)>max) ){
+ 			max = ipfd_i->offset+ipfd_i->len;
+ 		}
+ 	}
+ 
+ 
+ 	if( max != (ipfd_head->datalen) ){
+ 		/* we have not received all packets yet */
+ 		return NULL;
+ 	}
+ 
+ 	/* we have received an entire packet, defragment it and
+          * free all fragments 
+          */
+ 	ipfd_head->data = g_malloc(ipfd_head->datalen);
+ 
+ 	/* add all data fragments */
+ 	for(ipfd_i=ipfd_head;ipfd_i;ipfd_i=ipfd_i->next){
+ 		if(ipfd_i->len){
+ 			memcpy(ipfd_head->data+ipfd_i->offset,ipfd_i->data,ipfd_i->len);
+ 			g_free(ipfd_i->data);
+ 			ipfd_i->data=NULL;
+ 		}
+ 	}
+ 
+ 	/* mark this packet as defragmented.
+            allows us to skip any trailing fragments */
+ 	ipfd_head->flags |= IPD_DEFRAGMENTED;
+ 
+ 	return ipfd_head;
+ }
+ 
  
+ 
  void
  capture_ip(const u_char *pd, int offset, packet_counts *ld) {
    if (!BYTES_ARE_IN_FRAME(offset, IPH_MIN_LEN)) {
***************
*** 797,802 ****
--- 1022,1028 ----
    guint16    flags;
    guint8     nxt;
    guint16    ipsum;
+   ip_fragment_data *ipfd_head;
    tvbuff_t   *next_tvb;
  
    if (check_col(pinfo->fd, COL_PROTOCOL))
***************
*** 840,846 ****
    }
  
    hlen = lo_nibble(iph.ip_v_hl) * 4;	/* IP header length, in bytes */
! 
    if (tree) {
      ti = proto_tree_add_item(tree, proto_ip, tvb, offset, hlen, FALSE);
      ip_tree = proto_item_add_subtree(ti, ett_ip);
--- 1066,1072 ----
    }
  
    hlen = lo_nibble(iph.ip_v_hl) * 4;	/* IP header length, in bytes */
!  
    if (tree) {
      ti = proto_tree_add_item(tree, proto_ip, tvb, offset, hlen, FALSE);
      ip_tree = proto_item_add_subtree(ti, ett_ip);
***************
*** 849,863 ****
    if (hlen < IPH_MIN_LEN) {
      if (check_col(pinfo->fd, COL_INFO))
        col_add_fstr(pinfo->fd, COL_INFO, "Bogus IP header length (%u, must be at least %u)",
! 	hlen, IPH_MIN_LEN);
      if (tree) {
        proto_tree_add_uint_format(ip_tree, hf_ip_hdr_len, tvb, offset, 1, hlen,
! 	"Header length: %u bytes (bogus, must be at least %u)", hlen,
! 	IPH_MIN_LEN);
      }
      return;
    }
!   
    if (tree) {
      proto_tree_add_uint(ip_tree, hf_ip_version, tvb, offset, 1, hi_nibble(iph.ip_v_hl));
      proto_tree_add_uint_format(ip_tree, hf_ip_hdr_len, tvb, offset, 1, hlen,
--- 1075,1089 ----
    if (hlen < IPH_MIN_LEN) {
      if (check_col(pinfo->fd, COL_INFO))
        col_add_fstr(pinfo->fd, COL_INFO, "Bogus IP header length (%u, must be at least %u)",
!        hlen, IPH_MIN_LEN);
      if (tree) {
        proto_tree_add_uint_format(ip_tree, hf_ip_hdr_len, tvb, offset, 1, hlen,
!        "Header length: %u bytes (bogus, must be at least %u)", hlen,
!        IPH_MIN_LEN);
      }
      return;
    }
! 
    if (tree) {
      proto_tree_add_uint(ip_tree, hf_ip_version, tvb, offset, 1, hi_nibble(iph.ip_v_hl));
      proto_tree_add_uint_format(ip_tree, hf_ip_hdr_len, tvb, offset, 1, hlen,
***************
*** 896,901 ****
--- 1122,1128 ----
  
      proto_tree_add_uint(ip_tree, hf_ip_frag_offset, tvb, offset +  6, 2,
        (iph.ip_off & IP_OFFSET)*8);
+ 
      proto_tree_add_uint(ip_tree, hf_ip_ttl, tvb, offset +  8, 1, iph.ip_ttl);
      proto_tree_add_uint_format(ip_tree, hf_ip_proto, tvb, offset +  9, 1, iph.ip_p,
  	"Protocol: %s (0x%02x)", ipprotostr(iph.ip_p), iph.ip_p);
***************
*** 903,915 ****
      ipsum = ip_checksum(tvb_get_ptr(tvb, offset, hlen), hlen);
      if (ipsum == 0) {
  	proto_tree_add_uint_format(ip_tree, hf_ip_checksum, tvb, offset + 10, 2, iph.ip_sum,
!             "Header checksum: 0x%04x (correct)", iph.ip_sum);
      }
      else {
! 	proto_tree_add_boolean_hidden(ip_tree, hf_ip_checksum_bad, tvb, offset + 10, 2, TRUE);
  	proto_tree_add_uint_format(ip_tree, hf_ip_checksum, tvb, offset + 10, 2, iph.ip_sum,
!             "Header checksum: 0x%04x (incorrect, should be 0x%04x)", iph.ip_sum,
! 	    in_cksum_shouldbe(iph.ip_sum, ipsum));
      }
  
      proto_tree_add_ipv4(ip_tree, hf_ip_src, tvb, offset + 12, 4, iph.ip_src);
--- 1130,1142 ----
      ipsum = ip_checksum(tvb_get_ptr(tvb, offset, hlen), hlen);
      if (ipsum == 0) {
  	proto_tree_add_uint_format(ip_tree, hf_ip_checksum, tvb, offset + 10, 2, iph.ip_sum,
!               "Header checksum: 0x%04x (correct)", iph.ip_sum);
      }
      else {
! 	proto_tree_add_item_hidden(ip_tree, hf_ip_checksum_bad, tvb, offset + 10, 2, TRUE);
  	proto_tree_add_uint_format(ip_tree, hf_ip_checksum, tvb, offset + 10, 2, iph.ip_sum,
!           "Header checksum: 0x%04x (incorrect, should be 0x%04x)", iph.ip_sum,
! 	  in_cksum_shouldbe(iph.ip_sum, ipsum));
      }
  
      proto_tree_add_ipv4(ip_tree, hf_ip_src, tvb, offset + 12, 4, iph.ip_src);
***************
*** 931,938 ****
--- 1158,1168 ----
    }
  
    pinfo->ipproto = iph.ip_p;
+ 
    pinfo->iplen = iph.ip_len;
+ 
    pinfo->iphdrlen = lo_nibble(iph.ip_v_hl);
+ 
    SET_ADDRESS(&pinfo->net_src, AT_IPv4, 4, tvb_get_ptr(tvb, offset + IPH_SRC, 4));
    SET_ADDRESS(&pinfo->src, AT_IPv4, 4, tvb_get_ptr(tvb, offset + IPH_SRC, 4));
    SET_ADDRESS(&pinfo->net_dst, AT_IPv4, 4, tvb_get_ptr(tvb, offset + IPH_DST, 4));
***************
*** 940,948 ****
  
    /* Skip over header + options */
    offset += hlen;
!   nxt = iph.ip_p;
!   if (iph.ip_off & IP_OFFSET) {
!     /* fragmented */
      if (check_col(pinfo->fd, COL_INFO))
        col_add_fstr(pinfo->fd, COL_INFO, "Fragmented IP protocol (proto=%s 0x%02x, off=%u)",
  	ipprotostr(iph.ip_p), iph.ip_p, (iph.ip_off & IP_OFFSET) * 8);
--- 1170,1235 ----
  
    /* Skip over header + options */
    offset += hlen;
!   nxt = iph.ip_p;	/* XXX - what if this isn't the same for all fragments? */
! 
!   /* If ip_defragment is on and this is a fragment, then just add the fragment
!    * to the hashtable. In this case we will not call any other dissectors
!    * since the dissector will be automatically called for the reconstructed ip
!    * packet when sufficient fragments have been received to defragment
!    * the entire packet.
!    */
!   if (ip_defragment && (iph.ip_off & (IP_MF|IP_OFFSET))) {
!     /* We're reassembling, and this is part of a fragmented datagram.
!        Add the fragment to the hash table. */
!     ipfd_head = ip_fragment_add(tvb, offset, pinfo, iph.ip_id, iph.ip_off);
!     if (ipfd_head != NULL) {
!       /* OK, we have the complete reassembled payload.
!          Allocate a new tvbuff, referring to the reassembled payload. */
!       next_tvb = tvb_new_real_data(ipfd_head->data, ipfd_head->datalen,
! 	ipfd_head->datalen, "Reassembled");
! 
!       /* Add the tvbuff to the list of tvbuffs to which the tvbuff we
!          were handed refers, so it'll get cleaned up when that tvbuff
!          is cleaned up. */
!       tvb_set_child_real_data_tvbuff(tvb, next_tvb);
! 
!       /* Add the decrypted data to the data source list. */
!       pinfo->fd->data_src = g_slist_append(pinfo->fd->data_src, next_tvb);
! 
!       /* It's not fragmented. */
!       pinfo->fragmented = FALSE;
!     } else {
!       /* We don't have the complete reassembled payload. */
!       next_tvb = NULL;
!     }
!   } else {
!     /* If this is the first fragment, dissect its contents, otherwise
!        just show it as a fragment.
! 
!        XXX - if we eventually don't save the reassembled contents of all
!        fragmented datagrams, we may want to always reassemble. */
!     if (iph.ip_off & IP_OFFSET) {
!       /* Not the first fragment - don't dissect it. */
!       next_tvb = NULL;
!     } else {
!       /* First fragment, or not fragmented.  Dissect what we have here. */
! 
!       /* Get a tvbuff for the payload. */
!       next_tvb = tvb_new_subset(tvb, offset, -1, -1);
! 
!       /*
!        * If this is the first fragment, but not the only fragment,
!        * tell the next protocol that.
!        */
!       if (iph.ip_off & IP_MF)
!         pinfo->fragmented = TRUE;
!       else
!         pinfo->fragmented = FALSE;
!     }
!   }
! 
!   if (next_tvb == NULL) {
!     /* Just show this as a fragment. */
      if (check_col(pinfo->fd, COL_INFO))
        col_add_fstr(pinfo->fd, COL_INFO, "Fragmented IP protocol (proto=%s 0x%02x, off=%u)",
  	ipprotostr(iph.ip_p), iph.ip_p, (iph.ip_off & IP_OFFSET) * 8);
***************
*** 950,964 ****
      return;
    }
  
-   /*
-    * If this is the first fragment, but not the only fragment,
-    * tell the next protocol that.
-    */
-   if (iph.ip_off & IP_MF)
-     pinfo->fragmented = TRUE;
-   else
-     pinfo->fragmented = FALSE;
- 
    /* Hand off to the next protocol.
  
       XXX - setting the columns only after trying various dissectors means
--- 1237,1242 ----
***************
*** 966,972 ****
       even be labelled as an IP frame; ideally, if a frame being dissected
       throws an exception, it'll be labelled as a mangled frame of the
       type in question. */
-   next_tvb = tvb_new_subset(tvb, offset, -1, -1);
    if (!dissector_try_port(ip_dissector_table, nxt, next_tvb, pinfo, tree)) {
      /* Unknown protocol */
      if (check_col(pinfo->fd, COL_INFO))
--- 1244,1249 ----
***************
*** 1127,1144 ****
           truncated, so we can checksum it. */
  
        computed_cksum = ip_checksum(tvb_get_ptr(tvb, 0, reported_length),
! 				   reported_length);
        if (computed_cksum == 0) {
          proto_tree_add_uint_format(icmp_tree, hf_icmp_checksum, tvb, 2, 2,
! 			cksum,
! 			"Checksum: 0x%04x (correct)", cksum);
        } else {
!         proto_tree_add_boolean_hidden(icmp_tree, hf_icmp_checksum_bad,
! 			tvb, 2, 2, TRUE);
          proto_tree_add_uint_format(icmp_tree, hf_icmp_checksum, tvb, 2, 2,
! 			cksum,
! 			"Checksum: 0x%04x (incorrect, should be 0x%04x)",
! 			cksum, in_cksum_shouldbe(cksum, computed_cksum));
        }
      } else {
        proto_tree_add_uint(icmp_tree, hf_icmp_checksum, tvb, 2, 2, cksum);
--- 1404,1421 ----
           truncated, so we can checksum it. */
  
        computed_cksum = ip_checksum(tvb_get_ptr(tvb, 0, reported_length),
! 	  			     reported_length);
        if (computed_cksum == 0) {
          proto_tree_add_uint_format(icmp_tree, hf_icmp_checksum, tvb, 2, 2,
!  			  cksum,
! 			  "Checksum: 0x%04x (correct)", cksum);
        } else {
!         proto_tree_add_item_hidden(icmp_tree, hf_icmp_checksum_bad,
! 			  tvb, 2, 2, TRUE);
          proto_tree_add_uint_format(icmp_tree, hf_icmp_checksum, tvb, 2, 2,
! 		  cksum,
! 		  "Checksum: 0x%04x (incorrect, should be 0x%04x)",
! 		  cksum, in_cksum_shouldbe(cksum, computed_cksum));
        }
      } else {
        proto_tree_add_uint(icmp_tree, hf_icmp_checksum, tvb, 2, 2, cksum);
***************
*** 1468,1476 ****
--- 1745,1758 ----
  		&ett_ip_option_sec,
  		&ett_ip_option_route,
  		&ett_ip_option_timestamp,
+ 		&ett_ip_fragments,
  	};
  	module_t *ip_module;
  
+ 	/* Fragment hash table for ip defragmentation */
+ 	ip_fragment_table = g_hash_table_new(ip_fragment_hash,
+ 			ip_fragment_equal);
+ 
  	proto_ip = proto_register_protocol("Internet Protocol", "IP", "ip");
  	proto_register_field_array(proto_ip, hf, array_length(hf));
  	proto_register_subtree_array(ett, array_length(ett));
***************
*** 1484,1491 ****
--- 1766,1778 ----
  	    "Decode IPv4 TOS field as DiffServ field",
  "Whether the IPv4 type-of-service field should be decoded as a Differentiated Services field",
  	    &g_ip_dscp_actif);
+ 	prefs_register_bool_preference(ip_module, "defragment",
+ 		"Reassemble fragmented IP datagrams",
+ 		"Whether fragmented IP datagrams should be reassembled",
+ 		&ip_defragment);
  
  	register_dissector("ip", dissect_ip, proto_ip);
+ 	register_init_routine(ip_defragment_init);
  }
  
  void
***************
*** 1539,1541 ****
--- 1826,1829 ----
  {
    dissector_add("ip.proto", IP_PROTO_ICMP, dissect_icmp, proto_icmp);
  }
+