mirror of
https://github.com/2dust/v2rayN.git
synced 2026-10-11 07:45:06 +03:00
Route IPv6 into the Xray TUN when EnableIPv6Address is on (#10329)
* Route IPv6 into the Xray TUN on NAT66 hosts and when IPv6 is enabled #10080 dropped ::/0 from autoSystemRoutingTable whenever the host held no address inside 2000::/3. A host behind NAT66 holds only unique local addresses, yet it reaches the IPv6 internet through its IPv6 default gateway. With ::/0 gone, its IPv6 follows the system default route, bypasses the tunnel and exposes the real IPv6 address (#10327). The check also overrode EnableIPv6Address. Before #9930, enabling it always added ::/0; since #10080 the option only assigned the interface address, so users who had enabled it were affected as well. Route ::/0 when EnableIPv6Address is on, and otherwise when the host has IPv6 connectivity: a global IPv6 address or an IPv6 default gateway. Routes whose next hop is ::, such as the one the tunnel installs, do not count. A host with neither, as in #10051, still skips ::/0. The RouteExcludeAddress branch uses the same decision. Reproduced in network namespaces with Xray 26.7.28: on a NAT66 client the 7.25.4 TUN inbound let curl -6 reach the echo server from the NAT66 address, while the inbound generated by this change sends it through the proxy, with EnableIPv6Address on or off. Fixes #10327 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Simplify: route ::/0 when EnableIPv6Address is on Drop the rename and the gateway detection; keep HasGlobalIPv6Address and let EnableIPv6Address override it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0142Bna7M2GhesX7P7d4CSrt --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -587,13 +587,11 @@ public class CoreConfigV2rayServiceTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Arguments(true)]
|
||||
[Arguments(false)]
|
||||
public async Task GenerateClientConfigContent_Tun_ShouldSkipIPv6RouteWithoutGlobalIPv6(bool enableIPv6Address)
|
||||
public async Task GenerateClientConfigContent_Tun_ShouldSkipIPv6RouteWithoutGlobalIPv6()
|
||||
{
|
||||
// A host without a global IPv6 address has no IPv6 traffic that could bypass the tunnel,
|
||||
// while ::/0 would pull IPv6 attempts into a tunnel they cannot leave.
|
||||
var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address);
|
||||
var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address: false);
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
|
||||
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
|
||||
@@ -611,6 +609,26 @@ public class CoreConfigV2rayServiceTests
|
||||
await ipv6Routes.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GenerateClientConfigContent_Tun_ShouldRouteIPv6WhenEnabledWithoutGlobalIPv6()
|
||||
{
|
||||
// Hosts behind NAT66 hold only ULA addresses, so EnableIPv6Address must still route ::/0.
|
||||
var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address: true);
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
|
||||
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
|
||||
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray, hasGlobalIPv6Address: false);
|
||||
|
||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||
|
||||
await result.Success.Should().BeTrue();
|
||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
|
||||
|
||||
await tunInbound.Should().NotBeNull();
|
||||
await tunInbound!.settings.autoSystemRoutingTable.Should().Contain("::/0");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GenerateClientConfigContent_TunRouteExcludeAddress_ShouldSkipIPv6RangesWithoutGlobalIPv6()
|
||||
{
|
||||
|
||||
@@ -67,12 +67,10 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
|
||||
tunInbound.settings.gateway = [address];
|
||||
// Route both families into the tunnel regardless of EnableIPv6Address. That option only
|
||||
// controls whether the interface gets an IPv6 address; leaving ::/0 out of the routing
|
||||
// table makes IPv6 follow the system default route and bypass the tunnel entirely.
|
||||
// A host without a global IPv6 address is the exception: it has nothing to leak,
|
||||
// and IPv6 sent into the tunnel would have no way back out.
|
||||
tunInbound.settings.autoSystemRoutingTable = context.HasGlobalIPv6Address
|
||||
// Without ::/0, IPv6 bypasses the tunnel. Skip it only when the host has no global IPv6
|
||||
// address and EnableIPv6Address is off; NAT66 hosts have only ULA addresses.
|
||||
var routeIPv6 = _config.TunModeItem.EnableIPv6Address || context.HasGlobalIPv6Address;
|
||||
tunInbound.settings.autoSystemRoutingTable = routeIPv6
|
||||
? ["0.0.0.0/0", "::/0"]
|
||||
: ["0.0.0.0/0"];
|
||||
if (_config.TunModeItem.EnableIPv6Address == true)
|
||||
@@ -108,7 +106,7 @@ public partial class CoreConfigV2rayService
|
||||
.Where(x => x != null).ToList();
|
||||
|
||||
var includeList = new List<IPNetwork2> { wholeInternet };
|
||||
var includeListV6 = context.HasGlobalIPv6Address
|
||||
var includeListV6 = routeIPv6
|
||||
? new List<IPNetwork2> { wholeInternetV6 }
|
||||
: new List<IPNetwork2>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user