mirror of
https://github.com/2dust/v2rayN.git
synced 2026-08-18 13:12:05 +03:00
Fix infinite TUN routing loop on traffic to the TUN's own addresses (#9897)
* Restore the silently dropped embedded TUN routing rules tun_singbox_rules declares "network": "udp" as a bare string, but Rule4Sbox.network is a List<string>. System.Text.Json therefore throws on the very first rule, and JsonUtils.Deserialize swallows it and returns default. GenRouting only checks for null before AddRange, so the whole embedded rule set is dropped without a trace. Both rules it carries have thus never reached a generated config: the NetBIOS/mDNS reject and the multicast reject. The mismatch predates the sing-box 1.12 migration - the template has used the string form since the rules file was introduced. - declare network as an array so the template matches Rule4Sbox * Drop traffic addressed to the TUN's own addresses With auto_route the TUN steals the default route, so a packet whose destination is the TUN interface's own address is handed to sing-box instead of being looped back by the kernel. Routing then matches ip_is_private and sends it to `direct`, whose interface is auto-detected as the TUN again, so the packet is written straight back into the TUN and re-enters routing. The loop never terminates and pins a CPU core. Seen in the wild on macOS: a WebRTC client offered the TUN's own fc00::172:18:0:1 ULA as an ICE candidate and the resulting STUN connectivity checks sustained ~8k packets/s out of the interface at 800% CPU, 21 GB written to the TUN over five hours - against 3 GB read, the asymmetry that gives the loop away. Nothing legitimate is addressed to those addresses, so reject them before any outbound rule can match. Use method "drop" rather than the default ICMP unreachable, whose destination would be the looping address itself. - reject the TUN inbound's own addresses, taken from the generated inbound so the two cannot drift apart
This commit is contained in:
@@ -626,4 +626,71 @@ public class CoreConfigSingboxServiceTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateClientConfigContent_TunEnabled_ShouldKeepEmbeddedTunRules()
|
||||
{
|
||||
// The embedded tun rules reject local-network noise (NetBIOS/mDNS, multicast).
|
||||
// They are deserialized into List<Rule4Sbox>, so a schema mismatch in the
|
||||
// embedded template makes JsonUtils.Deserialize return null and silently
|
||||
// drops every one of them.
|
||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||
config.TunModeItem.EnableTun = true;
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
|
||||
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.sing_box);
|
||||
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with
|
||||
{
|
||||
IsTunEnabled = true,
|
||||
};
|
||||
|
||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||
|
||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||
|
||||
cfg.route.rules.Should().Contain(
|
||||
r => r.action == "reject"
|
||||
&& r.network != null && r.network.Contains("udp")
|
||||
&& r.port != null && r.port.Contains(5353),
|
||||
"the embedded tun rules must reject mDNS/NetBIOS noise");
|
||||
cfg.route.rules.Should().Contain(
|
||||
r => r.action == "reject"
|
||||
&& r.ip_cidr != null && r.ip_cidr.Contains("224.0.0.0/3"),
|
||||
"the embedded tun rules must reject multicast traffic");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateClientConfigContent_TunEnabled_ShouldRejectTrafficToTunOwnAddresses()
|
||||
{
|
||||
// Regression test: traffic addressed to the TUN interface's own addresses must
|
||||
// never reach an outbound. auto_route hijacks the default route, so `direct`
|
||||
// writes such a packet straight back into the TUN, which routes it to the
|
||||
// outbound again - an infinite loop that pins a CPU core. Observed in the wild
|
||||
// with WebRTC ICE connectivity checks against the TUN's own fc00::/7 ULA
|
||||
// address, sustaining ~8k packets/s out of the TUN interface.
|
||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||
config.TunModeItem.EnableTun = true;
|
||||
config.TunModeItem.EnableIPv6Address = true;
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
|
||||
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.sing_box);
|
||||
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with
|
||||
{
|
||||
IsTunEnabled = true,
|
||||
};
|
||||
|
||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||
|
||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||
var tun = cfg.inbounds.First(i => i.type == "tun");
|
||||
tun.address.Should().NotBeNullOrEmpty();
|
||||
|
||||
foreach (var address in tun.address!)
|
||||
{
|
||||
cfg.route.rules.Should().Contain(
|
||||
r => r.action == "reject" && r.ip_cidr != null && r.ip_cidr.Contains(address),
|
||||
$"traffic to the TUN's own address '{address}' must be rejected, not routed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
[
|
||||
{
|
||||
"network": "udp",
|
||||
"network": [
|
||||
"udp"
|
||||
],
|
||||
"port": [
|
||||
135,
|
||||
137,
|
||||
|
||||
@@ -43,6 +43,22 @@ public partial class CoreConfigSingboxService
|
||||
_coreConfig.route.rules.AddRange(tunRules);
|
||||
}
|
||||
|
||||
// Traffic addressed to the TUN interface's own addresses must never reach an
|
||||
// outbound. auto_route hijacks the default route, so `direct` writes such a
|
||||
// packet straight back into the TUN, which hands it to the outbound again -
|
||||
// an infinite loop that pins a CPU core. Drop instead of rejecting so no
|
||||
// ICMP unreachable is generated back towards the same addresses.
|
||||
var tunAddresses = _coreConfig.inbounds.FirstOrDefault(i => i.type == "tun")?.address;
|
||||
if (tunAddresses?.Count > 0)
|
||||
{
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
ip_cidr = [.. tunAddresses],
|
||||
action = "reject",
|
||||
method = "drop",
|
||||
});
|
||||
}
|
||||
|
||||
var lstDirectExe = BuildRoutingDirectExe();
|
||||
if (lstDirectExe.Count > 0)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user