From bf376a8fac2cc3c5935c100e5c468e058b03253b Mon Sep 17 00:00:00 2001 From: dr mike <91358784+drmikecrypto@users.noreply.github.com> Date: Sun, 28 Jun 2026 06:35:41 +0330 Subject: [PATCH] fix(hysteria2): emit https server_url for sing-box realm outbound (#9642) sing-box requires realm.server_url as a full URL (https://host:port). Bare host:port caused "missing host in realm server_url" on import. Also forward stun_servers and clear ResolveRealm success message. Fixes #9635 --- .../Singbox/CoreConfigSingboxServiceTests.cs | 35 +++++++++++++++++++ v2rayN/ServiceLib.Tests/Fmt/HyRealmTests.cs | 30 ++++++++++++++++ v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs | 1 + .../Models/CoreConfigs/SingboxConfig.cs | 1 + v2rayN/ServiceLib/Models/Dto/HyRealm.cs | 9 +++++ .../Singbox/SingboxOutboundService.cs | 3 +- 6 files changed, 78 insertions(+), 1 deletion(-) diff --git a/v2rayN/ServiceLib.Tests/CoreConfig/Singbox/CoreConfigSingboxServiceTests.cs b/v2rayN/ServiceLib.Tests/CoreConfig/Singbox/CoreConfigSingboxServiceTests.cs index 5847365b..10b89551 100644 --- a/v2rayN/ServiceLib.Tests/CoreConfig/Singbox/CoreConfigSingboxServiceTests.cs +++ b/v2rayN/ServiceLib.Tests/CoreConfig/Singbox/CoreConfigSingboxServiceTests.cs @@ -1,8 +1,10 @@ using AwesomeAssertions; using ServiceLib.Common; using ServiceLib.Enums; +using ServiceLib.Handler.Fmt; using ServiceLib.Manager; using ServiceLib.Models; +using ServiceLib.Models.Dto; using ServiceLib.Services.CoreConfig; using Xunit; @@ -557,4 +559,37 @@ public class CoreConfigSingboxServiceTests cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Global)); cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Direct)); } + + [Fact] + public void GenerateClientConfigContent_Hysteria2Realm_ShouldEmitHttpsServerUrl() + { + var shareLink = + "hysteria2+realm://public@realm.hy2.io/my-realm-id?auth=uuid&stun=turn.cloudflare.com%3A3478&sni=cloudflare.com&pinSHA256=xxx#Realm-Test"; + var node = Hysteria2Fmt.ResolveRealm(shareLink, out _); + node.Should().NotBeNull(); + node!.CoreType = ECoreType.sing_box; + + var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box); + config.CoreTypeItem = + [ + new CoreTypeItem { ConfigType = EConfigType.Hysteria2, CoreType = ECoreType.sing_box } + ]; + CoreConfigTestFactory.BindAppManagerConfig(config); + var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box); + + var result = new CoreConfigSingboxService(context).GenerateClientConfigContent(); + + result.Success.Should().BeTrue($"ret msg: {result.Msg}"); + var cfg = JsonUtils.Deserialize(result.Data!.ToString())!; + var proxy = cfg.outbounds.First(o => o.tag == Global.ProxyTag); + + proxy.type.Should().Be("hysteria2"); + proxy.realm.Should().NotBeNull(); + proxy.realm!.server_url.Should().StartWith("https://"); + proxy.realm.server_url.Should().Contain("realm.hy2.io"); + proxy.realm.token.Should().Be("public"); + proxy.realm.realm_id.Should().Be("my-realm-id"); + proxy.realm.stun_servers.Should().Contain("turn.cloudflare.com:3478"); + proxy.server.Should().BeNull(); + } } diff --git a/v2rayN/ServiceLib.Tests/Fmt/HyRealmTests.cs b/v2rayN/ServiceLib.Tests/Fmt/HyRealmTests.cs index 33bc1828..3cce2ed5 100644 --- a/v2rayN/ServiceLib.Tests/Fmt/HyRealmTests.cs +++ b/v2rayN/ServiceLib.Tests/Fmt/HyRealmTests.cs @@ -1,4 +1,6 @@ using AwesomeAssertions; +using ServiceLib.Handler.Fmt; +using ServiceLib.Models.Dto; using Xunit; namespace ServiceLib.Tests.Fmt; @@ -58,4 +60,32 @@ public class HyRealmTests uri.Should().Contain("hysteria2+realm://mytoken@rendezvous.example.com"); uri.Should().EndWith("#remark"); } + + [Fact] + public void ToServerUrl_ShouldIncludeSchemeForSingbox() + { + var realm = new HyRealm( + IsHttp: false, + Token: "public", + RendezvousHost: "realm.hy2.io", + RendezvousPort: 443, + RealmName: "my-realm-id", + StunList: ["turn.cloudflare.com:3478"] + ); + + realm.ToServerUrl().Should().Be("https://realm.hy2.io:443"); + } + + [Fact] + public void ResolveRealm_Issue9635_ShouldProduceHttpsServerUrl() + { + var str = "hysteria2+realm://public@realm.hy2.io/my-realm-id?auth=uuid&stun=turn.cloudflare.com%3A3478&sni=cloudflare.com&pinSHA256=xxx#Realm-Test"; + var resolved = Hysteria2Fmt.ResolveRealm(str, out _); + resolved.Should().NotBeNull(); + + HyRealm.TryParse(resolved!.GetProtocolExtra().Hy2RealmUrl, out var realm).Should().BeTrue(); + realm!.ToServerUrl().Should().StartWith("https://"); + realm.ToServerUrl().Should().Contain("realm.hy2.io"); + realm.StunList.Should().Contain("turn.cloudflare.com:3478"); + } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs b/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs index 318ed4d4..97a55225 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs @@ -110,6 +110,7 @@ public class Hysteria2Fmt : BaseFmt Hy2RealmUrl = realm.ToUri(), }); + msg = string.Empty; return item; } diff --git a/v2rayN/ServiceLib/Models/CoreConfigs/SingboxConfig.cs b/v2rayN/ServiceLib/Models/CoreConfigs/SingboxConfig.cs index 82890a18..a835b15f 100644 --- a/v2rayN/ServiceLib/Models/CoreConfigs/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/CoreConfigs/SingboxConfig.cs @@ -257,6 +257,7 @@ public class HyRealm4Sbox public string? server_url { get; set; } public string? token { get; set; } public string? realm_id { get; set; } + public List? stun_servers { get; set; } } public class Server4Sbox : BaseServer4Sbox diff --git a/v2rayN/ServiceLib/Models/Dto/HyRealm.cs b/v2rayN/ServiceLib/Models/Dto/HyRealm.cs index 7dbce1a3..620f48ad 100644 --- a/v2rayN/ServiceLib/Models/Dto/HyRealm.cs +++ b/v2rayN/ServiceLib/Models/Dto/HyRealm.cs @@ -15,6 +15,15 @@ public record HyRealm( { public string RendezvousHostPort => RendezvousPort > 0 ? $"{RendezvousHost}:{RendezvousPort}" : RendezvousHost; + /// + /// sing-box realm.server_url requires a scheme (https:// or http://). + /// + public string ToServerUrl() + { + var scheme = IsHttp ? "http" : "https"; + return $"{scheme}://{RendezvousHostPort}"; + } + public static bool TryParse(string? str, out HyRealm? realm) { realm = null; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index 9de5fa52..6e359fc0 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -275,9 +275,10 @@ public partial class CoreConfigSingboxService { var realm4Sbox = new HyRealm4Sbox() { - server_url = realm.RendezvousHostPort, + server_url = realm.ToServerUrl(), token = realm.Token, realm_id = realm.RealmName, + stun_servers = realm.StunList?.Count > 0 ? realm.StunList : null, }; outbound.realm = realm4Sbox; outbound.server = null;