Unconnected raw ICMP sockets receive every ICMP packet arriving at the
host, so any ICMP traffic refreshed the read deadline of every flow and
stale flows (with their raw sockets and goroutines) were only reclaimed
by LRU eviction while processing all host ICMP traffic in the meantime.
Expire flows based on their own activity only, and on Linux attach a
classic BPF ident filter to each raw socket so other flows' packets are
dropped in the kernel instead of waking every flow.
When To() is 255.255.255.255 / ffff:...:ffff, Next() is invalid. Using
From() as the end key duplicated the start element and caused EEXIST.
Omit the end element so the half-open interval covers through max.
FixesSagerNet/sing-box#4316
The acceptLoop was rewriting any TCP destination within the TUN
address prefix to 127.0.0.1/::1. This incorrectly caught the
gateway address and other subnet addresses, not just the interface
address itself.
NF_ACCEPT is a terminal verdict in nftables — when a packet returns
from NFQUEUE with NF_ACCEPT, it exits the current chain immediately
and continues to the next hook priority. Rules placed after the queue
statement in the same chain are never evaluated.
This meant that the `ct mark set meta mark` rule (which saves the
bypass decision to conntrack for subsequent packets) was dead code.
The first SYN packet received the correct mark from NFQUEUE, but
conntrack never stored it, so all subsequent packets of the same
connection were redirected to sing-box userspace.
Fix: use NF_REPEAT instead of NF_ACCEPT for bypass and reset verdicts.
NF_REPEAT re-enters the chain from the beginning with the mark already
set on skb->mark. Reorder the prematch chain rules so mark-checking
rules (ct mark set, reject) come before the queue statement:
1. meta mark == outputMark → ct mark set meta mark, return
2. meta mark == resetMark → reject with tcp reset
3. ct mark == outputMark → return
4. TCP SYN → queue to NFQUEUE
This is the standard pattern used by Suricata and other NFQUEUE-based
systems (NF_REPEAT + mark-based skip).
Tested on Orange Pi Zero 3 (arm64, kernel 6.12.58) with sing-box 1.13.3.
Bypass correctly saves ct mark, subsequent packets skip NFQUEUE entirely.
REDIRECT in the OUTPUT chain rewrites the destination to 127.0.0.1,
then ip_route_me_harder() reroutes with the socket's bound interface
constraint (flowi4_oif). Since 127.0.0.1 is only reachable via lo,
the routing lookup fails and the packet is silently dropped.
Add a fallback routing table with `local 127.0.0.1` entries for each
non-loopback interface. When the local table lookup fails due to OIF
mismatch, the fallback table provides a matching RTN_LOCAL route.
The kernel then overrides dev_out to loopback (route.c:2857), so the
packet is delivered locally to the redirect server as intended.
This fixes NetworkManager connectivity checks and other tools that
use SO_BINDTODEVICE (e.g. curl --interface).