mirror of
https://github.com/lgcosta/pf-linux-port.git
synced 2026-11-15 14:54:44 -03:00
checksums: GSO-aware recompute after translation (software for plain skbs, CHECKSUM_PARTIAL for GSO); 06 gate asserts 2MB integrity through rdr
This commit is contained in:
1 parent
d9fc3a7e3f
commit
5af7adb08e
2 files changed
+66
-19
No files matched your search
+47
-19
@@ -112,19 +112,61 @@ pfk_reroute(struct sk_buff *skb)
|
|||||||
* ip_output() recomputes the IP header checksum on every packet it sends;
|
* ip_output() recomputes the IP header checksum on every packet it sends;
|
||||||
* Linux does not, so fix it up here after pf touched the header.
|
* Linux does not, so fix it up here after pf touched the header.
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
|
* pf rewrote the addresses of this packet. OpenBSD recomputes the IP header
|
||||||
|
* checksum in ip_output() and the transport checksum is patched incrementally
|
||||||
|
* by pf - but that only works on a *materialized* checksum, and VM-to-VM
|
||||||
|
* traffic routinely arrives with the transport checksum still uncomputed
|
||||||
|
* (the sender's offload never completed on the tap). So recompute from
|
||||||
|
* scratch here:
|
||||||
|
*
|
||||||
|
* - GSO segments: hand the job back to the NIC (CHECKSUM_PARTIAL with the
|
||||||
|
* transport offset); computing in software would only cover the first
|
||||||
|
* segment and silently corrupt the rest (this is what kills bulk TCP).
|
||||||
|
* - everything else: compute the transport checksum over the new
|
||||||
|
* pseudo-header in software, then the IP header checksum.
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
pfk_fix_ip_csum(struct sk_buff *skb)
|
pfk_fix_csum_after_translate(struct sk_buff *skb)
|
||||||
{
|
{
|
||||||
struct iphdr *iph;
|
struct iphdr *iph;
|
||||||
|
unsigned int hlen, len;
|
||||||
|
__sum16 *csum;
|
||||||
|
__u8 proto;
|
||||||
|
|
||||||
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
|
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
|
||||||
return;
|
return;
|
||||||
iph = ip_hdr(skb);
|
iph = ip_hdr(skb);
|
||||||
iph->check = 0;
|
hlen = iph->ihl * 4;
|
||||||
iph->check = csum_fold(csum_partial(skb->data, iph->ihl * 4, 0));
|
proto = iph->protocol;
|
||||||
/* the transport checksum was already fixed by pf */
|
len = skb->len - hlen;
|
||||||
|
|
||||||
|
if (proto == IPPROTO_TCP || proto == IPPROTO_UDP) {
|
||||||
|
if (!pskb_may_pull(skb, hlen + (proto == IPPROTO_TCP ?
|
||||||
|
sizeof(struct tcphdr) : sizeof(struct udphdr))))
|
||||||
|
return;
|
||||||
|
csum = (__sum16 *)(skb->data + hlen +
|
||||||
|
(proto == IPPROTO_TCP ? offsetof(struct tcphdr, check)
|
||||||
|
: offsetof(struct udphdr, check)));
|
||||||
|
if (skb_is_gso(skb)) {
|
||||||
|
skb->csum_start = (unsigned short)
|
||||||
|
(((unsigned char *)skb->data + hlen) - skb->head);
|
||||||
|
skb->csum_offset = proto == IPPROTO_TCP ?
|
||||||
|
offsetof(struct tcphdr, check) :
|
||||||
|
offsetof(struct udphdr, check);
|
||||||
|
skb->ip_summed = CHECKSUM_PARTIAL;
|
||||||
|
} else {
|
||||||
|
*csum = 0;
|
||||||
|
*csum = csum_tcpudp_magic(iph->saddr, iph->daddr, len,
|
||||||
|
proto, skb_checksum(skb, hlen, len, 0));
|
||||||
skb->ip_summed = CHECKSUM_NONE;
|
skb->ip_summed = CHECKSUM_NONE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iph = ip_hdr(skb); /* pskb_may_pull may have moved the data */
|
||||||
|
iph->check = 0;
|
||||||
|
iph->check = csum_fold(csum_partial((__u8 *)iph, iph->ihl * 4, 0));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
pfk_pkt_work(struct work_struct *w)
|
pfk_pkt_work(struct work_struct *w)
|
||||||
@@ -140,20 +182,6 @@ pfk_pkt_work(struct work_struct *w)
|
|||||||
orig_src = ip_hdr(pk->skb)->saddr;
|
orig_src = ip_hdr(pk->skb)->saddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* pf patches transport checksums incrementally when it rewrites an
|
|
||||||
* address. VM-to-VM traffic commonly arrives with an unmaterialized
|
|
||||||
* checksum (CHECKSUM_PARTIAL carried over from the sender's offload),
|
|
||||||
* so compute it in software first - otherwise pf's fixup adjusts a
|
|
||||||
* placeholder and the peer drops the packet.
|
|
||||||
*/
|
|
||||||
if (pk->skb->ip_summed == CHECKSUM_PARTIAL &&
|
|
||||||
skb_checksum_help(pk->skb)) {
|
|
||||||
kfree_skb(pk->skb);
|
|
||||||
kfree(pk);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m = pfk_m_wrap_skb(pk->skb, pk->af);
|
m = pfk_m_wrap_skb(pk->skb, pk->af);
|
||||||
if (!m) {
|
if (!m) {
|
||||||
pfk_pkt_reinject(pk); /* like "pf not consulted" */
|
pfk_pkt_reinject(pk); /* like "pf not consulted" */
|
||||||
@@ -172,7 +200,7 @@ pfk_pkt_work(struct work_struct *w)
|
|||||||
pskb_may_pull(pk->skb, sizeof(struct iphdr)) &&
|
pskb_may_pull(pk->skb, sizeof(struct iphdr)) &&
|
||||||
(ip_hdr(pk->skb)->saddr != orig_src ||
|
(ip_hdr(pk->skb)->saddr != orig_src ||
|
||||||
ip_hdr(pk->skb)->daddr != orig_dst))
|
ip_hdr(pk->skb)->daddr != orig_dst))
|
||||||
pfk_fix_ip_csum(pk->skb);
|
pfk_fix_csum_after_translate(pk->skb);
|
||||||
|
|
||||||
|
|
||||||
if (m == m0) {
|
if (m == m0) {
|
||||||
|
|||||||
@@ -40,5 +40,24 @@ pf0 '/opt/pf/pfctl -ss' | grep -E "$SRV_IP:80 .*$CLI_IP" >/dev/null && ok "state
|
|||||||
pf0 '/opt/pf/pfctl -s info' | grep -qiE 'match' && ok "counters readable" || bad "counters"
|
pf0 '/opt/pf/pfctl -s info' | grep -qiE 'match' && ok "counters readable" || bad "counters"
|
||||||
|
|
||||||
srv '[ -f /tmp/loop.pid ] && kill $(cat /tmp/loop.pid) 2>/dev/null; pkill nc 2>/dev/null; true' >/dev/null
|
srv '[ -f /tmp/loop.pid ] && kill $(cat /tmp/loop.pid) 2>/dev/null; pkill nc 2>/dev/null; true' >/dev/null
|
||||||
|
|
||||||
|
# bulk (GSO/TSO) through the redirect: 2 MB must arrive byte-identical.
|
||||||
|
# This is the regression that "materialize the checksum in software" introduced:
|
||||||
|
# computing the transport checksum once for a GSO skb corrupts every segment
|
||||||
|
# after the first, and the peer silently stalls (sshd mute, transfers hang).
|
||||||
|
B64=$(printf 'head -c 2000000 /dev/zero | tr "\0" A > /tmp/big.txt\n' | base64 | tr -d '\n')
|
||||||
|
srv "echo $B64 | base64 -d > /tmp/mkbig.sh; sh /tmp/mkbig.sh; pkill nc 2>/dev/null; sleep 1; nohup setsid nc -l -p 9091 < /tmp/big.txt >/dev/null 2>&1 & sleep 1; netstat -lnt | grep -q 9091 && echo BULK_UP" | grep -q BULK_UP || bad "bulk listener"
|
||||||
|
pf0 "printf 'pass in quick proto tcp from 10.0.1.0/24 to 10.0.1.1 port 9091 rdr-to $SRV_IP port 9091 keep state\npass out quick proto tcp keep state\n' > /tmp/g06b.conf; /opt/pf/pfctl -f /tmp/g06b.conf && echo BULK_RULE" >/dev/null
|
||||||
|
GOT=$(cli 'nc -w10 10.0.1.1 9091 | wc -c' | tr -d '[:space:]')
|
||||||
|
EXP=$(srv 'wc -c < /tmp/big.txt' | tr -d '[:space:]')
|
||||||
|
if [ "$GOT" = "$EXP" ] && [ "${GOT:-0}" -ge 2000000 ]; then
|
||||||
|
ok "2 MB through the redirect intact ($GOT bytes)"
|
||||||
|
else
|
||||||
|
bad "bulk through redirect: got $GOT want $EXP"
|
||||||
|
fi
|
||||||
|
# the firewall must still answer ssh (the GSO path must not wedge the box)
|
||||||
|
pf0 'echo SSH_ALIVE' | grep -q SSH_ALIVE && ok "pf0 sshd still responsive" || bad "pf0 unreachable after bulk"
|
||||||
|
srv 'pkill nc 2>/dev/null; true' >/dev/null
|
||||||
|
|
||||||
pf0 '/opt/pf/pfctl -d' >/dev/null
|
pf0 '/opt/pf/pfctl -d' >/dev/null
|
||||||
finish
|
finish
|
||||||
Reference in new issue
Block a user