Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 30 May 2001 19:57:09 -0400 (EDT)
From:      Louis Mamakos <louie@whizzo.transsys.com>
To:        FreeBSD-gnats-submit@freebsd.org
Cc:        freebsd-net@freebsd.org, darrenr@freebsd.org
Subject:   ipf packet munging bug using "to" option in rule
Message-ID:  <200105302357.f4UNv8k14534@whizzo.transsys.com>

next in thread | raw e-mail | index | archive | help

>Submitter-Id:	current-users
>Originator:	Louis Mamakos
>Organization:	dept of serendipity scheduling and managment
>Confidential:	no
>Synopsis:	ipf packet munging bug using "to" option in rule
>Severity:	serious
>Priority:	medium
>Category:	kern
>Class:		sw-bug
>Release:	FreeBSD 4.3-STABLE i386
>Environment:
System: FreeBSD whizzo.transsys.com 4.3-STABLE FreeBSD 4.3-STABLE #18: Sat May 12 20:38:19 EDT 2001 louie@whizzo.transsys.com:/usr/src/sys/compile/SAYSHELL i386


>Description:

I'm using ipf on a machine to perform some stupid packet tricks, including
the dubious operation of fowarding traffic out an alternative network
interface based on the source address of the packet.  The reasons why
are beyond the scope of this PR.

I have a rule that looks something like this:

pass out quick on de0 from any to any head 11
block out quick on de0 to tun0 from 144.202.0.0/16 to !144.202.0.0/16 group 11
...

The intent is that when packets from 144.202.0.0/16 arrive on this
host, that they get forcebly shoved out the tun0 network interface,
rather than taking the expect forwarding path via the de0 interface.

This works MOST OF THE TIME.  However, sending packets larger than a
certain size results in the packet being mangled.  It turns out this
happens when the packet is large enough that it can no longer be
contained within a single mbuf, and there's an mbuf cluster attached.
There is code in sys/netinet/ip_fil.c:ipfr_fastroute() which does
something like this (paraphrased):n

                ip->ip_len = htons(ip->ip_len);
                ip->ip_off = htons(ip->ip_off);
                if (!ip->ip_sum)
                        ip->ip_sum = in_cksum(m, hlen);

                error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst,
                                          ro->ro_rt);
                if (i) {
                        ip->ip_id = ntohs(ip->ip_id);
                        ip->ip_len = ntohs(ip->ip_len);
                        ip->ip_off = ntohs(ip->ip_off);
                }

The problem is that if the outbound network interface saves a pointer
to the cluster, it will pick up up the version of the packet after
it's been modified by the ntohs() calls after the if_output routine
is called.  In my case, this happens on a tunnel interface but
other network interfaces which can DMA directly from the mbufs rather
than copying the data will suffer from the same problem.


>How-To-Repeat:
	as described, sorta..
>Fix:

I did a brutal hack in ipfr_fastroute() to make a copy of the
mbuf if it's not M_WRITABLE(), and then arranged for the 
if (i) case in the code above to never get run to corrupt the
data in the mbuf.

I don't understand the global structure of the ipf code well
enough to know if that's a "proper" way to solve the problem
or not.  After applying the workaround, traffic is now working 
where previously I was getting nasty checksum errors and the
like due to some of the fields being byte-swapped.  I would
imagine that big-endian systems and those with network interfaces
that require the transmit data to be copied wouldn't have
seen this problem previously.

--- /sys/netinet/ip_fil.c	Sun Feb 25 10:53:34 2001
+++ /tmp/ip_fil.c	Wed May 30 19:50:45 2001
@@ -1284,6 +1284,32 @@
 	struct route iproute;
 	frentry_t *fr;
 
+#ifdef	__FreeBSD__
+	/*
+	 * HOT FIX/KLUDGE:
+	 *
+	 * If the mbuf we're about to send is not writable (because of
+	 * a cluster reference, for example) we'll need to make a copy
+	 * of it since this routine modifies the contents.  
+	 *
+	 * If you have non-crappy network hardware that can transmit data
+	 * from the mbuf, rather than making a copy, this is gonna be a
+	 * problem.
+	 */
+
+	if (M_WRITABLE(m) == 0) {
+	  if ((m0 = m_dup(m, M_DONTWAIT)) != 0) {
+	    m_freem(m);
+	    m = m0;
+	  } else {
+	    error = ENOBUFS;
+	    m_freem(m);
+	    ipl_frouteok[1]++;
+	    return 0;
+	  }
+	}
+#endif
+
 	hlen = fin->fin_hlen;
 	ip = mtod(m0, struct ip *);
 
@@ -1379,12 +1405,17 @@
 # if	BSD >= 199306
 		int i = 0;
 
+#ifdef		__FreeBSD__
+		/* HOT FIX: we've already made a copy of the mbuf
+		   above, so we won't need to restore it here. */
+#else		/* __FreeBSD__ */
 #  ifdef	MCLISREFERENCED
 		if ((m->m_flags & M_EXT) && MCLISREFERENCED(m))
 #  else
 		if (m->m_flags & M_EXT)
 #  endif
 			i = 1;
+#endif		/* __FreeBSD__ */
 # endif
 # ifndef sparc
 #  ifndef __FreeBSD__
@@ -1399,7 +1430,9 @@
 		error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst,
 					  ro->ro_rt);
 		if (i) {
+#  ifndef __FreeBSD__
 			ip->ip_id = ntohs(ip->ip_id);
+#  endif
 			ip->ip_len = ntohs(ip->ip_len);
 			ip->ip_off = ntohs(ip->ip_off);
 		}





To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-net" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200105302357.f4UNv8k14534>