Skip to content

share_forward: NAT'd GRE (PPTP) egresses with stale IP header checksum after first packet #312

Description

@Alfamonk

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

  1. Automatic outbound NAT, net.pf.share_forward=1 (default)
  2. From a LAN host, dial any PPTP server on the internet (stock Linux pptpd here)
  3. tcpdump -n -v -i <wan> proto 47
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    supportCommunity support or awaiting triage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions