Describe the bug
With net.pf.share_forward=1 (default; Firewall > Settings > Advanced > "Shared forwarding"), outbound-NAT'd GRE (IP proto 47) egresses with an invalid IP header checksum for every packet after the first of a flow.
The source address is rewritten, but the checksum is only adjusted by the TTL-decrement delta - the address change is never accounted for. Peers silently discard the packets. The first packet of the flow (which creates the state) is correct; every later packet on the established-state fast path is not.
Result: PPTP clients cannot complete PPP LCP. Control setup on TCP/1723 succeeds (SCCRQ/SCCRP, OCRQ/OCRP, SLI), GRE opens, the server acks GRE seq 0 and then never acks again, re-sending its own LCP Conf-Request until the client gives up (Windows error 619). TCP/UDP are unaffected, so ordinary traffic hides this completely.
Environment
- OPNsense
26.7.1_1, kernel FreeBSD 15.1-RELEASE-p1 stable/26.7-n283674-12334a596709 amd64
- WAN
pppoe0 (MTU 1492) over VirtIO, LAN vtnet0, outbound NAT automatic
- Checksum / TSO / LRO offload all disabled - not a factor
- Single LAN client to a single PPTP server, so this is not the known multi-client GRE Call-ID limitation
To Reproduce
- Automatic outbound NAT,
net.pf.share_forward=1 (default)
- From a LAN host, dial any PPTP server on the internet (stock Linux
pptpd here)
tcpdump -n -v -i <wan> proto 47
- First GRE packet is fine; every later outbound GRE packet shows
bad cksum
Evidence
Consecutive outbound GRE packets of one call, captured pre-NAT (LAN) and post-NAT (WAN), checksums verified arithmetically. Addresses redacted:
| packet |
pre-NAT |
post-NAT emitted |
correct value |
|
| seq 0 |
valid |
0xbdee |
0xbdee |
OK |
| seq 1 |
valid |
0x1745 |
0xbde1 |
BAD |
| seq 2 |
valid |
0x1753 |
0xbdef |
BAD |
| seq 3 |
valid |
0x1752 |
0xbdee |
BAD |
Signature: every bad value equals the pre-NAT checksum + 0x0100 - exactly and only the one's-complement delta for ttl 128 -> 127. The source-address rewrite contributes nothing to the emitted checksum.
All packets arrive from the LAN host with valid checksums, so the corruption is introduced by the firewall. With the workaround applied: 1230 outbound GRE packets, 0 bad, and PPP completes normally (LCP -> CHAP -> CCP -> IPCP).
Expected behavior
NAT'd GRE should egress with a correctly recomputed IP header checksum, as TCP and UDP do.
Workaround
net.pf.share_forward=0 (uncheck "Shared forwarding"). Immediate and complete; an already-established tunnel survives the change.
Possibly related
#20 (port forwarding with policy routing), #52 (panic with route-to), forum topic 4546 (captive portal redirection). The 26.7 notes mention "pf: do not mangle IP header before shared forwarding".
Full pcap pair (LAN + WAN) available on request.
Describe the bug
With
net.pf.share_forward=1(default; Firewall > Settings > Advanced > "Shared forwarding"), outbound-NAT'd GRE (IP proto 47) egresses with an invalid IP header checksum for every packet after the first of a flow.The source address is rewritten, but the checksum is only adjusted by the TTL-decrement delta - the address change is never accounted for. Peers silently discard the packets. The first packet of the flow (which creates the state) is correct; every later packet on the established-state fast path is not.
Result: PPTP clients cannot complete PPP LCP. Control setup on TCP/1723 succeeds (SCCRQ/SCCRP, OCRQ/OCRP, SLI), GRE opens, the server acks GRE seq 0 and then never acks again, re-sending its own LCP Conf-Request until the client gives up (Windows error 619). TCP/UDP are unaffected, so ordinary traffic hides this completely.
Environment
26.7.1_1, kernelFreeBSD 15.1-RELEASE-p1 stable/26.7-n283674-12334a596709 amd64pppoe0(MTU 1492) over VirtIO, LANvtnet0, outbound NATautomaticTo Reproduce
net.pf.share_forward=1(default)pptpdhere)tcpdump -n -v -i <wan> proto 47bad cksumEvidence
Consecutive outbound GRE packets of one call, captured pre-NAT (LAN) and post-NAT (WAN), checksums verified arithmetically. Addresses redacted:
0xbdee0xbdee0x17450xbde10x17530xbdef0x17520xbdeeSignature: every bad value equals the pre-NAT checksum
+ 0x0100- exactly and only the one's-complement delta forttl 128 -> 127. The source-address rewrite contributes nothing to the emitted checksum.All packets arrive from the LAN host with valid checksums, so the corruption is introduced by the firewall. With the workaround applied: 1230 outbound GRE packets, 0 bad, and PPP completes normally (LCP -> CHAP -> CCP -> IPCP).
Expected behavior
NAT'd GRE should egress with a correctly recomputed IP header checksum, as TCP and UDP do.
Workaround
net.pf.share_forward=0(uncheck "Shared forwarding"). Immediate and complete; an already-established tunnel survives the change.Possibly related
#20 (port forwarding with policy routing), #52 (panic with route-to), forum topic 4546 (captive portal redirection). The 26.7 notes mention "pf: do not mangle IP header before shared forwarding".
Full pcap pair (LAN + WAN) available on request.