Ethereal-dev: Re: [ethereal-dev] tcp header checksum validation

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: Fri, 9 Jun 2000 21:51:20 -0700
On Fri, Jun 09, 2000 at 04:03:36PM -0700, Skip Hansen wrote:
> I recently added code to validate the TCP header checksums

Presumably you mean "TCP checksums"; the checksum includes the TCP
payload as well as the header.

> The attached patch adds TCP header checksum validation.  The text " (correct)" 
> is appended to the TCP header checksum field if the checksum is correct, 
> otherwise the text " (incorrect, should be 0x????) is appended.  If the network 
> address is not type IPv4 then nothing is appended.
> 
> The changes have been tested under FreeBSD version 3.2 with an x86 processor. I 
> believe the code should also function correctly on a big endian machine,

...except that most big-endian machines require strict alignment of
integral data types bigger than one byte, so it's probably more likely
to fail there - although it'll probably fail on Alpha-based systems as
well, even though most OSes run Alphas in little-endian mode.

There is no guarantee that "&pd[offset]", pointing to the first byte of
the TCP header, is aligned on a 2-byte boundary, but
"tcp_checksum_state()" casts it to an "unsigned short *" and
dereferences it - on processors that require strict alignment, this will
probably cause a trap unless the compiler generates "conservative" (and
more expensive) code (some compilers have, I think, options to do that;
I don't know whether GCC does) that doesn't assume pointers are properly
aligned (or doesn't assume it if it sees that the pointer comes from a
cast, if its dataflow analysis is sophisticated enough to detect that,
or if it just punts entirely if the function contains such a cast).  The
trap might be handled by the OS, which might do the fetch for you, but
the performance penalty for that might be severe.

x86 processors allow unaligned references (with some performance penalty
on many processors), but Alpha, SPARC, MIPS, and, I think, PA-RISC and
some POWER-family (POWER, POWER2, PowerPC) processors do not allow them.

I've done a similar patch, that checksums the header+body of both TCP
segments and UDP datagrams; however:

	1) it doesn't check whether the IP datagram containing the TCP
	   segment or UDP datagram is fragmented - currently, if it is,
	   the checksum couldn't be computed, as we currently don't
	   reassemble fragmented IP datagrams, so we'd have to punt on
	   those;

	2) it uses the BSD kernel C-language checksum routine, which (at
	   least in the current version of FreeBSD) still has the old
	   BSD copyright notice, including the "give credit" clause:

 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.

	   which apparently conflicts with this clause of the GPL:

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

	   as it "imposes ... further restrictions on the recipients'
	   exercise of the rights granted herein", so adding "BSD
	   Classic" code to a GPLed program is apparently not permitted
	   by the GPL.  (I think it might be possible to code with the
	   new BSD license - Berkeley have, apparently, removed the
	   "give credit" clause from their copyright notice, and in some
	   fashion applied that change to code such as 4.4-Lite that
	   they've already published, although I don't know how one goes
	   about effacing that clause from 4.4-Lite or 4.4-Lite-derived
	   code.)

My patch also handles IPv6 as well as IPv4, and the BSD checksum code
handles unaligned data; I've attached it, along with a modified version
of the BSD checksum code, and a header that declares the "in_cksum()"
that it exports.  (The patch is one attachment; the BSD-licensed
routines are another, so they're not being aggregated, so the GPL clause
doesn't apply; put *that* in your pipe and smoke it, GPL lawyers.  :-) I
guess that if they're lawyers, though, they could find some way to liken
it to supplying, separately, the two components of a binary nerve
gas....)

(It's a patch against the current version in the CVS tree; see

	http://ethereal.zing.org/development.html#anoncvs

for instructions on how to access that tree via anonymous CVS.

It should also apply to one of the nightly source snapshots; see

	http://ethereal.zing.org/distribution/nightly-builds/

for those if you are, for example, stuck behind a firewall that doesn't
let you do anonymous CVS.)
Index: Makefile.am
===================================================================
RCS file: /usr/local/cvsroot/ethereal/Makefile.am,v
retrieving revision 1.203
diff -c -r1.203 Makefile.am
*** Makefile.am	2000/06/07 22:57:43	1.203
--- Makefile.am	2000/06/10 04:45:42
***************
*** 255,260 ****
--- 255,262 ----
  	exceptions.h	\
  	follow.c       \
  	follow.h       \
+ 	in_cksum.c     \
+ 	in_cksum.h     \
  	inet_v6defs.h  \
  	ipproto.c      \
  	ipv4.c         \
Index: packet-ip.c
===================================================================
RCS file: /usr/local/cvsroot/ethereal/packet-ip.c,v
retrieving revision 1.92
diff -c -r1.92 packet-ip.c
*** packet-ip.c	2000/06/05 03:21:01	1.92
--- packet-ip.c	2000/06/10 04:45:46
***************
*** 40,45 ****
--- 40,46 ----
  #include <glib.h>
  #include "packet.h"
  #include "resolv.h"
+ #include "in_cksum.h"
  
  #ifdef NEED_SNPRINTF_H
  # ifdef HAVE_STDARG_H
***************
*** 777,797 ****
  
  static char *ip_checksum_state(e_ip *iph)
  {
!     unsigned long Sum;
!     unsigned char *Ptr, *PtrEnd;
!     unsigned short word;
! 
!     Sum    = 0;
!     PtrEnd = (lo_nibble(iph->ip_v_hl) * 4 + (char *)iph);
!     for (Ptr = (unsigned char *) iph; Ptr < PtrEnd; Ptr += 2) {
! 	memcpy(&word, Ptr, sizeof word);
!         Sum += word;
!     }
! 
!     Sum = (Sum & 0xFFFF) + (Sum >> 16);
!     Sum = (Sum & 0xFFFF) + (Sum >> 16);
  
!     if (Sum != 0xffff)
          return "incorrect";
  
      return "correct";
--- 778,788 ----
  
  static char *ip_checksum_state(e_ip *iph)
  {
!     vec_t cksum_vec[1];
  
!     cksum_vec[0].ptr = (const guint8 *)iph;
!     cksum_vec[0].len = lo_nibble(iph->ip_v_hl) * 4;
!     if (in_cksum(&cksum_vec[0], 1) != 0)
          return "incorrect";
  
      return "correct";
Index: packet-tcp.c
===================================================================
RCS file: /usr/local/cvsroot/ethereal/packet-tcp.c,v
retrieving revision 1.75
diff -c -r1.75 packet-tcp.c
*** packet-tcp.c	2000/05/31 05:07:49	1.75
--- packet-tcp.c	2000/06/10 04:45:48
***************
*** 40,45 ****
--- 40,46 ----
  #include "globals.h"
  #include "resolv.h"
  #include "follow.h"
+ #include "in_cksum.h"
  
  #ifdef NEED_SNPRINTF_H
  # ifdef HAVE_STDARG_H
***************
*** 422,427 ****
--- 423,430 ----
    guint      hlen;
    guint      optlen;
    guint      packet_max = pi.len;
+   vec_t      cksum_vec[4];
+   guint32    phdr[2];
  
    /* To do: Check for {cap len,pkt len} < struct len */
    /* Avoids alignment problems on many architectures. */
***************
*** 497,503 ****
      proto_tree_add_boolean(field_tree, hf_tcp_flags_syn, NullTVB, offset + 13, 1, th.th_flags);
      proto_tree_add_boolean(field_tree, hf_tcp_flags_fin, NullTVB, offset + 13, 1, th.th_flags);
      proto_tree_add_uint(tcp_tree, hf_tcp_window_size, NullTVB, offset + 14, 2, th.th_win);
!     proto_tree_add_uint(tcp_tree, hf_tcp_checksum, NullTVB, offset + 16, 2, th.th_sum);
      if (th.th_flags & TH_URG)
        proto_tree_add_uint(tcp_tree, hf_tcp_urgent_pointer, NullTVB, offset + 18, 2, th.th_urp);
    }
--- 500,543 ----
      proto_tree_add_boolean(field_tree, hf_tcp_flags_syn, NullTVB, offset + 13, 1, th.th_flags);
      proto_tree_add_boolean(field_tree, hf_tcp_flags_fin, NullTVB, offset + 13, 1, th.th_flags);
      proto_tree_add_uint(tcp_tree, hf_tcp_window_size, NullTVB, offset + 14, 2, th.th_win);
!     if (pi.captured_len >= pi.len) {
!       /* The packet isn't truncated, so we can checksum it.
!          XXX - we have to check whether this is part of a fragmented
! 	 IP datagram, too.... */
! 
!       /* Set up the fields of the pseudo-header. */
!       cksum_vec[0].ptr = pi.src.data;
!       cksum_vec[0].len = pi.src.len;
!       cksum_vec[1].ptr = pi.dst.data;
!       cksum_vec[1].len = pi.dst.len;
!       cksum_vec[2].ptr = (const guint8 *)&phdr;
!       switch (pi.src.type) {
! 
!       case AT_IPv4:
! 	phdr[0] = htonl((IP_PROTO_TCP<<16) + END_OF_FRAME);
! 	cksum_vec[2].len = 4;
! 	break;
! 
!       case AT_IPv6:
!         phdr[0] = htonl(END_OF_FRAME);
!         phdr[1] = htonl(IP_PROTO_TCP);
!         cksum_vec[2].len = 8;
!         break;
! 
!       default:
!         /* TCP runs only atop IPv4 and IPv6.... */
!         g_assert_not_reached();
!         break;
!       }
!       cksum_vec[3].ptr = &pd[offset];
!       cksum_vec[3].len = END_OF_FRAME;
!       proto_tree_add_uint_format(tcp_tree, hf_tcp_checksum, NullTVB,
!          offset + 16, 2, th.th_sum, "Checksum: 0x%04x (%s)", th.th_sum,
! 	(in_cksum(&cksum_vec[0], 4) == 0) ? "correct" : "incorrect");
!     } else {
!       proto_tree_add_uint_format(tcp_tree, hf_tcp_checksum, NullTVB,
!        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, NullTVB, offset + 18, 2, th.th_urp);
    }
Index: packet-udp.c
===================================================================
RCS file: /usr/local/cvsroot/ethereal/packet-udp.c,v
retrieving revision 1.72
diff -c -r1.72 packet-udp.c
*** packet-udp.c	2000/05/31 05:07:52	1.72
--- packet-udp.c	2000/06/10 04:45:49
***************
*** 43,48 ****
--- 43,49 ----
  #include <glib.h>
  #include "globals.h"
  #include "resolv.h"
+ #include "in_cksum.h"
  
  #include "plugins.h"
  #include "packet-udp.h"
***************
*** 145,150 ****
--- 146,153 ----
    guint16    uh_sport, uh_dport, uh_ulen, uh_sum;
    proto_tree *udp_tree;
    proto_item *ti;
+   vec_t      cksum_vec[4];
+   guint32    phdr[2];
  
    if (!BYTES_ARE_IN_FRAME(offset, sizeof(e_udphdr))) {
      dissect_data(pd, offset, fd, tree);
***************
*** 177,184 ****
      proto_tree_add_uint_hidden(udp_tree, hf_udp_port, NullTVB, offset+2, 2, uh_dport);
  
      proto_tree_add_uint(udp_tree, hf_udp_length, NullTVB, offset + 4, 2,  uh_ulen);
!     proto_tree_add_uint_format(udp_tree, hf_udp_checksum, NullTVB, offset + 6, 2, uh_sum,
! 	"Checksum: 0x%04x", uh_sum);
    }
  
    /* Skip over header */
--- 180,223 ----
      proto_tree_add_uint_hidden(udp_tree, hf_udp_port, NullTVB, offset+2, 2, uh_dport);
  
      proto_tree_add_uint(udp_tree, hf_udp_length, NullTVB, offset + 4, 2,  uh_ulen);
!     if (pi.captured_len >= pi.len) {
!       /* The packet isn't truncated, so we can checksum it.
!          XXX - we have to check whether this is part of a fragmented
! 	 IP datagram, too.... */
! 
!       /* Set up the fields of the pseudo-header. */
!       cksum_vec[0].ptr = pi.src.data;
!       cksum_vec[0].len = pi.src.len;
!       cksum_vec[1].ptr = pi.dst.data;
!       cksum_vec[1].len = pi.dst.len;
!       cksum_vec[2].ptr = (const guint8 *)&phdr;
!       switch (pi.src.type) {
! 
!       case AT_IPv4:
! 	phdr[0] = htonl((IP_PROTO_UDP<<16) + END_OF_FRAME);
! 	cksum_vec[2].len = 4;
! 	break;
! 
!       case AT_IPv6:
!         phdr[0] = htonl(END_OF_FRAME);
!         phdr[1] = htonl(IP_PROTO_UDP);
!         cksum_vec[2].len = 8;
!         break;
! 
!       default:
!         /* TCP runs only atop IPv4 and IPv6.... */
!         g_assert_not_reached();
!         break;
!       }
!       cksum_vec[3].ptr = &pd[offset];
!       cksum_vec[3].len = END_OF_FRAME;
!       proto_tree_add_uint_format(udp_tree, hf_udp_checksum, NullTVB,
!         offset + 6, 2, uh_sum, "Checksum: 0x%04x (%s)", uh_sum,
! 	(in_cksum(&cksum_vec[0], 4) == 0) ? "correct" : "incorrect");
!     } else {
!       proto_tree_add_uint_format(udp_tree, hf_udp_checksum, NullTVB,
!         offset + 6, 2, uh_sum, "Checksum: 0x%04x", uh_sum);
!     }
    }
  
    /* Skip over header */
typedef struct {
	const guint8 *ptr;
	int	len;
} vec_t;

extern int in_cksum(const vec_t *vec, int veclen);

/*
 * Copyright (c) 1988, 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)in_cksum.c	8.1 (Berkeley) 6/10/93
 * $FreeBSD: src/sys/netinet/in_cksum.c,v 1.5.4.1 1999/08/29 16:29:34 peter Exp $
 */

#include <glib.h>

#include "in_cksum.h"

/*
 * Checksum routine for Internet Protocol family headers (Portable Version).
 *
 * This routine is very heavily used in the network
 * code and should be modified for each CPU to be as fast as possible.
 */

#define ADDCARRY(x)  (x > 65535 ? x -= 65535 : x)
#define REDUCE {l_util.l = sum; sum = l_util.s[0] + l_util.s[1]; ADDCARRY(sum);}

int
in_cksum(const vec_t *vec, int veclen)
{
	register const guint16 *w;
	register int sum = 0;
	register int mlen = 0;
	int byte_swapped = 0;

	union {
		char	c[2];
		guint16	s;
	} s_util;
	union {
		guint16 s[2];
		long	l;
	} l_util;

	for (; veclen != 0; vec++, veclen--) {
		if (vec->len == 0)
			continue;
		w = (const guint16 *)vec->ptr;
		if (mlen == -1) {
			/*
			 * The first byte of this chunk is the continuation
			 * of a word spanning between this chunk and the
			 * last chunk.
			 *
			 * s_util.c[0] is already saved when scanning previous
			 * chunk.
			 */
			s_util.c[1] = *(char *)w;
			sum += s_util.s;
			w = (const guint16 *)((const guint8 *)w + 1);
			mlen = vec->len - 1;
		} else
			mlen = vec->len;
		/*
		 * Force to even boundary.
		 */
		if ((1 & (int) w) && (mlen > 0)) {
			REDUCE;
			sum <<= 8;
			s_util.c[0] = *(const guint8 *)w;
			w = (const guint16 *)((const guint8 *)w + 1);
			mlen--;
			byte_swapped = 1;
		}
		/*
		 * Unroll the loop to make overhead from
		 * branches &c small.
		 */
		while ((mlen -= 32) >= 0) {
			sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
			sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7];
			sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11];
			sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15];
			w += 16;
		}
		mlen += 32;
		while ((mlen -= 8) >= 0) {
			sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
			w += 4;
		}
		mlen += 8;
		if (mlen == 0 && byte_swapped == 0)
			continue;
		REDUCE;
		while ((mlen -= 2) >= 0) {
			sum += *w++;
		}
		if (byte_swapped) {
			REDUCE;
			sum <<= 8;
			byte_swapped = 0;
			if (mlen == -1) {
				s_util.c[1] = *(char *)w;
				sum += s_util.s;
				mlen = 0;
			} else
				mlen = -1;
		} else if (mlen == -1)
			s_util.c[0] = *(char *)w;
	}
	if (mlen == -1) {
		/* The last mbuf has odd # of bytes. Follow the
		   standard (the odd byte may be shifted left by 8 bits
		   or not as determined by endian-ness of the machine) */
		s_util.c[1] = 0;
		sum += s_util.s;
	}
	REDUCE;
	return (~sum & 0xffff);
}