diff --git a/.editorconfig b/.editorconfig index 3aa0aa54a50..0e8f9fbc5e2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -100,7 +100,6 @@ csharp_style_prefer_tuple_swap = true:warning csharp_style_prefer_utf8_string_literals = true:warning csharp_style_throw_expression = true:warning csharp_style_unused_value_assignment_preference = discard_variable:warning -csharp_style_unused_value_expression_statement_preference = discard_variable:warning csharp_using_directive_placement = outside_namespace:warning csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:warning csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:warning diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index a3cbb55c9bc..baeae29a35d 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -335,13 +335,13 @@ jobs: env: RELEASE_TAG: ${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }} - QCOW2_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.4/debian13-loong64.qcow2 - EFI_CODE_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.4/edk2-loongarch64-code.fd - EFI_VARS_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.4/edk2-loongarch64-vars.fd + QCOW2_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/debian13-loong64.qcow2 + EFI_CODE_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/edk2-loongarch64-code.fd + EFI_VARS_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/edk2-loongarch64-vars.fd QCOW2_IMAGE: debian13-loong64.qcow2 EFI_CODE: edk2-loongarch64-code.fd EFI_VARS: edk2-loongarch64-vars.fd - QEMU_VERSION: 10.2.2 + QEMU_VERSION: 10.2.3 steps: - name: Prepare host tools diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 11ae9ad229e..8c73a6c09e5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,7 +52,7 @@ jobs: fetch-depth: '0' - name: Setup .NET - uses: actions/setup-dotnet@v5.2.0 + uses: actions/setup-dotnet@v5.3.0 with: dotnet-version: '10.0.1xx' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f0f0f872dbf..633bb8e2e97 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: fetch-depth: '0' - name: Setup .NET - uses: actions/setup-dotnet@v5.2.0 + uses: actions/setup-dotnet@v5.3.0 with: dotnet-version: '8.0.x' diff --git a/package-osx.sh b/package-osx.sh index 5ed390ca903..4eb14366a35 100755 --- a/package-osx.sh +++ b/package-osx.sh @@ -22,7 +22,17 @@ cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF CFBundleDevelopmentRegion - English + en + CFBundleLocalizations + + zh-Hans + zh-Hant + en + fa + fr + ru + hu + CFBundleDisplayName v2rayN CFBundleExecutable diff --git a/v2rayN/Directory.Build.props b/v2rayN/Directory.Build.props index 951c1ae4a12..3c7a8a3d482 100644 --- a/v2rayN/Directory.Build.props +++ b/v2rayN/Directory.Build.props @@ -1,7 +1,7 @@ - 7.22.1 + 7.22.5 diff --git a/v2rayN/Directory.Packages.props b/v2rayN/Directory.Packages.props index a136d4d7e5f..086c726f88a 100644 --- a/v2rayN/Directory.Packages.props +++ b/v2rayN/Directory.Packages.props @@ -7,33 +7,33 @@ - - + + - + + - - + + - + - + - + - - + - + diff --git a/v2rayN/ServiceLib.Tests/CoreConfig/CoreConfigTestFactory.cs b/v2rayN/ServiceLib.Tests/CoreConfig/CoreConfigTestFactory.cs index 812a5a32aad..63bc45cac35 100644 --- a/v2rayN/ServiceLib.Tests/CoreConfig/CoreConfigTestFactory.cs +++ b/v2rayN/ServiceLib.Tests/CoreConfig/CoreConfigTestFactory.cs @@ -187,7 +187,7 @@ public static CoreConfigContext CreateContext(Config config, ProfileItem node, E SimpleDnsItem = config.SimpleDNSItem, AllProxiesMap = new Dictionary { [node.IndexId] = node }, FullConfigTemplate = null, - IsTunEnabled = false, + IsTunEnabled = config.TunModeItem.EnableTun, ProtectDomainList = [], }; } @@ -206,4 +206,12 @@ public static Config CreateConfigWithBootstrapDNS(ECoreType coreType, string boo config.SimpleDNSItem.BootstrapDNS = bootstrapDns; return config; } + + public static Config CreateConfigWithTunRouteExcludeAddress(ECoreType coreType) + { + var config = CreateConfig(coreType); + config.TunModeItem.EnableTun = true; + config.TunModeItem.RouteExcludeAddress = ["10.0.0.1/32", "192.168.1.0/24", "fc00::/7"]; + return config; + } } diff --git a/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs b/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs index 29b26648b3c..4847050d8c1 100644 --- a/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs +++ b/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayServiceTests.cs @@ -536,4 +536,27 @@ public void GenerateClientConfigContent_RawDnsEnabled_ShouldUseCustomDnsConfig() directOutbound.Should().NotBeNull(); directOutbound!.settings.domainStrategy.Should().Be("UseIPv4"); } + + [Fact] + public void GenerateClientConfigContent_TunRouteExcludeAddress() + { + var config = CoreConfigTestFactory.CreateConfigWithTunRouteExcludeAddress(ECoreType.Xray); + CoreConfigTestFactory.BindAppManagerConfig(config); + + var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main"); + var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray); + + var result = new CoreConfigV2rayService(context).GenerateClientConfigContent(); + + result.Success.Should().BeTrue(); + + var cfg = JsonUtils.Deserialize(result.Data!.ToString())!; + var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun"); + + tunInbound.Should().NotBeNull(); + + tunInbound!.settings.autoSystemRoutingTable.Should().NotContain("0.0.0.0/0"); + tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.0/32"); + tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.2/31"); + } } diff --git a/v2rayN/ServiceLib/Common/CountryExtension.cs b/v2rayN/ServiceLib/Common/CountryExtension.cs new file mode 100644 index 00000000000..152172d85b6 --- /dev/null +++ b/v2rayN/ServiceLib/Common/CountryExtension.cs @@ -0,0 +1,92 @@ +namespace ServiceLib.Common; + +/// +/// Extension methods for country code utilities +/// +public static class CountryExtension +{ + /// + /// Country code to emoji flag mapping for common countries + /// + private static readonly Dictionary CountryEmojiMap = new(StringComparer.OrdinalIgnoreCase) + { + // Asia + { "CN", "🇨🇳" }, // China + { "HK", "🇭🇰" }, // Hong Kong + { "TW", "🇹🇼" }, // Taiwan + { "JP", "🇯🇵" }, // Japan + { "SG", "🇸🇬" }, // Singapore + { "KR", "🇰🇷" }, // South Korea + { "TH", "🇹🇭" }, // Thailand + { "VN", "🇻🇳" }, // Vietnam + { "ID", "🇮🇩" }, // Indonesia + { "PH", "🇵🇭" }, // Philippines + { "MY", "🇲🇾" }, // Malaysia + { "IN", "🇮🇳" }, // India + { "PK", "🇵🇰" }, // Pakistan + { "BD", "🇧🇩" }, // Bangladesh + { "LK", "🇱🇰" }, // Sri Lanka + { "KH", "🇰🇭" }, // Cambodia + { "LA", "🇱🇦" }, // Laos + { "MM", "🇲🇲" }, // Myanmar + + // Americas + { "US", "🇺🇸" }, // United States + { "CA", "🇨🇦" }, // Canada + { "MX", "🇲🇽" }, // Mexico + { "BR", "🇧🇷" }, // Brazil + { "AR", "🇦🇷" }, // Argentina + { "CL", "🇨🇱" }, // Chile + { "CO", "🇨🇴" }, // Colombia + + // Europe + { "GB", "🇬🇧" }, // United Kingdom + { "DE", "🇩🇪" }, // Germany + { "FR", "🇫🇷" }, // France + { "IT", "🇮🇹" }, // Italy + { "ES", "🇪🇸" }, // Spain + { "RU", "🇷🇺" }, // Russia + { "NL", "🇳🇱" }, // Netherlands + { "CH", "🇨🇭" }, // Switzerland + { "SE", "🇸🇪" }, // Sweden + { "NO", "🇳🇴" }, // Norway + { "DK", "🇩🇰" }, // Denmark + { "FI", "🇫🇮" }, // Finland + { "PL", "🇵🇱" }, // Poland + { "CZ", "🇨🇿" }, // Czech Republic + { "AT", "🇦🇹" }, // Austria + { "GR", "🇬🇷" }, // Greece + { "PT", "🇵🇹" }, // Portugal + { "TR", "🇹🇷" }, // Turkey + { "UA", "🇺🇦" }, // Ukraine + { "RO", "🇷🇴" }, // Romania + + // Middle East & Central Asia + { "AE", "🇦🇪" }, // United Arab Emirates + { "SA", "🇸🇦" }, // Saudi Arabia + { "IL", "🇮🇱" }, // Israel + { "KZ", "🇰🇿" }, // Kazakhstan + + // Oceania + { "AU", "🇦🇺" }, // Australia + { "NZ", "🇳🇿" }, // New Zealand + + // Africa + { "ZA", "🇿🇦" }, // South Africa + { "EG", "🇪🇬" }, // Egypt + }; + + /// + /// Converts country code to flag emoji using predefined mapping + /// Example: "US" -> "🇺🇸", "CN" -> "🇨🇳" + /// + public static string? CountryToEmoji(this string? countryCode) + { + if (countryCode.IsNullOrEmpty()) + { + return null; + } + + return CountryEmojiMap.TryGetValue(countryCode, out var emoji) ? emoji : null; + } +} diff --git a/v2rayN/ServiceLib/Enums/EServerColName.cs b/v2rayN/ServiceLib/Enums/EServerColName.cs index 9f50f4df270..8800cdf6071 100644 --- a/v2rayN/ServiceLib/Enums/EServerColName.cs +++ b/v2rayN/ServiceLib/Enums/EServerColName.cs @@ -12,6 +12,7 @@ public enum EServerColName SubRemarks, DelayVal, SpeedVal, + IpInfo, TodayDown, TodayUp, diff --git a/v2rayN/ServiceLib/Events/AppEvents.cs b/v2rayN/ServiceLib/Events/AppEvents.cs index 5824bfc0099..2f97bc6b27f 100644 --- a/v2rayN/ServiceLib/Events/AppEvents.cs +++ b/v2rayN/ServiceLib/Events/AppEvents.cs @@ -7,6 +7,7 @@ public static class AppEvents public static readonly EventChannel AddServerViaScanRequested = new(); public static readonly EventChannel AddServerViaClipboardRequested = new(); public static readonly EventChannel SubscriptionsUpdateRequested = new(); + public static readonly EventChannel HasUpdateNotified = new(); public static readonly EventChannel ProfilesRefreshRequested = new(); public static readonly EventChannel SubscriptionsRefreshRequested = new(); diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index ca167fa3141..cadd0c53bd5 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -55,7 +55,7 @@ public class Global public const string DnsOutboundTag = "dns"; public const string DnsTag = "dns-module"; public const string DirectDnsTag = "direct-dns"; - public const string BalancerTagSuffix = "-round"; + public const string BalancerTagSuffix = "-balancer"; public const string StreamSecurity = "tls"; public const string StreamSecurityReality = "reality"; public const string Loopback = "127.0.0.1"; @@ -149,6 +149,9 @@ public class Global public static readonly List SpeedTestUrls = [ @"https://cachefly.cachefly.net/50mb.test", + @"https://cachefly.cachefly.net/100mb.test", + @"https://cachefly.cachefly.net/1mb.test", + @"https://cachefly.cachefly.net/10mb.test", @"https://speed.cloudflare.com/__down?bytes=10000000", @"https://speed.cloudflare.com/__down?bytes=50000000", @"https://speed.cloudflare.com/__down?bytes=99999999", @@ -157,6 +160,8 @@ public class Global public static readonly List SpeedPingTestUrls = [ @"https://www.google.com/generate_204", + @"https://www.youtube.com/generate_204", + @"https://www.googlevideo.com/generate_204", @"https://www.gstatic.com/generate_204", @"https://www.apple.com/library/test/success.html", @"http://www.msftconnecttest.com/connecttest.txt" @@ -207,6 +212,10 @@ public class Global public const string NaiveQuicProtocolShare = "naive+quic://"; + public const string SOCKS5Protocol = "socks5://"; + + public const string SOCKS4Protocol = "socks4://"; + public static readonly Dictionary ProtocolShares = new() { { EConfigType.VMess, "vmess://" }, @@ -505,6 +514,7 @@ public class Global public static readonly List InboundTags = [ + "tun", "socks", "socks2", "socks3" @@ -675,15 +685,15 @@ public class Global { "one.one.one.one", ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] }, { "1dot1dot1dot1.cloudflare-dns.com", ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] }, { "cloudflare-dns.com", ["104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9"] }, - { "dns.cloudflare.com", ["104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5"] }, + { "dns.cloudflare.com", ["162.159.61.8", "172.64.41.8", "2a06:98c1:52::8", "2803:f800:53::8"] }, { "dot.pub", ["1.12.12.12", "120.53.53.53"] }, { "doh.pub", ["1.12.12.12", "120.53.53.53"] }, { "dns.quad9.net", ["9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9"] }, { "dns.yandex.net", ["77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff"] }, - { "dns.sb", ["185.222.222.222", "2a09::"] }, + { "dns.sb", ["45.11.45.11", "185.222.222.222", "2a09::", "2a11::"] }, { "dns.umbrella.com", ["208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53"] }, { "dns.sse.cisco.com", ["208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53"] }, - { "engage.cloudflareclient.com", ["162.159.192.1"] } + { "engage.cloudflareclient.com", ["162.159.192.1", "2606:4700:d0::a29f:c001"] } }; public static readonly List ExpectedIPs = diff --git a/v2rayN/ServiceLib/Handler/AutoStartupHandler.cs b/v2rayN/ServiceLib/Handler/AutoStartupHandler.cs index fd7d7fc9e1c..acd9a5156df 100644 --- a/v2rayN/ServiceLib/Handler/AutoStartupHandler.cs +++ b/v2rayN/ServiceLib/Handler/AutoStartupHandler.cs @@ -111,7 +111,8 @@ public static void AutoStartTaskService(string taskName, string fileName, string task.Settings.RunOnlyIfIdle = false; task.Settings.IdleSettings.StopOnIdleEnd = false; task.Settings.ExecutionTimeLimit = TimeSpan.Zero; - task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(30) }); + task.Settings.Priority = ProcessPriorityClass.Normal; + task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser }); task.Principal.RunLevel = Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest; task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName))); diff --git a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs index eb1af21b136..b7481e1aaa8 100644 --- a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs +++ b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs @@ -91,6 +91,20 @@ public static async Task Build(Config config, Pr context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = actRuleNode; } } + if (context.IsTunEnabled && context.AppConfig.TunModeItem.RouteExcludeAddress is { Count: > 0 }) + { + foreach (var addr in context.AppConfig.TunModeItem.RouteExcludeAddress) + { + try + { + IPNetwork2.Parse(addr); + } + catch + { + validatorResult.Errors.Add(string.Format(ResUI.MsgTunRouteExcludeInvalidAddress, addr)); + } + } + } return new CoreConfigContextBuilderResult(context, validatorResult); } diff --git a/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs index e94635b7041..2b8199400a6 100644 --- a/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs +++ b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs @@ -134,6 +134,14 @@ private static void ValidateNodeAndCoreSupport(ProfileItem item, ECoreType coreT { v.Error(string.Format(ResUI.MsgInvalidProperty, "TLS Certificate")); } + + // Check for deprecated allowInsecure property when TLS is enabled + if (item.AllowInsecure == "true" + && item.Cert.IsNullOrEmpty() + && item.CertSha.IsNullOrEmpty()) + { + v.Warning(ResUI.MsgAllowInsecureDeprecated); + } } if (item.StreamSecurity == Global.StreamSecurityReality) diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 8f04f0f9b21..22fe0ab2595 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -255,6 +255,7 @@ public static async Task AddServer(Config config, ProfileItem profileItem) item.Cert = profileItem.Cert; item.CertSha = profileItem.CertSha; item.EchConfigList = profileItem.EchConfigList; + item.VerifyPeerCertByName = profileItem.VerifyPeerCertByName; item.Finalmask = profileItem.Finalmask; item.ProtoExtra = profileItem.ProtoExtra; item.TransportExtra = profileItem.TransportExtra; @@ -923,6 +924,7 @@ from t33 in t3b.DefaultIfEmpty() Delay = t33?.Delay ?? 0, Speed = t33?.Speed ?? 0, Sort = t33?.Sort ?? 0, + IpInfo = t33?.IpInfo ?? string.Empty, TodayDown = (t22?.TodayDown ?? 0).ToString("D16"), TodayUp = (t22?.TodayUp ?? 0).ToString("D16"), TotalDown = (t22?.TotalDown ?? 0).ToString("D16"), @@ -943,6 +945,7 @@ from t33 in t3b.DefaultIfEmpty() EServerColName.StreamSecurity => lstProfile.OrderBy(t => t.StreamSecurity).ToList(), EServerColName.DelayVal => lstProfile.OrderBy(t => t.Delay).ToList(), EServerColName.SpeedVal => lstProfile.OrderBy(t => t.Speed).ToList(), + EServerColName.IpInfo => lstProfile.OrderBy(t => t.IpInfo).ToList(), EServerColName.SubRemarks => lstProfile.OrderBy(t => t.Subid).ToList(), EServerColName.TodayDown => lstProfile.OrderBy(t => t.TodayDown).ToList(), EServerColName.TodayUp => lstProfile.OrderBy(t => t.TodayUp).ToList(), @@ -963,6 +966,7 @@ from t33 in t3b.DefaultIfEmpty() EServerColName.StreamSecurity => lstProfile.OrderByDescending(t => t.StreamSecurity).ToList(), EServerColName.DelayVal => lstProfile.OrderByDescending(t => t.Delay).ToList(), EServerColName.SpeedVal => lstProfile.OrderByDescending(t => t.Speed).ToList(), + EServerColName.IpInfo => lstProfile.OrderByDescending(t => t.IpInfo).ToList(), EServerColName.SubRemarks => lstProfile.OrderByDescending(t => t.Subid).ToList(), EServerColName.TodayDown => lstProfile.OrderByDescending(t => t.TodayDown).ToList(), EServerColName.TodayUp => lstProfile.OrderByDescending(t => t.TodayUp).ToList(), diff --git a/v2rayN/ServiceLib/Handler/ConnectionHandler.cs b/v2rayN/ServiceLib/Handler/ConnectionHandler.cs index 05825d934d2..6e8df9f85fb 100644 --- a/v2rayN/ServiceLib/Handler/ConnectionHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConnectionHandler.cs @@ -4,53 +4,41 @@ public static class ConnectionHandler { private static readonly string _tag = "ConnectionHandler"; + /// + /// Runs ping and IP checks and returns a formatted result string. + /// public static async Task RunAvailabilityCheck() { var time = await GetRealPingTimeInfo(); - var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None; + var ip = time > 0 ? await GetIPInfo() : Global.None; return string.Format(ResUI.TestMeOutput, time, ip); } + /// + /// Gets IP information using the default local proxy. + /// private static async Task GetIPInfo() { - var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl; - if (url.IsNullOrEmpty()) - { - return null; - } - - var downloadHandle = new DownloadService(); - var result = await downloadHandle.TryDownloadString(url, true, ""); - if (result == null) - { - return null; - } - - var ipInfo = JsonUtils.Deserialize(result); - if (ipInfo == null) - { - return null; - } - - var ip = ipInfo.ip ?? ipInfo.clientIp ?? ipInfo.ip_addr ?? ipInfo.query; - var country = ipInfo.country_code ?? ipInfo.country ?? ipInfo.countryCode ?? ipInfo.location?.country_code; + var webProxy = await GetWebProxy(); - return $"({country ?? "unknown"}) {ip}"; + var ipInfo = await GetIPInfo(webProxy); + return ipInfo?.ToString() ?? Global.None; } + /// + /// Measures real ping time using configured test URL. + /// private static async Task GetRealPingTimeInfo() { var responseTime = -1; try { - var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); - var webProxy = new WebProxy($"socks5://{Global.Loopback}:{port}"); - var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl; + var webProxy = await GetWebProxy(); for (var i = 0; i < 2; i++) { - responseTime = await GetRealPingTime(url, webProxy, 10); + responseTime = await GetRealPingTime(webProxy, 10); if (responseTime > 0) { break; @@ -66,8 +54,21 @@ private static async Task GetRealPingTimeInfo() return responseTime; } - public static async Task GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout) + /// + /// Creates local SOCKS proxy instance. + /// + private static async Task GetWebProxy() { + var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); + return new WebProxy($"socks5://{Global.Loopback}:{port}"); + } + + /// + /// Measures response time by sending HTTP requests through proxy. + /// + public static async Task GetRealPingTime(IWebProxy? webProxy, int downloadTimeout) + { + var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl; var responseTime = -1; try { @@ -95,4 +96,41 @@ public static async Task GetRealPingTime(string url, IWebProxy? webProxy, i } return responseTime; } + + /// + /// Gets IP and country information through specified proxy. + /// + public static async Task GetIPInfo(IWebProxy? webProxy) + { + try + { + var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl; + if (url.IsNullOrEmpty()) + { + return null; + } + + var downloadHandle = new DownloadService(); + var result = await downloadHandle.TryDownloadString(url, webProxy, ""); + if (result == null) + { + return null; + } + + var ipInfo = JsonUtils.Deserialize(result); + if (ipInfo == null) + { + return null; + } + + var ip = ipInfo.ip ?? ipInfo.clientIp ?? ipInfo.ip_addr ?? ipInfo.query; + var country = ipInfo.country_code ?? ipInfo.country ?? ipInfo.countryCode ?? ipInfo.location?.country_code ?? "unknown"; + + return new IpInfoResult(country, ip); + } + catch + { + return null; + } + } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs index ff7d6d20ea2..0f82242763e 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs @@ -73,6 +73,10 @@ protected static int ToUriQuery(ProfileItem item, string? securityDef, ref Dicti { dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList)); } + if (item.VerifyPeerCertByName.IsNotEmpty()) + { + dicQuery.Add("vcn", Utils.UrlEncode(item.VerifyPeerCertByName)); + } if (item.CertSha.IsNotEmpty()) { dicQuery.Add("pcs", Utils.UrlEncode(item.CertSha)); @@ -227,6 +231,7 @@ protected static int ResolveUriQuery(NameValueCollection query, ref ProfileItem item.SpiderX = GetQueryDecoded(query, "spx"); item.Mldsa65Verify = GetQueryDecoded(query, "pqv"); item.EchConfigList = GetQueryDecoded(query, "ech"); + item.VerifyPeerCertByName = GetQueryDecoded(query, "vcn"); item.CertSha = GetQueryDecoded(query, "pcs"); var finalmaskDecoded = GetQueryDecoded(query, "fm"); diff --git a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs index 611e515916d..7691d874ba2 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs @@ -53,7 +53,9 @@ public class FmtHandler { return ShadowsocksFmt.Resolve(str, out msg); } - else if (str.StartsWith(Global.ProtocolShares[EConfigType.SOCKS])) + else if (str.StartsWith(Global.ProtocolShares[EConfigType.SOCKS]) + || str.StartsWith(Global.SOCKS5Protocol) + || str.StartsWith(Global.SOCKS4Protocol)) { return SocksFmt.Resolve(str, out msg); } @@ -65,7 +67,8 @@ public class FmtHandler { return VLESSFmt.Resolve(str, out msg); } - else if (str.StartsWith(Global.ProtocolShares[EConfigType.Hysteria2]) || str.StartsWith(Global.Hysteria2ProtocolShare)) + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Hysteria2]) + || str.StartsWith(Global.Hysteria2ProtocolShare)) { return Hysteria2Fmt.Resolve(str, out msg); } diff --git a/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs index b3a543010b7..62b36e07382 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs @@ -99,12 +99,17 @@ public class SocksFmt : BaseFmt }; // parse base64 UserInfo var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); - var userInfo = Utils.Base64Decode(rawUserInfo); - var userInfoParts = userInfo.Split([':'], 2); - if (userInfoParts.Length == 2) + if (rawUserInfo.IsNotEmpty()) { - item.Username = userInfoParts.First(); - item.Password = userInfoParts[1]; + var userInfoParts = rawUserInfo.Contains(':') + ? rawUserInfo.Split(":", 2) + : Utils.Base64Decode(rawUserInfo).Split(":", 2); + + if (userInfoParts.Length == 2) + { + item.Username = userInfoParts.First(); + item.Password = userInfoParts.Last(); + } } return item; diff --git a/v2rayN/ServiceLib/Manager/AppManager.cs b/v2rayN/ServiceLib/Manager/AppManager.cs index 34f6b3f1ba8..b532ea1667d 100644 --- a/v2rayN/ServiceLib/Manager/AppManager.cs +++ b/v2rayN/ServiceLib/Manager/AppManager.cs @@ -48,6 +48,13 @@ public bool IsRunningCore(ECoreType type) } } + public Dictionary LastCheckUpdateResults { get; set; } = new(); + + public void SetLastCheckUpdateResult(ECoreType coreType, string result) + { + LastCheckUpdateResults[coreType] = result; + } + #endregion Property #region App diff --git a/v2rayN/ServiceLib/Manager/CertPemManager.cs b/v2rayN/ServiceLib/Manager/CertPemManager.cs index 0a58e2deccf..7f5d388984d 100644 --- a/v2rayN/ServiceLib/Manager/CertPemManager.cs +++ b/v2rayN/ServiceLib/Manager/CertPemManager.cs @@ -4,16 +4,15 @@ namespace ServiceLib.Manager; /// -/// Manager for certificate operations with CA pinning to prevent MITM attacks +/// Manager for certificate operations with CA pinning to prevent MITM attacks /// public class CertPemManager { private static readonly string _tag = "CertPemManager"; private static readonly Lazy _instance = new(() => new()); - public static CertPemManager Instance => _instance.Value; /// - /// Trusted CA certificate thumbprints (SHA256) to prevent MITM attacks + /// Trusted CA certificate thumbprints (SHA256) to prevent MITM attacks /// private static readonly HashSet TrustedCaThumbprints = new(StringComparer.OrdinalIgnoreCase) { @@ -24,21 +23,17 @@ public class CertPemManager "D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4", // Comodo AAA Services root "85A0DD7DD720ADB7FF05F83D542B209DC7FF4528F7D677B18389FEA5E5C49E86", // QuoVadis Root CA 2 "18F1FC7F205DF8ADDDEB7FE007DD57E3AF375A9C4D8D73546BF4F1FED1E18D35", // QuoVadis Root CA 3 - "CECDDC905099D8DADFC5B1D209B737CBE2C18CFB2C10C0FF0BCF0D3286FC1AA2", // XRamp Global CA Root "C3846BF24B9E93CA64274C0EC67C1ECC5E024FFCACD2D74019350E81FE546AE4", // Go Daddy Class 2 CA "1465FA205397B876FAA6F0A9958E5590E40FCC7FAA4FB7C2C8677521FB5FB658", // Starfield Class 2 CA "3E9099B5015E8F486C00BCEA9D111EE721FABA355A89BCF1DF69561E3DC6325C", // DigiCert Assured ID Root CA "4348A0E9444C78CB265E058D5E8944B4D84F9662BD26DB257F8934A443C70161", // DigiCert Global Root CA "7431E5F4C3C1CE4690774F0B61E05440883BA9A01ED00BA6ABD7806ED3B118CF", // DigiCert High Assurance EV Root CA "62DD0BE9B9F50A163EA0F8E75C053B1ECA57EA55C8688F647C6881F2C8357B95", // SwissSign Gold CA - G2 - "F1C1B50AE5A20DD8030EC9F6BC24823DD367B5255759B4E71B61FCE9F7375D73", // SecureTrust CA - "4200F5043AC8590EBB527D209ED1503029FBCBD41CA1B506EC27F15ADE7DAC69", // Secure Global CA "0C2CD63DF7806FA399EDE809116B575BF87989F06518F9808C860503178BAF66", // COMODO Certification Authority "1793927A0614549789ADCE2F8F34F7F0B66D0F3AE3A3B84D21EC15DBBA4FADC7", // COMODO ECC Certification Authority "41C923866AB4CAD6B7AD578081582E020797A6CBDF4FFF78CE8396B38937D7F5", // OISTE WISeKey Global Root GA CA "E3B6A2DB2ED7CE48842F7AC53241C7B71D54144BFB40C11F3F1D0B42F5EEA12D", // Certigna "C0A6F4DC63A24BFDCF54EF2A6A082A0A72DE35803E2FF5FF527AE5D87206DFD5", // ePKI Root Certification Authority - "EAA962C4FA4A6BAFEBE415196D351CCD888D4F53F3FA8AE6D7C466A94E6042BB", // certSIGN ROOT CA "6C61DAC3A2DEF031506BE036D2A6FE401994FBD13DF9C8D466599274C446EC98", // NetLock Arany (Class Gold) Főtanúsítvány "3C5F81FEA5FAB82C64BFA2EAECAFCDE8E077FC8620A7CAE537163DF36EDBF378", // Microsec e-Szigno Root CA 2009 "CBB522D7B7F127AD6A0113865BDF1CD4102E7D0759AF635A7CF4720DC963C53B", // GlobalSign Root CA - R3 @@ -46,10 +41,6 @@ public class CertPemManager "45140B3247EB9CC8C5B4F0D7B53091F73292089E6E5A63E2749DD3ACA9198EDA", // Go Daddy Root Certificate Authority - G2 "2CE1CB0BF9D2F9E102993FBE215152C3B2DD0CABDE1C68E5319B839154DBB7F5", // Starfield Root Certificate Authority - G2 "568D6905A2C88708A4B3025190EDCFEDB1974A606A13C6E5290FCB2AE63EDAB5", // Starfield Services Root Certificate Authority - G2 - "0376AB1D54C5F9803CE4B2E201A0EE7EEF7B57B636E8A93C9B8D4860C96F5FA7", // AffirmTrust Commercial - "0A81EC5A929777F145904AF38D5D509F66B5E2C58FCDB531058B0E17F3F0B41B", // AffirmTrust Networking - "70A73F7F376B60074248904534B11482D5BF0E698ECC498DF52577EBF2E93B9A", // AffirmTrust Premium - "BD71FDF6DA97E4CF62D1647ADD2581B07D79ADF8397EB4ECBA9C5E8488821423", // AffirmTrust Premium ECC "5C58468D55F58E497E743982D2B50010B6D165374ACF83A7D4A32DB768C4408E", // Certum Trusted Network CA "BFD88FE1101C41AE3E801BF8BE56350EE9BAD1A6B9BD515EDC5C6D5B8711AC44", // TWCA Root Certification Authority "513B2CECB810D4CDE5DD85391ADFC6C2DD60D87BB736D2B521484AA47A0EBEF6", // Security Communication RootCA2 @@ -62,7 +53,6 @@ public class CertPemManager "E23D4A036D7B70E9F595B1422079D2B91EDFBB1FB651A0633EAA8A9DC5F80703", // CA Disig Root R2 "9A6EC012E1A7DA9DBE34194D478AD7C0DB1822FB071DF12981496ED104384113", // ACCVRAIZ1 "59769007F7685D0FCD50872F9F95D5755A5B2B457D81F3692B610A98672F0E1B", // TWCA Global Root CA - "DD6936FE21F8F077C123A1A521C12224F72255B73E03A7260693E8A24B0FA389", // TeliaSonera Root CA v1 "91E2F5788D5810EBA7BA58737DE1548A8ECACD014598BC0B143E041B17052552", // T-TeleSec GlobalRoot Class 2 "F356BEA244B7A91EB35D53CA9AD7864ACE018E2D35D5F8F96DDF68A6F41AA474", // Atos TrustedRoot 2011 "8A866FD1B276B57E578E921C65828A2BED58E9F2F288054134B7F1F4BFC9CC74", // QuoVadis Root CA 1 G3 @@ -116,16 +106,12 @@ public class CertPemManager "C741F70F4B2A8D88BF2E71C14122EF53EF10EBA0CFA5E64CFA20F418853073E0", // Microsoft RSA Root Certificate Authority 2017 "BEB00B30839B9BC32C32E4447905950641F26421B15ED089198B518AE2EA1B99", // e-Szigno Root CA 2017 "657CFE2FA73FAA38462571F332A2363A46FCE7020951710702CDFBB6EEDA3305", // certSIGN Root CA G2 - "97552015F5DDFC3C8788C006944555408894450084F100867086BC1A2BB58DC8", // Trustwave Global Certification Authority - "945BBC825EA554F489D1FD51A73DDF2EA624AC7019A05205225C22A78CCFA8B4", // Trustwave Global ECC P256 Certification Authority - "55903859C8C0C3EBB8759ECE4E2557225FF5758BBD38EBD48276601E1BD58097", // Trustwave Global ECC P384 Certification Authority "88F438DCF8FFD1FA8F429115FFE5F82AE1E06E0C70C375FAAD717B34A49E7265", // NAVER Global Root Certification Authority "554153B13D2CF9DDB753BFBE1A4E0AE08D0AA4187058FE60A2B862B2E4B87BCB", // AC RAIZ FNMT-RCM SERVIDORES SEGUROS "319AF0A7729E6F89269C131EA6A3A16FCD86389FDCAB3C47A4A675C161A3F974", // GlobalSign Secure Mail Root R45 "5CBF6FB81FD417EA4128CD6F8172A3C9402094F74AB2ED3A06B4405D04F30B19", // GlobalSign Secure Mail Root E45 "4FA3126D8D3A11D1C4855A4F807CBAD6CF919D3A5A88B03BEA2C6372D93C40C9", // GlobalSign Root R46 "CBB9C44D84B8043E1050EA31A69F514955D7BFD2E2C6B49301019AD61D9F5058", // GlobalSign Root E46 - "9A296A5182D1D451A2E37F439B74DAAFA267523329F90F9A0D2007C334E23C9A", // GLOBALTRUST 2020 "FB8FEC759169B9106B1E511644C618C51304373F6C0643088D8BEFFD1B997599", // ANF Secure Server Root CA "6B328085625318AA50D173C98D8BDA09D57E27413D114CF787A0F5D06C030CF6", // Certum EC-384 CA "FE7696573855773E37A95E7AD4D9CC96C30157C15D31765BA9B15704E1AE78FD", // Certum Trusted Root CA @@ -179,7 +165,6 @@ public class CertPemManager "578AF4DED0853F4E5998DB4AEAF9CBEA8D945F60B620A38D1A3C13B2BC7BA8E1", // Telekom Security TLS ECC Root 2020 "78A656344F947E9CC0F734D9053D32F6742086B6B9CD2CAE4FAE1A2E4EFDE048", // Telekom Security SMIME RSA Root 2023 "EFC65CADBB59ADB6EFE84DA22311B35624B71B3B1EA0DA8B6655174EC8978646", // Telekom Security TLS RSA Root 2023 - "BEF256DAF26E9C69BDEC1602359798F3CAF71821A03E018257C53C65617F3D4A", // FIRMAPROFESIONAL CA ROOT-A WEB "3F63BB2814BE174EC8B6439CF08D6D56F0B7C405883A5648A334424D6B3EC558", // TWCA CYBER Root CA "3A0072D49FFC04E996C59AEB75991D3C340F3615D6FD4DCE90AC0B3D88EAD4F4", // TWCA Global Root CA G2 "3F034BB5704D44B2D08545A02057DE93EBF3905FCE721ACBC730C06DDAEE904E", // SecureSign Root CA12 @@ -200,10 +185,13 @@ public class CertPemManager "B49141502D00663D740F2E7EC340C52800962666121A36D09CF7DD2B90384FB4", // e-Szigno TLS Root CA 2023 }; + public static CertPemManager Instance => _instance.Value; + /// - /// Get certificate in PEM format from a server with CA pinning validation + /// Get certificate in PEM format from a server with CA pinning validation /// - public async Task<(string?, string?)> GetCertPemAsync(string target, string serverName, int timeout = 4, bool allowInsecure = false) + public async Task<(string?, string?)> GetCertPemAsync(string target, string serverName, int timeout = 4, + List? verifyPeerCertByName = null, bool allowInsecure = false) { try { @@ -216,13 +204,14 @@ public class CertPemManager await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token); var callback = new RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) => - ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, allowInsecure)); + ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, verifyPeerCertByName ?? [], + allowInsecure)); await using var ssl = new SslStream(client.GetStream(), false, callback); var sslOptions = new SslClientAuthenticationOptions { TargetHost = serverName, - RemoteCertificateValidationCallback = callback + RemoteCertificateValidationCallback = callback, }; await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token); @@ -249,9 +238,10 @@ public class CertPemManager } /// - /// Get certificate chain in PEM format from a server with CA pinning validation + /// Get certificate chain in PEM format from a server with CA pinning validation /// - public async Task<(List, string?)> GetCertChainPemAsync(string target, string serverName, int timeout = 4, bool allowInsecure = false) + public async Task<(List, string?)> GetCertChainPemAsync(string target, string serverName, int timeout = 4, + List? verifyPeerCertByName = null, bool allowInsecure = false) { var pemList = new List(); try @@ -265,13 +255,14 @@ public class CertPemManager await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token); var callback = new RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) => - ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, allowInsecure)); + ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, verifyPeerCertByName ?? [], + allowInsecure)); await using var ssl = new SslStream(client.GetStream(), false, callback); var sslOptions = new SslClientAuthenticationOptions { TargetHost = serverName, - RemoteCertificateValidationCallback = callback + RemoteCertificateValidationCallback = callback, }; await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token); @@ -301,13 +292,14 @@ public class CertPemManager } /// - /// Validate server certificate with CA pinning + /// Validate server certificate with CA pinning /// - private bool ValidateServerCertificate( + private static bool ValidateServerCertificate( object _, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors, + List verifyPeerCertByName, bool allowInsecure) { if (certificate == null) @@ -321,22 +313,21 @@ private bool ValidateServerCertificate( return true; } - // Check certificate name mismatch - if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch)) - { - return false; - } - // Build certificate chain var cert2 = certificate as X509Certificate2 ?? new X509Certificate2(certificate); var certChain = chain ?? new X509Chain(); - certChain.ChainPolicy.RevocationMode = X509RevocationMode.Online; - certChain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; - certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; - certChain.ChainPolicy.VerificationTime = DateTime.Now; + if (chain == null) + { + certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + certChain.ChainPolicy.VerificationTime = DateTime.UtcNow; - certChain.Build(cert2); + if (!certChain.Build(cert2)) + { + return false; + } + } // Find root CA if (certChain.ChainElements.Count == 0) @@ -344,10 +335,37 @@ private bool ValidateServerCertificate( return false; } - var rootCert = certChain.ChainElements[certChain.ChainElements.Count - 1].Certificate; + var rootCert = certChain.ChainElements[^1].Certificate; var rootThumbprint = rootCert.GetCertHashString(HashAlgorithmName.SHA256); - return TrustedCaThumbprints.Contains(rootThumbprint); + if (!TrustedCaThumbprints.Contains(rootThumbprint)) + { + return false; + } + + if (!sslPolicyErrors.HasFlag( + SslPolicyErrors.RemoteCertificateNameMismatch)) + { + return true; + } + + if (verifyPeerCertByName.Count == 0) + { + return false; + } + + foreach (var ext in cert2.Extensions) + { + if (ext is not X509SubjectAlternativeNameExtension san) + { + continue; + } + + return san.EnumerateDnsNames().Any(dnsName => + verifyPeerCertByName.Contains(dnsName, StringComparer.OrdinalIgnoreCase)); + } + + return false; } public static string ExportCertToPem(X509Certificate2 cert) @@ -358,8 +376,8 @@ public static string ExportCertToPem(X509Certificate2 cert) } /// - /// Parse concatenated PEM certificates string into a list of individual certificates - /// Normalizes format: removes line breaks from base64 content for better compatibility + /// Parse concatenated PEM certificates string into a list of individual certificates + /// Normalizes format: removes line breaks from base64 content for better compatibility /// /// Concatenated PEM certificates string (supports both \r\n and \n line endings) /// List of individual PEM certificate strings with normalized format @@ -411,18 +429,13 @@ public static List ParsePemChain(string pemChain) } /// - /// Concatenate a list of PEM certificates into a single string + /// Concatenate a list of PEM certificates into a single string /// /// List of individual PEM certificate strings /// Concatenated PEM certificates string - public static string ConcatenatePemChain(IEnumerable pemList) + public static string ConcatenatePemChain(IEnumerable? pemList) { - if (pemList == null) - { - return string.Empty; - } - - return string.Concat(pemList); + return pemList == null ? string.Empty : string.Concat(pemList); } public static string GetCertSha256Thumbprint(string pemCert, bool includeColon = false) @@ -431,11 +444,7 @@ public static string GetCertSha256Thumbprint(string pemCert, bool includeColon = { var cert = X509Certificate2.CreateFromPem(pemCert); var thumbprint = cert.GetCertHashString(HashAlgorithmName.SHA256); - if (includeColon) - { - return string.Join(":", thumbprint.Chunk(2).Select(c => new string(c))); - } - return thumbprint; + return includeColon ? string.Join(":", thumbprint.Chunk(2).Select(c => new string(c))) : thumbprint; } catch { diff --git a/v2rayN/ServiceLib/Manager/CoreInfoManager.cs b/v2rayN/ServiceLib/Manager/CoreInfoManager.cs index e4b5c7f30b0..bda3108ae44 100644 --- a/v2rayN/ServiceLib/Manager/CoreInfoManager.cs +++ b/v2rayN/ServiceLib/Manager/CoreInfoManager.cs @@ -50,6 +50,50 @@ public string GetCoreExecFile(CoreInfo? coreInfo, out string msg) return fileName; } + public List GetCheckUpdateCoreTypes() + { + var lst = new List(); + + if (RuntimeInformation.ProcessArchitecture != Architecture.X86) + { + if (IsCheckUpdateSupported(ECoreType.v2rayN)) + { + lst.Add(ECoreType.v2rayN); + } + + if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10)) + { + lst.Add(ECoreType.Xray); + lst.Add(ECoreType.mihomo); + lst.Add(ECoreType.sing_box); + } + } + + return lst; + } + + public bool IsCheckUpdateSupported(ECoreType type) + { + return type switch + { + ECoreType.v2rayN => !Utils.IsPackagedInstall(), + ECoreType.Xray => true, + ECoreType.mihomo => true, + ECoreType.sing_box => true, + _ => false, + }; + } + + public bool GetCheckPreRelease(ECoreType type, bool preRelease) + { + return type switch + { + ECoreType.v2rayN => preRelease, + ECoreType.Xray => preRelease, + _ => false, + }; + } + private void InitCoreInfo() { var urlN = GetCoreUrl(ECoreType.v2rayN); @@ -69,6 +113,7 @@ private void InitCoreInfo() DownloadUrlLinux64 = urlN + "/download/{0}/v2rayN-linux-64.zip", DownloadUrlLinuxArm64 = urlN + "/download/{0}/v2rayN-linux-arm64.zip", DownloadUrlLinuxRiscV64 = urlN + "/download/{0}/v2rayN-linux-riscv64.zip", + DownloadUrlLinuxLoong64 = urlN + "/download/{0}/v2rayN-linux-loong64.zip", DownloadUrlOSX64 = urlN + "/download/{0}/v2rayN-macos-64.zip", DownloadUrlOSXArm64 = urlN + "/download/{0}/v2rayN-macos-arm64.zip", }, @@ -113,6 +158,7 @@ private void InitCoreInfo() DownloadUrlLinux64 = urlXray + "/download/{0}/Xray-linux-64.zip", DownloadUrlLinuxArm64 = urlXray + "/download/{0}/Xray-linux-arm64-v8a.zip", DownloadUrlLinuxRiscV64 = urlXray + "/download/{0}/Xray-linux-riscv64.zip", + DownloadUrlLinuxLoong64 = urlXray + "/download/{0}/Xray-linux-loong64.zip", DownloadUrlOSX64 = urlXray + "/download/{0}/Xray-macos-64.zip", DownloadUrlOSXArm64 = urlXray + "/download/{0}/Xray-macos-arm64-v8a.zip", Match = "Xray", @@ -136,6 +182,7 @@ private void InitCoreInfo() DownloadUrlLinux64 = urlMihomo + "/download/{0}/mihomo-linux-amd64-v1-{0}.gz", DownloadUrlLinuxArm64 = urlMihomo + "/download/{0}/mihomo-linux-arm64-{0}.gz", DownloadUrlLinuxRiscV64 = urlMihomo + "/download/{0}/mihomo-linux-riscv64-{0}.gz", + DownloadUrlLinuxLoong64 = urlMihomo + "/download/{0}/mihomo-linux-loong64-abi2-{0}.gz", DownloadUrlOSX64 = urlMihomo + "/download/{0}/mihomo-darwin-amd64-v1-{0}.gz", DownloadUrlOSXArm64 = urlMihomo + "/download/{0}/mihomo-darwin-arm64-{0}.gz", Match = "Mihomo", @@ -179,6 +226,7 @@ private void InitCoreInfo() DownloadUrlLinux64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-amd64.tar.gz", DownloadUrlLinuxArm64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-arm64.tar.gz", DownloadUrlLinuxRiscV64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-riscv64.tar.gz", + DownloadUrlLinuxLoong64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-loong64.tar.gz", DownloadUrlOSX64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-amd64.tar.gz", DownloadUrlOSXArm64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-arm64.tar.gz", Match = "sing-box", @@ -270,6 +318,7 @@ private static string GetCoreUrl(ECoreType eCoreType) names.Add("mihomo-linux-amd64"); names.Add("mihomo-linux-arm64"); names.Add("mihomo-linux-riscv64"); + names.Add("mihomo-linux-loong64-abi2"); } else if (Utils.IsMacOS()) { diff --git a/v2rayN/ServiceLib/Manager/ProfileExManager.cs b/v2rayN/ServiceLib/Manager/ProfileExManager.cs index 739bd550a7b..cbe90a09d61 100644 --- a/v2rayN/ServiceLib/Manager/ProfileExManager.cs +++ b/v2rayN/ServiceLib/Manager/ProfileExManager.cs @@ -150,6 +150,14 @@ public void SetTestMessage(string indexId, string message) IndexIdEnqueue(indexId); } + public void SetTestIpInfo(string indexId, string ipInfo) + { + var profileEx = GetProfileExItem(indexId); + + profileEx.IpInfo = ipInfo; + IndexIdEnqueue(indexId); + } + public void SetSort(string indexId, int sort) { var profileEx = GetProfileExItem(indexId); diff --git a/v2rayN/ServiceLib/Manager/TaskManager.cs b/v2rayN/ServiceLib/Manager/TaskManager.cs index d1c7315785f..1e5b0c706da 100644 --- a/v2rayN/ServiceLib/Manager/TaskManager.cs +++ b/v2rayN/ServiceLib/Manager/TaskManager.cs @@ -70,6 +70,18 @@ private async Task ScheduledTasks() } } + //Execute once 24 hour + if (numOfExecuted % 1440 == 1) + { + try + { + await UpdateTaskRunCheckUpdate(); + } + catch (Exception ex) + { + Logging.SaveLog("ScheduledTasks - UpdateTaskRunCheckUpdate", ex); + } + } numOfExecuted++; } } @@ -117,4 +129,23 @@ private async Task UpdateTaskRunGeo(int hours) }).UpdateGeoFileAll(); } } + + private async Task UpdateTaskRunCheckUpdate() + { + Logging.SaveLog("Execute check update"); + + var updateService = new UpdateService(_config, async (success, msg) => await Task.CompletedTask); + + var msgs = await updateService.CheckHasUpdateOnlyAll(_config.CheckUpdateItem.CheckPreReleaseUpdate); + foreach (var msg in msgs) + { + await _updateFunc?.Invoke(false, msg); + } + NoticeManager.Instance.Enqueue(string.Join("\n", msgs)); + + if (msgs.Count > 0) + { + AppEvents.HasUpdateNotified.Publish(true); + } + } } diff --git a/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs b/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs index e9e7f5f8577..e3026184ad0 100644 --- a/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs @@ -148,6 +148,7 @@ public class TunModeItem public bool EnableIPv6Address { get; set; } public string IcmpRouting { get; set; } public bool EnableLegacyProtect { get; set; } + public List? RouteExcludeAddress { get; set; } } [Serializable] @@ -159,6 +160,8 @@ public class SpeedTestItem public int MixedConcurrencyCount { get; set; } public string IPAPIUrl { get; set; } public string UdpTestTarget { get; set; } + public int? SpeedTestPageSize { get; set; } + public int? SpeedTestDelayInterval { get; set; } } [Serializable] diff --git a/v2rayN/ServiceLib/Models/CoreConfigs/CoreInfo.cs b/v2rayN/ServiceLib/Models/CoreConfigs/CoreInfo.cs index dc55db91840..1cb23af03da 100644 --- a/v2rayN/ServiceLib/Models/CoreConfigs/CoreInfo.cs +++ b/v2rayN/ServiceLib/Models/CoreConfigs/CoreInfo.cs @@ -13,6 +13,7 @@ public class CoreInfo public string? DownloadUrlLinux64 { get; set; } public string? DownloadUrlLinuxArm64 { get; set; } public string? DownloadUrlLinuxRiscV64 { get; set; } + public string? DownloadUrlLinuxLoong64 { get; set; } public string? DownloadUrlOSX64 { get; set; } public string? DownloadUrlOSXArm64 { get; set; } public string? Match { get; set; } diff --git a/v2rayN/ServiceLib/Models/CoreConfigs/SingboxConfig.cs b/v2rayN/ServiceLib/Models/CoreConfigs/SingboxConfig.cs index 1514cae4524..a5249acb778 100644 --- a/v2rayN/ServiceLib/Models/CoreConfigs/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/CoreConfigs/SingboxConfig.cs @@ -110,6 +110,7 @@ public class Inbound4Sbox public bool? endpoint_independent_nat { get; set; } public string? stack { get; set; } public List users { get; set; } + public List? route_exclude_address { get; set; } } public class User4Sbox diff --git a/v2rayN/ServiceLib/Models/CoreConfigs/V2rayConfig.cs b/v2rayN/ServiceLib/Models/CoreConfigs/V2rayConfig.cs index 4daac3c68f2..c2dd39fe107 100644 --- a/v2rayN/ServiceLib/Models/CoreConfigs/V2rayConfig.cs +++ b/v2rayN/ServiceLib/Models/CoreConfigs/V2rayConfig.cs @@ -86,7 +86,7 @@ public class Inboundsettings4Ray public string? autoOutboundsInterface { get; set; } - // public List? dns { get; set; } + public List? dns { get; set; } } public class UsersItem4Ray @@ -276,6 +276,7 @@ public class BalancersItem4Ray public List? selector { get; set; } public BalancersStrategy4Ray? strategy { get; set; } public string? tag { get; set; } + public string? fallbackTag { get; set; } } public class BalancersStrategy4Ray @@ -372,6 +373,7 @@ public class TlsSettings4Ray public string? spiderX { get; set; } public string? mldsa65Verify { get; set; } public List? certificates { get; set; } + public string? verifyPeerCertByName { get; set; } public string? pinnedPeerCertSha256 { get; set; } public bool? disableSystemRoot { get; set; } public string? echConfigList { get; set; } diff --git a/v2rayN/ServiceLib/Models/Dto/CheckUpdateModel.cs b/v2rayN/ServiceLib/Models/Dto/CheckUpdateModel.cs index 8ccb8a39d47..bb9b607536e 100644 --- a/v2rayN/ServiceLib/Models/Dto/CheckUpdateModel.cs +++ b/v2rayN/ServiceLib/Models/Dto/CheckUpdateModel.cs @@ -3,8 +3,10 @@ namespace ServiceLib.Models.Dto; public class CheckUpdateModel : ReactiveObject { public bool? IsSelected { get; set; } - public string? CoreType { get; set; } + public ECoreType? CoreType { get; set; } [Reactive] public string? Remarks { get; set; } public string? FileName { get; set; } public bool? IsFinished { get; set; } + public bool IsGeoFile { get; set; } + public string CoreTypeForStorage => IsGeoFile ? "GeoFiles" : (CoreType?.ToString() ?? ""); } diff --git a/v2rayN/ServiceLib/Models/Dto/IPAPIInfo.cs b/v2rayN/ServiceLib/Models/Dto/IPAPIInfo.cs index e1e14363fbb..0257ab67b4b 100644 --- a/v2rayN/ServiceLib/Models/Dto/IPAPIInfo.cs +++ b/v2rayN/ServiceLib/Models/Dto/IPAPIInfo.cs @@ -17,3 +17,12 @@ public class LocationInfo { public string? country_code { get; set; } } + +public readonly record struct IpInfoResult(string Country, string? Ip) +{ + public override string ToString() + { + var emoji = Utils.IsWindows() ? null : Country.CountryToEmoji(); + return $"{emoji}({Country}) {Ip}"; + } +} diff --git a/v2rayN/ServiceLib/Models/Dto/ProfileItemModel.cs b/v2rayN/ServiceLib/Models/Dto/ProfileItemModel.cs index b75835a4fea..7c8b96df874 100644 --- a/v2rayN/ServiceLib/Models/Dto/ProfileItemModel.cs +++ b/v2rayN/ServiceLib/Models/Dto/ProfileItemModel.cs @@ -26,6 +26,9 @@ public class ProfileItemModel : ReactiveObject [Reactive] public string SpeedVal { get; set; } + [Reactive] + public string IpInfo { get; set; } + [Reactive] public string TodayUp { get; set; } diff --git a/v2rayN/ServiceLib/Models/Dto/SpeedTestResult.cs b/v2rayN/ServiceLib/Models/Dto/SpeedTestResult.cs index bac7bf0f90d..2e0ab1a5b26 100644 --- a/v2rayN/ServiceLib/Models/Dto/SpeedTestResult.cs +++ b/v2rayN/ServiceLib/Models/Dto/SpeedTestResult.cs @@ -8,4 +8,6 @@ public class SpeedTestResult public string? Delay { get; set; } public string? Speed { get; set; } + + public string? IpInfo { get; set; } } diff --git a/v2rayN/ServiceLib/Models/Entities/ProfileExItem.cs b/v2rayN/ServiceLib/Models/Entities/ProfileExItem.cs index 7d2cc7895c2..85d1d53674e 100644 --- a/v2rayN/ServiceLib/Models/Entities/ProfileExItem.cs +++ b/v2rayN/ServiceLib/Models/Entities/ProfileExItem.cs @@ -10,4 +10,5 @@ public class ProfileExItem public decimal Speed { get; set; } public int Sort { get; set; } public string? Message { get; set; } + public string? IpInfo { get; set; } } diff --git a/v2rayN/ServiceLib/Models/Entities/ProfileItem.cs b/v2rayN/ServiceLib/Models/Entities/ProfileItem.cs index cb5deabdcfc..94dd3a69b88 100644 --- a/v2rayN/ServiceLib/Models/Entities/ProfileItem.cs +++ b/v2rayN/ServiceLib/Models/Entities/ProfileItem.cs @@ -191,6 +191,7 @@ public void SetTransportExtra(TransportExtraItem transportExtra) public string Cert { get; set; } public string CertSha { get; set; } public string EchConfigList { get; set; } + public string VerifyPeerCertByName { get; set; } public string Finalmask { get; set; } public string ProtoExtra { get; set; } diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index cab76bb5ef5..b7cf06dacb6 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -564,6 +564,15 @@ public static string LvTestDelay { } } + /// + /// 查找类似 IP Info 的本地化字符串。 + /// + public static string LvTestIpInfo { + get { + return ResourceManager.GetString("LvTestIpInfo", resourceCulture); + } + } + /// /// 查找类似 Speed (MB/s) 的本地化字符串。 /// @@ -870,6 +879,15 @@ public static string menuBackupAndRestore { } } + /// + /// 查找类似 Only Check 的本地化字符串。 + /// + public static string menuCheckOnly { + get { + return ResourceManager.GetString("menuCheckOnly", resourceCulture); + } + } + /// /// 查找类似 Check Update 的本地化字符串。 /// @@ -1302,6 +1320,15 @@ public static string menuMsgViewSelectAll { } } + /// + /// 查找类似 New Update 的本地化字符串。 + /// + public static string menuNewUpdate { + get { + return ResourceManager.GetString("menuNewUpdate", resourceCulture); + } + } + /// /// 查找类似 Open the storage location 的本地化字符串。 /// @@ -1887,6 +1914,24 @@ public static string menuWebsiteItem { } } + /// + /// 查找类似 Warning: Xray will disable allowInsecure (skip certificate verification) in August 2026. Please switch to pinnedPeerCertSha256 (fixed certificate fingerprint) as soon as possible. allowInsecure will not be usable after its expiration. 的本地化字符串。 + /// + public static string MsgAllowInsecureDeprecated { + get { + return ResourceManager.GetString("MsgAllowInsecureDeprecated", resourceCulture); + } + } + + /// + /// 查找类似 {0} has a new version available: {1} 的本地化字符串。 + /// + public static string MsgCheckUpdateHasNewVersion { + get { + return ResourceManager.GetString("MsgCheckUpdateHasNewVersion", resourceCulture); + } + } + /// /// 查找类似 Core '{0}' does not support network type '{1}' 的本地化字符串。 /// @@ -2040,6 +2085,15 @@ public static string MsgNeedUrl { } } + /// + /// 查找类似 Not Support 的本地化字符串。 + /// + public static string MsgNotSupport { + get { + return ResourceManager.GetString("MsgNotSupport", resourceCulture); + } + } + /// /// 查找类似 Not support protocol '{0}' 的本地化字符串。 /// @@ -2175,6 +2229,15 @@ public static string MsgSubscriptionPrevProfileNotFound { } } + /// + /// 查找类似 Invalid address in TUN route exclude list: {0} 的本地化字符串。 + /// + public static string MsgTunRouteExcludeInvalidAddress { + get { + return ResourceManager.GetString("MsgTunRouteExcludeInvalidAddress", resourceCulture); + } + } + /// /// 查找类似 Unpacking... 的本地化字符串。 /// @@ -3484,7 +3547,25 @@ public static string TbRoundRobin { } /// - /// 查找类似 socks: local port, socks2: second local port, socks3: LAN port 的本地化字符串。 + /// 查找类似 Route Exclude Address 的本地化字符串。 + /// + public static string TbRouteExcludeAddress { + get { + return ResourceManager.GetString("TbRouteExcludeAddress", resourceCulture); + } + } + + /// + /// 查找类似 Use commas (,) to separate. 的本地化字符串。 + /// + public static string TbRouteExcludeAddressTip { + get { + return ResourceManager.GetString("TbRouteExcludeAddressTip", resourceCulture); + } + } + + /// + /// 查找类似 tun: TUN inbound, socks: local port, socks2: second local port, socks3: LAN port 的本地化字符串。 /// public static string TbRoutingInboundTagTips { get { @@ -3925,7 +4006,7 @@ public static string TbSettingsEnableCacheFile4Sbox { } /// - /// 查找类似 Check for pre-release updates 的本地化字符串。 + /// 查找类似 Check for pre-release 的本地化字符串。 /// public static string TbSettingsEnableCheckPreReleaseUpdate { get { @@ -4653,6 +4734,15 @@ public static string TbValidateDirectExpectedIPsDesc { } } + /// + /// 查找类似 Verify Peer Cert By Name 的本地化字符串。 + /// + public static string TbVerifyPeerCertByName { + get { + return ResourceManager.GetString("TbVerifyPeerCertByName", resourceCulture); + } + } + /// /// 查找类似 The delay: {0} ms, {1} 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 9ac5aaa8bf8..246417a6086 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1731,4 +1731,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Export v2rayN Internal Share Link to Clipboard + + {0} has a new version available: {1} + + + Only Check + + + Not Support + + + IP Info + + + New Update + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index bca6c2b7c47..b97dfcb5a5e 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1675,7 +1675,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Ajouter [NaïveProxy] - Insecure Concurrency + Concurrence non sûre Nom d’utilisateur @@ -1699,10 +1699,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Allow insecure cert fetch (self-signed) - Only for fetching self-signed certificates. This may expose you to MITM risks. + Pour obtenir des certificats auto-signés uniquement. Risque MITM. - Adresse sortante locale (SendThrough) + Adresse de sortie locale (SendThrough) Pour environnements multi-interfaces, entrez l'adresse IPv4 de la machine locale @@ -1717,15 +1717,30 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Pour les environnements multi-interfaces, entrez le nom de l'interface à lier. Ne fonctionne que sur les systèmes Windows et en mode TUN - Test Configurations UDP Delay + Tester la latence UDP des profils - UDP Test Url + URL de test UDP PreSharedKey - Export v2rayN Internal Share Link to Clipboard + Exporter le lien interne v2rayN vers le presse-papiers + + + {0} a une nouvelle version disponible: {1} + + + Uniquement vérifier + + + Non supporté + + + Détails IP + + + Nouvelle MAJ \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index ab481d0e675..f1e159c3b45 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1731,4 +1731,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Export v2rayN Internal Share Link to Clipboard + + {0} has a new version available: {1} + + + Only Check + + + Not Support + + + IP Info + + + New Update + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index b80c4e56097..ec97d10a905 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -691,7 +691,7 @@ Automatically adjust column width after subscription update - Check for pre-release updates + Check for pre-release Exception @@ -1336,7 +1336,7 @@ Enable second mixed port - socks: local port, socks2: second local port, socks3: LAN port + tun: TUN inbound, socks: local port, socks2: second local port, socks3: LAN port Theme @@ -1581,6 +1581,9 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if EchConfigList + + Verify Peer Cert By Name + Full certificate (chain), PEM format @@ -1731,4 +1734,31 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Export v2rayN Internal Share Link to Clipboard + + {0} has a new version available: {1} + + + Only Check + + + Not Support + + + IP Info + + + New Update + + + Warning: Xray will disable allowInsecure (skip certificate verification) in August 2026. Please switch to pinnedPeerCertSha256 (fixed certificate fingerprint) as soon as possible. allowInsecure will not be usable after its expiration. + + + Route Exclude Address + + + Use commas (,) to separate. + + + Invalid address in TUN route exclude list: {0} + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 0eb0cb816aa..f74e45c28ca 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -121,7 +121,7 @@ Ссылка успешно скопирована в буфер обмена - Сначала проверьте настройки сервера + Недопустимая конфигурация, проверьте или выберите заново Недопустимый формат конфигурации @@ -166,7 +166,7 @@ Некорректная конфигурация. Проверьте - Исходная конфигурация + Инициализация конфигурации {0} {1} является последней версией @@ -256,7 +256,7 @@ Операция не удалась, проверьте и повторите попытку - Введите примечания + Введите псевдоним Выберите метод шифрования @@ -307,7 +307,7 @@ {0}: одно из обязательных полей - Примечания + Псевдоним URL (необязательно) @@ -334,13 +334,13 @@ Введите корректный пользовательский DNS - *Путь ws/http upgrade/xhttp + *Путь ws/httpupgrade/xhttp *HTTP2-путь - *QUIC-ключ / KCP-seed + *Ключ шифрования QUIC Имя сервиса gRPC @@ -349,13 +349,13 @@ *http-хосты, разделённые запятыми (,) - *Хост ws/http upgrade/xhttp + *Хост ws/httpupgrade/xhttp *HTTP2-хосты, разделённые запятыми (,) - Безопасность *QUIC + *Метод шифрования QUIC Тип raw-камуфляжа @@ -400,7 +400,7 @@ Фильтр, нажмите Enter для выполнения - Проверить обновления + Обновить Закрыть @@ -415,13 +415,13 @@ Помощь - Настройки + Параметры Продвижение - Перезагрузить + Перезапустить службу Настройки маршрутизации @@ -445,10 +445,10 @@ Настройки групп подписки - Обновить подписку без прокси + Обновить все подписки без прокси - Обновить подписку с прокси + Обновить все подписки через прокси Системный прокси @@ -562,7 +562,7 @@ Сортировка - User-Agent + User-Agent (необязательно) Отмена @@ -577,7 +577,7 @@ Адрес - Разрешить небезопасные + Пропустить проверку сертификата (allowInsecure) ALPN @@ -616,10 +616,10 @@ TLS - *По-умолчанию raw + *По умолчанию raw; при неверном выборе подключение будет невозможно - Ядро + Тип ядра Управление потоком (Flow) @@ -640,7 +640,7 @@ Шифрование - Пользователь (необязательно) + Имя пользователя (необязательно) Шифрование @@ -649,7 +649,7 @@ Порт SOCKS - * После установки этого значения служба SOCKS будет запущена с использованием Xray/sing-box(TUN) для обеспечения таких функций, как отображение скорости + *Порт SOCKS пользовательской конфигурации можно не указывать. Если он задан, Xray/sing-box (TUN) дополнительно запустит предварительную службу SOCKS для разделения трафика, отображения скорости и других функций Обзор @@ -682,7 +682,7 @@ Настройки типа ядра - Разрешить небезопасные + По умолчанию пропускать проверку сертификата (allowInsecure) «Freedom»: стратегия обработки доменов исходящего трафика @@ -703,7 +703,7 @@ Показывать скорость в реальном времени (требуется перезапуск) - Сохранить старые при удалении дублей + При удалении дубликатов сохранять элементы с меньшим порядковым номером Записывать логи @@ -730,13 +730,13 @@ Смешанный порт - Запускать при старте системы + Запускать при старте системы (может не сработать) Включить статистику трафика (требуется перезапуск) - URL конвертации подписок + URL конвертации подписок (необязательно) Настройки системного прокси @@ -787,13 +787,13 @@ Запущено от имени администратора - Спуститься вниз + Переместить в самый низ Вниз - Подняться наверх + Переместить в самый верх Вверх @@ -805,7 +805,7 @@ Веб-сайт {0} - Добавить + Добавить набор правил Импортировать правила @@ -841,7 +841,7 @@ Добавить правило - Экспорт выделенных правил + Экспортировать выбранные правила в буфер обмена Список правил @@ -859,7 +859,7 @@ Документация RuleObject - Поддерживаются DNS-объекты, нажмите для просмотра документации + Заполните DNS-объект (формат JSON), нажмите для просмотра документации Для группы оставьте это поле пустым @@ -871,13 +871,13 @@ Системные прокси изменены - Только маршрут + Только для маршрутизации (routeOnly) Не использовать прокси для локальных (интранет) адресов - Тест задержки и скорости всех серверов (Ctrl+E) + Многопоточный тест задержки и скорости (Ctrl+E) Задержка (мс) @@ -886,10 +886,10 @@ Скорость (МБ/с) - Не удалось запустить ядро, посмотрите логи + Не удалось запустить ядро, см. сообщение - Фильтр по примечаниям (регулярные выражения) + Фильтр по псевдониму (регулярные выражения) Отображать журнал @@ -937,7 +937,7 @@ Шрифт (требуется перезапуск) - Скопируйте файл шрифта TTF/TTC в каталог guiFonts и заново откройте окно настроек + Скопируйте файл шрифта TTF/TTC в каталог guiFonts и перезапустите приложение PAC-порт = +3; Xray API порт = +4; mihomo API порт = +5; @@ -1009,7 +1009,7 @@ Пользовательский DNS для sing-box - Заполните структуру DNS, нажмите, чтобы открыть документ + Заполните структуру DNS (формат JSON), нажмите для просмотра документации Нажмите, чтобы импортировать конфигурацию DNS по умолчанию @@ -1033,7 +1033,7 @@ Добавить сервер [Hysteria2] - Максимальная пропускная способность Hysteria (загрузка/отдача) + Максимальная пропускная способность Hysteria (отдача/загрузка) Использовать системный файл hosts @@ -1045,13 +1045,13 @@ Управление перегрузками - Примечания к предыдущему прокси + Псевдоним предыдущего прокси - Примечания к следующему прокси + Псевдоним следующего прокси - Убедитесь, что примечание существует и является уникальным + Убедитесь, что псевдоним существует и является уникальным Автоматическая маршрутизация @@ -1177,7 +1177,7 @@ Глобальный режим - Не менять + Как в исходной конфигурации Правила @@ -1210,7 +1210,7 @@ Экспорт ссылок в формате Base64 в буфер обмена - Экспортировать полную конфигурацию в буфер обмена + Экспортировать выбранную полную конфигурацию в буфер обмена Показать или скрыть главное окно @@ -1300,7 +1300,7 @@ Не используйте небезопасный адрес подписки по протоколу HTTP - Установите шрифт в систему, выберите или введите имя шрифта, перезапустите настройки + Установите шрифт в систему, выберите или введите имя шрифта и перезапустите приложение Вы уверены, что хотите выйти? @@ -1318,13 +1318,13 @@ XHTTP-режим - Сырой JSON, формат: { XHTTP Object } + Сырой JSON, формат: { XHTTPObject } Сворачивать в трей при закрытии окна - Количество одновременно выполняемых тестов при многоэтапном тестировании + Количество параллельных задач при многопоточном тестировании Исключения: не использовать прокси для указанных адресов. Разделяйте запятой (,) @@ -1336,7 +1336,7 @@ Включить второй смешанный порт - socks: локальный порт, socks2: второй локальный порт, socks3: LAN-порт + tun: входящий TUN, socks: локальный порт, socks2: второй локальный порт, socks3: LAN-порт Тема @@ -1357,7 +1357,7 @@ Удалено {0} недействительных - Диапазон портов сервера + Диапазон портов для переключения (Port Hopping) Заменит указанный порт, перечисляйте через запятую (,) @@ -1369,7 +1369,7 @@ URL для тестирования текущего соединения - Можно указать название (Remarks) из конфигурации, убедитесь, что оно существует и уникально + Можно указать псевдоним из конфигурации, убедитесь, что он существует и уникален Неверный пароль, попробуйте ещё раз. @@ -1384,7 +1384,7 @@ Удалённый DNS - Внутренний DNS + DNS для прямых подключений Стратегия разрешения прямых соединений @@ -1489,13 +1489,13 @@ Тип группы политик - Добавить группу политик + Добавить группу политик Добавить цепочку прокси - Добавить дочернюю конфигурацию + Добавить дочернюю конфигурацию Удалить дочернюю конфигурацию @@ -1534,7 +1534,7 @@ Bootstrap DNS - Разрешает домены DNS-серверов, требуется IP-адрес + Для разрешения доменных имён DNS-серверов необходимо указать IP-адрес Тест реальной задержки @@ -1547,7 +1547,7 @@ Привязанный сертификат (заполните любое из полей) -При указании сертификат будет привязан, а «Разрешить небезопасные» отключится. +При указании сертификат будет привязан, а «Пропустить проверку сертификата» отключится. Получение сертификата может завершиться неудачей при использовании самоподписанного сертификата или при наличии ненадёжного / вредоносного ЦС в системе. @@ -1731,4 +1731,19 @@ Экспорт внутренней ссылки v2rayN в буфер обмена + + Доступна новая версия {0}: {1} + + + Проверить + + + Не поддерживается + + + Информация об IP + + + Доступно обновление + \ 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 4b8e035191a..4654b723948 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -691,7 +691,7 @@ 自动调整配置列宽在更新订阅后 - 检查 Pre-Release 更新 (请谨慎启用) + 检查 Pre-Release 例外 @@ -1333,7 +1333,7 @@ 开启第二个本地监听端口 - Socks:本地端口,Socks2:第二个本地端口,Socks3:局域网端口 + Tun:TUN 入站,Socks:本地端口,Socks2:第二个本地端口,Socks3:局域网端口 主题 @@ -1728,4 +1728,34 @@ 导出 v2rayN 内部分享链接至剪贴板 (多选) + + {0} 有新版本可用:{1} + + + 仅检查 + + + 不支持 + + + IP 信息 + + + 有更新 + + + 警告:Xray 将在 2026.8.1 禁用跳过证书验证 allowInsecure ,请尽快改用证书固定指纹 pinnedPeerCertSha256。到期后无法使用 + + + Verify Peer Cert By Name + + + 路由排除地址 + + + 使用逗号 (,) 分隔 + + + TUN 路由排除列表包含无效地址:{0} + \ 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 1a8d6675f1b..2f1c1857cf6 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -691,7 +691,7 @@ 在更新訂閱後自動調整列寬 - 檢查 Pre-Release 更新 (請謹慎啟用) + 檢查 Pre-Release 例外 @@ -1728,4 +1728,31 @@ 匯出 v2rayN 內部分享連結至剪貼簿(多選) + + {0} 有新版本可用:{1} + + + 僅檢查 + + + 不支援 + + + IP 資訊 + + + 有更新 + + + 警告:Xray 將在 2026.8.1 停用跳過憑證驗證 allowInsecure ,請盡快改用憑證固定指紋 pinnedPeerCertSha256。到期後無法使用 allowInsecure。 + + + Verify Peer Cert By Name + + + 路由排除位址 + + + 使用逗號 (,) 分隔 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Sample/SampleTunInbound b/v2rayN/ServiceLib/Sample/SampleTunInbound index 95cefbc18a1..2edc5ea0643 100644 --- a/v2rayN/ServiceLib/Sample/SampleTunInbound +++ b/v2rayN/ServiceLib/Sample/SampleTunInbound @@ -8,6 +8,9 @@ "172.18.0.1/30", "fdfe:dcba:9876::1/126" ], + "dns": [ + "172.18.0.1" + ], "autoSystemRoutingTable": [ "0.0.0.0/0", "::/0" diff --git a/v2rayN/ServiceLib/Sample/tun_singbox_inbound b/v2rayN/ServiceLib/Sample/tun_singbox_inbound index db5b0b32e0e..b23346c161e 100644 --- a/v2rayN/ServiceLib/Sample/tun_singbox_inbound +++ b/v2rayN/ServiceLib/Sample/tun_singbox_inbound @@ -1,6 +1,6 @@ { "type": "tun", - "tag": "tun-in", + "tag": "tun", "interface_name": "singbox_tun", "address": [ "172.18.0.1/30", diff --git a/v2rayN/ServiceLib/ServiceLib.csproj b/v2rayN/ServiceLib/ServiceLib.csproj index 6a506e36f95..5b0b807261b 100644 --- a/v2rayN/ServiceLib/ServiceLib.csproj +++ b/v2rayN/ServiceLib/ServiceLib.csproj @@ -6,6 +6,7 @@ + true diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs index 2e13c624b25..3aa06d4e058 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs @@ -71,6 +71,7 @@ private void GenInbounds() { tunInbound.address = ["172.18.0.1/30"]; } + tunInbound.route_exclude_address = _config.TunModeItem.RouteExcludeAddress; _coreConfig.inbounds.Add(tunInbound); } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs index 2b6bdf2b647..b39312ebd0c 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs @@ -87,8 +87,14 @@ private void GenRouting() }); _coreConfig.route.rules.Add(new() { - protocol = ["dns"], - action = "hijack-dns" + type = "logical", + mode = "or", + action = "hijack-dns", + rules = + [ + new() { port = [53] }, + new() { protocol = ["dns"] }, + ], }); } else @@ -96,7 +102,7 @@ private void GenRouting() _coreConfig.route.rules.Add(new() { port = [53], - action = "hijack-dns" + action = "hijack-dns", }); } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs index f294de460e2..78050667332 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs @@ -104,6 +104,7 @@ private void GenBalancer(EMultipleLoad multipleLoad, string selector = Global.Pr }, }, tag = balancerTag, + fallbackTag = multipleLoad == EMultipleLoad.Fallback ? Global.DirectTag : null, }; _coreConfig.routing.balancers ??= new(); _coreConfig.routing.balancers.Add(balancer); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs index 239c68add93..7f950a83b32 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs @@ -30,14 +30,19 @@ private void GenInbounds() inbound3.listen = listen; _coreConfig.inbounds.Add(inbound3); - //auth + // auth if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) { inbound3.settings.auth = "password"; - inbound3.settings.accounts = new List - { - new() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } - }; + inbound3.settings.accounts = + [ + new() + { + user = _config.Inbound.First().User, + pass = _config.Inbound.First().Pass, + }, + + ]; } } else @@ -53,12 +58,15 @@ private void GenInbounds() { _config.TunModeItem.Mtu = Global.TunMtus.First(); } - var tunInbound = JsonUtils.Deserialize(EmbedUtils.GetEmbedText(Global.V2raySampleTunInbound)) ?? new Inbounds4Ray { }; + var tunInbound = + JsonUtils.Deserialize(EmbedUtils.GetEmbedText(Global.V2raySampleTunInbound)) ?? + new Inbounds4Ray(); tunInbound.settings.name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "xray_tun"; tunInbound.settings.MTU = _config.TunModeItem.Mtu; - if (_config.TunModeItem.EnableIPv6Address == false) + if (!_config.TunModeItem.EnableIPv6Address) { tunInbound.settings.gateway = ["172.18.0.1/30"]; + tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"]; } var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx(); if (!bindInterface.IsNullOrEmpty()) @@ -66,6 +74,53 @@ private void GenInbounds() tunInbound.settings.autoOutboundsInterface = bindInterface; } tunInbound.sniffing = inbound.sniffing; + + if (_config.TunModeItem.RouteExcludeAddress is { Count: > 0 }) + { + var wholeInternet = IPNetwork2.Parse("0.0.0.0/0"); + var wholeInternetV6 = IPNetwork2.Parse("::/0"); + + var excludeList = _config.TunModeItem.RouteExcludeAddress.Select(IPNetwork2.Parse) + .Where(x => x != null).ToList(); + + var includeList = new List { wholeInternet }; + var includeListV6 = new List { wholeInternetV6 }; + + foreach (var exclude in excludeList) + { + var temp = new List(); + if (exclude.AddressFamily == AddressFamily.InterNetwork) + { + foreach (var net in includeList) + { + temp.AddRange(net.Subtract(exclude)); + } + includeList = temp; + } + else if (exclude.AddressFamily == AddressFamily.InterNetworkV6) + { + foreach (var net in includeListV6) + { + temp.AddRange(net.Subtract(exclude)); + } + includeListV6 = temp; + } + } + + includeList = IPNetwork2.Supernet(includeList.ToArray()).ToList(); + includeListV6 = IPNetwork2.Supernet(includeListV6.ToArray()).ToList(); + + if (_config.TunModeItem.EnableIPv6Address) + { + tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString()) + .Concat(includeListV6.Select(x => x.ToString())).ToList(); + } + else + { + tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString()).ToList(); + } + } + _coreConfig.inbounds.Add(tunInbound); } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs index 0cd35085b83..beb0aa5b197 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -384,6 +384,7 @@ private void FillBoundStreamSettings(Outbounds4Ray outbound) alpn = _node.GetAlpn(), fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint, echConfigList = _node.EchConfigList.NullIfEmpty(), + verifyPeerCertByName = _node.VerifyPeerCertByName.NullIfEmpty(), }; if (sni.IsNotEmpty()) { diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs index 4f7431fc71c..c3bcf61643c 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs @@ -27,6 +27,7 @@ private void GenRouting() }); _coreConfig.routing.rules.Add(new() { + inboundTag = ["tun"], port = "53", outboundTag = Global.DnsOutboundTag, }); diff --git a/v2rayN/ServiceLib/Services/DownloadService.cs b/v2rayN/ServiceLib/Services/DownloadService.cs index 77d3a7c162a..1dbdfae243e 100644 --- a/v2rayN/ServiceLib/Services/DownloadService.cs +++ b/v2rayN/ServiceLib/Services/DownloadService.cs @@ -3,7 +3,7 @@ namespace ServiceLib.Services; /// -///Download +/// Download /// public class DownloadService { @@ -13,7 +13,10 @@ public class DownloadService private static readonly string _tag = "DownloadService"; - public async Task DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Func updateFunc) + /// + /// Downloads data with the specified proxy and reports progress messages. + /// + public async Task DownloadDataAsync(string url, IWebProxy webProxy, int downloadTimeout, Func updateFunc) { try { @@ -36,6 +39,9 @@ await DownloaderHelper.Instance.DownloadDataAsync4Speed(webProxy, return 0; } + /// + /// Downloads a file and reports progress through events. + /// public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout) { try @@ -64,6 +70,9 @@ await DownloaderHelper.Instance.DownloadFileAsync(webProxy, } } + /// + /// Gets redirect target URL without following redirects automatically. + /// public async Task UrlRedirectAsync(string url, bool blProxy) { var webRequestHandler = new SocketsHttpHandler @@ -86,11 +95,23 @@ await DownloaderHelper.Instance.DownloadFileAsync(webProxy, } } + /// + /// Tries to download string content using proxy switch setting. + /// public async Task TryDownloadString(string url, bool blProxy, string userAgent) + { + var webProxy = await GetWebProxy(blProxy); + return await TryDownloadString(url, webProxy, userAgent); + } + + /// + /// Tries to download string content with a specified proxy. + /// + public async Task TryDownloadString(string url, IWebProxy? webProxy, string userAgent) { try { - var result1 = await DownloadStringAsync(url, blProxy, userAgent, 15); + var result1 = await DownloadStringAsync(url, webProxy, userAgent, 15); if (result1.IsNotEmpty()) { return result1; @@ -108,7 +129,7 @@ await DownloaderHelper.Instance.DownloadFileAsync(webProxy, try { - var result2 = await DownloadStringViaDownloader(url, blProxy, userAgent, 15); + var result2 = await DownloadStringViaDownloader(url, webProxy, userAgent, 15); if (result2.IsNotEmpty()) { return result2; @@ -128,14 +149,12 @@ await DownloaderHelper.Instance.DownloadFileAsync(webProxy, } /// - /// DownloadString + /// Downloads string content via HttpClient. /// - /// - private async Task DownloadStringAsync(string url, bool blProxy, string userAgent, int timeout) + private async Task DownloadStringAsync(string url, IWebProxy? webProxy, string userAgent, int timeout) { try { - var webProxy = await GetWebProxy(blProxy); var client = new HttpClient(new SocketsHttpHandler() { Proxy = webProxy, @@ -172,15 +191,12 @@ await DownloaderHelper.Instance.DownloadFileAsync(webProxy, } /// - /// DownloadString + /// Downloads string content via DownloaderHelper. /// - /// - private async Task DownloadStringViaDownloader(string url, bool blProxy, string userAgent, int timeout) + private async Task DownloadStringViaDownloader(string url, IWebProxy? webProxy, string userAgent, int timeout) { try { - var webProxy = await GetWebProxy(blProxy); - if (userAgent.IsNullOrEmpty()) { userAgent = Utils.GetVersion(false); @@ -200,6 +216,9 @@ await DownloaderHelper.Instance.DownloadFileAsync(webProxy, return null; } + /// + /// Creates local SOCKS proxy when proxy switch is enabled. + /// private async Task GetWebProxy(bool blProxy) { if (!blProxy) @@ -215,6 +234,9 @@ await DownloaderHelper.Instance.DownloadFileAsync(webProxy, return new WebProxy($"socks5://{Global.Loopback}:{port}"); } + /// + /// Checks whether the specified TCP endpoint is reachable. + /// private async Task SocketCheck(string ip, int port) { try diff --git a/v2rayN/ServiceLib/Services/SpeedtestService.cs b/v2rayN/ServiceLib/Services/SpeedtestService.cs index 251522bacab..11778b22016 100644 --- a/v2rayN/ServiceLib/Services/SpeedtestService.cs +++ b/v2rayN/ServiceLib/Services/SpeedtestService.cs @@ -8,6 +8,8 @@ public class SpeedtestService(Config config, Func updateF private readonly Config? _config = config; private readonly Func? _updateFunc = updateFunc; private static readonly ConcurrentBag _lstExitLoop = new(); + private readonly int _speedTestPageSize = config.SpeedTestItem.SpeedTestPageSize ?? Global.SpeedTestPageSize; + private readonly TimeSpan _delayInterval = TimeSpan.FromSeconds(config.SpeedTestItem.SpeedTestDelayInterval ?? 1); public void RunLoop(ESpeedActionType actionType, List selecteds) { @@ -135,32 +137,39 @@ private async Task> GetClearItem(ESpeedActionType actionTyp private async Task RunTcpingAsync(List selecteds) { - List tasks = []; - foreach (var it in selecteds) + var pageSize = Math.Min(selecteds.Count, _speedTestPageSize); + var lstBatch = GetTestBatchItem(selecteds, pageSize); + + foreach (var lst in lstBatch) { - tasks.Add(Task.Run(async () => + List tasks = []; + foreach (var it in lst) { - try + tasks.Add(Task.Run(async () => { - var responseTime = await GetTcpingTime(it.Address, it.Port); + try + { + var responseTime = await GetTcpingTime(it.Address, it.Port); - ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime); - await UpdateFunc(it.IndexId, responseTime.ToString()); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - })); + ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime); + await UpdateFunc(it.IndexId, responseTime.ToString()); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + })); + } + await Task.WhenAll(tasks); + await Task.Delay(_delayInterval); } - await Task.WhenAll(tasks); } private async Task RunRealPingBatchAsync(List lstSelected, string exitLoopKey, int pageSize = 0) { if (pageSize <= 0) { - pageSize = lstSelected.Count < Global.SpeedTestPageSize ? lstSelected.Count : Global.SpeedTestPageSize; + pageSize = Math.Min(lstSelected.Count, _speedTestPageSize); } var lstTest = GetTestBatchItem(lstSelected, pageSize); @@ -172,7 +181,7 @@ private async Task RunRealPingBatchAsync(List lstSelected, strin { lstFailed.AddRange(lst); } - await Task.Delay(100); + await Task.Delay(_delayInterval); } //Retest the failed part @@ -249,7 +258,7 @@ private async Task RunUdpTestBatchAsync(List lstSelected, string { if (pageSize <= 0) { - pageSize = lstSelected.Count < Global.SpeedTestPageSize ? lstSelected.Count : Global.SpeedTestPageSize; + pageSize = Math.Min(lstSelected.Count, _speedTestPageSize); } var lstTest = GetTestBatchItem(lstSelected, pageSize); @@ -261,7 +270,7 @@ private async Task RunUdpTestBatchAsync(List lstSelected, string { lstFailed.AddRange(lst); } - await Task.Delay(100); + await Task.Delay(_delayInterval); } //Retest the failed part @@ -392,10 +401,23 @@ private async Task RunMixedTestAsync(List selecteds, int concurr private async Task DoRealPing(ServerTestItem it) { var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}"); - var responseTime = await ConnectionHandler.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10); + var responseTime = await ConnectionHandler.GetRealPingTime(webProxy, 10); ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime); await UpdateFunc(it.IndexId, responseTime.ToString()); + + if (responseTime > 0) + { + var ipInfo = await ConnectionHandler.GetIPInfo(webProxy); + var ipStr = ipInfo?.ToString() ?? Global.None; + ProfileExManager.Instance.SetTestIpInfo(it.IndexId, ipStr); + await UpdateIpInfoFunc(it.IndexId, ipStr); + } + else + { + await UpdateIpInfoFunc(it.IndexId, ResUI.SpeedtestingSkip); + } + return responseTime; } @@ -491,4 +513,9 @@ private async Task UpdateFunc(string indexId, string delay, string speed = "") ProfileExManager.Instance.SetTestMessage(indexId, speed); } } + + private async Task UpdateIpInfoFunc(string indexId, string ip) + { + await _updateFunc?.Invoke(new() { IndexId = indexId, IpInfo = ip }); + } } diff --git a/v2rayN/ServiceLib/Services/UpdateService.cs b/v2rayN/ServiceLib/Services/UpdateService.cs index ee5efc145fd..cc2cefab9fa 100644 --- a/v2rayN/ServiceLib/Services/UpdateService.cs +++ b/v2rayN/ServiceLib/Services/UpdateService.cs @@ -100,6 +100,43 @@ public async Task CheckUpdateCore(ECoreType type, bool preRelease) } } + public async Task CheckHasUpdateOnly(ECoreType type, bool preRelease) + { + if (!CoreInfoManager.Instance.IsCheckUpdateSupported(type)) + { + return new UpdateResult(false, ResUI.MsgNotSupport); + } + + var downloadHandle = new DownloadService(); + var checkPreRelease = CoreInfoManager.Instance.GetCheckPreRelease(type, preRelease); + return await CheckUpdateAsync(downloadHandle, type, checkPreRelease); + } + + public async Task> CheckHasUpdateOnlyAll(bool preRelease) + { + var msgs = new List(); + foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes()) + { + if (!(_config.CheckUpdateItem.SelectedCoreTypes?.Contains(type.ToString()) ?? true)) + { + continue; + } + + var result = await CheckHasUpdateOnly(type, preRelease); + if (result.Success && result.Version != null) + { + var msg = string.Format(ResUI.MsgCheckUpdateHasNewVersion, type, result.Version); + msgs.Add(msg); + AppManager.Instance.SetLastCheckUpdateResult(type, msg); + } + else + { + AppManager.Instance.SetLastCheckUpdateResult(type, result.Msg); + } + } + return msgs; + } + public async Task UpdateGeoFileAll() { await UpdateGeoFiles(); @@ -305,6 +342,7 @@ private async Task ParseDownloadUrl(ECoreType type, UpdateResult r { Architecture.Arm64 => coreInfo?.DownloadUrlLinuxArm64, Architecture.RiscV64 => coreInfo?.DownloadUrlLinuxRiscV64, + Architecture.LoongArch64 => coreInfo?.DownloadUrlLinuxLoong64, Architecture.X64 => coreInfo?.DownloadUrlLinux64, _ => null, }; diff --git a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs index 169e8a4424d..f5906f2159d 100644 --- a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs @@ -464,7 +464,9 @@ private async Task FetchCert() domain += $":{SelectedSource.Port}"; } - (Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName, allowInsecure: AllowInsecureCertFetch); + (Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName, + verifyPeerCertByName: Utils.String2List(SelectedSource.VerifyPeerCertByName), + allowInsecure: AllowInsecureCertFetch); UpdateCertTip(certError); } @@ -489,7 +491,9 @@ private async Task FetchCertChain() domain += $":{SelectedSource.Port}"; } - var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName, allowInsecure: AllowInsecureCertFetch); + var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName, + verifyPeerCertByName: Utils.String2List(SelectedSource.VerifyPeerCertByName), + allowInsecure: AllowInsecureCertFetch); Cert = CertPemManager.ConcatenatePemChain(certs); UpdateCertTip(certError); } diff --git a/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs b/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs index dc31b41ba29..e2a8a3ce195 100644 --- a/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs @@ -3,12 +3,13 @@ namespace ServiceLib.ViewModels; public class CheckUpdateViewModel : MyReactiveObject { private const string _geo = "GeoFiles"; - private readonly string _v2rayN = ECoreType.v2rayN.ToString(); + private readonly ECoreType _v2rayN = ECoreType.v2rayN; private List _lstUpdated = []; private static readonly string _tag = "CheckUpdateViewModel"; public IObservableCollection CheckUpdateModels { get; } = new ObservableCollectionExtended(); public ReactiveCommand CheckUpdateCmd { get; } + public ReactiveCommand CheckOnlyCmd { get; } [Reactive] public bool EnableCheckPreReleaseUpdate { get; set; } public CheckUpdateViewModel(Func>? updateView) @@ -23,12 +24,19 @@ public CheckUpdateViewModel(Func>? updateView) _ = UpdateView(_v2rayN, ex.Message); }); + CheckOnlyCmd = ReactiveCommand.CreateFromTask(CheckOnly); + CheckOnlyCmd.ThrownExceptions.Subscribe(ex => + { + Logging.SaveLog(_tag, ex); + _ = UpdateView(_v2rayN, ex.Message); + }); + EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate; this.WhenAnyValue( x => x.EnableCheckPreReleaseUpdate, y => y == true) - .Subscribe(c => _config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate); + .Subscribe(c => _ = OnCheckPreReleaseUpdateChanged()); RefreshCheckUpdateItems(); } @@ -37,21 +45,15 @@ private void RefreshCheckUpdateItems() { CheckUpdateModels.Clear(); - if (RuntimeInformation.ProcessArchitecture != Architecture.X86) + foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes()) { - CheckUpdateModels.Add(GetCheckUpdateModel(_v2rayN)); - //Not Windows and under Win10 - if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10)) - { - CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.Xray.ToString())); - CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString())); - CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString())); - } + CheckUpdateModels.Add(GetCheckUpdateModel(type)); } - CheckUpdateModels.Add(GetCheckUpdateModel(_geo)); + + CheckUpdateModels.Add(GetGeoFileCheckUpdateModel()); } - private CheckUpdateModel GetCheckUpdateModel(string coreType) + private CheckUpdateModel GetCheckUpdateModel(ECoreType coreType) { if (coreType == _v2rayN && Utils.IsPackagedInstall()) { @@ -59,34 +61,112 @@ private CheckUpdateModel GetCheckUpdateModel(string coreType) { IsSelected = false, CoreType = coreType, - Remarks = ResUI.menuCheckUpdate + " (Not Support)", + IsGeoFile = false, + Remarks = ResUI.menuCheckUpdate + $" ({ResUI.MsgNotSupport})", }; } + AppManager.Instance.LastCheckUpdateResults.TryGetValue(coreType, out var lastResult); return new() { - IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType) ?? true, + IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType.ToString()) ?? true, CoreType = coreType, + IsGeoFile = false, + Remarks = lastResult ?? ResUI.menuCheckUpdate, + }; + } + + private CheckUpdateModel GetGeoFileCheckUpdateModel() + { + return new() + { + IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(_geo) ?? true, + CoreType = null, + IsGeoFile = true, Remarks = ResUI.menuCheckUpdate, }; } + private async Task OnCheckPreReleaseUpdateChanged() + { + if (_config.CheckUpdateItem.CheckPreReleaseUpdate == EnableCheckPreReleaseUpdate) + { + return; + } + _config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate; + await SaveSelectedCoreTypes(); + } + private async Task SaveSelectedCoreTypes() { - _config.CheckUpdateItem.SelectedCoreTypes = CheckUpdateModels.Where(t => t.IsSelected == true).Select(t => t.CoreType ?? "").ToList(); + _config.CheckUpdateItem.SelectedCoreTypes = + CheckUpdateModels.Where(t => t.IsSelected == true) + .Select(t => t.CoreTypeForStorage) + .ToList(); + await ConfigHandler.SaveConfig(_config); } + private async Task CheckOnly() + { + await Task.Run(CheckOnlyTask); + } + private async Task CheckUpdate() { await Task.Run(CheckUpdateTask); } + private async Task CheckOnlyTask() + { + await SaveSelectedCoreTypes(); + + for (var k = CheckUpdateModels.Count - 1; k >= 0; k--) + { + var item = CheckUpdateModels[k]; + if (item.IsSelected != true) + { + continue; + } + + await UpdateView(item.CoreType, "..."); + + if (item.IsGeoFile || item.CoreType == null) + { + await UpdateView(item.CoreType, ResUI.menuCheckOnly + $" ({ResUI.MsgNotSupport})"); + continue; + } + + if (item.CoreType == null) + { + await UpdateView(item.CoreType, ResUI.MsgNotSupport); + continue; + } + + var updateService = new UpdateService(_config, async (success, msg) => await Task.CompletedTask); + var result = await updateService.CheckHasUpdateOnly(item.CoreType.Value, EnableCheckPreReleaseUpdate); + if (result.Success && result.Version != null) + { + await UpdateView(item.CoreType, string.Format(ResUI.MsgCheckUpdateHasNewVersion, item.CoreType, result.Version)); + } + else + { + await UpdateView(item.CoreType, result.Msg); + } + } + } + private async Task CheckUpdateTask() { _lstUpdated.Clear(); - _lstUpdated = CheckUpdateModels.Where(x => x.IsSelected == true) - .Select(x => new CheckUpdateModel() { CoreType = x.CoreType }).ToList(); + _lstUpdated = CheckUpdateModels + .Where(x => x.IsSelected == true) + .Select(x => new CheckUpdateModel() + { + CoreType = x.CoreType, + IsGeoFile = x.IsGeoFile + }) + .ToList(); await SaveSelectedCoreTypes(); for (var k = CheckUpdateModels.Count - 1; k >= 0; k--) @@ -98,7 +178,8 @@ private async Task CheckUpdateTask() } await UpdateView(item.CoreType, "..."); - if (item.CoreType == _geo) + + if (item.IsGeoFile) { await CheckUpdateGeo(); } @@ -106,16 +187,16 @@ private async Task CheckUpdateTask() { if (Utils.IsPackagedInstall()) { - await UpdateView(_v2rayN, "Not Support"); + await UpdateView(_v2rayN, ResUI.MsgNotSupport); continue; } await CheckUpdateN(EnableCheckPreReleaseUpdate); } - else if (item.CoreType == ECoreType.Xray.ToString()) + else if (item.CoreType == ECoreType.Xray) { await CheckUpdateCore(item, EnableCheckPreReleaseUpdate); } - else + else if (item.CoreType.HasValue) { await CheckUpdateCore(item, false); } @@ -124,7 +205,7 @@ private async Task CheckUpdateTask() await UpdateFinished(); } - private void UpdatedPlusPlus(string coreType, string fileName) + private void UpdatedPlusPlus(ECoreType? coreType, string fileName) { var item = _lstUpdated.FirstOrDefault(x => x.CoreType == coreType); if (item == null) @@ -142,14 +223,14 @@ private async Task CheckUpdateGeo() { async Task _updateUI(bool success, string msg) { - await UpdateView(_geo, msg); + await UpdateView(null, msg); if (success) { - UpdatedPlusPlus(_geo, ""); + UpdatedPlusPlus(null, ""); } } await new UpdateService(_config, _updateUI).UpdateGeoFileAll() - .ContinueWith(t => UpdatedPlusPlus(_geo, "")); + .ContinueWith(t => UpdatedPlusPlus(null, "")); } private async Task CheckUpdateN(bool preRelease) @@ -175,13 +256,15 @@ async Task _updateUI(bool success, string msg) if (success) { await UpdateView(model.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfullyMore); - UpdatedPlusPlus(model.CoreType, msg); } } - var type = (ECoreType)Enum.Parse(typeof(ECoreType), model.CoreType); - await new UpdateService(_config, _updateUI).CheckUpdateCore(type, preRelease) - .ContinueWith(t => UpdatedPlusPlus(model.CoreType, "")); + + if (model.CoreType.HasValue) + { + await new UpdateService(_config, _updateUI).CheckUpdateCore(model.CoreType.Value, preRelease) + .ContinueWith(t => UpdatedPlusPlus(model.CoreType, "")); + } } private async Task UpdateFinished() @@ -257,7 +340,7 @@ private async Task UpgradeCore() { foreach (var item in _lstUpdated) { - if (item.FileName.IsNullOrEmpty()) + if (item.FileName.IsNullOrEmpty() || item.IsGeoFile) { continue; } @@ -267,7 +350,9 @@ private async Task UpgradeCore() { continue; } - var toPath = Utils.GetBinPath("", item.CoreType); + + var coreTypeStr = item.CoreType?.ToString() ?? ""; + var toPath = Utils.GetBinPath("", coreTypeStr); if (fileName.Contains(".tar.gz")) { @@ -284,7 +369,7 @@ private async Task UpgradeCore() } else if (fileName.Contains(".gz")) { - FileUtils.DecompressFile(fileName, toPath, item.CoreType); + FileUtils.DecompressFile(fileName, toPath, coreTypeStr); } else { @@ -296,7 +381,7 @@ private async Task UpgradeCore() var filesList = new DirectoryInfo(toPath).GetFiles().Select(u => u.FullName).ToList(); foreach (var file in filesList) { - await Utils.SetLinuxChmod(Path.Combine(toPath, item.CoreType.ToLower())); + await Utils.SetLinuxChmod(Path.Combine(toPath, coreTypeStr.ToLower())); } } @@ -309,11 +394,12 @@ private async Task UpgradeCore() } } - private async Task UpdateView(string coreType, string msg) + private async Task UpdateView(ECoreType? coreType, string msg) { var item = new CheckUpdateModel() { CoreType = coreType, + IsGeoFile = coreType == null, Remarks = msg, }; @@ -327,7 +413,7 @@ private async Task UpdateView(string coreType, string msg) public async Task UpdateViewResult(CheckUpdateModel model) { - var found = CheckUpdateModels.FirstOrDefault(t => t.CoreType == model.CoreType); + var found = CheckUpdateModels.FirstOrDefault(t => t.CoreType == model.CoreType && t.IsGeoFile == model.IsGeoFile); if (found == null) { return; diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index 53ad06c8068..4b3f41b9393 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -65,6 +65,8 @@ public class MainWindowViewModel : MyReactiveObject [Reactive] public bool BlIsWindows { get; set; } + [Reactive] public bool BlNewUpdate { get; set; } + #endregion Menu #region Init @@ -251,6 +253,11 @@ public MainWindowViewModel(Func>? updateView) .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy)); + AppEvents.HasUpdateNotified + .AsObservable() + .ObserveOn(RxSchedulers.MainThreadScheduler) + .Subscribe(async bl => BlNewUpdate = bl); + #endregion AppEvents _ = Init(); @@ -297,10 +304,22 @@ private async Task UpdateTaskHandler(bool success, string msg) { var indexIdOld = _config.IndexId; await RefreshServers(); - if (indexIdOld != _config.IndexId) + + // If indexId changed or subIndexId is empty, directly reload. + if (indexIdOld != _config.IndexId || _config.SubIndexId.IsNullOrEmpty()) { await Reload(); } + else + { + // The activity config belongs to the current group. + var profile = await AppManager.Instance.GetProfileItem(_config.IndexId); + if (profile != null && profile.Subid == _config.SubIndexId) + { + await Reload(); + } + } + if (_config.UiItem.EnableAutoAdjustMainLvColWidth) { AppEvents.AdjustMainLvColWidthRequested.Publish(); diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index 122772c4cfc..583502c8686 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -4,29 +4,29 @@ public class OptionSettingViewModel : MyReactiveObject { #region Core - [Reactive] public int localPort { get; set; } + [Reactive] public int LocalPort { get; set; } [Reactive] public bool SecondLocalPortEnabled { get; set; } - [Reactive] public bool udpEnabled { get; set; } - [Reactive] public bool sniffingEnabled { get; set; } - public IList destOverride { get; set; } - [Reactive] public bool routeOnly { get; set; } - [Reactive] public bool allowLANConn { get; set; } - [Reactive] public bool newPort4LAN { get; set; } - [Reactive] public string user { get; set; } - [Reactive] public string pass { get; set; } - [Reactive] public bool muxEnabled { get; set; } - [Reactive] public bool logEnabled { get; set; } - [Reactive] public string loglevel { get; set; } - [Reactive] public bool defAllowInsecure { get; set; } - [Reactive] public string defFingerprint { get; set; } - [Reactive] public string defUserAgent { get; set; } - [Reactive] public string sendThrough { get; set; } - [Reactive] public string bindInterface { get; set; } - [Reactive] public string mux4SboxProtocol { get; set; } - [Reactive] public bool enableCacheFile4Sbox { get; set; } - [Reactive] public int? hyUpMbps { get; set; } - [Reactive] public int? hyDownMbps { get; set; } - [Reactive] public bool enableFragment { get; set; } + [Reactive] public bool UdpEnabled { get; set; } + [Reactive] public bool SniffingEnabled { get; set; } + public IList DestOverride { get; set; } + [Reactive] public bool RouteOnly { get; set; } + [Reactive] public bool AllowLANConn { get; set; } + [Reactive] public bool NewPort4LAN { get; set; } + [Reactive] public string User { get; set; } + [Reactive] public string Pass { get; set; } + [Reactive] public bool MuxEnabled { get; set; } + [Reactive] public bool LogEnabled { get; set; } + [Reactive] public string Loglevel { get; set; } + [Reactive] public bool DefAllowInsecure { get; set; } + [Reactive] public string DefFingerprint { get; set; } + [Reactive] public string DefUserAgent { get; set; } + [Reactive] public string SendThrough { get; set; } + [Reactive] public string BindInterface { get; set; } + [Reactive] public string Mux4SboxProtocol { get; set; } + [Reactive] public bool EnableCacheFile4Sbox { get; set; } + [Reactive] public int? HyUpMbps { get; set; } + [Reactive] public int? HyDownMbps { get; set; } + [Reactive] public bool EnableFragment { get; set; } #endregion Core @@ -83,9 +83,9 @@ public class OptionSettingViewModel : MyReactiveObject #region System proxy - [Reactive] public bool notProxyLocalAddress { get; set; } - [Reactive] public string systemProxyAdvancedProtocol { get; set; } - [Reactive] public string systemProxyExceptions { get; set; } + [Reactive] public bool NotProxyLocalAddress { get; set; } + [Reactive] public string SystemProxyAdvancedProtocol { get; set; } + [Reactive] public string SystemProxyExceptions { get; set; } [Reactive] public string CustomSystemProxyPacPath { get; set; } [Reactive] public string CustomSystemProxyScriptPath { get; set; } @@ -100,6 +100,7 @@ public class OptionSettingViewModel : MyReactiveObject [Reactive] public bool TunEnableIPv6Address { get; set; } [Reactive] public string TunIcmpRouting { get; set; } [Reactive] public bool TunEnableLegacyProtect { get; set; } + [Reactive] public string TunRouteExcludeAddress { get; set; } #endregion Tun mode @@ -142,28 +143,28 @@ private async Task Init() #region Core var inbound = _config.Inbound.First(); - localPort = inbound.LocalPort; + LocalPort = inbound.LocalPort; SecondLocalPortEnabled = inbound.SecondLocalPortEnabled; - udpEnabled = inbound.UdpEnabled; - sniffingEnabled = inbound.SniffingEnabled; - routeOnly = inbound.RouteOnly; - allowLANConn = inbound.AllowLANConn; - newPort4LAN = inbound.NewPort4LAN; - user = inbound.User; - pass = inbound.Pass; - muxEnabled = _config.CoreBasicItem.MuxEnabled; - logEnabled = _config.CoreBasicItem.LogEnabled; - loglevel = _config.CoreBasicItem.Loglevel; - defAllowInsecure = _config.CoreBasicItem.DefAllowInsecure; - defFingerprint = _config.CoreBasicItem.DefFingerprint; - defUserAgent = _config.CoreBasicItem.DefUserAgent; - sendThrough = _config.CoreBasicItem.SendThrough ?? string.Empty; - bindInterface = _config.CoreBasicItem.BindInterface ?? string.Empty; - mux4SboxProtocol = _config.Mux4SboxItem.Protocol; - enableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox; - hyUpMbps = _config.HysteriaItem.UpMbps; - hyDownMbps = _config.HysteriaItem.DownMbps; - enableFragment = _config.CoreBasicItem.EnableFragment; + UdpEnabled = inbound.UdpEnabled; + SniffingEnabled = inbound.SniffingEnabled; + RouteOnly = inbound.RouteOnly; + AllowLANConn = inbound.AllowLANConn; + NewPort4LAN = inbound.NewPort4LAN; + User = inbound.User; + Pass = inbound.Pass; + MuxEnabled = _config.CoreBasicItem.MuxEnabled; + LogEnabled = _config.CoreBasicItem.LogEnabled; + Loglevel = _config.CoreBasicItem.Loglevel; + DefAllowInsecure = _config.CoreBasicItem.DefAllowInsecure; + DefFingerprint = _config.CoreBasicItem.DefFingerprint; + DefUserAgent = _config.CoreBasicItem.DefUserAgent; + SendThrough = _config.CoreBasicItem.SendThrough ?? string.Empty; + BindInterface = _config.CoreBasicItem.BindInterface ?? string.Empty; + Mux4SboxProtocol = _config.Mux4SboxItem.Protocol; + EnableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox; + HyUpMbps = _config.HysteriaItem.UpMbps; + HyDownMbps = _config.HysteriaItem.DownMbps; + EnableFragment = _config.CoreBasicItem.EnableFragment; #endregion Core @@ -211,9 +212,9 @@ private async Task Init() #region System proxy - notProxyLocalAddress = _config.SystemProxyItem.NotProxyLocalAddress; - systemProxyAdvancedProtocol = _config.SystemProxyItem.SystemProxyAdvancedProtocol; - systemProxyExceptions = _config.SystemProxyItem.SystemProxyExceptions; + NotProxyLocalAddress = _config.SystemProxyItem.NotProxyLocalAddress; + SystemProxyAdvancedProtocol = _config.SystemProxyItem.SystemProxyAdvancedProtocol; + SystemProxyExceptions = _config.SystemProxyItem.SystemProxyExceptions; CustomSystemProxyPacPath = _config.SystemProxyItem.CustomSystemProxyPacPath; CustomSystemProxyScriptPath = _config.SystemProxyItem.CustomSystemProxyScriptPath; @@ -228,6 +229,7 @@ private async Task Init() TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address; TunIcmpRouting = _config.TunModeItem.IcmpRouting; TunEnableLegacyProtect = _config.TunModeItem.EnableLegacyProtect; + TunRouteExcludeAddress = Utils.List2String(_config.TunModeItem.RouteExcludeAddress, true); #endregion Tun mode @@ -297,13 +299,13 @@ private async Task InitCoreType() private async Task SaveSettingAsync() { - if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString()) - || localPort <= 0 || localPort >= Global.MaxPort) + if (LocalPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(LocalPort.ToString()) + || LocalPort <= 0 || LocalPort >= Global.MaxPort) { NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort); return; } - var sendThroughValue = sendThrough.TrimEx(); + var sendThroughValue = SendThrough.TrimEx(); if (sendThroughValue.IsNotEmpty() && !Utils.IsIpv4(sendThroughValue)) { NoticeManager.Instance.Enqueue(ResUI.FillCorrectSendThroughIPv4); @@ -328,33 +330,33 @@ private async Task SaveSettingAsync() //} //Core - _config.Inbound.First().LocalPort = localPort; + _config.Inbound.First().LocalPort = LocalPort; _config.Inbound.First().SecondLocalPortEnabled = SecondLocalPortEnabled; - _config.Inbound.First().UdpEnabled = udpEnabled; - _config.Inbound.First().SniffingEnabled = sniffingEnabled; - _config.Inbound.First().DestOverride = destOverride?.ToList(); - _config.Inbound.First().RouteOnly = routeOnly; - _config.Inbound.First().AllowLANConn = allowLANConn; - _config.Inbound.First().NewPort4LAN = newPort4LAN; - _config.Inbound.First().User = user; - _config.Inbound.First().Pass = pass; + _config.Inbound.First().UdpEnabled = UdpEnabled; + _config.Inbound.First().SniffingEnabled = SniffingEnabled; + _config.Inbound.First().DestOverride = DestOverride?.ToList(); + _config.Inbound.First().RouteOnly = RouteOnly; + _config.Inbound.First().AllowLANConn = AllowLANConn; + _config.Inbound.First().NewPort4LAN = NewPort4LAN; + _config.Inbound.First().User = User; + _config.Inbound.First().Pass = Pass; if (_config.Inbound.Count > 1) { _config.Inbound.RemoveAt(1); } - _config.CoreBasicItem.LogEnabled = logEnabled; - _config.CoreBasicItem.Loglevel = loglevel; - _config.CoreBasicItem.MuxEnabled = muxEnabled; - _config.CoreBasicItem.DefAllowInsecure = defAllowInsecure; - _config.CoreBasicItem.DefFingerprint = defFingerprint; - _config.CoreBasicItem.DefUserAgent = defUserAgent; - _config.CoreBasicItem.SendThrough = sendThrough.TrimEx(); - _config.CoreBasicItem.BindInterface = bindInterface.TrimEx(); - _config.Mux4SboxItem.Protocol = mux4SboxProtocol; - _config.CoreBasicItem.EnableCacheFile4Sbox = enableCacheFile4Sbox; - _config.HysteriaItem.UpMbps = hyUpMbps ?? 0; - _config.HysteriaItem.DownMbps = hyDownMbps ?? 0; - _config.CoreBasicItem.EnableFragment = enableFragment; + _config.CoreBasicItem.LogEnabled = LogEnabled; + _config.CoreBasicItem.Loglevel = Loglevel; + _config.CoreBasicItem.MuxEnabled = MuxEnabled; + _config.CoreBasicItem.DefAllowInsecure = DefAllowInsecure; + _config.CoreBasicItem.DefFingerprint = DefFingerprint; + _config.CoreBasicItem.DefUserAgent = DefUserAgent; + _config.CoreBasicItem.SendThrough = SendThrough.TrimEx(); + _config.CoreBasicItem.BindInterface = BindInterface.TrimEx(); + _config.Mux4SboxItem.Protocol = Mux4SboxProtocol; + _config.CoreBasicItem.EnableCacheFile4Sbox = EnableCacheFile4Sbox; + _config.HysteriaItem.UpMbps = HyUpMbps ?? 0; + _config.HysteriaItem.DownMbps = HyDownMbps ?? 0; + _config.CoreBasicItem.EnableFragment = EnableFragment; _config.GuiItem.AutoRun = AutoRun; _config.GuiItem.EnableStatistics = EnableStatistics; @@ -383,9 +385,9 @@ private async Task SaveSettingAsync() _config.SpeedTestItem.IPAPIUrl = IPAPIUrl; //systemProxy - _config.SystemProxyItem.SystemProxyExceptions = systemProxyExceptions; - _config.SystemProxyItem.NotProxyLocalAddress = notProxyLocalAddress; - _config.SystemProxyItem.SystemProxyAdvancedProtocol = systemProxyAdvancedProtocol; + _config.SystemProxyItem.SystemProxyExceptions = SystemProxyExceptions; + _config.SystemProxyItem.NotProxyLocalAddress = NotProxyLocalAddress; + _config.SystemProxyItem.SystemProxyAdvancedProtocol = SystemProxyAdvancedProtocol; _config.SystemProxyItem.CustomSystemProxyPacPath = CustomSystemProxyPacPath; _config.SystemProxyItem.CustomSystemProxyScriptPath = CustomSystemProxyScriptPath; @@ -397,6 +399,7 @@ private async Task SaveSettingAsync() _config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address; _config.TunModeItem.IcmpRouting = TunIcmpRouting; _config.TunModeItem.EnableLegacyProtect = TunEnableLegacyProtect; + _config.TunModeItem.RouteExcludeAddress = Utils.String2List(TunRouteExcludeAddress); //coreType await SaveCoreType(); diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 609ae0d16c9..1af0ac14724 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -303,6 +303,10 @@ public async Task SetSpeedTestResult(SpeedTestResult result) { item.SpeedVal = result.Speed ?? string.Empty; } + if (result.IpInfo.IsNotEmpty()) + { + item.IpInfo = result.IpInfo ?? string.Empty; + } await Task.CompletedTask; } @@ -437,6 +441,7 @@ from t33 in t3b.DefaultIfEmpty() Speed = t33?.Speed ?? 0, DelayVal = t33?.Delay != 0 ? $"{t33?.Delay}" : string.Empty, SpeedVal = t33?.Speed > 0 ? $"{t33?.Speed}" : t33?.Message ?? string.Empty, + IpInfo = t33?.IpInfo ?? string.Empty, TodayDown = t22 == null ? "" : Utils.HumanFy(t22.TodayDown), TodayUp = t22 == null ? "" : Utils.HumanFy(t22.TodayUp), TotalDown = t22 == null ? "" : Utils.HumanFy(t22.TotalDown), diff --git a/v2rayN/v2rayN.Desktop/App.axaml.cs b/v2rayN/v2rayN.Desktop/App.axaml.cs index 35371f0f193..c1c448cb78e 100644 --- a/v2rayN/v2rayN.Desktop/App.axaml.cs +++ b/v2rayN/v2rayN.Desktop/App.axaml.cs @@ -1,3 +1,4 @@ +using v2rayN.Desktop.Common; using v2rayN.Desktop.Views; namespace v2rayN.Desktop; @@ -24,11 +25,34 @@ public override void OnFrameworkInitializationCompleted() desktop.Exit += OnExit; desktop.MainWindow = new MainWindow(); + + if (OperatingSystem.IsMacOS()) + { + Current?.TryGetFeature()?.Activated += OnMacOSActivated; + } } base.OnFrameworkInitializationCompleted(); } + private void OnMacOSActivated(object? sender, ActivatedEventArgs args) + { + if (args.Kind != ActivationKind.Reopen) + { + return; + } + + Dispatcher.UIThread.Post(() => + { + ((ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow as MainWindow)?.ShowHideWindow(true); + + if (!AppManager.Instance.Config.UiItem.MacOSShowInDock) + { + MacAppUtils.SetActivationPolicyAccessory(); + } + }); + } + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { if (e.ExceptionObject != null) diff --git a/v2rayN/v2rayN.Desktop/Common/MacAppUtils.cs b/v2rayN/v2rayN.Desktop/Common/MacAppUtils.cs new file mode 100644 index 00000000000..b38f75072d7 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Common/MacAppUtils.cs @@ -0,0 +1,27 @@ +using System.Runtime.InteropServices; + +namespace v2rayN.Desktop.Common; + +internal static class MacAppUtils +{ + private const string LibObjC = "/usr/lib/libobjc.dylib"; + private const nint ActivationPolicyAccessory = 1; + + public static void SetActivationPolicyAccessory() + => objc_msgSend( + objc_msgSend(objc_getClass("NSApplication"), sel_registerName("sharedApplication")), + sel_registerName("setActivationPolicy:"), + ActivationPolicyAccessory); + + [DllImport(LibObjC)] + private static extern nint objc_getClass(string name); + + [DllImport(LibObjC)] + private static extern nint sel_registerName(string name); + + [DllImport(LibObjC, EntryPoint = "objc_msgSend")] + private static extern nint objc_msgSend(nint receiver, nint selector); + + [DllImport(LibObjC, EntryPoint = "objc_msgSend")] + private static extern void objc_msgSend(nint receiver, nint selector, nint argument); +} diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index dbf031b11a6..574d4716862 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -1060,7 +1060,7 @@ Grid.Row="8" ColumnDefinitions="300,Auto" IsVisible="False" - RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> + + + vm.AllowInsecureCertFetch, v => v.togAllowInsecureCertFetch.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AllowInsecureCertFetch, v => v.txtAllowInsecureCertFetchTips.IsVisible).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.VerifyPeerCertByName, v => v.txtVerifyPeerCertByName.Text).DisposeWith(disposables); //reality this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); diff --git a/v2rayN/v2rayN.Desktop/Views/CheckUpdateView.axaml b/v2rayN/v2rayN.Desktop/Views/CheckUpdateView.axaml index 0055f9adc05..5d103373170 100644 --- a/v2rayN/v2rayN.Desktop/Views/CheckUpdateView.axaml +++ b/v2rayN/v2rayN.Desktop/Views/CheckUpdateView.axaml @@ -33,6 +33,12 @@ Margin="{StaticResource Margin4}" HorizontalAlignment="Left" /> +