mirror of
https://github.com/2dust/v2rayN.git
synced 2026-10-30 09:05:01 +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]
|
[Test]
|
||||||
[Arguments(true)]
|
public async Task GenerateClientConfigContent_Tun_ShouldSkipIPv6RouteWithoutGlobalIPv6()
|
||||||
[Arguments(false)]
|
|
||||||
public async Task GenerateClientConfigContent_Tun_ShouldSkipIPv6RouteWithoutGlobalIPv6(bool enableIPv6Address)
|
|
||||||
{
|
{
|
||||||
// A host without a global IPv6 address has no IPv6 traffic that could bypass the tunnel,
|
// 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.
|
// 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);
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
|
|
||||||
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
|
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
|
||||||
@@ -611,6 +609,26 @@ public class CoreConfigV2rayServiceTests
|
|||||||
await ipv6Routes.Should().BeEmpty();
|
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]
|
[Test]
|
||||||
public async Task GenerateClientConfigContent_TunRouteExcludeAddress_ShouldSkipIPv6RangesWithoutGlobalIPv6()
|
public async Task GenerateClientConfigContent_TunRouteExcludeAddress_ShouldSkipIPv6RangesWithoutGlobalIPv6()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -67,12 +67,10 @@ public partial class CoreConfigV2rayService
|
|||||||
|
|
||||||
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
|
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
|
||||||
tunInbound.settings.gateway = [address];
|
tunInbound.settings.gateway = [address];
|
||||||
// Route both families into the tunnel regardless of EnableIPv6Address. That option only
|
// Without ::/0, IPv6 bypasses the tunnel. Skip it only when the host has no global IPv6
|
||||||
// controls whether the interface gets an IPv6 address; leaving ::/0 out of the routing
|
// address and EnableIPv6Address is off; NAT66 hosts have only ULA addresses.
|
||||||
// table makes IPv6 follow the system default route and bypass the tunnel entirely.
|
var routeIPv6 = _config.TunModeItem.EnableIPv6Address || context.HasGlobalIPv6Address;
|
||||||
// A host without a global IPv6 address is the exception: it has nothing to leak,
|
tunInbound.settings.autoSystemRoutingTable = routeIPv6
|
||||||
// and IPv6 sent into the tunnel would have no way back out.
|
|
||||||
tunInbound.settings.autoSystemRoutingTable = context.HasGlobalIPv6Address
|
|
||||||
? ["0.0.0.0/0", "::/0"]
|
? ["0.0.0.0/0", "::/0"]
|
||||||
: ["0.0.0.0/0"];
|
: ["0.0.0.0/0"];
|
||||||
if (_config.TunModeItem.EnableIPv6Address == true)
|
if (_config.TunModeItem.EnableIPv6Address == true)
|
||||||
@@ -108,7 +106,7 @@ public partial class CoreConfigV2rayService
|
|||||||
.Where(x => x != null).ToList();
|
.Where(x => x != null).ToList();
|
||||||
|
|
||||||
var includeList = new List<IPNetwork2> { wholeInternet };
|
var includeList = new List<IPNetwork2> { wholeInternet };
|
||||||
var includeListV6 = context.HasGlobalIPv6Address
|
var includeListV6 = routeIPv6
|
||||||
? new List<IPNetwork2> { wholeInternetV6 }
|
? new List<IPNetwork2> { wholeInternetV6 }
|
||||||
: new List<IPNetwork2>();
|
: new List<IPNetwork2>();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user