Compare commits

..

4 commits

Author SHA1 Message Date
2dust
ed1275e29f Upgrade Downloader to 4.1.1 and update API usage
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release Linux / rpm (push) Blocked by required conditions
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2026-02-27 11:21:51 +08:00
dependabot[bot]
0cf07e925f
Bump actions/download-artifact from 7 to 8 (#8851)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 11:04:20 +08:00
dependabot[bot]
49e487886d
Bump actions/upload-artifact from 6.0.0 to 7.0.0 (#8850)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6.0.0...v7.0.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 11:04:16 +08:00
DHR60
ad07f281c7
Fix routing (#8849)
Fix
2026-02-27 11:04:00 +08:00
11 changed files with 135 additions and 87 deletions

View file

@ -50,7 +50,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64" dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@v6.0.0 uses: actions/upload-artifact@v7.0.0
with: with:
name: v2rayN-linux name: v2rayN-linux
path: | path: |
@ -169,7 +169,7 @@ jobs:
fetch-depth: '0' fetch-depth: '0'
- name: Restore build artifacts - name: Restore build artifacts
uses: actions/download-artifact@v7 uses: actions/download-artifact@v8
with: with:
name: v2rayN-linux name: v2rayN-linux
path: ${{ github.workspace }}/v2rayN/Release path: ${{ github.workspace }}/v2rayN/Release
@ -190,7 +190,7 @@ jobs:
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
- name: Upload RPM artifacts - name: Upload RPM artifacts
uses: actions/upload-artifact@v6.0.0 uses: actions/upload-artifact@v7.0.0
with: with:
name: v2rayN-rpm name: v2rayN-rpm
path: dist/rpm/**/*.rpm path: dist/rpm/**/*.rpm

View file

@ -45,7 +45,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64 dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@v6.0.0 uses: actions/upload-artifact@v7.0.0
with: with:
name: v2rayN-macos name: v2rayN-macos
path: | path: |

View file

@ -45,7 +45,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64 dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@v6.0.0 uses: actions/upload-artifact@v7.0.0
with: with:
name: v2rayN-windows-desktop name: v2rayN-windows-desktop
path: | path: |

View file

@ -42,7 +42,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64 dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@v6.0.0 uses: actions/upload-artifact@v7.0.0
with: with:
name: v2rayN-windows name: v2rayN-windows
path: | path: |

View file

@ -11,7 +11,7 @@
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.3.8" /> <PackageVersion Include="ReactiveUI.Avalonia" Version="11.3.8" />
<PackageVersion Include="CliWrap" Version="3.10.0" /> <PackageVersion Include="CliWrap" Version="3.10.0" />
<PackageVersion Include="Downloader" Version="4.0.3" /> <PackageVersion Include="Downloader" Version="4.1.1" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" /> <PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" /> <PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" /> <PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />

View file

@ -24,13 +24,13 @@ public class DownloaderHelper
var downloadOpt = new DownloadConfiguration() var downloadOpt = new DownloadConfiguration()
{ {
Timeout = timeout * 1000, BlockTimeout = timeout * 1000,
MaxTryAgainOnFailure = 2, MaxTryAgainOnFailure = 2,
RequestConfiguration = RequestConfiguration =
{ {
Headers = headers, Headers = headers,
UserAgent = userAgent, UserAgent = userAgent,
Timeout = timeout * 1000, ConnectTimeout = timeout * 1000,
Proxy = webProxy Proxy = webProxy
} }
}; };
@ -62,11 +62,11 @@ public class DownloaderHelper
var downloadOpt = new DownloadConfiguration() var downloadOpt = new DownloadConfiguration()
{ {
Timeout = timeout * 1000, BlockTimeout = timeout * 1000,
MaxTryAgainOnFailure = 2, MaxTryAgainOnFailure = 2,
RequestConfiguration = RequestConfiguration =
{ {
Timeout= timeout * 1000, ConnectTimeout= timeout * 1000,
Proxy = webProxy Proxy = webProxy
} }
}; };
@ -139,11 +139,11 @@ public class DownloaderHelper
var downloadOpt = new DownloadConfiguration() var downloadOpt = new DownloadConfiguration()
{ {
Timeout = timeout * 1000, BlockTimeout = timeout * 1000,
MaxTryAgainOnFailure = 2, MaxTryAgainOnFailure = 2,
RequestConfiguration = RequestConfiguration =
{ {
Timeout= timeout * 1000, ConnectTimeout= timeout * 1000,
Proxy = webProxy Proxy = webProxy
} }
}; };

View file

@ -26,11 +26,15 @@ public partial class CoreConfigSingboxService
{ {
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? []; var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
useDirectDns = rules?.LastOrDefault() is { } lastRule && if (rules?.LastOrDefault() is { } lastRule && lastRule.OutboundTag == Global.DirectTag)
lastRule.OutboundTag == Global.DirectTag && {
(lastRule.Port == "0-65535" || var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0;
lastRule.Network == "tcp,udp" || var noProcess = lastRule.Process == null || lastRule.Process.Count == 0;
lastRule.Ip?.Contains("0.0.0.0/0") == true); var isAnyIp = lastRule.Ip == null || lastRule.Ip.Count == 0 || lastRule.Ip.Contains("0.0.0.0/0");
var isAnyPort = string.IsNullOrEmpty(lastRule.Port) || lastRule.Port == "0-65535";
var isAnyNetwork = string.IsNullOrEmpty(lastRule.Network) || lastRule.Network == "tcp,udp";
useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork;
}
} }
_coreConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag; _coreConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
var simpleDnsItem = context.SimpleDnsItem; var simpleDnsItem = context.SimpleDnsItem;

View file

@ -20,11 +20,14 @@ public partial class CoreConfigSingboxService
{ {
proxyOutboundList.AddRange(BuildGroupProxyOutbounds(baseTagName)); proxyOutboundList.AddRange(BuildGroupProxyOutbounds(baseTagName));
} }
if (withSelector)
{
var proxyTags = proxyOutboundList.Where(n => n.tag.StartsWith(Global.ProxyTag)).Select(n => n.tag).ToList(); var proxyTags = proxyOutboundList.Where(n => n.tag.StartsWith(Global.ProxyTag)).Select(n => n.tag).ToList();
if (proxyTags.Count > 1) if (proxyTags.Count > 1)
{ {
proxyOutboundList.InsertRange(0, BuildSelectorOutbounds(proxyTags, baseTagName)); proxyOutboundList.InsertRange(0, BuildSelectorOutbounds(proxyTags, baseTagName));
} }
}
return proxyOutboundList; return proxyOutboundList;
} }

View file

@ -60,6 +60,12 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
GenStatistic(); GenStatistic();
var finalRule = BuildFinalRule();
if (!string.IsNullOrEmpty(finalRule?.balancerTag))
{
_coreConfig.routing.rules.Add(finalRule);
}
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true; ret.Success = true;
ret.Data = ApplyFullConfigTemplate(); ret.Data = ApplyFullConfigTemplate();
@ -234,6 +240,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
GenLog(); GenLog();
GenOutbounds(); GenOutbounds();
_coreConfig.routing.domainStrategy = Global.AsIs;
_coreConfig.routing.rules.Clear(); _coreConfig.routing.rules.Clear();
_coreConfig.inbounds.Clear(); _coreConfig.inbounds.Clear();
_coreConfig.inbounds.Add(new() _coreConfig.inbounds.Add(new()
@ -244,6 +251,8 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
protocol = EInboundProtocol.mixed.ToString(), protocol = EInboundProtocol.mixed.ToString(),
}); });
_coreConfig.routing.rules.Add(BuildFinalRule());
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true; ret.Success = true;
ret.Data = JsonUtils.Serialize(_coreConfig); ret.Data = JsonUtils.Serialize(_coreConfig);

View file

@ -70,17 +70,16 @@ public partial class CoreConfigV2rayService
dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null; dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null;
dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null; dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null;
if (_coreConfig.routing.domainStrategy == Global.IPIfNonMatch)
{
// DNS routing // DNS routing
var finalRule = BuildFinalRule();
dnsItem.tag = Global.DnsTag; dnsItem.tag = Global.DnsTag;
_coreConfig.routing.rules.Add(new RulesItem4Ray _coreConfig.routing.rules.Add(new()
{ {
type = "field", type = "field",
inboundTag = new List<string> { Global.DnsTag }, inboundTag = [Global.DnsTag],
outboundTag = Global.ProxyTag, outboundTag = finalRule.outboundTag,
balancerTag = finalRule.balancerTag
}); });
}
_coreConfig.dns = dnsItem; _coreConfig.dns = dnsItem;
} }
@ -93,45 +92,6 @@ public partial class CoreConfigV2rayService
private void FillDnsServers(Dns4Ray dnsItem) private void FillDnsServers(Dns4Ray dnsItem)
{ {
var simpleDNSItem = context.SimpleDnsItem; var simpleDNSItem = context.SimpleDnsItem;
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
{
var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';')
.Select(addr => addr.Trim())
.Where(addr => !string.IsNullOrEmpty(addr))
.Select(addr => addr.StartsWith("dhcp", StringComparison.OrdinalIgnoreCase) ? "localhost" : addr)
.Distinct()
.ToList() ?? new List<string> { defaultAddress };
return addresses.Count > 0 ? addresses : new List<string> { defaultAddress };
}
static object? CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
{
var (domain, scheme, port, path) = Utils.ParseUrl(dnsAddress);
var domainFinal = dnsAddress;
int? portFinal = null;
if (scheme.IsNullOrEmpty() || scheme.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
{
domainFinal = domain;
portFinal = port > 0 ? port : null;
}
else if (scheme.StartsWith("tcp", StringComparison.OrdinalIgnoreCase))
{
domainFinal = scheme + "://" + domain;
portFinal = port > 0 ? port : null;
}
var dnsServer = new DnsServer4Ray
{
address = domainFinal,
port = portFinal,
skipFallback = true,
domains = domains.Count > 0 ? domains : null,
expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null
};
return JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});
}
var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.First()); var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.First());
var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.First()); var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.First());
@ -252,17 +212,6 @@ public partial class CoreConfigV2rayService
dnsItem.servers ??= []; dnsItem.servers ??= [];
void AddDnsServers(List<string> dnsAddresses, List<string> domains, List<string>? expectedIPs = null)
{
if (domains.Count > 0)
{
foreach (var dnsAddress in dnsAddresses)
{
dnsItem.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs));
}
}
}
AddDnsServers(remoteDNSAddress, proxyDomainList); AddDnsServers(remoteDNSAddress, proxyDomainList);
AddDnsServers(directDNSAddress, directDomainList); AddDnsServers(directDNSAddress, directDomainList);
AddDnsServers(remoteDNSAddress, proxyGeositeList); AddDnsServers(remoteDNSAddress, proxyGeositeList);
@ -273,14 +222,73 @@ public partial class CoreConfigV2rayService
AddDnsServers(bootstrapDNSAddress, dnsServerDomains); AddDnsServers(bootstrapDNSAddress, dnsServerDomains);
} }
var useDirectDns = rules?.LastOrDefault() is { } lastRule var useDirectDns = false;
&& lastRule.OutboundTag == Global.DirectTag
&& (lastRule.Port == "0-65535" if (rules?.LastOrDefault() is { } lastRule && lastRule.OutboundTag == Global.DirectTag)
|| lastRule.Network == "tcp,udp" {
|| lastRule.Ip?.Contains("0.0.0.0/0") == true); var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0;
var noProcess = lastRule.Process == null || lastRule.Process.Count == 0;
var isAnyIp = lastRule.Ip == null || lastRule.Ip.Count == 0 || lastRule.Ip.Contains("0.0.0.0/0");
var isAnyPort = string.IsNullOrEmpty(lastRule.Port) || lastRule.Port == "0-65535";
var isAnyNetwork = string.IsNullOrEmpty(lastRule.Network) || lastRule.Network == "tcp,udp";
useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork;
}
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress; var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
dnsItem.servers.AddRange(defaultDnsServers); dnsItem.servers.AddRange(defaultDnsServers);
return;
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
{
var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';')
.Select(addr => addr.Trim())
.Where(addr => !string.IsNullOrEmpty(addr))
.Select(addr => addr.StartsWith("dhcp", StringComparison.OrdinalIgnoreCase) ? "localhost" : addr)
.Distinct()
.ToList() ?? [defaultAddress];
return addresses.Count > 0 ? addresses : new List<string> { defaultAddress };
}
static object? CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
{
var (domain, scheme, port, path) = Utils.ParseUrl(dnsAddress);
var domainFinal = dnsAddress;
int? portFinal = null;
if (scheme.IsNullOrEmpty() || scheme.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
{
domainFinal = domain;
portFinal = port > 0 ? port : null;
}
else if (scheme.StartsWith("tcp", StringComparison.OrdinalIgnoreCase))
{
domainFinal = scheme + "://" + domain;
portFinal = port > 0 ? port : null;
}
var dnsServer = new DnsServer4Ray
{
address = domainFinal,
port = portFinal,
skipFallback = true,
domains = domains.Count > 0 ? domains : null,
expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null
};
return JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});
}
void AddDnsServers(List<string> dnsAddresses, List<string> domains, List<string>? expectedIPs = null)
{
if (domains.Count <= 0)
{
return;
}
foreach (var dnsAddress in dnsAddresses)
{
dnsItem.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs));
}
}
} }
private void FillDnsHosts(Dns4Ray dnsItem) private void FillDnsHosts(Dns4Ray dnsItem)

View file

@ -181,4 +181,28 @@ public partial class CoreConfigV2rayService
return tag; return tag;
} }
private RulesItem4Ray BuildFinalRule()
{
var finalRule = new RulesItem4Ray()
{
type = "field",
network = "tcp,udp",
outboundTag = Global.ProxyTag,
};
var balancer =
_coreConfig?.routing?.balancers?.FirstOrDefault(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix, null);
var domainStrategy = _coreConfig.routing?.domainStrategy ?? Global.AsIs;
if (balancer is not null)
{
finalRule.outboundTag = null;
finalRule.balancerTag = balancer.tag;
}
if (domainStrategy == Global.IPIfNonMatch)
{
finalRule.network = null;
finalRule.ip = ["0.0.0.0/0", "::/0"];
}
return finalRule;
}
} }