Subject: Re: pf trouble (packet corruption?)
To: der Mouse <mouse@Rodents.Montreal.QC.CA>
From: Daniel Hartmeier <daniel@benzedrine.cx>
List: tech-net
Date: 01/21/2006 13:10:20
On Fri, Jan 20, 2006 at 02:47:23PM -0500, der Mouse wrote:

> Any pf gurus in the house?  I'm havinnng some trouble with pf;
> preliminary indications are that it's corrupting packet contents.

I suspect this is caused by a NetBSD-specific change to pf.c pf_route()

void
pf_route(struct mbuf **m, struct pf_rule *r, int dir, struct ifnet
*oifp,
    struct pf_state *s)
{

	...

#ifdef __NetBSD__
	struct tcphdr		 th;
#endif

	...

	/* Catch routing changes wrt. hardware checksumming for TCP or UDP. */
#ifdef __OpenBSD__
	if (m0->m_pkthdr.csum & M_TCPV4_CSUM_OUT) {
		if (!(ifp->if_capabilities & IFCAP_CSUM_TCPv4) ||
		    ifp->if_bridge != NULL) {
			in_delayed_cksum(m0);
			m0->m_pkthdr.csum &= ~M_TCPV4_CSUM_OUT; /* Clear */
		}
	} else if (m0->m_pkthdr.csum & M_UDPV4_CSUM_OUT) {
		if (!(ifp->if_capabilities & IFCAP_CSUM_UDPv4) ||
		    ifp->if_bridge != NULL) {
			in_delayed_cksum(m0);
			m0->m_pkthdr.csum &= ~M_UDPV4_CSUM_OUT; /* Clear */
		}
	}
#else
	m_copydata(m0, sizeof(*ip), sizeof(th), &th);
	th.th_sum = 0;
	m_copyback(m0, sizeof(*ip), sizeof(th), &th);
	in4_cksum(m0, IPPROTO_TCP, sizeof(*ip), m0->m_pkthdr.len - sizeof(*ip));
#endif

The purpose of the original OpenBSD section is to deal with the case
where the TCP/UDP checksum calculation can be done in hardware on the
initial outgoing interface, but not the one pf re-routes the packet out
through. In this case, a flag in the mbuf needs to be cleared, so
software checksum calculation isn't skipped (afterwards, in stack code
outside pf). Note that these flags can only be present for TCP or UDP
packets, not ICMP. The code above doesn't need to manually check whether
the packet is TCP or UDP, because an ICMP packet could never have the
M_TCPV4_CSUM_OUT or M_UDPV4_CSUM_OUT flag.

The NetBSD replacement seems wrong when the packet is not TCP/UDP (like
ICMP in your case). The TCP checksum th_sum is located 16 bytes off the
beginning of the TCP header and is 2 bytes long. This is exactly the
part that gets corrupted in your ICMP payload.

Daniel