share_forward: NAT'd GRE (PPTP) egresses with stale IP header checksum after first packet
@fichtner is already working on this.
Since Aug 11, 2026.
- Dominant language
- C
- Stars
- 505
- Forks
- 202
- PR merge metrics
- No merged PRs in 30d
Description
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, kernelFreeBSD 15.1-RELEASE-p1 stable/26.7-n283674-12334a596709 amd64 - WAN
pppoe0(MTU 1492) over VirtIO, LANvtnet0, outbound NATautomatic - 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
pptpdhere) 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
opnsense/src#20 (port forwarding with policy routing), opnsense/src#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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.