diff --git a/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs b/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs index 65bda048..e8b08809 100644 --- a/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs +++ b/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs @@ -21,6 +21,61 @@ public class CoreConfigV2rayServiceTests await v2rayConfig.inbounds.Should().Contain(i => i.protocol == nameof(EInboundProtocol.mixed)); } + [Test] + public async Task GenerateClientConfigContent_RealityFragment_ShouldUsePacketRangeOnlyForReality() + { + static ProfileItem CreateVlessNode(string indexId, string streamSecurity) + { + var node = new ProfileItem + { + IndexId = indexId, + ConfigType = EConfigType.VLESS, + CoreType = ECoreType.Xray, + Remarks = indexId, + Address = "example.com", + Port = 443, + Password = "01234567-89ab-cdef-0123-456789abcdef", + Network = nameof(ETransport.raw), + StreamSecurity = streamSecurity, + PublicKey = "test-public-key", + Sni = "example.com", + Fingerprint = "chrome", + }; + node.SetProtocolExtra(new ProtocolExtraItem { VlessEncryption = Global.None }); + node.SetTransportExtra(new TransportExtraItem { RawHeaderType = Global.None }); + return node; + } + + static string GetFragmentPackets(Outbounds4Ray outbound) + { + var finalmask = JsonUtils.ParseJson(JsonUtils.Serialize(outbound.streamSettings!.finalmask)); + return finalmask!["tcp"]![0]!["settings"]!["packets"]!.GetValue(); + } + + var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray); + config.CoreBasicItem.EnableFragment = true; + CoreConfigTestFactory.BindAppManagerConfig(config); + + var tlsNode = CreateVlessNode("tls-node", Global.StreamSecurity); + var realityNode = CreateVlessNode("reality-node", Global.StreamSecurityReality); + var group = CoreConfigTestFactory.CreatePolicyGroupNode(ECoreType.Xray, "group", "group", + [tlsNode.IndexId, realityNode.IndexId]); + var context = CoreConfigTestFactory.CreateContext(config, group, ECoreType.Xray); + context.AllProxiesMap[tlsNode.IndexId] = tlsNode; + context.AllProxiesMap[realityNode.IndexId] = realityNode; + context.AllProxiesMap[group.IndexId] = group; + + var result = new CoreConfigV2rayService(context).GenerateClientConfigContent(); + + await result.Success.Should().BeTrue(); + var cfg = JsonUtils.Deserialize(result.Data!.ToString())!; + var tlsOutbound = cfg.outbounds.Single(o => o.streamSettings?.security == Global.StreamSecurity); + var realityOutbound = cfg.outbounds.Single(o => o.streamSettings?.security == Global.StreamSecurityReality); + + await GetFragmentPackets(tlsOutbound).Should().BeEqualTo("tlshello"); + await GetFragmentPackets(realityOutbound).Should().BeEqualTo("1-3"); + } + [Test] public async Task GenerateClientConfigContent_HttpOutbound_ShouldEmitHeadersInSettings() { diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs index 6ae4f222..964e560d 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -809,8 +809,6 @@ public partial class CoreConfigV2rayService && (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true)) .ToList(); - var fragmentMask = BuildFragmentsMasks(); - foreach (var outbound in actOutboundWithTlsList) { var finalMaskJsonObj = JsonUtils.ParseJson(JsonUtils.Serialize(outbound.streamSettings?.finalmask)) as JsonObject ?? new JsonObject(); @@ -818,6 +816,13 @@ public partial class CoreConfigV2rayService var tcpFinalmaskList = finalMaskJsonObj["tcp"] as JsonArray ?? []; if (tcpFinalmaskList.Count == 0) { + var fragmentMask = BuildFragmentsMasks(); + // Xray's tlshello fragment is incompatible with REALITY. + if (outbound.streamSettings.security == Global.StreamSecurityReality + && fragmentMask.settings?.packets == "tlshello") + { + fragmentMask.settings!.packets = "1-3"; + } tcpFinalmaskList.Add(JsonUtils.SerializeToNode(fragmentMask)); finalMaskJsonObj["tcp"] = tcpFinalmaskList; }