Environment
- sing-box for Apple: 1.14.0
- sing-box core: 1.14.0-rc.1
- sing-tun: v0.9.0-beta.2
- Go: go1.26.6, ios/arm64
- Device: iPhone 16 (iPhone17,3)
- OS: iOS 27.0 beta (24A5424a)
- TUN stack:
system
- TUN addresses: dual stack (one IPv4 prefix and one IPv6 prefix)
This was reproduced twice with the official, unmodified core/framework. No locally patched sing-tun code was installed.
What happened
The Network Extension hit critical memory pressure during a burst of dual-stack TCP connection retries and then panicked in rewriteIPv4TCP because an IPv6 address reached netip.Addr.As4().
Both reproductions followed the same pattern:
- Start the Network Extension with a dual-stack system TUN.
- Generate high connection churn, including repeated TCP attempts to currently unreachable IPv6 destinations.
- The in-process OOM monitor reports critical memory pressure.
- About 1–2 minutes after launch, the Network Extension aborts with the same panic.
The two observed timelines were:
- Start 07:31:51Z; memory-pressure report 07:32:21Z; panic 07:34:54Z.
- Start 08:46:30Z; memory-pressure report 08:47:12Z; panic 08:48:42Z.
Panic
panic: As4 called on IPv6 address
net/netip.Addr.As4(...)
github.com/sagernet/sing-tun.rewriteIPv4TCP(...)
github.com/sagernet/sing-tun@v0.9.0-beta.2/stack_system.go:553
github.com/sagernet/sing-tun.(*System).processIPv4TCP(...)
github.com/sagernet/sing-tun@v0.9.0-beta.2/stack_system.go:483
github.com/sagernet/sing-tun.(*System).processIPv4(...)
github.com/sagernet/sing-tun.(*System).processPacket(...)
github.com/sagernet/sing-tun.(*System).tunLoop(...)
The native exception is SIGABRT, with exceptionReason set to As4 called on IPv6 address; it was not a jetsam termination.
Memory-pressure evidence from the second reproduction
At 08:47:12Z, 42 seconds after launch:
memoryUsage: 40 MB
availableMemory: 9.5 MB
heapAlloc: 19 MB
heapInuse: 25 MB
heapSys: 33 MB
sys: 42 MB
totalAlloc: 1.7 GB
numGC: 305
numGoroutine: 228
Heap inuse_space top entries:
7.24 MB github.com/sagernet/sing-tun.(*TCPNat).Lookup
3.27 MB github.com/sagernet/sing-tun.(*ForwardDispatcher).insertEntry
4.27 MB github.com/sagernet/sing-tun.(*ForwardDispatcher).installSimple (cumulative)
Heap inuse_objects included:
43,734 github.com/sagernet/sing-tun.(*TCPNat).Lookup
32,769 github.com/sagernet/sing-tun.(*ForwardDispatcher).installSimple
Source observation
In the system stack, IPv4 and IPv6 TCP processing appear to share the same tcpNat. Both families call Lookup/LookupBack, while processIPv4TCP passes the returned session addresses to rewriteIPv4TCP, which calls As4() without checking the address family.
I cannot yet prove whether the wrong-family session is caused by NAT-port reuse, a stale entry, or a race, but the panic proves that an IPv6 address was returned to the IPv4 rewrite path. It may be worth either separating the TCP NAT state by address family or validating the session family before rewriting.
Expected behavior
High dual-stack connection churn, including unreachable IPv6 destinations, should not allow an IPv6 session to reach the IPv4 rewrite path and should not panic the Network Extension.
Full crash/OOM reports and pprof data can be provided privately if useful. The active configuration is not attached because it contains proxy credentials and subscription data.
Environment
systemThis was reproduced twice with the official, unmodified core/framework. No locally patched sing-tun code was installed.
What happened
The Network Extension hit critical memory pressure during a burst of dual-stack TCP connection retries and then panicked in
rewriteIPv4TCPbecause an IPv6 address reachednetip.Addr.As4().Both reproductions followed the same pattern:
The two observed timelines were:
Panic
The native exception is
SIGABRT, withexceptionReasonset toAs4 called on IPv6 address; it was not a jetsam termination.Memory-pressure evidence from the second reproduction
At 08:47:12Z, 42 seconds after launch:
Heap
inuse_spacetop entries:Heap
inuse_objectsincluded:Source observation
In the system stack, IPv4 and IPv6 TCP processing appear to share the same
tcpNat. Both families callLookup/LookupBack, whileprocessIPv4TCPpasses the returned session addresses torewriteIPv4TCP, which callsAs4()without checking the address family.I cannot yet prove whether the wrong-family session is caused by NAT-port reuse, a stale entry, or a race, but the panic proves that an IPv6 address was returned to the IPv4 rewrite path. It may be worth either separating the TCP NAT state by address family or validating the session family before rewriting.
Expected behavior
High dual-stack connection churn, including unreachable IPv6 destinations, should not allow an IPv6 session to reach the IPv4 rewrite path and should not panic the Network Extension.
Full crash/OOM reports and pprof data can be provided privately if useful. The active configuration is not attached because it contains proxy credentials and subscription data.