diff --git a/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayN/ServiceLib/Models/SingboxConfig.cs index d5dd3038..6d464b2c 100644 --- a/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -8,6 +8,7 @@ public class SingboxConfig public Dns4Sbox? dns { get; set; } public List inbounds { get; set; } public List outbounds { get; set; } + public List? endpoints { get; set; } public Route4Sbox route { get; set; } public Experimental4Sbox? experimental { get; set; } } @@ -96,10 +97,8 @@ public class User4Sbox public string password { get; set; } } -public class Outbound4Sbox +public class Outbound4Sbox : BaseServer4Sbox { - public string type { get; set; } - public string tag { get; set; } public string? server { get; set; } public int? server_port { get; set; } public List? server_ports { get; set; } @@ -114,7 +113,6 @@ public class Outbound4Sbox public int? recv_window_conn { get; set; } public int? recv_window { get; set; } public bool? disable_mtu_discovery { get; set; } - public string? detour { get; set; } public string? method { get; set; } public string? username { get; set; } public string? password { get; set; } @@ -122,26 +120,14 @@ public class Outbound4Sbox public string? version { get; set; } public string? network { get; set; } public string? packet_encoding { get; set; } - public List? local_address { get; set; } - public string? private_key { get; set; } - public string? peer_public_key { get; set; } - public List? reserved { get; set; } - public int? mtu { get; set; } public string? plugin { get; set; } public string? plugin_opts { get; set; } - public Tls4Sbox? tls { get; set; } - public Multiplex4Sbox? multiplex { get; set; } - public Transport4Sbox? transport { get; set; } - public HyObfs4Sbox? obfs { get; set; } public List? outbounds { get; set; } public bool? interrupt_exist_connections { get; set; } - public Rule4Sbox? domain_resolver { get; set; } } -public class Endpoints4Sbox +public class Endpoints4Sbox : BaseServer4Sbox { - public string type { get; set; } - public string tag { get; set; } public bool? system { get; set; } public string? name { get; set; } public int? mtu { get; set; } @@ -219,14 +205,11 @@ public class HyObfs4Sbox public string? password { get; set; } } -public class Server4Sbox +public class Server4Sbox : BaseServer4Sbox { - public string? tag { get; set; } - public string? detour { get; set; } public string? inet4_range { get; set; } public string? inet6_range { get; set; } public string? client_subnet { get; set; } - public string? type { get; set; } public string? server { get; set; } public string? server_resolver { get; set; } [JsonPropertyName("interface")] public string? Interface { get; set; } @@ -277,3 +260,33 @@ public class Ruleset4Sbox public string? download_detour { get; set; } public string? update_interval { get; set; } } + +public abstract class DialFields4Sbox +{ + public string? detour { get; set; } + public string? bind_interface { get; set; } + public string? inet4_bind_address { get; set; } + public string? inet6_bind_address { get; set; } + public int? routing_mark { get; set; } + public bool? reuse_addr { get; set; } + public string? netns { get; set; } + public string? connect_timeout { get; set; } + public bool? tcp_fast_open { get; set; } + public bool? tcp_multi_path { get; set; } + public bool? udp_fragment { get; set; } + public Rule4Sbox? domain_resolver { get; set; } // or string + public string? network_strategy { get; set; } + public List? network_type { get; set; } + public List? fallback_network_type { get; set; } + public string? fallback_delay { get; set; } + public Tls4Sbox? tls { get; set; } + public Multiplex4Sbox? multiplex { get; set; } + public Transport4Sbox? transport { get; set; } + public HyObfs4Sbox? obfs { get; set; } +} + +public abstract class BaseServer4Sbox : DialFields4Sbox +{ + public string type { get; set; } + public string tag { get; set; } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 77ce148c..85adfd28 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -1,6 +1,7 @@ using System.Data; using System.Net; using System.Net.NetworkInformation; +using System.Reactive; using DynamicData; using ServiceLib.Models; @@ -55,7 +56,18 @@ public class CoreConfigSingboxService await GenInbounds(singboxConfig); - await GenOutbound(node, singboxConfig.outbounds.First()); + if (node.ConfigType == EConfigType.WireGuard) + { + singboxConfig.outbounds.RemoveAt(0); + var endpoints = new Endpoints4Sbox(); + await GenEndpoint(node, endpoints); + endpoints.tag = Global.ProxyTag; + singboxConfig.endpoints = new() { endpoints }; + } + else + { + await GenOutbound(node, singboxConfig.outbounds.First()); + } await GenMoreOutbounds(node, singboxConfig); @@ -204,16 +216,29 @@ public class CoreConfigSingboxService continue; } - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(item, outbound); - outbound.tag = Global.ProxyTag + inbound.listen_port.ToString(); - singboxConfig.outbounds.Add(outbound); + var server = await GenServer(item); + if (server is null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + var tag = Global.ProxyTag + inbound.listen_port.ToString(); + server.tag = tag; + if (server is Endpoints4Sbox endpoint) + { + singboxConfig.endpoints ??= new(); + singboxConfig.endpoints.Add(endpoint); + } + else if (server is Outbound4Sbox outbound) + { + singboxConfig.outbounds.Add(outbound); + } //rule Rule4Sbox rule = new() { inbound = new List { inbound.tag }, - outbound = outbound.tag + outbound = tag }; singboxConfig.route.rules.Add(rule); } @@ -277,7 +302,18 @@ public class CoreConfigSingboxService } await GenLog(singboxConfig); - await GenOutbound(node, singboxConfig.outbounds.First()); + if (node.ConfigType == EConfigType.WireGuard) + { + singboxConfig.outbounds.RemoveAt(0); + var endpoints = new Endpoints4Sbox(); + await GenEndpoint(node, endpoints); + endpoints.tag = Global.ProxyTag; + singboxConfig.endpoints = new() { endpoints }; + } + else + { + await GenOutbound(node, singboxConfig.outbounds.First()); + } await GenMoreOutbounds(node, singboxConfig); await GenDnsDomains(null, singboxConfig, null); @@ -731,15 +767,6 @@ public class CoreConfigSingboxService outbound.congestion_control = node.HeaderType; break; } - case EConfigType.WireGuard: - { - outbound.private_key = node.Id; - outbound.peer_public_key = node.PublicKey; - outbound.reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(); - outbound.local_address = Utils.String2List(node.RequestHost); - outbound.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(); - break; - } case EConfigType.Anytls: { outbound.password = node.Id; @@ -758,6 +785,76 @@ public class CoreConfigSingboxService return 0; } + private async Task GenEndpoint(ProfileItem node, Endpoints4Sbox endpoint) + { + try + { + endpoint.address = Utils.String2List(node.RequestHost); + // Utils.GetFreePort() 9090 ? + endpoint.listen_port = Utils.GetFreePort(); + endpoint.type = Global.ProtocolTypes[node.ConfigType]; + + if (Utils.IsDomain(node.Address)) + { + var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + endpoint.domain_resolver = new() + { + server = "local_local", + strategy = string.IsNullOrEmpty(item?.DomainStrategy4Freedom) ? null : item?.DomainStrategy4Freedom + }; + } + + switch (node.ConfigType) + { + case EConfigType.WireGuard: + { + var peer = new Peer4Sbox + { + public_key = node.PublicKey, + reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(), + address = node.Address, + port = node.Port, + // TODO default ["0.0.0.0/0", "::/0"] + allowed_ips = new() { "0.0.0.0/0", "::/0" }, + }; + endpoint.private_key = node.Id; + endpoint.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(); + break; + } + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return await Task.FromResult(0); + } + + private async Task GenServer(ProfileItem node) + { + try + { + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); + if (node.ConfigType == EConfigType.WireGuard) + { + var endpoint = JsonUtils.Deserialize(txtOutbound); + await GenEndpoint(node, endpoint); + return endpoint; + } + else + { + var outbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(node, outbound); + return outbound; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return await Task.FromResult(null); + } + private async Task GenOutboundMux(ProfileItem node, Outbound4Sbox outbound) { try @@ -924,7 +1021,8 @@ public class CoreConfigSingboxService } //current proxy - var outbound = singboxConfig.outbounds.First(); + BaseServer4Sbox? outbound = singboxConfig.endpoints?.FirstOrDefault(t => t.tag == Global.ProxyTag) == null ? singboxConfig.outbounds.First() : null; + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); //Previous proxy @@ -933,17 +1031,32 @@ public class CoreConfigSingboxService if (prevNode is not null && prevNode.ConfigType != EConfigType.Custom) { - var prevOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(prevNode, prevOutbound); prevOutboundTag = $"prev-{Global.ProxyTag}"; - prevOutbound.tag = prevOutboundTag; - singboxConfig.outbounds.Add(prevOutbound); + var prevServer = await GenServer(prevNode); + prevServer.tag = prevOutboundTag; + if (prevServer is Endpoints4Sbox endpoint) + { + singboxConfig.endpoints ??= new(); + singboxConfig.endpoints.Add(endpoint); + } + else if (prevServer is Outbound4Sbox outboundPrev) + { + singboxConfig.outbounds.Add(outboundPrev); + } } - var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag); + var nextServer = await GenChainOutbounds(subItem, outbound, prevOutboundTag); - if (nextOutbound is not null) + if (nextServer is not null) { - singboxConfig.outbounds.Insert(0, nextOutbound); + if (nextServer is Endpoints4Sbox endpoint) + { + singboxConfig.endpoints ??= new(); + singboxConfig.endpoints.Insert(0, endpoint); + } + else if (nextServer is Outbound4Sbox outboundNext) + { + singboxConfig.outbounds.Insert(0, outboundNext); + } } } catch (Exception ex) @@ -966,11 +1079,13 @@ public class CoreConfigSingboxService } var resultOutbounds = new List(); + var resultEndpoints = new List(); // For endpoints var prevOutbounds = new List(); // Separate list for prev outbounds + var prevEndpoints = new List(); // Separate list for prev endpoints var proxyTags = new List(); // For selector and urltest outbounds // Cache for chain proxies to avoid duplicate generation - var nextProxyCache = new Dictionary(); + var nextProxyCache = new Dictionary(); var prevProxyTags = new Dictionary(); // Map from profile name to tag int prevIndex = 0; // Index for prev outbounds @@ -982,19 +1097,18 @@ public class CoreConfigSingboxService // Handle proxy chain string? prevTag = null; - var currentOutbound = JsonUtils.Deserialize(txtOutbound); - var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null); - if (nextOutbound != null) + var currentServer = await GenServer(node); + var nextServer = nextProxyCache.GetValueOrDefault(node.Subid, null); + if (nextServer != null) { - nextOutbound = JsonUtils.DeepCopy(nextOutbound); + nextServer = JsonUtils.DeepCopy(nextServer); } var subItem = await AppHandler.Instance.GetSubItem(node.Subid); // current proxy - await GenOutbound(node, currentOutbound); - currentOutbound.tag = $"{Global.ProxyTag}-{index}"; - proxyTags.Add(currentOutbound.tag); + currentServer.tag = $"{Global.ProxyTag}-{index}"; + proxyTags.Add(currentServer.tag); if (!node.Subid.IsNullOrEmpty()) { @@ -1017,18 +1131,32 @@ public class CoreConfigSingboxService prevProxyTags[node.Subid] = prevTag; } - nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound); + nextServer = await GenChainOutbounds(subItem, currentServer, prevTag, nextServer); if (!nextProxyCache.ContainsKey(node.Subid)) { - nextProxyCache[node.Subid] = nextOutbound; + nextProxyCache[node.Subid] = nextServer; } } - if (nextOutbound is not null) + if (nextServer is not null) { - resultOutbounds.Add(nextOutbound); + if (nextServer is Endpoints4Sbox nextEndpoint) + { + resultEndpoints.Add(nextEndpoint); + } + else if (nextServer is Outbound4Sbox nextOutbound) + { + resultOutbounds.Add(nextOutbound); + } + } + if (currentServer is Endpoints4Sbox currentEndpoint) + { + resultEndpoints.Add(currentEndpoint); + } + else if (currentServer is Outbound4Sbox currentOutbound) + { + resultOutbounds.Add(currentOutbound); } - resultOutbounds.Add(currentOutbound); } // Add urltest outbound (auto selection based on latency) @@ -1061,6 +1189,9 @@ public class CoreConfigSingboxService resultOutbounds.AddRange(prevOutbounds); resultOutbounds.AddRange(singboxConfig.outbounds); singboxConfig.outbounds = resultOutbounds; + singboxConfig.endpoints ??= new List(); + resultEndpoints.AddRange(singboxConfig.endpoints); + singboxConfig.endpoints = resultEndpoints; } catch (Exception ex) { @@ -1082,7 +1213,7 @@ public class CoreConfigSingboxService /// /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. /// - private async Task GenChainOutbounds(SubItem subItem, Outbound4Sbox outbound, string? prevOutboundTag, Outbound4Sbox? nextOutbound = null) + private async Task GenChainOutbounds(SubItem subItem, BaseServer4Sbox outbound, string? prevOutboundTag, BaseServer4Sbox? nextOutbound = null) { try { @@ -1098,11 +1229,7 @@ public class CoreConfigSingboxService if (nextNode is not null && nextNode.ConfigType != EConfigType.Custom) { - if (nextOutbound == null) - { - nextOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(nextNode, nextOutbound); - } + nextOutbound ??= await GenServer(nextNode); nextOutbound.tag = outbound.tag; outbound.tag = $"mid-{outbound.tag}"; @@ -1426,13 +1553,24 @@ public class CoreConfigSingboxService return Global.ProxyTag; } - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(node, outbound); - outbound.tag = Global.ProxyTag + node.IndexId.ToString(); - singboxConfig.outbounds.Add(outbound); + var server = await GenServer(node); + if (server is null) + { + return Global.ProxyTag; + } - return outbound.tag; + server.tag = Global.ProxyTag + node.IndexId.ToString(); + if (server is Endpoints4Sbox endpoint) + { + singboxConfig.endpoints ??= new(); + singboxConfig.endpoints.Add(endpoint); + } + else if (server is Outbound4Sbox outbound) + { + singboxConfig.outbounds.Add(outbound); + } + + return server.tag; } private async Task GenDns(ProfileItem? node, SingboxConfig singboxConfig)