amd64 vs i386 in_cksum_hdr() with unaligned data
Karim Fodil-Lemelin
fodillemlinkarim at gmail.com
Fri Feb 1 15:22:19 UTC 2013
On 01/02/2013 9:53 AM, Karim Fodil-Lemelin wrote:
> Hi -net,
>
> Sorry for the lengthy email.
>
> TLDR: If the IP header is not aligned on an even address then the
> amd64 version of in_cksum_hdr() will not work while the i386 version
> of it will.
>
> I came across this problem while working on custom software using the
> FreeBSD bridge. Our code sometimes decapsulate packets with the
> resulting header starting on an odd address and we need to send it
> through the FreeBSD bridge where we hit in_cksum_hdr() in
> bridge_pfil(). While this always worked on i386 we started seeing
> 'reversed' checksums on amd64:
>
> 21:47:29.178620 IP (tos 0x0, ttl 63, id 3819, offset 0, flags [none],
> proto ICMP (1), length 84)
> 192.168.76.100 > 192.168.73.200: ICMP echo request, id 44019, seq
> 2, length 64
> 21:47:29.179972 IP (tos 0x0, ttl 62, id 1701, offset 0, flags [none],
> proto ICMP (1), length 84, bad cksum 875e (->5e87)!)
> 192.168.73.200 > 192.168.76.100: ICMP echo reply, id 44019, seq 2,
> length 64
>
> Please note the reversed checksum on the ICMP reply (as if someone had
> called htons on ip_sum ...). Needless to say this caused a lot of head
> scratching over here.
>
> Now it looks like the i386 version of in_cksum_hdr() is totally
> different then the amd64 one. A current workaround for us is to use
> in_cksum() in if_bridge.c by applying this patch:
>
> diff --git a/freebsd/sys/net/if_bridge.c b/freebsd/sys/net/if_bridge.c
> index 24a4522..fbd9aec 100644
> --- a/freebsd/sys/net/if_bridge.c
> +++ b/freebsd/sys/net/if_bridge.c
> @@ -3894,9 +3894,11 @@ ipfwpass:
> #endif
> /* Recalculate the ip checksum */
> ip->ip_sum = 0;
> +#if 0 /* Dirty Hack */
> if (hlen == sizeof(struct ip))
> ip->ip_sum = in_cksum_hdr(ip);
> else
> +#endif
> ip->ip_sum = in_cksum(*mp, hlen);
>
> break;
>
> et voila! Problem fixed. or not ... At least this gets us going but I
> think the scope of this problem is much bigger then the bridge code
> alone.
>
> The FreeBSD source uses in_cksum_hdr() in many other places then
> if_bridge.c and while the i386 version of it is capable of dealing
> with unaligned addresses the amd64 one is not (we haven't checked
> other architectures). My question(s) to the list is what is the proper
> way to fix this? Should we replace all occurrence of in_cksum_hdr()
> with in_cksum()? Should we write another inline assembly of the
> in_cksum_hdr function for 64bit? Should in_cksum_hdr() in amd64
> changed to deal with misaligned addresses? Other solutions?
>
> Thanks,
>
> Karim.
> _______________________________________________
> freebsd-net at freebsd.org mailing list
> http://lists.freebsd.org/mailman/listinfo/freebsd-net
> To unsubscribe, send any mail to "freebsd-net-unsubscribe at freebsd.org"
Quick follow up on this, the following patch fixed the issue for us:
diff --git a/freebsd/sys/amd64/amd64/in_cksum.c
b/freebsd/sys/amd64/amd64/in_cksum.c
index ae02e91..71749e1 100644
--- a/freebsd/sys/amd64/amd64/in_cksum.c
+++ b/freebsd/sys/amd64/amd64/in_cksum.c
@@ -233,9 +233,13 @@ skip_start:
u_int in_cksum_hdr(const struct ip *ip)
{
- u_int64_t sum = in_cksumdata(ip, sizeof(struct ip));
- union q_util q_util;
- union l_util l_util;
- REDUCE16;
- return (~sum & 0xffff);
+ u_int64_t sum;
+ union q_util q_util;
+ union l_util l_util;
+ if ((uintptr_t)ip & 1)
+ sum = in_cksumdata(ip, sizeof(struct ip)) << 8;
+ else
+ sum = in_cksumdata(ip, sizeof(struct ip));
+ REDUCE16;
+ return (~sum & 0xffff);
}
More information about the freebsd-net
mailing list