diff --git a/v2rayN/ServiceLib/Common/JsonUtils.cs b/v2rayN/ServiceLib/Common/JsonUtils.cs index 773ba79c..6954e124 100644 --- a/v2rayN/ServiceLib/Common/JsonUtils.cs +++ b/v2rayN/ServiceLib/Common/JsonUtils.cs @@ -128,5 +128,8 @@ public class JsonUtils /// /// /// - public static JsonNode? SerializeToNode(object? obj) => JsonSerializer.SerializeToNode(obj); + public static JsonNode? SerializeToNode(object? obj, JsonSerializerOptions? options = null) + { + return JsonSerializer.SerializeToNode(obj, options); + } } diff --git a/v2rayN/ServiceLib/Common/Utils.cs b/v2rayN/ServiceLib/Common/Utils.cs index c5b670cf..b0d3827b 100644 --- a/v2rayN/ServiceLib/Common/Utils.cs +++ b/v2rayN/ServiceLib/Common/Utils.cs @@ -435,11 +435,11 @@ public class Utils return false; } - public static int GetFreePort(int defaultPort = 9090) + public static int GetFreePort(int defaultPort = 0) { try { - if (!Utils.PortInUse(defaultPort)) + if (!(defaultPort == 0 || Utils.PortInUse(defaultPort))) { return defaultPort; } diff --git a/v2rayN/ServiceLib/Enums/EConfigType.cs b/v2rayN/ServiceLib/Enums/EConfigType.cs index f56d0e0f..6698f962 100644 --- a/v2rayN/ServiceLib/Enums/EConfigType.cs +++ b/v2rayN/ServiceLib/Enums/EConfigType.cs @@ -11,5 +11,6 @@ public enum EConfigType Hysteria2 = 7, TUIC = 8, WireGuard = 9, - HTTP = 10 + HTTP = 10, + Anytls = 11 } diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 5592fa2e..180bd90c 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -167,7 +167,8 @@ public class Global { EConfigType.Trojan, "trojan://" }, { EConfigType.Hysteria2, "hysteria2://" }, { EConfigType.TUIC, "tuic://" }, - { EConfigType.WireGuard, "wireguard://" } + { EConfigType.WireGuard, "wireguard://" }, + { EConfigType.Anytls, "anytls://" } }; public static readonly Dictionary ProtocolTypes = new() @@ -180,7 +181,8 @@ public class Global { EConfigType.Trojan, "trojan" }, { EConfigType.Hysteria2, "hysteria2" }, { EConfigType.TUIC, "tuic" }, - { EConfigType.WireGuard, "wireguard" } + { EConfigType.WireGuard, "wireguard" }, + { EConfigType.Anytls, "anytls" } }; public static readonly List VmessSecurities = @@ -347,25 +349,42 @@ public class Global public static readonly List SingboxDomainStrategy4Out = [ - "ipv4_only", + "", + "ipv4_only", "prefer_ipv4", "prefer_ipv6", - "ipv6_only", - "" + "ipv6_only" ]; - public static readonly List DomainDNSAddress = + public static readonly List DomainDirectDNSAddress = [ - "223.5.5.5", - "223.6.6.6", + "https://dns.alidns.com/dns-query", + "https://doh.pub/dns-query", + "223.5.5.5", + "119.29.29.29", "localhost" ]; - public static readonly List SingboxDomainDNSAddress = + public static readonly List DomainRemoteDNSAddress = + [ + "https://cloudflare-dns.com/dns-query", + "https://dns.cloudflare.com/dns-query", + "https://dns.google/dns-query", + "https://doh.dns.sb/dns-query", + "https://doh.opendns.com/dns-query", + "https://common.dot.dns.yandex.net", + "8.8.8.8", + "1.1.1.1", + "185.222.222.222", + "208.67.222.222", + "77.88.8.8" + ]; + + public static readonly List DomainPureIPDNSAddress = [ "223.5.5.5", - "223.6.6.6", - "dhcp://auto" + "119.29.29.29", + "localhost" ]; public static readonly List Languages = @@ -535,5 +554,30 @@ public class Global BlockTag ]; + public static readonly Dictionary> PredefinedHosts = new() + { + { "dns.google", new List { "8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844" } }, + { "dns.alidns.com", new List { "223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1" } }, + { "one.one.one.one", new List { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } }, + { "1dot1dot1dot1.cloudflare-dns.com", new List { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } }, + { "cloudflare-dns.com", new List { "104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9" } }, + { "dns.cloudflare.com", new List { "104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5" } }, + { "dot.pub", new List { "1.12.12.12", "120.53.53.53" } }, + { "dns.quad9.net", new List { "9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9" } }, + { "dns.yandex.net", new List { "77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff" } }, + { "dns.sb", new List { "185.222.222.222", "2a09::" } }, + { "dns.umbrella.com", new List { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } }, + { "dns.sse.cisco.com", new List { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } }, + { "engage.cloudflareclient.com", new List { "162.159.192.1", "2606:4700:d0::a29f:c001" } } + }; + + public static readonly List ExpectedIPs = + [ + "geoip:cn", + "geoip:ir", + "geoip:ru", + "" + ]; + #endregion const } diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index c08ca28e..9040d70c 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -112,6 +112,11 @@ public class ConfigHandler config.ConstItem ??= new ConstItem(); + if (config.SimpleDNSItem == null) + { + InitBuiltinSimpleDNS(config); + } + config.SpeedTestItem ??= new(); if (config.SpeedTestItem.SpeedTestTimeout < 10) { @@ -261,6 +266,7 @@ public class ConfigHandler EConfigType.Hysteria2 => await AddHysteria2Server(config, item), EConfigType.TUIC => await AddTuicServer(config, item), EConfigType.WireGuard => await AddWireguardServer(config, item), + EConfigType.Anytls => await AddAnytlsServer(config, item), _ => -1, }; return ret; @@ -785,6 +791,35 @@ public class ConfigHandler return 0; } + /// + /// Add or edit a Anytls server + /// Validates and processes Anytls-specific settings + /// + /// Current configuration + /// Anytls profile to add + /// Whether to save to file + /// 0 if successful, -1 if failed + public static async Task AddAnytlsServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.Anytls; + profileItem.CoreType = ECoreType.sing_box; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Network = string.Empty; + if (profileItem.StreamSecurity.IsNullOrEmpty()) + { + profileItem.StreamSecurity = Global.StreamSecurity; + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + await AddServerCommon(config, profileItem, toFile); + return 0; + } + /// /// Sort the server list by the specified column /// Updates the sort order in the profile extension data @@ -1294,6 +1329,7 @@ public class ConfigHandler EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false), EConfigType.TUIC => await AddTuicServer(config, profileItem, false), EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false), + EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false), _ => -1, }; @@ -2152,6 +2188,38 @@ public class ConfigHandler #endregion DNS + #region Simple DNS + + public static int InitBuiltinSimpleDNS(Config config) + { + config.SimpleDNSItem = new SimpleDNSItem() + { + UseSystemHosts = false, + AddCommonHosts = true, + FakeIP = false, + BlockBindingQuery = true, + DirectDNS = Global.DomainDirectDNSAddress.FirstOrDefault(), + RemoteDNS = Global.DomainRemoteDNSAddress.FirstOrDefault(), + SingboxOutboundsResolveDNS = Global.DomainDirectDNSAddress.FirstOrDefault(), + SingboxFinalResolveDNS = Global.DomainPureIPDNSAddress.FirstOrDefault() + }; + return 0; + } + + public static async Task GetExternalSimpleDNSItem(string url) + { + var downloadHandle = new DownloadService(); + var templateContent = await downloadHandle.TryDownloadString(url, true, ""); + if (templateContent.IsNullOrEmpty()) + return null; + var template = JsonUtils.Deserialize(templateContent); + if (template == null) + return null; + return template; + } + + #endregion Simple DNS + #region Regional Presets /// @@ -2173,6 +2241,8 @@ public class ConfigHandler await SQLiteHelper.Instance.DeleteAllAsync(); await InitBuiltinDNS(config); + InitBuiltinSimpleDNS(config); + return true; case EPresetType.Russia: @@ -2183,6 +2253,8 @@ public class ConfigHandler await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json")); await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json")); + config.SimpleDNSItem = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[1] + "simple_dns.json"); + return true; case EPresetType.Iran: @@ -2193,6 +2265,8 @@ public class ConfigHandler await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json")); await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json")); + config.SimpleDNSItem = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[2] + "simple_dns.json"); + return true; } diff --git a/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayN/ServiceLib/Handler/CoreHandler.cs index 92c30171..f66d3700 100644 --- a/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -101,7 +101,7 @@ public class CoreHandler public async Task LoadCoreConfigSpeedtest(List selecteds) { - var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC) ? ECoreType.sing_box : ECoreType.Xray; + var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) ? ECoreType.sing_box : ECoreType.Xray; var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); var configPath = Utils.GetBinConfigPath(fileName); var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType); diff --git a/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs new file mode 100644 index 00000000..3f1d40dc --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs @@ -0,0 +1,49 @@ +using static QRCoder.PayloadGenerator; + +namespace ServiceLib.Handler.Fmt; +public class AnytlsFmt : BaseFmt +{ + public static ProfileItem? Resolve(string str, out string msg) + { + msg = ResUI.ConfigurationFormatIncorrect; + + var parsedUrl = Utils.TryUri(str); + if (parsedUrl == null) + { + return null; + } + + ProfileItem item = new() + { + ConfigType = EConfigType.Anytls, + Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), + Address = parsedUrl.IdnHost, + Port = parsedUrl.Port, + }; + var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); + item.Id = rawUserInfo; + + var query = Utils.ParseQueryString(parsedUrl.Query); + _ = ResolveStdTransport(query, ref item); + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; + } + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var pw = item.Id; + var dicQuery = new Dictionary(); + _ = GetStdTransport(item, Global.None, ref dicQuery); + + return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark); + } +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs index 3e8ab2ae..814d753d 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs @@ -18,6 +18,7 @@ public class FmtHandler EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item), EConfigType.TUIC => TuicFmt.ToUri(item), EConfigType.WireGuard => WireguardFmt.ToUri(item), + EConfigType.Anytls => AnytlsFmt.ToUri(item), _ => null, }; @@ -75,6 +76,10 @@ public class FmtHandler { return WireguardFmt.Resolve(str, out msg); } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Anytls])) + { + return AnytlsFmt.Resolve(str, out msg); + } else { msg = ResUI.NonvmessOrssProtocol; diff --git a/v2rayN/ServiceLib/Models/Config.cs b/v2rayN/ServiceLib/Models/Config.cs index 15996608..91b49b29 100644 --- a/v2rayN/ServiceLib/Models/Config.cs +++ b/v2rayN/ServiceLib/Models/Config.cs @@ -48,6 +48,7 @@ public class Config public List Inbound { get; set; } public List GlobalHotkeys { get; set; } public List CoreTypeItem { get; set; } + public SimpleDNSItem SimpleDNSItem { get; set; } #endregion other entities } diff --git a/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayN/ServiceLib/Models/ConfigItems.cs index c6254b9f..998b49f1 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -253,3 +253,21 @@ public class WindowSizeItem public int Width { get; set; } public int Height { get; set; } } + +[Serializable] +public class SimpleDNSItem +{ + public bool? UseSystemHosts { get; set; } + public bool? AddCommonHosts { get; set; } + public bool? FakeIP { get; set; } + public bool? BlockBindingQuery { get; set; } + public string? DirectDNS { get; set; } + public string? RemoteDNS { get; set; } + public string? SingboxOutboundsResolveDNS { get; set; } + public string? SingboxFinalResolveDNS { get; set; } + public string? RayStrategy4Freedom { get; set; } + public string? SingboxStrategy4Direct { get; set; } + public string? SingboxStrategy4Proxy { get; set; } + public string? Hosts { get; set; } + public string? DirectExpectedIPs { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/DNSItem.cs b/v2rayN/ServiceLib/Models/DNSItem.cs index 59ab9c9b..9474d906 100644 --- a/v2rayN/ServiceLib/Models/DNSItem.cs +++ b/v2rayN/ServiceLib/Models/DNSItem.cs @@ -9,7 +9,7 @@ public class DNSItem public string Id { get; set; } public string Remarks { get; set; } - public bool Enabled { get; set; } = true; + public bool Enabled { get; set; } = false; public ECoreType CoreType { get; set; } public bool UseSystemHosts { get; set; } public string? NormalDNS { get; set; } diff --git a/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayN/ServiceLib/Models/SingboxConfig.cs index 9ea1157d..cbaf77e6 100644 --- a/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace ServiceLib.Models; public class SingboxConfig @@ -6,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; } } @@ -29,14 +32,15 @@ public class Dns4Sbox public bool? independent_cache { get; set; } public bool? reverse_mapping { get; set; } public string? client_subnet { get; set; } - public Fakeip4Sbox? fakeip { get; set; } } public class Route4Sbox { + public Rule4Sbox? default_domain_resolver { get; set; } // or string public bool? auto_detect_interface { get; set; } public List rules { get; set; } public List? rule_set { get; set; } + public string? final { get; set; } } [Serializable] @@ -49,6 +53,7 @@ public class Rule4Sbox public string? mode { get; set; } public bool? ip_is_private { get; set; } public string? client_subnet { get; set; } + public int? rewrite_ttl { get; set; } public bool? invert { get; set; } public string? clash_mode { get; set; } public List? inbound { get; set; } @@ -67,6 +72,27 @@ public class Rule4Sbox public List? process_name { get; set; } public List? rule_set { get; set; } public List? rules { get; set; } + public string? action { get; set; } + public string? strategy { get; set; } + public List? sniffer { get; set; } + public string? rcode { get; set; } + public List? query_type { get; set; } + public List? answer { get; set; } + public List? ns { get; set; } + public List? extra { get; set; } + public string? method { get; set; } + public bool? no_drop { get; set; } + public bool? source_ip_is_private { get; set; } + public bool? ip_accept_any { get; set; } + public int? source_port { get; set; } + public List? source_port_range { get; set; } + public List? network_type { get; set; } + public bool? network_is_expensive { get; set; } + public bool? network_is_constrained { get; set; } + public List? wifi_ssid { get; set; } + public List? wifi_bssid { get; set; } + public bool? rule_set_ip_cidr_match_source { get; set; } + public bool? rule_set_ip_cidr_accept_empty { get; set; } } [Serializable] @@ -76,7 +102,6 @@ public class Inbound4Sbox public string tag { get; set; } public string listen { get; set; } public int? listen_port { get; set; } - public string? domain_strategy { get; set; } public string interface_name { get; set; } public List? address { get; set; } public int? mtu { get; set; } @@ -84,8 +109,6 @@ public class Inbound4Sbox public bool? strict_route { get; set; } public bool? endpoint_independent_nat { get; set; } public string? stack { get; set; } - public bool? sniff { get; set; } - public bool? sniff_override_destination { get; set; } public List users { get; set; } } @@ -95,10 +118,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; } @@ -113,7 +134,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; } @@ -121,21 +141,36 @@ 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 class Endpoints4Sbox : BaseServer4Sbox +{ + public bool? system { get; set; } + public string? name { get; set; } + public int? mtu { get; set; } + public List address { get; set; } + public string private_key { get; set; } + public int? listen_port { get; set; } + public string? udp_timeout { get; set; } + public int? workers { get; set; } + public List peers { get; set; } +} + +public class Peer4Sbox +{ + public string address { get; set; } + public int port { get; set; } + public string public_key { get; set; } + public string? pre_shared_key { get; set; } + public List allowed_ips { get; set; } + public int? persistent_keepalive_interval { get; set; } + public List reserved { get; set; } +} + public class Tls4Sbox { public bool enabled { get; set; } @@ -191,15 +226,25 @@ public class HyObfs4Sbox public string? password { get; set; } } -public class Server4Sbox +public class Server4Sbox : BaseServer4Sbox { - public string? tag { get; set; } + public string? inet4_range { get; set; } + public string? inet6_range { get; set; } + public string? client_subnet { get; set; } + public string? server { get; set; } + public new string? domain_resolver { get; set; } + [JsonPropertyName("interface")] public string? Interface { get; set; } + public int? server_port { get; set; } + public string? path { get; set; } + public Headers4Sbox? headers { get; set; } + // public List? path { get; set; } // hosts + public Dictionary>? predefined { get; set; } + // Deprecated public string? address { get; set; } public string? address_resolver { get; set; } public string? address_strategy { get; set; } public string? strategy { get; set; } - public string? detour { get; set; } - public string? client_subnet { get; set; } + // Deprecated End } public class Experimental4Sbox @@ -229,13 +274,6 @@ public class Stats4Sbox public List? users { get; set; } } -public class Fakeip4Sbox -{ - public bool enabled { get; set; } - public string inet4_range { get; set; } - public string inet6_range { get; set; } -} - public class CacheFile4Sbox { public bool enabled { get; set; } @@ -254,3 +292,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/Models/V2rayConfig.cs b/v2rayN/ServiceLib/Models/V2rayConfig.cs index 2e5ed4b4..664ab923 100644 --- a/v2rayN/ServiceLib/Models/V2rayConfig.cs +++ b/v2rayN/ServiceLib/Models/V2rayConfig.cs @@ -5,7 +5,7 @@ namespace ServiceLib.Models; public class V2rayConfig { public Log4Ray log { get; set; } - public object dns { get; set; } + public Dns4Ray dns { get; set; } public List inbounds { get; set; } public List outbounds { get; set; } public Routing4Ray routing { get; set; } @@ -203,7 +203,8 @@ public class Response4Ray public class Dns4Ray { - public List servers { get; set; } + public Dictionary>? hosts { get; set; } + public List servers { get; set; } } public class DnsServer4Ray @@ -211,6 +212,8 @@ public class DnsServer4Ray public string? address { get; set; } public List? domains { get; set; } public bool? skipFallback { get; set; } + public List? expectedIPs { get; set; } + public List? unexpectedIPs { get; set; } } public class Routing4Ray diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 6167fd75..be6fea63 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -654,6 +654,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Add [Anytls] Configuration 的本地化字符串。 + /// + public static string menuAddAnytlsServer { + get { + return ResourceManager.GetString("menuAddAnytlsServer", resourceCulture); + } + } + /// /// 查找类似 Add a custom configuration Configuration 的本地化字符串。 /// @@ -2202,6 +2211,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Add Common DNS Hosts 的本地化字符串。 + /// + public static string TbAddCommonDNSHosts { + get { + return ResourceManager.GetString("TbAddCommonDNSHosts", resourceCulture); + } + } + /// /// 查找类似 Address 的本地化字符串。 /// @@ -2238,6 +2256,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Apply to Proxy Domains Only 的本地化字符串。 + /// + public static string TbApplyProxyDomainsOnly { + get { + return ResourceManager.GetString("TbApplyProxyDomainsOnly", resourceCulture); + } + } + /// /// 查找类似 Auto refresh 的本地化字符串。 /// @@ -2265,6 +2292,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Block SVCB and HTTPS Queries 的本地化字符串。 + /// + public static string TbBlockSVCBHTTPSQueries { + get { + return ResourceManager.GetString("TbBlockSVCBHTTPSQueries", resourceCulture); + } + } + /// /// 查找类似 Browse 的本地化字符串。 /// @@ -2319,6 +2355,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Enable Custom DNS 的本地化字符串。 + /// + public static string TbCustomDNSEnable { + get { + return ResourceManager.GetString("TbCustomDNSEnable", resourceCulture); + } + } + + /// + /// 查找类似 Custom DNS Enabled, This Page's Settings Invalid 的本地化字符串。 + /// + public static string TbCustomDNSEnabledPageInvalid { + get { + return ResourceManager.GetString("TbCustomDNSEnabledPageInvalid", resourceCulture); + } + } + /// /// 查找类似 Display GUI 的本地化字符串。 /// @@ -2337,6 +2391,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 DNS Hosts: ("domain1 ip1 ip2" per line) 的本地化字符串。 + /// + public static string TbDNSHostsConfig { + get { + return ResourceManager.GetString("TbDNSHostsConfig", resourceCulture); + } + } + /// /// 查找类似 Supports DNS Object; Click to view documentation 的本地化字符串。 /// @@ -2382,6 +2445,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Domestic DNS 的本地化字符串。 + /// + public static string TbDomesticDNS { + get { + return ResourceManager.GetString("TbDomesticDNS", resourceCulture); + } + } + /// /// 查找类似 Edit 的本地化字符串。 /// @@ -2400,6 +2472,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 FakeIP 的本地化字符串。 + /// + public static string TbFakeIP { + get { + return ResourceManager.GetString("TbFakeIP", resourceCulture); + } + } + /// /// 查找类似 Fingerprint 的本地化字符串。 /// @@ -2589,6 +2670,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Prevent DNS Leaks 的本地化字符串。 + /// + public static string TbPreventDNSLeaks { + get { + return ResourceManager.GetString("TbPreventDNSLeaks", resourceCulture); + } + } + /// /// 查找类似 Private Key 的本地化字符串。 /// @@ -2625,6 +2715,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Remote DNS 的本地化字符串。 + /// + public static string TbRemoteDNS { + get { + return ResourceManager.GetString("TbRemoteDNS", resourceCulture); + } + } + /// /// 查找类似 Camouflage domain(host) 的本地化字符串。 /// @@ -2733,6 +2832,69 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 sing-box Direct Resolution Strategy 的本地化字符串。 + /// + public static string TbSBDirectResolveStrategy { + get { + return ResourceManager.GetString("TbSBDirectResolveStrategy", resourceCulture); + } + } + + /// + /// 查找类似 The sing-box DoH resolution server can be overwritten 的本地化字符串。 + /// + public static string TbSBDoHOverride { + get { + return ResourceManager.GetString("TbSBDoHOverride", resourceCulture); + } + } + + /// + /// 查找类似 sing-box DoH Resolver Server 的本地化字符串。 + /// + public static string TbSBDoHResolverServer { + get { + return ResourceManager.GetString("TbSBDoHResolverServer", resourceCulture); + } + } + + /// + /// 查找类似 Fallback DNS Resolution, Suggest IP 的本地化字符串。 + /// + public static string TbSBFallbackDNSResolve { + get { + return ResourceManager.GetString("TbSBFallbackDNSResolve", resourceCulture); + } + } + + /// + /// 查找类似 Resolve Outbound Domains 的本地化字符串。 + /// + public static string TbSBOutboundDomainResolve { + get { + return ResourceManager.GetString("TbSBOutboundDomainResolve", resourceCulture); + } + } + + /// + /// 查找类似 Outbound DNS Resolution (sing-box) 的本地化字符串。 + /// + public static string TbSBOutboundsResolverDNS { + get { + return ResourceManager.GetString("TbSBOutboundsResolverDNS", resourceCulture); + } + } + + /// + /// 查找类似 sing-box Remote Resolution Strategy 的本地化字符串。 + /// + public static string TbSBRemoteResolveStrategy { + get { + return ResourceManager.GetString("TbSBRemoteResolveStrategy", resourceCulture); + } + } + /// /// 查找类似 Encryption method (security) 的本地化字符串。 /// @@ -3678,6 +3840,33 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Validate Regional Domain IPs 的本地化字符串。 + /// + public static string TbValidateDirectExpectedIPs { + get { + return ResourceManager.GetString("TbValidateDirectExpectedIPs", resourceCulture); + } + } + + /// + /// 查找类似 When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs 的本地化字符串。 + /// + public static string TbValidateDirectExpectedIPsDesc { + get { + return ResourceManager.GetString("TbValidateDirectExpectedIPsDesc", resourceCulture); + } + } + + /// + /// 查找类似 xray Freedom Resolution Strategy 的本地化字符串。 + /// + public static string TbXrayFreedomResolveStrategy { + get { + return ResourceManager.GetString("TbXrayFreedomResolveStrategy", resourceCulture); + } + } + /// /// 查找类似 The delay: {0} ms, {1} 的本地化字符串。 /// @@ -3687,6 +3876,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Advanced DNS Settings 的本地化字符串。 + /// + public static string ThAdvancedDNSSettings { + get { + return ResourceManager.GetString("ThAdvancedDNSSettings", resourceCulture); + } + } + + /// + /// 查找类似 Basic DNS Settings 的本地化字符串。 + /// + public static string ThBasicDNSSettings { + get { + return ResourceManager.GetString("ThBasicDNSSettings", resourceCulture); + } + } + /// /// 查找类似 Active 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 3bc07d9f..e772a8e6 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1395,4 +1395,73 @@ Can fill in the configuration remarks, please make sure it exist and are unique + + Add [Anytls] Configuration + + + Remote DNS + + + Domestic DNS + + + Outbound DNS Resolution (sing-box) + + + Resolve Outbound Domains + + + sing-box DoH Resolver Server + + + Fallback DNS Resolution, Suggest IP + + + xray Freedom Resolution Strategy + + + sing-box Direct Resolution Strategy + + + sing-box Remote Resolution Strategy + + + Add Common DNS Hosts + + + The sing-box DoH resolution server can be overwritten + + + FakeIP + + + Block SVCB and HTTPS Queries + + + Prevent DNS Leaks + + + DNS Hosts: ("domain1 ip1 ip2" per line) + + + Apply to Proxy Domains Only + + + Basic DNS Settings + + + Advanced DNS Settings + + + Validate Regional Domain IPs + + + When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs + + + Enable Custom DNS + + + Custom DNS Enabled, This Page's Settings Invalid + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 114e1a21..f161adcc 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1395,4 +1395,73 @@ Can fill in the configuration remarks, please make sure it exist and are unique + + Add [Anytls] Configuration + + + Remote DNS + + + Domestic DNS + + + Outbound DNS Resolution (sing-box) + + + Resolve Outbound Domains + + + sing-box DoH Resolver Server + + + Fallback DNS Resolution, Suggest IP + + + xray Freedom Resolution Strategy + + + sing-box Direct Resolution Strategy + + + sing-box Remote Resolution Strategy + + + Add Common DNS Hosts + + + The sing-box DoH resolution server can be overwritten + + + FakeIP + + + Block SVCB and HTTPS Queries + + + Prevent DNS Leaks + + + DNS Hosts: ("domain1 ip1 ip2" per line) + + + Apply to Proxy Domains Only + + + Basic DNS Settings + + + Advanced DNS Settings + + + Validate Regional Domain IPs + + + When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs + + + Enable Custom DNS + + + Custom DNS Enabled, This Page's Settings Invalid + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 4be88f75..ffb5b1cd 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1395,4 +1395,73 @@ Can fill in the configuration remarks, please make sure it exist and are unique + + Add [Anytls] Configuration + + + Remote DNS + + + Domestic DNS + + + Outbound DNS Resolution (sing-box) + + + Resolve Outbound Domains + + + sing-box DoH Resolver Server + + + Fallback DNS Resolution, Suggest IP + + + xray Freedom Resolution Strategy + + + sing-box Direct Resolution Strategy + + + sing-box Remote Resolution Strategy + + + Add Common DNS Hosts + + + The sing-box DoH resolution server can be overwritten + + + FakeIP + + + Block SVCB and HTTPS Queries + + + Prevent DNS Leaks + + + DNS Hosts: ("domain1 ip1 ip2" per line) + + + Apply to Proxy Domains Only + + + Basic DNS Settings + + + Advanced DNS Settings + + + Validate Regional Domain IPs + + + When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs + + + Enable Custom DNS + + + Custom DNS Enabled, This Page's Settings Invalid + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index b987c364..63e31f51 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1,17 +1,17 @@ - @@ -807,9 +807,6 @@ Вверх (U) - - Переместить вверх/вниз - Фильтр, поддерживает regex @@ -969,6 +966,9 @@ URL для тестирования скорости + + Переместить вверх/вниз + PublicKey @@ -1395,4 +1395,73 @@ Can fill in the configuration remarks, please make sure it exist and are unique + + Add [Anytls] Configuration + + + Remote DNS + + + Domestic DNS + + + Outbound DNS Resolution (sing-box) + + + Resolve Outbound Domains + + + sing-box DoH Resolver Server + + + Fallback DNS Resolution, Suggest IP + + + xray Freedom Resolution Strategy + + + sing-box Direct Resolution Strategy + + + sing-box Remote Resolution Strategy + + + Add Common DNS Hosts + + + The sing-box DoH resolution server can be overwritten + + + FakeIP + + + Block SVCB and HTTPS Queries + + + Prevent DNS Leaks + + + DNS Hosts: ("domain1 ip1 ip2" per line) + + + Apply to Proxy Domains Only + + + Basic DNS Settings + + + Advanced DNS Settings + + + Validate Regional Domain IPs + + + When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs + + + Enable Custom DNS + + + Custom DNS Enabled, This Page's Settings Invalid + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 27240b80..e8863c73 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1392,4 +1392,73 @@ 可以填写配置文件别名,请确保存在并唯一 + + 添加 [Anytls] 配置文件 + + + 远程 DNS + + + 直连 DNS + + + 出站 DNS 解析(sing-box) + + + 解析出站域名 + + + sing-box DoH 解析服务器 + + + 兜底解析其他 DNS 域名,建议设为 ip + + + xray freedom 解析策略 + + + sing-box 直连解析策略 + + + sing-box 远程解析策略 + + + 添加常用 DNS Hosts + + + 开启后可覆盖 sing-box DoH 解析服务器 + + + FakeIP + + + 阻止 SVCB 和 HTTPS 查询 + + + 避免 DNS 泄漏 + + + DNS Hosts:(“域名1 ip1 ip2” 一行一个) + + + 仅对代理域名生效 + + + DNS 基础设置 + + + DNS 进阶设置 + + + 校验相应地区域名 IP + + + 配置后,会对相应地区域名(如 geosite:cn)的返回 IP 进行校验,仅返回期望 IP + + + 启用自定义 DNS + + + 自定义 DNS 已启用,此页面配置将无效 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 4ea33211..4b7a44a5 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1392,4 +1392,73 @@ 可以填寫設定檔別名,請確保存在並唯一 + + 新增 [Anytls] 設定檔 + + + Remote DNS + + + Domestic DNS + + + Outbound DNS Resolution (sing-box) + + + Resolve Outbound Domains + + + sing-box DoH Resolver Server + + + Fallback DNS Resolution, Suggest IP + + + xray Freedom Resolution Strategy + + + sing-box Direct Resolution Strategy + + + sing-box Remote Resolution Strategy + + + Add Common DNS Hosts + + + The sing-box DoH resolution server can be overwritten + + + FakeIP + + + Block SVCB and HTTPS Queries + + + Prevent DNS Leaks + + + DNS Hosts: ("domain1 ip1 ip2" per line) + + + Apply to Proxy Domains Only + + + Basic DNS Settings + + + Advanced DNS Settings + + + Validate Regional Domain IPs + + + When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs + + + Enable Custom DNS + + + Custom DNS Enabled, This Page's Settings Invalid + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig b/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig index f88422a1..b07fd72c 100644 --- a/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig +++ b/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig @@ -1,4 +1,4 @@ -{ +{ "log": { "level": "debug", "timestamp": true @@ -14,22 +14,10 @@ { "type": "direct", "tag": "direct" - }, - { - "type": "block", - "tag": "block" - }, - { - "tag": "dns_out", - "type": "dns" } ], "route": { "rules": [ - { - "protocol": [ "dns" ], - "outbound": "dns_out" - } ] } } \ No newline at end of file diff --git a/v2rayN/ServiceLib/Sample/dns_singbox_normal b/v2rayN/ServiceLib/Sample/dns_singbox_normal index 0921fe64..b32b439c 100644 --- a/v2rayN/ServiceLib/Sample/dns_singbox_normal +++ b/v2rayN/ServiceLib/Sample/dns_singbox_normal @@ -2,28 +2,33 @@ "servers": [ { "tag": "remote", - "address": "tcp://8.8.8.8", - "strategy": "prefer_ipv4", + "type": "tcp", + "server": "8.8.8.8", "detour": "proxy" }, { "tag": "local", - "address": "223.5.5.5", - "strategy": "prefer_ipv4", - "detour": "direct" - }, - { - "tag": "block", - "address": "rcode://success" + "type": "udp", + "server": "223.5.5.5" } ], "rules": [ + { + "domain_suffix": [ + "googleapis.cn", + "gstatic.com" + ], + "server": "remote", + "strategy": "prefer_ipv4" + }, { "rule_set": [ "geosite-cn" ], - "server": "local" + "server": "local", + "strategy": "prefer_ipv4" } ], - "final": "remote" + "final": "remote", + "strategy": "prefer_ipv4" } diff --git a/v2rayN/ServiceLib/Sample/tun_singbox_dns b/v2rayN/ServiceLib/Sample/tun_singbox_dns index d8ca9808..3dd55eef 100644 --- a/v2rayN/ServiceLib/Sample/tun_singbox_dns +++ b/v2rayN/ServiceLib/Sample/tun_singbox_dns @@ -2,29 +2,33 @@ "servers": [ { "tag": "remote", - "address": "tcp://8.8.8.8", - "strategy": "prefer_ipv4", + "type": "tcp", + "server": "8.8.8.8", "detour": "proxy" }, { "tag": "local", - "address": "223.5.5.5", - "strategy": "prefer_ipv4", - "detour": "direct" - }, - { - "tag": "block", - "address": "rcode://success" + "type": "udp", + "server": "223.5.5.5" } ], "rules": [ { - "rule_set": [ - "geosite-cn", - "geosite-geolocation-cn" + "domain_suffix": [ + "googleapis.cn", + "gstatic.com" ], - "server": "local" + "server": "remote", + "strategy": "prefer_ipv4" + }, + { + "rule_set": [ + "geosite-cn" + ], + "server": "local", + "strategy": "prefer_ipv4" } ], - "final": "remote" -} + "final": "remote", + "strategy": "prefer_ipv4" +} \ No newline at end of file diff --git a/v2rayN/ServiceLib/Sample/tun_singbox_rules b/v2rayN/ServiceLib/Sample/tun_singbox_rules index df1dc4ec..a4276134 100644 --- a/v2rayN/ServiceLib/Sample/tun_singbox_rules +++ b/v2rayN/ServiceLib/Sample/tun_singbox_rules @@ -8,13 +8,13 @@ 139, 5353 ], - "outbound": "block" + "action": "reject" }, { "ip_cidr": [ "224.0.0.0/3", "ff00::/8" ], - "outbound": "block" + "action": "reject" } ] \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index fdc03058..452e1976 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -1,6 +1,11 @@ +using System.Collections.Generic; using System.Data; using System.Net; using System.Net.NetworkInformation; +using System.Reactive; +using System.Text; +using DynamicData; +using ServiceLib.Models; namespace ServiceLib.Services.CoreConfig; @@ -53,13 +58,24 @@ 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); await GenRouting(singboxConfig); - await GenDns(node, singboxConfig); + await GenDns(singboxConfig); await GenExperimental(singboxConfig); @@ -202,21 +218,42 @@ 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); } - await GenDnsDomains(null, singboxConfig, null); + var rawDNSItem = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + if (rawDNSItem != null && rawDNSItem.Enabled == true) + { + await GenDnsDomainsCompatible(singboxConfig, rawDNSItem); + } + else + { + await GenDnsDomains(singboxConfig, _config.SimpleDNSItem); + } //var dnsServer = singboxConfig.dns?.servers.FirstOrDefault(); //if (dnsServer != null) //{ @@ -275,9 +312,28 @@ 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); + var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + if (item != null && item.Enabled == true) + { + await GenDnsDomainsCompatible(singboxConfig, item); + } + else + { + await GenDnsDomains(singboxConfig, _config.SimpleDNSItem); + } singboxConfig.route.rules.Clear(); singboxConfig.inbounds.Clear(); @@ -379,7 +435,7 @@ public class CoreConfigSingboxService } await GenOutboundsList(proxyProfiles, singboxConfig); - await GenDns(null, singboxConfig); + await GenDns(singboxConfig); await ConvertGeo2Ruleset(singboxConfig); ret.Success = true; @@ -534,15 +590,6 @@ public class CoreConfigSingboxService singboxConfig.inbounds.Add(inbound); inbound.listen_port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); - inbound.sniff = _config.Inbound.First().SniffingEnabled; - inbound.sniff_override_destination = _config.Inbound.First().RouteOnly ? false : _config.Inbound.First().SniffingEnabled; - inbound.domain_strategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox; - - var routing = await ConfigHandler.GetDefaultRouting(_config); - if (routing.DomainStrategy4Singbox.IsNotEmpty()) - { - inbound.domain_strategy = routing.DomainStrategy4Singbox; - } if (_config.Inbound.First().SecondLocalPortEnabled) { @@ -587,8 +634,6 @@ public class CoreConfigSingboxService tunInbound.mtu = _config.TunModeItem.Mtu; tunInbound.strict_route = _config.TunModeItem.StrictRoute; tunInbound.stack = _config.TunModeItem.Stack; - tunInbound.sniff = _config.Inbound.First().SniffingEnabled; - //tunInbound.sniff_override_destination = _config.inbound.First().routeOnly ? false : _config.inbound.First().sniffingEnabled; if (_config.TunModeItem.EnableIPv6Address == false) { tunInbound.address = ["172.18.0.1/30"]; @@ -725,13 +770,9 @@ public class CoreConfigSingboxService outbound.congestion_control = node.HeaderType; break; } - case EConfigType.WireGuard: + case EConfigType.Anytls: { - 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(); + outbound.password = node.Id; break; } } @@ -747,6 +788,65 @@ public class CoreConfigSingboxService return 0; } + private async Task GenEndpoint(ProfileItem node, Endpoints4Sbox endpoint) + { + try + { + endpoint.address = Utils.String2List(node.RequestHost); + endpoint.type = Global.ProtocolTypes[node.ConfigType]; + + 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(); + endpoint.peers = new() { peer }; + 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 @@ -913,7 +1013,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 @@ -922,17 +1023,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) @@ -955,11 +1071,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 @@ -971,19 +1089,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()) { @@ -1006,18 +1123,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) @@ -1050,6 +1181,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) { @@ -1071,7 +1205,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 { @@ -1087,11 +1221,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}"; @@ -1110,7 +1240,13 @@ public class CoreConfigSingboxService { try { - var dnsOutbound = "dns_out"; + singboxConfig.route.final = Global.ProxyTag; + var item = _config.SimpleDNSItem; + singboxConfig.route.default_domain_resolver = new() + { + server = "outbound_resolver", + strategy = item.SingboxStrategy4Direct + }; if (_config.TunModeItem.EnableTun) { @@ -1126,7 +1262,7 @@ public class CoreConfigSingboxService singboxConfig.route.rules.Add(new() { port = new() { 53 }, - outbound = dnsOutbound, + action = "hijack-dns", process_name = lstDnsExe }); @@ -1137,13 +1273,25 @@ public class CoreConfigSingboxService }); } - if (!_config.Inbound.First().SniffingEnabled) + if (_config.Inbound.First().SniffingEnabled) { singboxConfig.route.rules.Add(new() { - port = [53], - network = ["udp"], - outbound = dnsOutbound + action = "sniff" + }); + singboxConfig.route.rules.Add(new() + { + protocol = new() { "dns" }, + action = "hijack-dns" + }); + } + else + { + singboxConfig.route.rules.Add(new() + { + port = new() { 53 }, + network = new() { "udp" }, + action = "hijack-dns" }); } @@ -1158,18 +1306,47 @@ public class CoreConfigSingboxService clash_mode = ERuleMode.Global.ToString() }); + var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox; + var defaultRouting = await ConfigHandler.GetDefaultRouting(_config); + if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty()) + { + domainStrategy = defaultRouting.DomainStrategy4Singbox; + } + var resolveRule = new Rule4Sbox + { + action = "resolve", + strategy = domainStrategy + }; + if (_config.RoutingBasicItem.DomainStrategy == "IPOnDemand") + { + singboxConfig.route.rules.Add(resolveRule); + } + var routing = await ConfigHandler.GetDefaultRouting(_config); + var ipRules = new List(); if (routing != null) { var rules = JsonUtils.Deserialize>(routing.RuleSet); - foreach (var item in rules ?? []) + foreach (var item1 in rules ?? []) { - if (item.Enabled) + if (item1.Enabled) { - await GenRoutingUserRule(item, singboxConfig); + await GenRoutingUserRule(item1, singboxConfig); + if (item1.Ip != null && item1.Ip.Count > 0) + { + ipRules.Add(item1); + } } } } + if (_config.RoutingBasicItem.DomainStrategy == "IPIfNonMatch") + { + singboxConfig.route.rules.Add(resolveRule); + foreach (var item2 in ipRules) + { + await GenRoutingUserRule(item2, singboxConfig); + } + } } catch (Exception ex) { @@ -1217,10 +1394,15 @@ public class CoreConfigSingboxService item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig); var rules = singboxConfig.route.rules; - var rule = new Rule4Sbox() + var rule = new Rule4Sbox(); + if (item.OutboundTag == "block") { - outbound = item.OutboundTag, - }; + rule.action = "reject"; + } + else + { + rule.outbound = item.OutboundTag; + } if (item.Port.IsNotEmpty()) { @@ -1344,24 +1526,28 @@ public class CoreConfigSingboxService { return false; } - else if (address.StartsWith("geoip:!")) - { - return false; - } else if (address.Equals("geoip:private")) { rule.ip_is_private = true; } else if (address.StartsWith("geoip:")) { - if (rule.geoip is null) - { rule.geoip = new(); } + rule.geoip ??= new(); rule.geoip?.Add(address.Substring(6)); } + else if (address.Equals("geoip:!private")) + { + rule.ip_is_private = false; + } + else if (address.StartsWith("geoip:!")) + { + rule.geoip ??= new(); + rule.geoip?.Add(address.Substring(6)); + rule.invert = true; + } else { - if (rule.ip_cidr is null) - { rule.ip_cidr = new(); } + rule.ip_cidr ??= new(); rule.ip_cidr?.Add(address); } return true; @@ -1381,16 +1567,294 @@ 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) + private async Task GenDns(SingboxConfig singboxConfig) + { + try + { + var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + if (item != null && item.Enabled == true) + { + return await GenDnsCompatible(singboxConfig); + } + + var simpleDNSItem = _config.SimpleDNSItem; + await GenDnsServers(singboxConfig, simpleDNSItem); + await GenDnsRules(singboxConfig, simpleDNSItem); + + singboxConfig.dns ??= new Dns4Sbox(); + singboxConfig.dns.independent_cache = true; + + var routing = await ConfigHandler.GetDefaultRouting(_config); + var useDirectDns = false; + if (routing != null) + { + var rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; + + useDirectDns = rules?.LastOrDefault() is { } lastRule && + lastRule.OutboundTag == Global.DirectTag && + (lastRule.Port == "0-65535" || + lastRule.Network == "tcp,udp" || + lastRule.Ip?.Contains("0.0.0.0/0") == true); + } + singboxConfig.dns.final = useDirectDns ? "dns_direct" : "dns_remote"; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private async Task GenDnsServers(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) + { + var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem); + + var directDns = ParseDnsAddress(simpleDNSItem.DirectDNS); + directDns.tag = "dns_direct"; + directDns.domain_resolver = "final_resolver"; + + var remoteDns = ParseDnsAddress(simpleDNSItem.RemoteDNS); + remoteDns.tag = "dns_remote"; + remoteDns.detour = Global.ProxyTag; + remoteDns.domain_resolver = "final_resolver"; + + var resolverDns = ParseDnsAddress(simpleDNSItem.SingboxOutboundsResolveDNS); + resolverDns.tag = "outbound_resolver"; + resolverDns.domain_resolver = "final_resolver"; + + var hostsDns = new Server4Sbox + { + tag = "dns_hosts", + type = "hosts", + }; + if (simpleDNSItem.AddCommonHosts == true) + { + hostsDns.predefined = Global.PredefinedHosts; + } + var userHostsMap = simpleDNSItem.Hosts? + .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) + .Where(line => !string.IsNullOrWhiteSpace(line)) + .Where(line => line.Contains(' ')) + .ToDictionary( + line => + { + var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + return parts[0]; + }, + line => + { + var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + var values = parts.Skip(1).ToList(); + return values; + } + ); + + if (userHostsMap != null) + { + foreach (var kvp in userHostsMap) + { + hostsDns.predefined[kvp.Key] = kvp.Value; + } + } + + foreach (var host in hostsDns.predefined) + { + if (finalDns.server == host.Key) + { + finalDns.domain_resolver = "dns_hosts"; + } + if (remoteDns.server == host.Key) + { + remoteDns.domain_resolver = "dns_hosts"; + } + if (resolverDns.server == host.Key) + { + resolverDns.domain_resolver = "dns_hosts"; + } + if (directDns.server == host.Key) + { + directDns.domain_resolver = "dns_hosts"; + } + } + + singboxConfig.dns ??= new Dns4Sbox(); + singboxConfig.dns.servers ??= new List(); + singboxConfig.dns.servers.Add(remoteDns); + singboxConfig.dns.servers.Add(directDns); + singboxConfig.dns.servers.Add(resolverDns); + singboxConfig.dns.servers.Add(hostsDns); + + // fake ip + if (simpleDNSItem.FakeIP == true) + { + var fakeip = new Server4Sbox + { + tag = "dns-fake", + type = "fakeip", + inet4_range = "198.18.0.0/15", + inet6_range = "fc00::/18", + }; + singboxConfig.dns.servers.Add(fakeip); + } + + return await Task.FromResult(0); + } + + private async Task GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem) + { + var finalDns = ParseDnsAddress(simpleDNSItem.SingboxFinalResolveDNS); + finalDns.tag = "final_resolver"; + singboxConfig.dns ??= new Dns4Sbox(); + singboxConfig.dns.servers ??= new List(); + singboxConfig.dns.servers.Add(finalDns); + return await Task.FromResult(finalDns); + } + + private async Task GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) + { + singboxConfig.dns ??= new Dns4Sbox(); + singboxConfig.dns.rules ??= new List(); + + singboxConfig.dns.rules.AddRange(new[] + { + new Rule4Sbox { ip_accept_any = true, server = "dns_hosts" }, + new Rule4Sbox + { + server = "dns_remote", + strategy = simpleDNSItem.SingboxStrategy4Proxy.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Proxy, + clash_mode = ERuleMode.Global.ToString() + }, + new Rule4Sbox + { + server = "dns_direct", + strategy = simpleDNSItem.SingboxStrategy4Direct.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Direct, + clash_mode = ERuleMode.Direct.ToString() + }, + new Rule4Sbox + { + query_type = new List { 64, 65 }, + action = "predefined", + rcode = "NOTIMP" + } + }); + + var routing = await ConfigHandler.GetDefaultRouting(_config); + if (routing == null) + return 0; + + var rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; + var expectedIPCidr = new List(); + var expectedIPsRegions = new List(); + var regionNames = new HashSet(); + + if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs)) + { + var ipItems = simpleDNSItem.DirectExpectedIPs + .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => s.Trim()) + .Where(s => !string.IsNullOrEmpty(s)) + .ToList(); + + foreach (var ip in ipItems) + { + if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase)) + { + var region = ip["geoip:".Length..]; + if (!string.IsNullOrEmpty(region)) + { + expectedIPsRegions.Add(region); + regionNames.Add(region); + regionNames.Add($"geolocation-{region}"); + regionNames.Add($"tld-{region}"); + } + } + else + { + expectedIPCidr.Add(ip); + } + } + } + + foreach (var item in rules) + { + if (!item.Enabled || item.Domain is null || item.Domain.Count == 0) + { + continue; + } + + var rule = new Rule4Sbox(); + var validDomains = item.Domain.Count(it => ParseV2Domain(it, rule)); + if (validDomains <= 0) + { + continue; + } + + if (item.OutboundTag == Global.DirectTag) + { + rule.server = "dns_direct"; + rule.strategy = string.IsNullOrEmpty(simpleDNSItem.SingboxStrategy4Direct) ? null : simpleDNSItem.SingboxStrategy4Direct; + + if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0) + { + var geositeSet = new HashSet(rule.geosite); + if (regionNames.Intersect(geositeSet).Any()) + { + if (expectedIPsRegions.Count > 0) + { + rule.geoip = expectedIPsRegions; + } + if (expectedIPCidr.Count > 0) + { + rule.ip_cidr = expectedIPCidr; + } + } + } + } + else if (item.OutboundTag == Global.ProxyTag) + { + if (simpleDNSItem.FakeIP == true) + { + var rule4Fake = JsonUtils.DeepCopy(rule); + rule4Fake.server = "dns-fake"; + singboxConfig.dns.rules.Add(rule4Fake); + } + rule.server = "dns_remote"; + rule.strategy = string.IsNullOrEmpty(simpleDNSItem.SingboxStrategy4Proxy) ? null : simpleDNSItem.SingboxStrategy4Proxy; + } + else if (item.OutboundTag == Global.BlockTag) + { + rule.action = "predefined"; + rule.rcode = "NOERROR"; + rule.answer = new List { "A" }; + } + + singboxConfig.dns.rules.Add(rule); + } + + return 0; + } + + private async Task GenDnsCompatible(SingboxConfig singboxConfig) { try { @@ -1412,7 +1876,14 @@ public class CoreConfigSingboxService } singboxConfig.dns = dns4Sbox; - await GenDnsDomains(node, singboxConfig, item); + if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty()) + { + await GenDnsDomainsCompatible(singboxConfig, item); + } + else + { + await GenDnsDomainsLegacyCompatible(singboxConfig, item); + } } catch (Exception ex) { @@ -1421,7 +1892,58 @@ public class CoreConfigSingboxService return 0; } - private async Task GenDnsDomains(ProfileItem? node, SingboxConfig singboxConfig, DNSItem? dNSItem) + private async Task GenDnsDomainsCompatible(SingboxConfig singboxConfig, DNSItem? dNSItem) + { + var dns4Sbox = singboxConfig.dns ?? new(); + dns4Sbox.servers ??= []; + dns4Sbox.rules ??= []; + + var tag = "outbound_resolver"; + var localDnsAddress = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress; + + if (localDnsAddress.StartsWith("tag://")) + { + tag = localDnsAddress.Substring(6); + + var localDnsTag = "local_local"; + + dns4Sbox.servers.Add(new() + { + tag = localDnsTag, + type = "local" + }); + + dns4Sbox.rules.Insert(0, new() + { + server = localDnsTag, + clash_mode = ERuleMode.Direct.ToString() + }); + } + else + { + var localDnsServer = ParseDnsAddress(localDnsAddress); + localDnsServer.tag = tag; + + dns4Sbox.servers.Add(localDnsServer); + + dns4Sbox.rules.Insert(0, new() + { + server = tag, + clash_mode = ERuleMode.Direct.ToString() + }); + } + + dns4Sbox.rules.Insert(0, new() + { + server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote", + clash_mode = ERuleMode.Global.ToString() + }); + + singboxConfig.dns = dns4Sbox; + return await Task.FromResult(0); + } + + private async Task GenDnsDomainsLegacyCompatible(SingboxConfig singboxConfig, DNSItem? dNSItem) { var dns4Sbox = singboxConfig.dns ?? new(); dns4Sbox.servers ??= []; @@ -1431,7 +1953,7 @@ public class CoreConfigSingboxService dns4Sbox.servers.Add(new() { tag = tag, - address = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress, + address = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress, detour = Global.DirectTag, strategy = string.IsNullOrEmpty(dNSItem?.DomainStrategy4Freedom) ? null : dNSItem?.DomainStrategy4Freedom, }); @@ -1460,20 +1982,102 @@ public class CoreConfigSingboxService }); } - //Tun2SocksAddress - if (_config.TunModeItem.EnableTun && node?.ConfigType == EConfigType.SOCKS && Utils.IsDomain(node?.Sni)) - { - dns4Sbox.rules.Insert(0, new() - { - server = tag, - domain = [node?.Sni] - }); - } - singboxConfig.dns = dns4Sbox; return await Task.FromResult(0); } + private static Server4Sbox? ParseDnsAddress(string address) + { + var addressFirst = address?.Split(address.Contains(',') ? ',' : ';').FirstOrDefault()?.Trim(); + if (string.IsNullOrEmpty(addressFirst)) + { + return null; + } + + var server = new Server4Sbox(); + + if (addressFirst is "local" or "localhost") + { + server.type = "local"; + return server; + } + + if (addressFirst.StartsWith("dhcp://", StringComparison.OrdinalIgnoreCase)) + { + var interface_name = addressFirst.Substring(7); + server.type = "dhcp"; + server.Interface = interface_name == "auto" ? null : interface_name; + return server; + } + + if (!addressFirst.Contains("://")) + { + // udp dns + server.type = "udp"; + server.server = addressFirst; + return server; + } + + try + { + var protocolEndIndex = addressFirst.IndexOf("://", StringComparison.Ordinal); + server.type = addressFirst.Substring(0, protocolEndIndex).ToLower(); + + var uri = new Uri(addressFirst); + server.server = uri.Host; + + if (!uri.IsDefaultPort) + { + server.server_port = uri.Port; + } + + if ((server.type == "https" || server.type == "h3") && !string.IsNullOrEmpty(uri.AbsolutePath) && uri.AbsolutePath != "/") + { + server.path = uri.AbsolutePath; + } + } + catch (UriFormatException) + { + var protocolEndIndex = addressFirst.IndexOf("://", StringComparison.Ordinal); + if (protocolEndIndex > 0) + { + server.type = addressFirst.Substring(0, protocolEndIndex).ToLower(); + var remaining = addressFirst.Substring(protocolEndIndex + 3); + + var portIndex = remaining.IndexOf(':'); + var pathIndex = remaining.IndexOf('/'); + + if (portIndex > 0) + { + server.server = remaining.Substring(0, portIndex); + var portPart = pathIndex > portIndex + ? remaining.Substring(portIndex + 1, pathIndex - portIndex - 1) + : remaining.Substring(portIndex + 1); + + if (int.TryParse(portPart, out var parsedPort)) + { + server.server_port = parsedPort; + } + } + else if (pathIndex > 0) + { + server.server = remaining.Substring(0, pathIndex); + } + else + { + server.server = remaining; + } + + if (pathIndex > 0 && (server.type == "https" || server.type == "h3")) + { + server.path = remaining.Substring(pathIndex); + } + } + } + + return server; + } + private async Task GenExperimental(SingboxConfig singboxConfig) { //if (_config.guiItem.enableStatistics) @@ -1491,7 +2095,8 @@ public class CoreConfigSingboxService singboxConfig.experimental.cache_file = new CacheFile4Sbox() { enabled = true, - path = Utils.GetBinPath("cache.db") + path = Utils.GetBinPath("cache.db"), + store_fakeip = _config.SimpleDNSItem.FakeIP == true }; } @@ -1512,13 +2117,15 @@ public class CoreConfigSingboxService //convert route geosite & geoip to ruleset foreach (var rule in singboxConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) { - rule.rule_set = rule?.geosite?.Select(t => $"{geosite}-{t}").ToList(); + rule.rule_set ??= new List(); + rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList()); rule.geosite = null; AddRuleSets(ruleSets, rule.rule_set); } foreach (var rule in singboxConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) { - rule.rule_set = rule?.geoip?.Select(t => $"{geoip}-{t}").ToList(); + rule.rule_set ??= new List(); + rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList()); rule.geoip = null; AddRuleSets(ruleSets, rule.rule_set); } @@ -1526,12 +2133,14 @@ public class CoreConfigSingboxService //convert dns geosite & geoip to ruleset foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) { - rule.rule_set = rule?.geosite?.Select(t => $"{geosite}-{t}").ToList(); + rule.rule_set ??= new List(); + rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList()); rule.geosite = null; } foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) { - rule.rule_set = rule?.geoip?.Select(t => $"{geoip}-{t}").ToList(); + rule.rule_set ??= new List(); + rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList()); rule.geoip = null; } foreach (var dnsRule in singboxConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? []) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index 9be1acf0..ec570b03 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -1,6 +1,8 @@ using System.Net; using System.Net.NetworkInformation; +using System.Text.Json; using System.Text.Json.Nodes; +using System.Text.Json.Serialization; namespace ServiceLib.Services.CoreConfig; @@ -1132,6 +1134,264 @@ public class CoreConfigV2rayService } private async Task GenDns(ProfileItem? node, V2rayConfig v2rayConfig) + { + try + { + var item = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); + if (item != null && item.Enabled == true) + { + return await GenDnsCompatible(node, v2rayConfig); + } + var simpleDNSItem = _config.SimpleDNSItem; + var domainStrategy4Freedom = simpleDNSItem?.RayStrategy4Freedom; + + //Outbound Freedom domainStrategy + if (domainStrategy4Freedom.IsNotEmpty()) + { + var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); + if (outbound != null) + { + outbound.settings = new() + { + domainStrategy = domainStrategy4Freedom, + userLevel = 0 + }; + } + } + + await GenDnsServers(node, v2rayConfig, simpleDNSItem); + await GenDnsHosts(v2rayConfig, simpleDNSItem); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private async Task GenDnsServers(ProfileItem? node, V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem) + { + static List ParseDnsAddresses(string? dnsInput, string defaultAddress) + { + var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';') + .Select(addr => addr.Trim()) + .Where(addr => !string.IsNullOrEmpty(addr)) + .Select(addr => addr.StartsWith("dhcp", StringComparison.OrdinalIgnoreCase) ? "localhost" : addr) + .Distinct() + .ToList() ?? new List { defaultAddress }; + return addresses.Count > 0 ? addresses : new List { defaultAddress }; + } + + static object CreateDnsServer(string dnsAddress, List domains, List? expectedIPs = null) + { + var dnsServer = new DnsServer4Ray + { + address = dnsAddress, + skipFallback = true, + domains = domains.Count > 0 ? domains : null, + expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null + }; + return JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }); + } + + var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.FirstOrDefault()); + var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.FirstOrDefault()); + + var directDomainList = new List(); + var directGeositeList = new List(); + var proxyDomainList = new List(); + var proxyGeositeList = new List(); + var expectedDomainList = new List(); + var expectedIPs = new List(); + var regionNames = new HashSet(); + + if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs)) + { + expectedIPs = simpleDNSItem.DirectExpectedIPs + .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => s.Trim()) + .Where(s => !string.IsNullOrEmpty(s)) + .ToList(); + + foreach (var ip in expectedIPs) + { + if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase)) + { + var region = ip["geoip:".Length..]; + if (!string.IsNullOrEmpty(region)) + { + regionNames.Add($"geosite:{region}"); + regionNames.Add($"geosite:geolocation-{region}"); + regionNames.Add($"geosite:tld-{region}"); + } + } + } + } + + var routing = await ConfigHandler.GetDefaultRouting(_config); + List? rules = null; + if (routing != null) + { + rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; + foreach (var item in rules) + { + if (!item.Enabled || item.Domain is null || item.Domain.Count == 0) + { + continue; + } + + foreach (var domain in item.Domain) + { + if (domain.StartsWith('#')) + continue; + var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ","); + + if (item.OutboundTag == Global.DirectTag) + { + if (normalizedDomain.StartsWith("geosite:")) + { + (regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain); + } + else + { + directDomainList.Add(normalizedDomain); + } + } + else if (item.OutboundTag == Global.ProxyTag) + { + if (normalizedDomain.StartsWith("geosite:")) + { + proxyGeositeList.Add(normalizedDomain); + } + else + { + proxyDomainList.Add(normalizedDomain); + } + } + } + } + } + + if (Utils.IsDomain(node?.Address)) + { + directDomainList.Add(node.Address); + } + + if (node?.Subid is not null) + { + var subItem = await AppHandler.Instance.GetSubItem(node.Subid); + if (subItem is not null) + { + foreach (var profile in new[] { subItem.PrevProfile, subItem.NextProfile }) + { + var profileNode = await AppHandler.Instance.GetProfileItemViaRemarks(profile); + if (profileNode is not null && + profileNode.ConfigType is not (EConfigType.Custom or EConfigType.Hysteria2 or EConfigType.TUIC) && + Utils.IsDomain(profileNode.Address)) + { + directDomainList.Add(profileNode.Address); + } + } + } + } + + v2rayConfig.dns ??= new Dns4Ray(); + v2rayConfig.dns.servers ??= new List(); + + void AddDnsServers(List dnsAddresses, List domains, List? expectedIPs = null) + { + if (domains.Count > 0) + { + foreach (var dnsAddress in dnsAddresses) + { + v2rayConfig.dns.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs)); + } + } + } + + AddDnsServers(remoteDNSAddress, proxyDomainList); + AddDnsServers(directDNSAddress, directDomainList); + AddDnsServers(remoteDNSAddress, proxyGeositeList); + AddDnsServers(directDNSAddress, directGeositeList); + AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs); + + var useDirectDns = rules?.LastOrDefault() is { } lastRule && + lastRule.OutboundTag == Global.DirectTag && + (lastRule.Port == "0-65535" || + lastRule.Network == "tcp,udp" || + lastRule.Ip?.Contains("0.0.0.0/0") == true); + + var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress; + v2rayConfig.dns.servers.AddRange(defaultDnsServers); + + return 0; + } + + private async Task GenDnsHosts(V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem) + { + if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty()) + { + return await Task.FromResult(0); + } + v2rayConfig.dns ??= new Dns4Ray(); + v2rayConfig.dns.hosts ??= new Dictionary>(); + if (simpleDNSItem.AddCommonHosts == true) + { + v2rayConfig.dns.hosts = Global.PredefinedHosts; + } + + if (simpleDNSItem.UseSystemHosts == true) + { + var systemHosts = Utils.GetSystemHosts(); + if (systemHosts.Count > 0) + { + var normalHost = v2rayConfig.dns.hosts; + if (normalHost != null) + { + foreach (var host in systemHosts) + { + if (normalHost[host.Key] != null) + { + continue; + } + normalHost[host.Key] = new List { host.Value }; + } + } + } + } + + var userHostsMap = simpleDNSItem.Hosts? + .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) + .Where(line => !string.IsNullOrWhiteSpace(line)) + .Where(line => line.Contains(' ')) + .ToDictionary( + line => + { + var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + return parts[0]; + }, + line => + { + var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + var values = parts.Skip(1).ToList(); + return values; + } + ); + + if (userHostsMap != null) + { + foreach (var kvp in userHostsMap) + { + v2rayConfig.dns.hosts[kvp.Key] = kvp.Value; + } + } + return await Task.FromResult(0); + } + + private async Task GenDnsCompatible(ProfileItem? node, V2rayConfig v2rayConfig) { try { @@ -1174,22 +1434,33 @@ public class CoreConfigV2rayService var systemHosts = Utils.GetSystemHosts(); if (systemHosts.Count > 0) { - var normalHost = obj["hosts"]; - if (normalHost != null) + var normalHost1 = obj["hosts"]; + if (normalHost1 != null) { foreach (var host in systemHosts) { - if (normalHost[host.Key] != null) + if (normalHost1[host.Key] != null) continue; - normalHost[host.Key] = host.Value; + normalHost1[host.Key] = host.Value; } } } } + var normalHost = obj["hosts"]; + if (normalHost != null) + { + foreach (var hostProp in normalHost.AsObject().ToList()) + { + if (hostProp.Value is JsonValue value && value.TryGetValue(out var ip)) + { + normalHost[hostProp.Key] = new JsonArray(ip); + } + } + } - await GenDnsDomains(node, obj, item); + await GenDnsDomainsCompatible(node, obj, item); - v2rayConfig.dns = obj; + v2rayConfig.dns = JsonUtils.Deserialize(obj.ToJsonString()); } catch (Exception ex) { @@ -1198,7 +1469,7 @@ public class CoreConfigV2rayService return 0; } - private async Task GenDnsDomains(ProfileItem? node, JsonNode dns, DNSItem? dNSItem) + private async Task GenDnsDomainsCompatible(ProfileItem? node, JsonNode dns, DNSItem? dNSItem) { if (node == null) { @@ -1241,7 +1512,7 @@ public class CoreConfigV2rayService { var dnsServer = new DnsServer4Ray() { - address = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress, + address = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress, skipFallback = true, domains = domainList }; @@ -1349,7 +1620,8 @@ public class CoreConfigV2rayService if (prevNode is not null && prevNode.ConfigType != EConfigType.Custom && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC) + && prevNode.ConfigType != EConfigType.TUIC + && prevNode.ConfigType != EConfigType.Anytls) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -1424,7 +1696,8 @@ public class CoreConfigV2rayService if (prevNode is not null && prevNode.ConfigType != EConfigType.Custom && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC) + && prevNode.ConfigType != EConfigType.TUIC + && prevNode.ConfigType != EConfigType.Anytls) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -1493,7 +1766,8 @@ public class CoreConfigV2rayService if (nextNode is not null && nextNode.ConfigType != EConfigType.Custom && nextNode.ConfigType != EConfigType.Hysteria2 - && nextNode.ConfigType != EConfigType.TUIC) + && nextNode.ConfigType != EConfigType.TUIC + && nextNode.ConfigType != EConfigType.Anytls) { if (nextOutbound == null) { diff --git a/v2rayN/ServiceLib/Services/SpeedtestService.cs b/v2rayN/ServiceLib/Services/SpeedtestService.cs index 998cedcc..9c97a217 100644 --- a/v2rayN/ServiceLib/Services/SpeedtestService.cs +++ b/v2rayN/ServiceLib/Services/SpeedtestService.cs @@ -358,8 +358,8 @@ public class SpeedtestService private List> GetTestBatchItem(List lstSelected, int pageSize) { List> lstTest = new(); - var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC)).ToList(); - var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC).ToList(); + var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)).ToList(); + var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls).ToList(); for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++) { diff --git a/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs index 1d6829b8..e6e985ca 100644 --- a/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs @@ -6,39 +6,52 @@ namespace ServiceLib.ViewModels; public class DNSSettingViewModel : MyReactiveObject { - [Reactive] public bool UseSystemHosts { get; set; } - [Reactive] public string DomainStrategy4Freedom { get; set; } - [Reactive] public string DomainDNSAddress { get; set; } - [Reactive] public string NormalDNS { get; set; } + [Reactive] public bool? UseSystemHosts { get; set; } + [Reactive] public bool? AddCommonHosts { get; set; } + [Reactive] public bool? FakeIP { get; set; } + [Reactive] public bool? BlockBindingQuery { get; set; } + [Reactive] public string? DirectDNS { get; set; } + [Reactive] public string? RemoteDNS { get; set; } + [Reactive] public string? SingboxOutboundsResolveDNS { get; set; } + [Reactive] public string? SingboxFinalResolveDNS { get; set; } + [Reactive] public string? RayStrategy4Freedom { get; set; } + [Reactive] public string? SingboxStrategy4Direct { get; set; } + [Reactive] public string? SingboxStrategy4Proxy { get; set; } + [Reactive] public string? Hosts { get; set; } + [Reactive] public string? DirectExpectedIPs { get; set; } - [Reactive] public string DomainStrategy4Freedom2 { get; set; } - [Reactive] public string DomainDNSAddress2 { get; set; } - [Reactive] public string NormalDNS2 { get; set; } - [Reactive] public string TunDNS2 { get; set; } + [Reactive] public bool UseSystemHostsCompatible { get; set; } + [Reactive] public string DomainStrategy4FreedomCompatible { get; set; } + [Reactive] public string DomainDNSAddressCompatible { get; set; } + [Reactive] public string NormalDNSCompatible { get; set; } + + [Reactive] public string DomainStrategy4Freedom2Compatible { get; set; } + [Reactive] public string DomainDNSAddress2Compatible { get; set; } + [Reactive] public string NormalDNS2Compatible { get; set; } + [Reactive] public string TunDNS2Compatible { get; set; } + [Reactive] public bool RayCustomDNSEnableCompatible { get; set; } + [Reactive] public bool SBCustomDNSEnableCompatible { get; set; } public ReactiveCommand SaveCmd { get; } - public ReactiveCommand ImportDefConfig4V2rayCmd { get; } - public ReactiveCommand ImportDefConfig4SingboxCmd { get; } + public ReactiveCommand ImportDefConfig4V2rayCompatibleCmd { get; } + public ReactiveCommand ImportDefConfig4SingboxCompatibleCmd { get; } public DNSSettingViewModel(Func>? updateView) { _config = AppHandler.Instance.Config; _updateView = updateView; - SaveCmd = ReactiveCommand.CreateFromTask(async () => - { - await SaveSettingAsync(); - }); + SaveCmd = ReactiveCommand.CreateFromTask(SaveSettingAsync); - ImportDefConfig4V2rayCmd = ReactiveCommand.CreateFromTask(async () => + ImportDefConfig4V2rayCompatibleCmd = ReactiveCommand.CreateFromTask(async () => { - NormalDNS = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName); + NormalDNSCompatible = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName); await Task.CompletedTask; }); - ImportDefConfig4SingboxCmd = ReactiveCommand.CreateFromTask(async () => + ImportDefConfig4SingboxCompatibleCmd = ReactiveCommand.CreateFromTask(async () => { - NormalDNS2 = EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName); - TunDNS2 = EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName); + NormalDNS2Compatible = EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName); + TunDNS2Compatible = EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName); await Task.CompletedTask; }); @@ -47,48 +60,80 @@ public class DNSSettingViewModel : MyReactiveObject private async Task Init() { - var item = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); + _config = AppHandler.Instance.Config; + var item = _config.SimpleDNSItem; UseSystemHosts = item.UseSystemHosts; - DomainStrategy4Freedom = item?.DomainStrategy4Freedom ?? string.Empty; - DomainDNSAddress = item?.DomainDNSAddress ?? string.Empty; - NormalDNS = item?.NormalDNS ?? string.Empty; + AddCommonHosts = item.AddCommonHosts; + FakeIP = item.FakeIP; + BlockBindingQuery = item.BlockBindingQuery; + DirectDNS = item.DirectDNS; + RemoteDNS = item.RemoteDNS; + RayStrategy4Freedom = item.RayStrategy4Freedom; + SingboxOutboundsResolveDNS = item.SingboxOutboundsResolveDNS; + SingboxFinalResolveDNS = item.SingboxFinalResolveDNS; + SingboxStrategy4Direct = item.SingboxStrategy4Direct; + SingboxStrategy4Proxy = item.SingboxStrategy4Proxy; + Hosts = item.Hosts; + DirectExpectedIPs = item.DirectExpectedIPs; + + var item1 = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); + RayCustomDNSEnableCompatible = item1.Enabled; + UseSystemHostsCompatible = item1.UseSystemHosts; + DomainStrategy4FreedomCompatible = item1?.DomainStrategy4Freedom ?? string.Empty; + DomainDNSAddressCompatible = item1?.DomainDNSAddress ?? string.Empty; + NormalDNSCompatible = item1?.NormalDNS ?? string.Empty; var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); - DomainStrategy4Freedom2 = item2?.DomainStrategy4Freedom ?? string.Empty; - DomainDNSAddress2 = item2?.DomainDNSAddress ?? string.Empty; - NormalDNS2 = item2?.NormalDNS ?? string.Empty; - TunDNS2 = item2?.TunDNS ?? string.Empty; + SBCustomDNSEnableCompatible = item2.Enabled; + DomainStrategy4Freedom2Compatible = item2?.DomainStrategy4Freedom ?? string.Empty; + DomainDNSAddress2Compatible = item2?.DomainDNSAddress ?? string.Empty; + NormalDNS2Compatible = item2?.NormalDNS ?? string.Empty; + TunDNS2Compatible = item2?.TunDNS ?? string.Empty; } private async Task SaveSettingAsync() { - if (NormalDNS.IsNotEmpty()) + _config.SimpleDNSItem.UseSystemHosts = UseSystemHosts; + _config.SimpleDNSItem.AddCommonHosts = AddCommonHosts; + _config.SimpleDNSItem.FakeIP = FakeIP; + _config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery; + _config.SimpleDNSItem.DirectDNS = DirectDNS; + _config.SimpleDNSItem.RemoteDNS = RemoteDNS; + _config.SimpleDNSItem.RayStrategy4Freedom = RayStrategy4Freedom; + _config.SimpleDNSItem.SingboxOutboundsResolveDNS = SingboxOutboundsResolveDNS; + _config.SimpleDNSItem.SingboxFinalResolveDNS = SingboxFinalResolveDNS; + _config.SimpleDNSItem.SingboxStrategy4Direct = SingboxStrategy4Direct; + _config.SimpleDNSItem.SingboxStrategy4Proxy = SingboxStrategy4Proxy; + _config.SimpleDNSItem.Hosts = Hosts; + _config.SimpleDNSItem.DirectExpectedIPs = DirectExpectedIPs; + + if (NormalDNSCompatible.IsNotEmpty()) { - var obj = JsonUtils.ParseJson(NormalDNS); + var obj = JsonUtils.ParseJson(NormalDNSCompatible); if (obj != null && obj["servers"] != null) { } else { - if (NormalDNS.Contains('{') || NormalDNS.Contains('}')) + if (NormalDNSCompatible.Contains('{') || NormalDNSCompatible.Contains('}')) { NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); return; } } } - if (NormalDNS2.IsNotEmpty()) + if (NormalDNS2Compatible.IsNotEmpty()) { - var obj2 = JsonUtils.Deserialize(NormalDNS2); + var obj2 = JsonUtils.Deserialize(NormalDNS2Compatible); if (obj2 == null) { NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); return; } } - if (TunDNS2.IsNotEmpty()) + if (TunDNS2Compatible.IsNotEmpty()) { - var obj2 = JsonUtils.Deserialize(TunDNS2); + var obj2 = JsonUtils.Deserialize(TunDNS2Compatible); if (obj2 == null) { NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); @@ -96,21 +141,26 @@ public class DNSSettingViewModel : MyReactiveObject } } - var item = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); - item.DomainStrategy4Freedom = DomainStrategy4Freedom; - item.DomainDNSAddress = DomainDNSAddress; - item.UseSystemHosts = UseSystemHosts; - item.NormalDNS = NormalDNS; - await ConfigHandler.SaveDNSItems(_config, item); + var item1 = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); + item1.Enabled = RayCustomDNSEnableCompatible; + item1.DomainStrategy4Freedom = DomainStrategy4FreedomCompatible; + item1.DomainDNSAddress = DomainDNSAddressCompatible; + item1.UseSystemHosts = UseSystemHostsCompatible; + item1.NormalDNS = NormalDNSCompatible; + await ConfigHandler.SaveDNSItems(_config, item1); var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); - item2.DomainStrategy4Freedom = DomainStrategy4Freedom2; - item2.DomainDNSAddress = DomainDNSAddress2; - item2.NormalDNS = JsonUtils.Serialize(JsonUtils.ParseJson(NormalDNS2)); - item2.TunDNS = JsonUtils.Serialize(JsonUtils.ParseJson(TunDNS2)); + item2.Enabled = RayCustomDNSEnableCompatible; + item2.DomainStrategy4Freedom = DomainStrategy4Freedom2Compatible; + item2.DomainDNSAddress = DomainDNSAddress2Compatible; + item2.NormalDNS = JsonUtils.Serialize(JsonUtils.ParseJson(NormalDNS2Compatible)); + item2.TunDNS = JsonUtils.Serialize(JsonUtils.ParseJson(TunDNS2Compatible)); await ConfigHandler.SaveDNSItems(_config, item2); - NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); - _ = _updateView?.Invoke(EViewAction.CloseWindow, null); + await ConfigHandler.SaveConfig(_config); + if (_updateView != null) + { + await _updateView(EViewAction.CloseWindow, null); + } } } diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index 7bae19be..36e20a87 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -20,6 +20,7 @@ public class MainWindowViewModel : MyReactiveObject public ReactiveCommand AddHysteria2ServerCmd { get; } public ReactiveCommand AddTuicServerCmd { get; } public ReactiveCommand AddWireguardServerCmd { get; } + public ReactiveCommand AddAnytlsServerCmd { get; } public ReactiveCommand AddCustomServerCmd { get; } public ReactiveCommand AddServerViaClipboardCmd { get; } public ReactiveCommand AddServerViaScanCmd { get; } @@ -111,6 +112,10 @@ public class MainWindowViewModel : MyReactiveObject { await AddServerAsync(true, EConfigType.WireGuard); }); + AddAnytlsServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.Anytls); + }); AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.Custom); diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index ad30f985..78bd2595 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -533,6 +533,26 @@ HorizontalAlignment="Left" Watermark="1500" /> + + + + + gridTls.IsVisible = false; break; + + case EConfigType.Anytls: + gridAnytls.IsVisible = true; + cmbStreamSecurity.Items.Add(Global.StreamSecurityReality); + cmbCoreType.IsEnabled = false; + break; } cmbStreamSecurity.ItemsSource = lstStreamSecurity; @@ -167,6 +173,10 @@ public partial class AddServerWindow : WindowBase this.Bind(ViewModel, vm => vm.SelectedSource.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables); break; + + case EConfigType.Anytls: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables); + break; } this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.SelectedValue).DisposeWith(disposables); diff --git a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml index efdf4eda..23ebe9fb 100644 --- a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml @@ -2,6 +2,7 @@ x:Class="v2rayN.Desktop.Views.DNSSettingWindow" xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:ctrls="clr-namespace:v2rayN.Desktop.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" @@ -35,25 +36,298 @@ - - - + + + + - - - - - - -