diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml
index 0632561cb77..baeae29a35d 100644
--- a/.github/workflows/build-linux.yml
+++ b/.github/workflows/build-linux.yml
@@ -341,7 +341,7 @@ jobs:
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/v2rayN/Directory.Packages.props b/v2rayN/Directory.Packages.props
index 671305e6bee..086c726f88a 100644
--- a/v2rayN/Directory.Packages.props
+++ b/v2rayN/Directory.Packages.props
@@ -7,32 +7,32 @@
-
-
+
+
-
+
+
-
-
+
+
-
+
-
+
-
+
-
-
+
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/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 51535990a43..2b8199400a6 100644
--- a/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs
+++ b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs
@@ -136,7 +136,9 @@ private static void ValidateNodeAndCoreSupport(ProfileItem item, ECoreType coreT
}
// Check for deprecated allowInsecure property when TLS is enabled
- if (item.AllowInsecure == "true")
+ if (item.AllowInsecure == "true"
+ && item.Cert.IsNullOrEmpty()
+ && item.CertSha.IsNullOrEmpty())
{
v.Warning(ResUI.MsgAllowInsecureDeprecated);
}
diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs
index e5da70070ad..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;
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/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/Models/Configs/ConfigItems.cs b/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs
index d5101275f41..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]
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 ed6219abe73..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
@@ -373,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/IPAPIInfo.cs b/v2rayN/ServiceLib/Models/Dto/IPAPIInfo.cs
index dddd84d55c2..0257ab67b4b 100644
--- a/v2rayN/ServiceLib/Models/Dto/IPAPIInfo.cs
+++ b/v2rayN/ServiceLib/Models/Dto/IPAPIInfo.cs
@@ -22,7 +22,7 @@ public readonly record struct IpInfoResult(string Country, string? Ip)
{
public override string ToString()
{
- var emoji = Country.CountryToEmoji();
+ var emoji = Utils.IsWindows() ? null : Country.CountryToEmoji();
return $"{emoji}({Country}) {Ip}";
}
}
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 2729e284c7b..b7cf06dacb6 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs
+++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs
@@ -2229,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... 的本地化字符串。
///
@@ -3537,6 +3546,24 @@ public static string TbRoundRobin {
}
}
+ ///
+ /// 查找类似 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 的本地化字符串。
///
@@ -4707,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.resx b/v2rayN/ServiceLib/Resx/ResUI.resx
index df5fdeb0783..ec97d10a905 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.resx
+++ b/v2rayN/ServiceLib/Resx/ResUI.resx
@@ -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
@@ -1749,4 +1752,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
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.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx
index 1577baf6da2..4654b723948 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx
+++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx
@@ -1746,4 +1746,16 @@
警告: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 f52b640f7db..2f1c1857cf6 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx
+++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx
@@ -1746,4 +1746,13 @@
警告: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/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/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/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/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs
index 23a21b99f05..583502c8686 100644
--- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs
+++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs
@@ -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
@@ -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
@@ -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/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/OptionSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml
index 2baea96c8d8..2038c702518 100644
--- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml
+++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml
@@ -818,107 +818,132 @@
-
-
-
-
+
+ ColumnDefinitions="Auto,Auto,Auto"
+ DockPanel.Dock="Top"
+ RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs
index f08c37c2f2b..f54a145ddce 100644
--- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs
+++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs
@@ -122,6 +122,7 @@ public OptionSettingWindow()
this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunIcmpRouting, v => v.cmbIcmpRoutingPolicy.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunEnableLegacyProtect, v => v.togEnableLegacyProtect.IsChecked).DisposeWith(disposables);
+ this.Bind(ViewModel, vm => vm.TunRouteExcludeAddress, v => v.txtRouteExcludeAddress.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType1, v => v.cmbCoreType1.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType2, v => v.cmbCoreType2.SelectedValue).DisposeWith(disposables);
diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayN/v2rayN/Views/AddServerWindow.xaml
index 16512751430..4a8ef291329 100644
--- a/v2rayN/v2rayN/Views/AddServerWindow.xaml
+++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml
@@ -1377,6 +1377,7 @@
+
@@ -1461,6 +1462,22 @@
HorizontalAlignment="Left"
Style="{StaticResource DefTextBox}" />
+
+
+
v.txtAllowInsecureCertFetchTips.Visibility);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).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/Views/OptionSettingWindow.xaml b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml
index df46d963939..2e972f31293 100644
--- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml
+++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml
@@ -1065,129 +1065,161 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs
index d48248268d9..1ede6bd7823 100644
--- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs
+++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs
@@ -126,6 +126,7 @@ public OptionSettingWindow()
this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunIcmpRouting, v => v.cmbIcmpRoutingPolicy.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunEnableLegacyProtect, v => v.togEnableLegacyProtect.IsChecked).DisposeWith(disposables);
+ this.Bind(ViewModel, vm => vm.TunRouteExcludeAddress, v => v.txtRouteExcludeAddress.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType1, v => v.cmbCoreType1.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType2, v => v.cmbCoreType2.Text).DisposeWith(disposables);