DHR60 2025-07-22 19:21:30 +08:00 committed by GitHub
commit 456cf21eba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 773 additions and 155 deletions

View File

@ -435,11 +435,11 @@ public class Utils
return false; return false;
} }
public static int GetFreePort(int defaultPort = 9090) public static int GetFreePort(int defaultPort = 0)
{ {
try try
{ {
if (!Utils.PortInUse(defaultPort)) if (!(defaultPort == 0 || Utils.PortInUse(defaultPort)))
{ {
return defaultPort; return defaultPort;
} }

View File

@ -11,5 +11,6 @@ public enum EConfigType
Hysteria2 = 7, Hysteria2 = 7,
TUIC = 8, TUIC = 8,
WireGuard = 9, WireGuard = 9,
HTTP = 10 HTTP = 10,
Anytls = 11
} }

View File

@ -167,7 +167,8 @@ public class Global
{ EConfigType.Trojan, "trojan://" }, { EConfigType.Trojan, "trojan://" },
{ EConfigType.Hysteria2, "hysteria2://" }, { EConfigType.Hysteria2, "hysteria2://" },
{ EConfigType.TUIC, "tuic://" }, { EConfigType.TUIC, "tuic://" },
{ EConfigType.WireGuard, "wireguard://" } { EConfigType.WireGuard, "wireguard://" },
{ EConfigType.Anytls, "anytls://" }
}; };
public static readonly Dictionary<EConfigType, string> ProtocolTypes = new() public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
@ -180,7 +181,8 @@ public class Global
{ EConfigType.Trojan, "trojan" }, { EConfigType.Trojan, "trojan" },
{ EConfigType.Hysteria2, "hysteria2" }, { EConfigType.Hysteria2, "hysteria2" },
{ EConfigType.TUIC, "tuic" }, { EConfigType.TUIC, "tuic" },
{ EConfigType.WireGuard, "wireguard" } { EConfigType.WireGuard, "wireguard" },
{ EConfigType.Anytls, "anytls" }
}; };
public static readonly List<string> VmessSecurities = public static readonly List<string> VmessSecurities =

View File

@ -261,6 +261,7 @@ public class ConfigHandler
EConfigType.Hysteria2 => await AddHysteria2Server(config, item), EConfigType.Hysteria2 => await AddHysteria2Server(config, item),
EConfigType.TUIC => await AddTuicServer(config, item), EConfigType.TUIC => await AddTuicServer(config, item),
EConfigType.WireGuard => await AddWireguardServer(config, item), EConfigType.WireGuard => await AddWireguardServer(config, item),
EConfigType.Anytls => await AddAnytlsServer(config, item),
_ => -1, _ => -1,
}; };
return ret; return ret;
@ -785,6 +786,35 @@ public class ConfigHandler
return 0; return 0;
} }
/// <summary>
/// Add or edit a Anytls server
/// Validates and processes Anytls-specific settings
/// </summary>
/// <param name="config">Current configuration</param>
/// <param name="profileItem">Anytls profile to add</param>
/// <param name="toFile">Whether to save to file</param>
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> 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;
}
/// <summary> /// <summary>
/// Sort the server list by the specified column /// Sort the server list by the specified column
/// Updates the sort order in the profile extension data /// Updates the sort order in the profile extension data
@ -1294,6 +1324,7 @@ public class ConfigHandler
EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false), EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false),
EConfigType.TUIC => await AddTuicServer(config, profileItem, false), EConfigType.TUIC => await AddTuicServer(config, profileItem, false),
EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false), EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false),
EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false),
_ => -1, _ => -1,
}; };

View File

@ -101,7 +101,7 @@ public class CoreHandler
public async Task<int> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds) public async Task<int> LoadCoreConfigSpeedtest(List<ServerTestItem> 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 fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
var configPath = Utils.GetBinConfigPath(fileName); var configPath = Utils.GetBinConfigPath(fileName);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType); var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);

View File

@ -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<string, string>();
_ = GetStdTransport(item, Global.None, ref dicQuery);
return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark);
}
}

View File

@ -18,6 +18,7 @@ public class FmtHandler
EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item), EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item),
EConfigType.TUIC => TuicFmt.ToUri(item), EConfigType.TUIC => TuicFmt.ToUri(item),
EConfigType.WireGuard => WireguardFmt.ToUri(item), EConfigType.WireGuard => WireguardFmt.ToUri(item),
EConfigType.Anytls => AnytlsFmt.ToUri(item),
_ => null, _ => null,
}; };
@ -75,6 +76,10 @@ public class FmtHandler
{ {
return WireguardFmt.Resolve(str, out msg); return WireguardFmt.Resolve(str, out msg);
} }
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Anytls]))
{
return AnytlsFmt.Resolve(str, out msg);
}
else else
{ {
msg = ResUI.NonvmessOrssProtocol; msg = ResUI.NonvmessOrssProtocol;

View File

@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
namespace ServiceLib.Models; namespace ServiceLib.Models;
public class SingboxConfig public class SingboxConfig
@ -6,6 +8,7 @@ public class SingboxConfig
public Dns4Sbox? dns { get; set; } public Dns4Sbox? dns { get; set; }
public List<Inbound4Sbox> inbounds { get; set; } public List<Inbound4Sbox> inbounds { get; set; }
public List<Outbound4Sbox> outbounds { get; set; } public List<Outbound4Sbox> outbounds { get; set; }
public List<Endpoints4Sbox>? endpoints { get; set; }
public Route4Sbox route { get; set; } public Route4Sbox route { get; set; }
public Experimental4Sbox? experimental { get; set; } public Experimental4Sbox? experimental { get; set; }
} }
@ -29,7 +32,6 @@ public class Dns4Sbox
public bool? independent_cache { get; set; } public bool? independent_cache { get; set; }
public bool? reverse_mapping { get; set; } public bool? reverse_mapping { get; set; }
public string? client_subnet { get; set; } public string? client_subnet { get; set; }
public Fakeip4Sbox? fakeip { get; set; }
} }
public class Route4Sbox public class Route4Sbox
@ -37,6 +39,7 @@ public class Route4Sbox
public bool? auto_detect_interface { get; set; } public bool? auto_detect_interface { get; set; }
public List<Rule4Sbox> rules { get; set; } public List<Rule4Sbox> rules { get; set; }
public List<Ruleset4Sbox>? rule_set { get; set; } public List<Ruleset4Sbox>? rule_set { get; set; }
public string? final { get; set; }
} }
[Serializable] [Serializable]
@ -49,6 +52,7 @@ public class Rule4Sbox
public string? mode { get; set; } public string? mode { get; set; }
public bool? ip_is_private { get; set; } public bool? ip_is_private { get; set; }
public string? client_subnet { get; set; } public string? client_subnet { get; set; }
public int? rewrite_ttl { get; set; }
public bool? invert { get; set; } public bool? invert { get; set; }
public string? clash_mode { get; set; } public string? clash_mode { get; set; }
public List<string>? inbound { get; set; } public List<string>? inbound { get; set; }
@ -67,6 +71,27 @@ public class Rule4Sbox
public List<string>? process_name { get; set; } public List<string>? process_name { get; set; }
public List<string>? rule_set { get; set; } public List<string>? rule_set { get; set; }
public List<Rule4Sbox>? rules { get; set; } public List<Rule4Sbox>? rules { get; set; }
public string? action { get; set; }
public string? strategy { get; set; }
public List<string>? sniffer { get; set; }
public string? rcode { get; set; }
public List<object>? query_type { get; set; }
public List<string>? answer { get; set; }
public List<string>? ns { get; set; }
public List<string>? 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<string>? source_port_range { get; set; }
public List<string>? network_type { get; set; }
public bool? network_is_expensive { get; set; }
public bool? network_is_constrained { get; set; }
public List<string>? wifi_ssid { get; set; }
public List<string>? 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] [Serializable]
@ -76,7 +101,6 @@ public class Inbound4Sbox
public string tag { get; set; } public string tag { get; set; }
public string listen { get; set; } public string listen { get; set; }
public int? listen_port { get; set; } public int? listen_port { get; set; }
public string? domain_strategy { get; set; }
public string interface_name { get; set; } public string interface_name { get; set; }
public List<string>? address { get; set; } public List<string>? address { get; set; }
public int? mtu { get; set; } public int? mtu { get; set; }
@ -84,8 +108,6 @@ public class Inbound4Sbox
public bool? strict_route { get; set; } public bool? strict_route { get; set; }
public bool? endpoint_independent_nat { get; set; } public bool? endpoint_independent_nat { get; set; }
public string? stack { get; set; } public string? stack { get; set; }
public bool? sniff { get; set; }
public bool? sniff_override_destination { get; set; }
public List<User4Sbox> users { get; set; } public List<User4Sbox> users { get; set; }
} }
@ -95,10 +117,8 @@ public class User4Sbox
public string password { get; set; } 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 string? server { get; set; }
public int? server_port { get; set; } public int? server_port { get; set; }
public List<string>? server_ports { get; set; } public List<string>? server_ports { get; set; }
@ -113,7 +133,6 @@ public class Outbound4Sbox
public int? recv_window_conn { get; set; } public int? recv_window_conn { get; set; }
public int? recv_window { get; set; } public int? recv_window { get; set; }
public bool? disable_mtu_discovery { get; set; } public bool? disable_mtu_discovery { get; set; }
public string? detour { get; set; }
public string? method { get; set; } public string? method { get; set; }
public string? username { get; set; } public string? username { get; set; }
public string? password { get; set; } public string? password { get; set; }
@ -121,21 +140,36 @@ public class Outbound4Sbox
public string? version { get; set; } public string? version { get; set; }
public string? network { get; set; } public string? network { get; set; }
public string? packet_encoding { get; set; } public string? packet_encoding { get; set; }
public List<string>? local_address { get; set; }
public string? private_key { get; set; }
public string? peer_public_key { get; set; }
public List<int>? reserved { get; set; }
public int? mtu { get; set; }
public string? plugin { get; set; } public string? plugin { get; set; }
public string? plugin_opts { 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<string>? outbounds { get; set; } public List<string>? outbounds { get; set; }
public bool? interrupt_exist_connections { 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<string> 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<Peer4Sbox> 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<string> allowed_ips { get; set; }
public int? persistent_keepalive_interval { get; set; }
public List<int> reserved { get; set; }
}
public class Tls4Sbox public class Tls4Sbox
{ {
public bool enabled { get; set; } public bool enabled { get; set; }
@ -191,15 +225,25 @@ public class HyObfs4Sbox
public string? password { get; set; } 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<string>? path { get; set; } // hosts
public Dictionary<string, object>? predefined { get; set; }
// Deprecated
public string? address { get; set; } public string? address { get; set; }
public string? address_resolver { get; set; } public string? address_resolver { get; set; }
public string? address_strategy { get; set; } public string? address_strategy { get; set; }
public string? strategy { get; set; } public string? strategy { get; set; }
public string? detour { get; set; } // Deprecated End
public string? client_subnet { get; set; }
} }
public class Experimental4Sbox public class Experimental4Sbox
@ -229,13 +273,6 @@ public class Stats4Sbox
public List<string>? users { get; set; } public List<string>? 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 class CacheFile4Sbox
{ {
public bool enabled { get; set; } public bool enabled { get; set; }
@ -254,3 +291,33 @@ public class Ruleset4Sbox
public string? download_detour { get; set; } public string? download_detour { get; set; }
public string? update_interval { 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<string>? network_type { get; set; }
public List<string>? 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; }
}

View File

@ -654,6 +654,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Add [Anytls] Configuration 的本地化字符串。
/// </summary>
public static string menuAddAnytlsServer {
get {
return ResourceManager.GetString("menuAddAnytlsServer", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Add a custom configuration Configuration 的本地化字符串。 /// 查找类似 Add a custom configuration Configuration 的本地化字符串。
/// </summary> /// </summary>

View File

@ -1395,4 +1395,7 @@
<data name="TbRuleOutboundTagTip" xml:space="preserve"> <data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>Can fill in the configuration remarks, please make sure it exist and are unique</value> <value>Can fill in the configuration remarks, please make sure it exist and are unique</value>
</data> </data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>Add [Anytls] Configuration</value>
</data>
</root> </root>

View File

@ -1395,4 +1395,7 @@
<data name="TbRuleOutboundTagTip" xml:space="preserve"> <data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>Can fill in the configuration remarks, please make sure it exist and are unique</value> <value>Can fill in the configuration remarks, please make sure it exist and are unique</value>
</data> </data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>Add [Anytls] Configuration</value>
</data>
</root> </root>

View File

@ -1395,4 +1395,7 @@
<data name="TbRuleOutboundTagTip" xml:space="preserve"> <data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>Can fill in the configuration remarks, please make sure it exist and are unique</value> <value>Can fill in the configuration remarks, please make sure it exist and are unique</value>
</data> </data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>Add [Anytls] Configuration</value>
</data>
</root> </root>

View File

@ -1395,4 +1395,7 @@
<data name="TbRuleOutboundTagTip" xml:space="preserve"> <data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>Can fill in the configuration remarks, please make sure it exist and are unique</value> <value>Can fill in the configuration remarks, please make sure it exist and are unique</value>
</data> </data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>Add [Anytls] Configuration</value>
</data>
</root> </root>

View File

@ -1392,4 +1392,7 @@
<data name="TbRuleOutboundTagTip" xml:space="preserve"> <data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>可以填写配置文件别名,请确保存在并唯一</value> <value>可以填写配置文件别名,请确保存在并唯一</value>
</data> </data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>添加 [Anytls] 配置文件</value>
</data>
</root> </root>

View File

@ -1392,4 +1392,7 @@
<data name="TbRuleOutboundTagTip" xml:space="preserve"> <data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>可以填寫設定檔別名,請確保存在並唯一</value> <value>可以填寫設定檔別名,請確保存在並唯一</value>
</data> </data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>新增 [Anytls] 設定檔</value>
</data>
</root> </root>

View File

@ -1,4 +1,4 @@
{ {
"log": { "log": {
"level": "debug", "level": "debug",
"timestamp": true "timestamp": true
@ -14,22 +14,10 @@
{ {
"type": "direct", "type": "direct",
"tag": "direct" "tag": "direct"
},
{
"type": "block",
"tag": "block"
},
{
"tag": "dns_out",
"type": "dns"
} }
], ],
"route": { "route": {
"rules": [ "rules": [
{
"protocol": [ "dns" ],
"outbound": "dns_out"
}
] ]
} }
} }

View File

@ -2,28 +2,33 @@
"servers": [ "servers": [
{ {
"tag": "remote", "tag": "remote",
"address": "tcp://8.8.8.8", "type": "tcp",
"strategy": "prefer_ipv4", "server": "8.8.8.8",
"detour": "proxy" "detour": "proxy"
}, },
{ {
"tag": "local", "tag": "local",
"address": "223.5.5.5", "type": "udp",
"strategy": "prefer_ipv4", "server": "223.5.5.5"
"detour": "direct"
},
{
"tag": "block",
"address": "rcode://success"
} }
], ],
"rules": [ "rules": [
{
"domain_suffix": [
"googleapis.cn",
"gstatic.com"
],
"server": "remote",
"strategy": "prefer_ipv4"
},
{ {
"rule_set": [ "rule_set": [
"geosite-cn" "geosite-cn"
], ],
"server": "local" "server": "local",
"strategy": "prefer_ipv4"
} }
], ],
"final": "remote" "final": "remote",
"strategy": "prefer_ipv4"
} }

View File

@ -2,29 +2,33 @@
"servers": [ "servers": [
{ {
"tag": "remote", "tag": "remote",
"address": "tcp://8.8.8.8", "type": "tcp",
"strategy": "prefer_ipv4", "server": "8.8.8.8",
"detour": "proxy" "detour": "proxy"
}, },
{ {
"tag": "local", "tag": "local",
"address": "223.5.5.5", "type": "udp",
"strategy": "prefer_ipv4", "server": "223.5.5.5"
"detour": "direct"
},
{
"tag": "block",
"address": "rcode://success"
} }
], ],
"rules": [ "rules": [
{ {
"rule_set": [ "domain_suffix": [
"geosite-cn", "googleapis.cn",
"geosite-geolocation-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"
}

View File

@ -8,13 +8,13 @@
139, 139,
5353 5353
], ],
"outbound": "block" "action": "reject"
}, },
{ {
"ip_cidr": [ "ip_cidr": [
"224.0.0.0/3", "224.0.0.0/3",
"ff00::/8" "ff00::/8"
], ],
"outbound": "block" "action": "reject"
} }
] ]

View File

@ -1,6 +1,9 @@
using System.Data; using System.Data;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Reactive;
using DynamicData;
using ServiceLib.Models;
namespace ServiceLib.Services.CoreConfig; namespace ServiceLib.Services.CoreConfig;
@ -53,7 +56,18 @@ public class CoreConfigSingboxService
await GenInbounds(singboxConfig); 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 GenMoreOutbounds(node, singboxConfig);
@ -202,16 +216,29 @@ public class CoreConfigSingboxService
continue; continue;
} }
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); var server = await GenServer(item);
await GenOutbound(item, outbound); if (server is null)
outbound.tag = Global.ProxyTag + inbound.listen_port.ToString(); {
singboxConfig.outbounds.Add(outbound); 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 //rule
Rule4Sbox rule = new() Rule4Sbox rule = new()
{ {
inbound = new List<string> { inbound.tag }, inbound = new List<string> { inbound.tag },
outbound = outbound.tag outbound = tag
}; };
singboxConfig.route.rules.Add(rule); singboxConfig.route.rules.Add(rule);
} }
@ -275,7 +302,18 @@ public class CoreConfigSingboxService
} }
await GenLog(singboxConfig); 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 GenMoreOutbounds(node, singboxConfig);
await GenDnsDomains(null, singboxConfig, null); await GenDnsDomains(null, singboxConfig, null);
@ -534,15 +572,6 @@ public class CoreConfigSingboxService
singboxConfig.inbounds.Add(inbound); singboxConfig.inbounds.Add(inbound);
inbound.listen_port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); 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) if (_config.Inbound.First().SecondLocalPortEnabled)
{ {
@ -587,8 +616,6 @@ public class CoreConfigSingboxService
tunInbound.mtu = _config.TunModeItem.Mtu; tunInbound.mtu = _config.TunModeItem.Mtu;
tunInbound.strict_route = _config.TunModeItem.StrictRoute; tunInbound.strict_route = _config.TunModeItem.StrictRoute;
tunInbound.stack = _config.TunModeItem.Stack; 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) if (_config.TunModeItem.EnableIPv6Address == false)
{ {
tunInbound.address = ["172.18.0.1/30"]; tunInbound.address = ["172.18.0.1/30"];
@ -621,6 +648,17 @@ public class CoreConfigSingboxService
outbound.server_port = node.Port; outbound.server_port = node.Port;
outbound.type = Global.ProtocolTypes[node.ConfigType]; outbound.type = Global.ProtocolTypes[node.ConfigType];
if (Utils.IsDomain(node.Address))
{
var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box);
var localDnsAddress = string.IsNullOrEmpty(item?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : item?.DomainDNSAddress;
outbound.domain_resolver = new()
{
server = localDnsAddress.StartsWith("tag://") ? localDnsAddress.Substring(6) : "local_resolver",
strategy = string.IsNullOrEmpty(item?.DomainStrategy4Freedom) ? null : item?.DomainStrategy4Freedom
};
}
switch (node.ConfigType) switch (node.ConfigType)
{ {
case EConfigType.VMess: case EConfigType.VMess:
@ -725,13 +763,9 @@ public class CoreConfigSingboxService
outbound.congestion_control = node.HeaderType; outbound.congestion_control = node.HeaderType;
break; break;
} }
case EConfigType.WireGuard: case EConfigType.Anytls:
{ {
outbound.private_key = node.Id; outbound.password = node.Id;
outbound.peer_public_key = node.PublicKey;
outbound.reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList();
outbound.local_address = Utils.String2List(node.RequestHost);
outbound.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt();
break; break;
} }
} }
@ -747,6 +781,76 @@ public class CoreConfigSingboxService
return 0; return 0;
} }
private async Task<int> GenEndpoint(ProfileItem node, Endpoints4Sbox endpoint)
{
try
{
endpoint.address = Utils.String2List(node.RequestHost);
endpoint.type = Global.ProtocolTypes[node.ConfigType];
if (Utils.IsDomain(node.Address))
{
var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box);
var localDnsAddress = string.IsNullOrEmpty(item?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : item?.DomainDNSAddress;
endpoint.domain_resolver = new()
{
server = localDnsAddress.StartsWith("tag://") ? localDnsAddress.Substring(6) : "local_resolver",
strategy = string.IsNullOrEmpty(item?.DomainStrategy4Freedom) ? null : item?.DomainStrategy4Freedom
};
}
switch (node.ConfigType)
{
case EConfigType.WireGuard:
{
var peer = new Peer4Sbox
{
public_key = node.PublicKey,
reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(),
address = node.Address,
port = node.Port,
// TODO default ["0.0.0.0/0", "::/0"]
allowed_ips = new() { "0.0.0.0/0", "::/0" },
};
endpoint.private_key = node.Id;
endpoint.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt();
endpoint.peers = new() { peer };
break;
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<BaseServer4Sbox?> GenServer(ProfileItem node)
{
try
{
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
if (node.ConfigType == EConfigType.WireGuard)
{
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
await GenEndpoint(node, endpoint);
return endpoint;
}
else
{
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(node, outbound);
return outbound;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult<BaseServer4Sbox?>(null);
}
private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound) private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
{ {
try try
@ -913,7 +1017,8 @@ public class CoreConfigSingboxService
} }
//current proxy //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); var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
//Previous proxy //Previous proxy
@ -922,17 +1027,32 @@ public class CoreConfigSingboxService
if (prevNode is not null if (prevNode is not null
&& prevNode.ConfigType != EConfigType.Custom) && prevNode.ConfigType != EConfigType.Custom)
{ {
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(prevNode, prevOutbound);
prevOutboundTag = $"prev-{Global.ProxyTag}"; prevOutboundTag = $"prev-{Global.ProxyTag}";
prevOutbound.tag = prevOutboundTag; var prevServer = await GenServer(prevNode);
singboxConfig.outbounds.Add(prevOutbound); 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) catch (Exception ex)
@ -955,11 +1075,13 @@ public class CoreConfigSingboxService
} }
var resultOutbounds = new List<Outbound4Sbox>(); var resultOutbounds = new List<Outbound4Sbox>();
var resultEndpoints = new List<Endpoints4Sbox>(); // For endpoints
var prevOutbounds = new List<Outbound4Sbox>(); // Separate list for prev outbounds var prevOutbounds = new List<Outbound4Sbox>(); // Separate list for prev outbounds
var prevEndpoints = new List<Endpoints4Sbox>(); // Separate list for prev endpoints
var proxyTags = new List<string>(); // For selector and urltest outbounds var proxyTags = new List<string>(); // For selector and urltest outbounds
// Cache for chain proxies to avoid duplicate generation // Cache for chain proxies to avoid duplicate generation
var nextProxyCache = new Dictionary<string, Outbound4Sbox?>(); var nextProxyCache = new Dictionary<string, BaseServer4Sbox?>();
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
int prevIndex = 0; // Index for prev outbounds int prevIndex = 0; // Index for prev outbounds
@ -971,19 +1093,18 @@ public class CoreConfigSingboxService
// Handle proxy chain // Handle proxy chain
string? prevTag = null; string? prevTag = null;
var currentOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); var currentServer = await GenServer(node);
var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null); var nextServer = nextProxyCache.GetValueOrDefault(node.Subid, null);
if (nextOutbound != null) if (nextServer != null)
{ {
nextOutbound = JsonUtils.DeepCopy(nextOutbound); nextServer = JsonUtils.DeepCopy(nextServer);
} }
var subItem = await AppHandler.Instance.GetSubItem(node.Subid); var subItem = await AppHandler.Instance.GetSubItem(node.Subid);
// current proxy // current proxy
await GenOutbound(node, currentOutbound); currentServer.tag = $"{Global.ProxyTag}-{index}";
currentOutbound.tag = $"{Global.ProxyTag}-{index}"; proxyTags.Add(currentServer.tag);
proxyTags.Add(currentOutbound.tag);
if (!node.Subid.IsNullOrEmpty()) if (!node.Subid.IsNullOrEmpty())
{ {
@ -1006,18 +1127,32 @@ public class CoreConfigSingboxService
prevProxyTags[node.Subid] = prevTag; prevProxyTags[node.Subid] = prevTag;
} }
nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound); nextServer = await GenChainOutbounds(subItem, currentServer, prevTag, nextServer);
if (!nextProxyCache.ContainsKey(node.Subid)) 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) // Add urltest outbound (auto selection based on latency)
@ -1050,6 +1185,9 @@ public class CoreConfigSingboxService
resultOutbounds.AddRange(prevOutbounds); resultOutbounds.AddRange(prevOutbounds);
resultOutbounds.AddRange(singboxConfig.outbounds); resultOutbounds.AddRange(singboxConfig.outbounds);
singboxConfig.outbounds = resultOutbounds; singboxConfig.outbounds = resultOutbounds;
singboxConfig.endpoints ??= new List<Endpoints4Sbox>();
resultEndpoints.AddRange(singboxConfig.endpoints);
singboxConfig.endpoints = resultEndpoints;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1071,7 +1209,7 @@ public class CoreConfigSingboxService
/// <returns> /// <returns>
/// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists.
/// </returns> /// </returns>
private async Task<Outbound4Sbox?> GenChainOutbounds(SubItem subItem, Outbound4Sbox outbound, string? prevOutboundTag, Outbound4Sbox? nextOutbound = null) private async Task<BaseServer4Sbox?> GenChainOutbounds(SubItem subItem, BaseServer4Sbox outbound, string? prevOutboundTag, BaseServer4Sbox? nextOutbound = null)
{ {
try try
{ {
@ -1087,11 +1225,7 @@ public class CoreConfigSingboxService
if (nextNode is not null if (nextNode is not null
&& nextNode.ConfigType != EConfigType.Custom) && nextNode.ConfigType != EConfigType.Custom)
{ {
if (nextOutbound == null) nextOutbound ??= await GenServer(nextNode);
{
nextOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(nextNode, nextOutbound);
}
nextOutbound.tag = outbound.tag; nextOutbound.tag = outbound.tag;
outbound.tag = $"mid-{outbound.tag}"; outbound.tag = $"mid-{outbound.tag}";
@ -1110,7 +1244,7 @@ public class CoreConfigSingboxService
{ {
try try
{ {
var dnsOutbound = "dns_out"; singboxConfig.route.final = Global.ProxyTag;
if (_config.TunModeItem.EnableTun) if (_config.TunModeItem.EnableTun)
{ {
@ -1126,7 +1260,7 @@ public class CoreConfigSingboxService
singboxConfig.route.rules.Add(new() singboxConfig.route.rules.Add(new()
{ {
port = new() { 53 }, port = new() { 53 },
outbound = dnsOutbound, action = "hijack-dns",
process_name = lstDnsExe process_name = lstDnsExe
}); });
@ -1137,13 +1271,25 @@ public class CoreConfigSingboxService
}); });
} }
if (!_config.Inbound.First().SniffingEnabled) if (_config.Inbound.First().SniffingEnabled)
{ {
singboxConfig.route.rules.Add(new() singboxConfig.route.rules.Add(new()
{ {
port = [53], action = "sniff"
network = ["udp"], });
outbound = dnsOutbound 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,7 +1304,24 @@ public class CoreConfigSingboxService
clash_mode = ERuleMode.Global.ToString() 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 routing = await ConfigHandler.GetDefaultRouting(_config);
var ipRules = new List<RulesItem>();
if (routing != null) if (routing != null)
{ {
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet); var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
@ -1167,9 +1330,21 @@ public class CoreConfigSingboxService
if (item.Enabled) if (item.Enabled)
{ {
await GenRoutingUserRule(item, singboxConfig); await GenRoutingUserRule(item, singboxConfig);
if (item.Ip != null && item.Ip.Count > 0)
{
ipRules.Add(item);
}
} }
} }
} }
if (_config.RoutingBasicItem.DomainStrategy == "IPIfNonMatch")
{
singboxConfig.route.rules.Add(resolveRule);
foreach (var item in ipRules)
{
await GenRoutingUserRule(item, singboxConfig);
}
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1217,10 +1392,15 @@ public class CoreConfigSingboxService
item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig); item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig);
var rules = singboxConfig.route.rules; 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()) if (item.Port.IsNotEmpty())
{ {
@ -1344,24 +1524,28 @@ public class CoreConfigSingboxService
{ {
return false; return false;
} }
else if (address.StartsWith("geoip:!"))
{
return false;
}
else if (address.Equals("geoip:private")) else if (address.Equals("geoip:private"))
{ {
rule.ip_is_private = true; rule.ip_is_private = true;
} }
else if (address.StartsWith("geoip:")) else if (address.StartsWith("geoip:"))
{ {
if (rule.geoip is null) rule.geoip ??= new();
{ rule.geoip = new(); }
rule.geoip?.Add(address.Substring(6)); 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 else
{ {
if (rule.ip_cidr is null) rule.ip_cidr ??= new();
{ rule.ip_cidr = new(); }
rule.ip_cidr?.Add(address); rule.ip_cidr?.Add(address);
} }
return true; return true;
@ -1381,13 +1565,24 @@ public class CoreConfigSingboxService
return Global.ProxyTag; return Global.ProxyTag;
} }
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); var server = await GenServer(node);
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); if (server is null)
await GenOutbound(node, outbound); {
outbound.tag = Global.ProxyTag + node.IndexId.ToString(); return Global.ProxyTag;
singboxConfig.outbounds.Add(outbound); }
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<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig) private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig)
@ -1412,7 +1607,14 @@ public class CoreConfigSingboxService
} }
singboxConfig.dns = dns4Sbox; singboxConfig.dns = dns4Sbox;
await GenDnsDomains(node, singboxConfig, item); if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty())
{
await GenDnsDomains(node, singboxConfig, item);
}
else
{
await GenDnsDomainsLegacy(node, singboxConfig, item);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1427,6 +1629,75 @@ public class CoreConfigSingboxService
dns4Sbox.servers ??= []; dns4Sbox.servers ??= [];
dns4Sbox.rules ??= []; dns4Sbox.rules ??= [];
var tag = "local_resolver";
var localDnsAddress = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.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 (dnsType, dnsHost, dnsPort, dnsPath) = ParseDnsAddress(localDnsAddress);
dns4Sbox.servers.Add(new()
{
tag = tag,
type = dnsType,
server = dnsHost,
Interface = dnsType == "dhcp" ? dnsHost : null,
server_port = dnsPort,
path = dnsPath
});
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()
});
//Tun2SocksAddress
if (_config.TunModeItem.EnableTun && node?.ConfigType == EConfigType.SOCKS && Utils.IsDomain(node?.Sni))
{
dns4Sbox.rules.Insert(0, new()
{
server = tag,
domain = [node?.Sni],
strategy = string.IsNullOrEmpty(dNSItem?.DomainStrategy4Freedom) ? null : dNSItem?.DomainStrategy4Freedom
});
}
singboxConfig.dns = dns4Sbox;
return await Task.FromResult(0);
}
private async Task<int> GenDnsDomainsLegacy(ProfileItem? node, SingboxConfig singboxConfig, DNSItem? dNSItem)
{
var dns4Sbox = singboxConfig.dns ?? new();
dns4Sbox.servers ??= [];
dns4Sbox.rules ??= [];
var tag = "local_local"; var tag = "local_local";
dns4Sbox.servers.Add(new() dns4Sbox.servers.Add(new()
{ {
@ -1474,6 +1745,91 @@ public class CoreConfigSingboxService
return await Task.FromResult(0); return await Task.FromResult(0);
} }
private (string type, string? host, int? port, string? path) ParseDnsAddress(string address)
{
string type = "udp";
string? host = null;
int? port = null;
string? path = null;
if (address is "local" or "localhost")
{
return ("local", null, null, null);
}
if (address.StartsWith("dhcp://", StringComparison.OrdinalIgnoreCase))
{
string interface_name = address.Substring(7);
return ("dhcp", interface_name == "auto" ? null : interface_name, null, null);
}
if (!address.Contains("://"))
{
// udp dns
host = address;
return (type, host, port, path);
}
try
{
int protocolEndIndex = address.IndexOf("://", StringComparison.Ordinal);
type = address.Substring(0, protocolEndIndex).ToLower();
var uri = new Uri(address);
host = uri.Host;
if (!uri.IsDefaultPort)
{
port = uri.Port;
}
if ((type == "https" || type == "h3") && !string.IsNullOrEmpty(uri.AbsolutePath) && uri.AbsolutePath != "/")
{
path = uri.AbsolutePath;
}
}
catch (UriFormatException)
{
int protocolEndIndex = address.IndexOf("://", StringComparison.Ordinal);
if (protocolEndIndex > 0)
{
type = address.Substring(0, protocolEndIndex).ToLower();
string remaining = address.Substring(protocolEndIndex + 3);
int portIndex = remaining.IndexOf(':');
int pathIndex = remaining.IndexOf('/');
if (portIndex > 0)
{
host = remaining.Substring(0, portIndex);
string portPart = pathIndex > portIndex
? remaining.Substring(portIndex + 1, pathIndex - portIndex - 1)
: remaining.Substring(portIndex + 1);
if (int.TryParse(portPart, out int parsedPort))
{
port = parsedPort;
}
}
else if (pathIndex > 0)
{
host = remaining.Substring(0, pathIndex);
}
else
{
host = remaining;
}
if (pathIndex > 0 && (type == "https" || type == "h3"))
{
path = remaining.Substring(pathIndex);
}
}
}
return (type, host, port, path);
}
private async Task<int> GenExperimental(SingboxConfig singboxConfig) private async Task<int> GenExperimental(SingboxConfig singboxConfig)
{ {
//if (_config.guiItem.enableStatistics) //if (_config.guiItem.enableStatistics)

View File

@ -1349,7 +1349,8 @@ public class CoreConfigV2rayService
if (prevNode is not null if (prevNode is not null
&& prevNode.ConfigType != EConfigType.Custom && prevNode.ConfigType != EConfigType.Custom
&& prevNode.ConfigType != EConfigType.Hysteria2 && prevNode.ConfigType != EConfigType.Hysteria2
&& prevNode.ConfigType != EConfigType.TUIC) && prevNode.ConfigType != EConfigType.TUIC
&& prevNode.ConfigType != EConfigType.Anytls)
{ {
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound); var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(prevNode, prevOutbound); await GenOutbound(prevNode, prevOutbound);
@ -1424,7 +1425,8 @@ public class CoreConfigV2rayService
if (prevNode is not null if (prevNode is not null
&& prevNode.ConfigType != EConfigType.Custom && prevNode.ConfigType != EConfigType.Custom
&& prevNode.ConfigType != EConfigType.Hysteria2 && prevNode.ConfigType != EConfigType.Hysteria2
&& prevNode.ConfigType != EConfigType.TUIC) && prevNode.ConfigType != EConfigType.TUIC
&& prevNode.ConfigType != EConfigType.Anytls)
{ {
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound); var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(prevNode, prevOutbound); await GenOutbound(prevNode, prevOutbound);
@ -1493,7 +1495,8 @@ public class CoreConfigV2rayService
if (nextNode is not null if (nextNode is not null
&& nextNode.ConfigType != EConfigType.Custom && nextNode.ConfigType != EConfigType.Custom
&& nextNode.ConfigType != EConfigType.Hysteria2 && nextNode.ConfigType != EConfigType.Hysteria2
&& nextNode.ConfigType != EConfigType.TUIC) && nextNode.ConfigType != EConfigType.TUIC
&& nextNode.ConfigType != EConfigType.Anytls)
{ {
if (nextOutbound == null) if (nextOutbound == null)
{ {

View File

@ -358,8 +358,8 @@ public class SpeedtestService
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize) private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
{ {
List<List<ServerTestItem>> lstTest = new(); List<List<ServerTestItem>> lstTest = new();
var lst1 = lstSelected.Where(t => t.ConfigType is not (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).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++) for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++)
{ {

View File

@ -20,6 +20,7 @@ public class MainWindowViewModel : MyReactiveObject
public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; } public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; } public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddWireguardServerCmd { get; } public ReactiveCommand<Unit, Unit> AddWireguardServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddAnytlsServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; } public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; } public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; } public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
@ -111,6 +112,10 @@ public class MainWindowViewModel : MyReactiveObject
{ {
await AddServerAsync(true, EConfigType.WireGuard); await AddServerAsync(true, EConfigType.WireGuard);
}); });
AddAnytlsServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.Anytls);
});
AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () => AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(true, EConfigType.Custom); await AddServerAsync(true, EConfigType.Custom);

View File

@ -533,6 +533,26 @@
HorizontalAlignment="Left" HorizontalAlignment="Left"
Watermark="1500" /> Watermark="1500" />
</Grid> </Grid>
<Grid
x:Name="gridAnytls"
Grid.Row="2"
ColumnDefinitions="180,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtId10"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
</Grid>
<Separator <Separator
x:Name="sepa2" x:Name="sepa2"

View File

@ -102,6 +102,12 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridTls.IsVisible = false; gridTls.IsVisible = false;
break; break;
case EConfigType.Anytls:
gridAnytls.IsVisible = true;
cmbStreamSecurity.Items.Add(Global.StreamSecurityReality);
cmbCoreType.IsEnabled = false;
break;
} }
cmbStreamSecurity.ItemsSource = lstStreamSecurity; cmbStreamSecurity.ItemsSource = lstStreamSecurity;
@ -167,6 +173,10 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables); 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); this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables);
break; 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.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.SelectedValue).DisposeWith(disposables);

View File

@ -46,6 +46,7 @@
<Separator /> <Separator />
<MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" /> <MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
<MenuItem x:Name="menuAddTuicServer" Header="{x:Static resx:ResUI.menuAddTuicServer}" /> <MenuItem x:Name="menuAddTuicServer" Header="{x:Static resx:ResUI.menuAddTuicServer}" />
<MenuItem x:Name="menuAddAnytlsServer" Header="{x:Static resx:ResUI.menuAddAnytlsServer}" />
</MenuItem> </MenuItem>
<MenuItem Padding="8,0"> <MenuItem Padding="8,0">

View File

@ -83,6 +83,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);

View File

@ -707,6 +707,35 @@
materialDesign:HintAssist.Hint="1500" materialDesign:HintAssist.Hint="1500"
Style="{StaticResource DefTextBox}" /> Style="{StaticResource DefTextBox}" />
</Grid> </Grid>
<Grid
x:Name="gridAnytls"
Grid.Row="2"
Visibility="Hidden">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtId10"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefTextBox}" />
</Grid>
<Separator <Separator
x:Name="sepa2" x:Name="sepa2"

View File

@ -95,6 +95,11 @@ public partial class AddServerWindow
gridTransport.Visibility = Visibility.Collapsed; gridTransport.Visibility = Visibility.Collapsed;
gridTls.Visibility = Visibility.Collapsed; gridTls.Visibility = Visibility.Collapsed;
break;
case EConfigType.Anytls:
gridAnytls.Visibility = Visibility.Visible;
cmbCoreType.IsEnabled = false;
cmbStreamSecurity.Items.Add(Global.StreamSecurityReality);
break; break;
} }
cmbStreamSecurity.ItemsSource = lstStreamSecurity; cmbStreamSecurity.ItemsSource = lstStreamSecurity;
@ -161,6 +166,10 @@ public partial class AddServerWindow
this.Bind(ViewModel, vm => vm.SelectedSource.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables); 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); this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables);
break; 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.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.Text).DisposeWith(disposables);

View File

@ -108,6 +108,10 @@
x:Name="menuAddTuicServer" x:Name="menuAddTuicServer"
Height="{StaticResource MenuItemHeight}" Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddTuicServer}" /> Header="{x:Static resx:ResUI.menuAddTuicServer}" />
<MenuItem
x:Name="menuAddAnytlsServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddAnytlsServer}" />
</MenuItem> </MenuItem>
</Menu> </Menu>
<Separator /> <Separator />

View File

@ -80,6 +80,7 @@ public partial class MainWindow
this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);