Skip to content

Partition fwmark field for routing_mark coexistence with auto_redirect#71

Open
loncharles wants to merge 1 commit into
SagerNet:devfrom
loncharles:feat/masked-mark-coexistence
Open

Partition fwmark field for routing_mark coexistence with auto_redirect#71
loncharles wants to merge 1 commit into
SagerNet:devfrom
loncharles:feat/masked-mark-coexistence

Conversation

@loncharles
Copy link
Copy Markdown

@loncharles loncharles commented May 13, 2026

Summary

  • Partition the 32-bit fwmark field: low 16 bits (0x0000FFFF) reserved
    for auto_redirect loop prevention, upper 16 bits available for
    routing_mark (WAN selection, policy routing, etc.)
  • All nftables mark operations changed from exact 32-bit match/overwrite
    to masked operations via helper functions
  • ip rules use fwmark mask for consistent masked matching
  • NFQUEUE verdicts preserve upper mark bits
  • Export AutoRedirectMarkMask constant for use by sing-box

Motivation

routing_mark and auto_redirect are currently mutually exclusive in
sing-box. routing_mark works correctly without auto_redirect, and
auto_redirect works correctly without routing_mark — the conflict
is purely an implementation limitation. The mark field operations use
full 32-bit equality matching and overwrites, so any routing_mark bits
would be clobbered by auto_redirect's loop prevention marks, and vice
versa.

This patch partitions the mark field so both can coexist. The approach
is the standard technique for fwmark field sharing in Linux netfilter.

Changes

nftables rules (redirect_nftables_rules.go, redirect_nftables.go):

  • Mark comparisons: meta mark == value(meta mark & 0xFFFF) == value
  • Mark writes: meta mark set valuemeta mark set (meta mark & 0xFFFF0000) | value
  • Same for ct mark comparisons and writes
  • Four helper functions: maskedMetaMarkCmp, maskedCtMarkCmp,
    maskedMetaMarkSet, maskedMetaMarkSetWithCtCopy

ip rules (tun_linux.go):

  • All mark-matching ip rules now include FRA_FWMASK = 0xFFFF

NFQUEUE (nfqueue_linux.go):

  • Verdict marks preserve upper 16 bits from the packet's existing mark

Constant (redirect.go):

  • AutoRedirectMarkMask = 0x0000FFFF exported for cross-package use

Test plan

  • Basic auto_redirect functionality (masked nftables rules, TUN active)
  • Multiple routing_marks coexist (0x10000, 0x20000, 0x30000)
  • Routing policy applied end-to-end based on upper mark bits
  • Mark overlap rejection (routing_mark 0x2024 and 0x1 both rejected)
  • ip rule /0xffff mask confirmed
  • NFQUEUE verdict marks preserved
  • Forwarded traffic (PREROUTING path) captured correctly

All nftables mark operations changed from exact 32-bit match/overwrite
to masked operations using the low 16 bits (0x0000FFFF) for auto_redirect
loop prevention, leaving the upper 16 bits available for routing_mark.

- Add masked comparison helpers for meta mark and ct mark
- Add masked set helpers that preserve bits outside the mask
- Apply masked operations to all mark sites in redirect rules,
  pre-match chains, loopback reroute, and prerouting UDP/ICMP
- Add fwmark mask to ip rules for consistent masked matching
- Preserve routing_mark bits in NFQUEUE verdict marks
- Export AutoRedirectMarkMask constant for cross-package use
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant