diff --git a/Proxd-man/Config/Outbound/BaseOutboundSetting.cs b/Proxd-man/Config/Outbound/BaseOutboundSetting.cs
index 05427ec..3905f72 100644
--- a/Proxd-man/Config/Outbound/BaseOutboundSetting.cs
+++ b/Proxd-man/Config/Outbound/BaseOutboundSetting.cs
@@ -20,11 +20,13 @@ public abstract class BaseOutboundSetting
///
/// 发送出站数据所使用的网卡地址.
///
- public string BindAddress { get; init; } = "0.0.0.0";
+ public string? BindAddress { get; init; }
///
/// 出站代理协议类型.
///
public OutboundProtocolType Protocol { get; init; }
+
+ public string? Address { get; init; }
public BaseStreamSetting StreamSetting { get; init; } = new RawStreamSetting();
@@ -41,12 +43,16 @@ public abstract class BaseOutboundSetting
{
var root = new JsonObject
{
- { "sendThrough", BindAddress },
{ "protocol", Protocol.ToString().ToLower() },
{ "tag", GetTag() },
{ "settings", GetProtocolSetting() }
};
+ if (!string.IsNullOrEmpty(BindAddress))
+ {
+ root.Add("sendThrough", BindAddress);
+ }
+
if (!string.IsNullOrEmpty(NextOutboundTag))
{
root.Add("proxySettings", new JsonObject
diff --git a/Proxd-man/Config/Outbound/ShadowSocksOutboundSetting.cs b/Proxd-man/Config/Outbound/ShadowSocksOutboundSetting.cs
index cb33604..6e89f36 100644
--- a/Proxd-man/Config/Outbound/ShadowSocksOutboundSetting.cs
+++ b/Proxd-man/Config/Outbound/ShadowSocksOutboundSetting.cs
@@ -12,7 +12,7 @@ public class ShadowSocksOutboundSetting : BaseOutboundSetting
public string? UserEmail { get; init; } = null;
public required string Method { get; init; }
- public required string Address { get; init; }
+ public new required string Address { get; init; }
public int Port { get; init; }
public required string Password { get; init; }
public bool EnableUdpOverTcp { get; init; } = false;
diff --git a/Proxd-man/Program.cs b/Proxd-man/Program.cs
index ee6f0aa..c5f5869 100644
--- a/Proxd-man/Program.cs
+++ b/Proxd-man/Program.cs
@@ -41,7 +41,7 @@ internal class Program
await xrayCoreManager.InitialXrayCoreProgram();
var subscribeManager = serviceProvider.GetRequiredService();
- subscribeManager.StartUpdateSubscribes();
+ await subscribeManager.CheckAndUpdateSubscribes();
// 监听 SIGTERM 和 SIGINT 信号, 以优雅退出
var cts = new CancellationTokenSource();
diff --git a/Proxd-man/Subscribe/SubscribeManager.cs b/Proxd-man/Subscribe/SubscribeManager.cs
index 106c22b..5b950df 100644
--- a/Proxd-man/Subscribe/SubscribeManager.cs
+++ b/Proxd-man/Subscribe/SubscribeManager.cs
@@ -22,7 +22,7 @@ public class SubscribeManager
_logger = logger;
_config = config;
- _config.OnChange((_, _) => Task.Run(async () => await UpdateSubscribes()));
+ _config.OnChange((_, _) => Task.Run(async () => await CheckAndUpdateSubscribes()));
}
public delegate void SubscribeUpdatedEventHandler(IDictionary> subscribeOutbounds);
@@ -30,7 +30,7 @@ public class SubscribeManager
private ProxdConfig Config => _config.Get(null);
- public async Task UpdateSubscribes(CancellationToken cancellationToken = default)
+ public async Task CheckAndUpdateSubscribes(CancellationToken cancellationToken = default)
{
var httpClient = new HttpClient();
@@ -100,7 +100,7 @@ public class SubscribeManager
{
while (!cts.IsCancellationRequested)
{
- await UpdateSubscribes(cts.Token);
+ await CheckAndUpdateSubscribes(cts.Token);
await Task.Delay(TimeSpan.FromHours(12), cts.Token);
}
}, cts.Token);
diff --git a/Proxd-man/XrayCoreManager.cs b/Proxd-man/XrayCoreManager.cs
index a3313ab..769e840 100644
--- a/Proxd-man/XrayCoreManager.cs
+++ b/Proxd-man/XrayCoreManager.cs
@@ -4,44 +4,37 @@ using System.Net;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text.Json;
+using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
-using Proxd_man.Subscribe;
+using Microsoft.Extensions.Options;
+using Proxd_man.Config;
+using Proxd_man.Config.Outbound;
namespace Proxd_man;
-public class XrayCoreManager
+public class XrayCoreManager(
+ ILogger logger,
+ IOptionsMonitor config)
{
-
private const string XrayLatestReleaseApiUrl = "https://api.github.com/repos/XTLS/Xray-core/releases/latest";
+
private const string XrayLatestReleaseAssetDownloadUrlPrefix =
"https://github.com/XTLS/Xray-core/releases/latest/download/";
-
- private readonly ILogger _logger;
- private readonly string _coreProgramDir;
- private readonly string _workingDirectory;
- public XrayCoreManager(
- ILogger logger,
- SubscribeManager subscribeManager,
- string coreProgramDir = "./xray-core",
- string workingDirectory = "./xray-core-run")
- {
- _logger = logger;
- _coreProgramDir = coreProgramDir;
- _workingDirectory = workingDirectory;
+ private string CoreProgramDir => config.CurrentValue.XrayCoreProgramDirectory;
+ private string WorkingDirectory => config.CurrentValue.XrayCoreWorkingDirectory;
- // TODO: 看看注册订阅更新事件后, 需要进行哪些操作.
- }
+ // TODO: 看看注册订阅更新事件后, 需要进行哪些操作.
public string GetXrayCoreProgramPath()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- return _coreProgramDir + "/xray.exe";
+ return CoreProgramDir + "/xray.exe";
}
- return _coreProgramDir + "/xray";
+ return CoreProgramDir + "/xray";
}
public string GetXrayCoreProgramDownloadUrl()
@@ -113,7 +106,7 @@ public class XrayCoreManager
return $"{XrayLatestReleaseAssetDownloadUrlPrefix}Xray-{os}-{arch}.zip";
}
-
+
private Process CreateXrayCoreProcess(string args, bool redirectOutput = true)
{
var programPath = GetXrayCoreProgramPath();
@@ -122,11 +115,11 @@ public class XrayCoreManager
throw new Exception("Xray-Core program not found: " + programPath);
}
- if (!Directory.Exists(_workingDirectory))
+ if (!Directory.Exists(WorkingDirectory))
{
- Directory.CreateDirectory(_workingDirectory);
+ Directory.CreateDirectory(WorkingDirectory);
}
-
+
return new Process
{
StartInfo = new ProcessStartInfo
@@ -136,7 +129,7 @@ public class XrayCoreManager
RedirectStandardOutput = redirectOutput,
UseShellExecute = false,
CreateNoWindow = true,
- WorkingDirectory = _workingDirectory
+ WorkingDirectory = WorkingDirectory
}
};
}
@@ -188,30 +181,30 @@ public class XrayCoreManager
public async Task FetchXrayCoreProgram()
{
- if (!Directory.Exists(_coreProgramDir))
+ if (!Directory.Exists(CoreProgramDir))
{
- Directory.CreateDirectory(_coreProgramDir);
+ Directory.CreateDirectory(CoreProgramDir);
}
var programArchiveUrl = GetXrayCoreProgramDownloadUrl();
var programArchiveDigestUrl = programArchiveUrl + ".dgst";
- _logger.LogDebug("Xray-Core 下载地址: {programArchiveUrl}", programArchiveUrl);
- _logger.LogDebug("Xray-Core 下载地址 (Digest): {programArchiveDigestUrl}", programArchiveDigestUrl);
+ logger.LogDebug("Xray-Core 下载地址: {programArchiveUrl}", programArchiveUrl);
+ logger.LogDebug("Xray-Core 下载地址 (Digest): {programArchiveDigestUrl}", programArchiveDigestUrl);
var tempArchiveFile = Path.GetTempFileName();
using var httpClient = new HttpClient();
- _logger.LogDebug("正在下载 Xray-Core...");
+ logger.LogDebug("正在下载 Xray-Core...");
await using var archiveStream = await httpClient.GetStreamAsync(programArchiveUrl);
await using (var programArchiveFileStream = new FileStream(tempArchiveFile, FileMode.Create, FileAccess.Write))
{
await archiveStream.CopyToAsync(programArchiveFileStream);
}
- _logger.LogDebug("正在获取 SHA-256 Digest...");
+ logger.LogDebug("正在获取 SHA-256 Digest...");
var digests = await httpClient.GetStringAsync(programArchiveDigestUrl);
- _logger.LogDebug("可用的 Digests: \n{digests}", digests);
+ logger.LogDebug("可用的 Digests: \n{digests}", digests);
// digests 每行都有一个不同算法的 digest HEX 值, 因此从 digests 读出开头为 SHA-256= 的行, 并提取出其右侧的 Sha256 值
var sha256Digest =
@@ -222,7 +215,7 @@ public class XrayCoreManager
throw new Exception("Failed to get SHA-256 digest.");
}
- _logger.LogDebug("SHA-256 Digest: {sha256Digest}", sha256Digest);
+ logger.LogDebug("SHA-256 Digest: {sha256Digest}", sha256Digest);
// Verify digest
using var sha256 = SHA256.Create();
@@ -230,17 +223,17 @@ public class XrayCoreManager
{
var localDigest = await sha256.ComputeHashAsync(localArchiveFileStream);
var localDigestHex = BitConverter.ToString(localDigest).Replace("-", "").ToLower();
- _logger.LogDebug("本地 SHA-256 Digest: {localDigest}", localDigestHex);
+ logger.LogDebug("本地 SHA-256 Digest: {localDigest}", localDigestHex);
if (localDigestHex != sha256Digest)
{
throw new Exception("SHA-256 digest mismatch.");
}
- _logger.LogDebug("SHA-256 Digest 验证通过.");
+ logger.LogDebug("SHA-256 Digest 验证通过.");
}
- ZipFile.ExtractToDirectory(tempArchiveFile, _coreProgramDir, true);
+ ZipFile.ExtractToDirectory(tempArchiveFile, CoreProgramDir, true);
// Set permissions
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
@@ -258,14 +251,14 @@ public class XrayCoreManager
public async Task InitialXrayCoreProgram()
{
- _logger.LogInformation("正在检查 Xray-Core 安装情况以及版本...");
+ logger.LogInformation("正在检查 Xray-Core 安装情况以及版本...");
var currentVersion = await GetCurrentXrayCoreVersion();
- _logger.LogDebug("当前 Xray-Core 版本: {currentVersion}", currentVersion);
+ logger.LogDebug("当前 Xray-Core 版本: {currentVersion}", currentVersion);
var requiredUpdate = false;
if (currentVersion == null)
{
requiredUpdate = true;
- _logger.LogInformation("未找到 Xray-Core, 开始下载最新版本...");
+ logger.LogInformation("未找到 Xray-Core, 开始下载最新版本...");
}
else
{
@@ -273,7 +266,7 @@ public class XrayCoreManager
if (currentVersion != latestXrayCoreVersion)
{
requiredUpdate = true;
- _logger.LogInformation("发现新版本 Xray-Core (Version: {latestVersion}), 开始下载最新版本...",
+ logger.LogInformation("发现新版本 Xray-Core (Version: {latestVersion}), 开始下载最新版本...",
latestXrayCoreVersion);
}
}
@@ -281,11 +274,11 @@ public class XrayCoreManager
if (requiredUpdate)
{
await FetchXrayCoreProgram();
- _logger.LogInformation("Xray-Core 更新完成.");
+ logger.LogInformation("Xray-Core 更新完成.");
}
else
{
- _logger.LogInformation("Xray-Core 已是最新版本, 无需更新.");
+ logger.LogInformation("Xray-Core 已是最新版本, 无需更新.");
}
}
@@ -297,4 +290,203 @@ public class XrayCoreManager
await process.WaitForExitAsync();
return process.ExitCode == 0;
}
+
+ ///
+ /// 生成最终的 Xray-Core 配置 JSON 对象.
+ ///
+ /// 注意, 在 Xray-Core 中, Inbound 和 Outbound 的首位配置很重要.
+ ///
+ /// 配置使用的入站设置.
+ /// 该配置使用的出站配置.
+ ///
+ ///
+ public JsonObject GenerateXrayCoreConfigJson(List inboundConfigs,
+ BaseOutboundSetting outboundSetting, bool onlyTest = false)
+ {
+ // 基本的设置.
+ var proxyConfig = new JsonObject
+ {
+ {
+ "log", new JsonObject
+ {
+ { "loglevel", "warning" }
+ }
+ },
+ {
+ "policy", new JsonObject
+ {
+ {
+ "levels", new JsonObject
+ {
+ // 给 DNS 使用的.
+ {
+ "1", new JsonObject
+ {
+ { "connIdle", 30 }
+ }
+ }
+ }
+ },
+ {
+ "system", new JsonObject
+ {
+ { "statsOutboundDownlink", true },
+ { "statsOutboundUplink", true }
+ }
+ }
+ }
+ },
+ { "stats", new JsonObject() }
+ };
+
+ // ------------- DNS 配置 -------------
+ var directResolveDomains = new JsonArray();
+ var localDnsServer = new JsonObject
+ {
+ { "address", "localhost" },
+ { "domains", directResolveDomains },
+ { "queryStrategy", "" }
+ };
+
+ if (!onlyTest)
+ {
+ directResolveDomains.Add("geosite:cn");
+ }
+
+ // 如果不是一个 IP 地址(v4 或 v6),则添加到 DNS 服务器列表中
+ if (!IPAddress.TryParse(outboundSetting.Address, out _))
+ {
+ directResolveDomains.Add($"full:{outboundSetting.Address}");
+ }
+
+ var dnsServers = new JsonArray(new JsonObject
+ {
+ { "address", config.CurrentValue.RemoteDnsServer },
+ { "domains", new JsonArray() },
+ { "queryStrategy", "" }
+ }, localDnsServer);
+
+ proxyConfig.Add("dns", new JsonObject
+ {
+ { "disableFallback", true },
+ { "servers", dnsServers }
+ });
+ // ------------- DNS 配置 ↑ -------------
+ // ------------ Inbound 配置 ------------
+
+ var inbounds = new JsonArray();
+ proxyConfig.Add("inbounds", inbounds);
+ foreach (var inboundConfig in inboundConfigs)
+ {
+ inbounds.Add(inboundConfig.ToJsonSetting());
+ }
+
+ // ------------ Inbound 配置 ↑ ------------
+
+ var outbounds = new JsonArray();
+ proxyConfig.Add("outbounds", outbounds);
+
+ var proxyOutboundSettings = outboundSetting.ToJsonSetting();
+ proxyOutboundSettings.Add("domainStrategy", "AsIs");
+ outbounds.Add(proxyOutboundSettings);
+
+ outbounds.Add(new FreedomOutboundSetting("bypass")
+ {
+ Name = "bypass"
+ }.ToJsonSetting());
+ outbounds.Add(new FreedomOutboundSetting
+ {
+ Name = "direct"
+ }.ToJsonSetting());
+ outbounds.Add(new BlackholeOutboundSetting
+ {
+ Name = "block"
+ }.ToJsonSetting());
+ // 添加 DNS 出站配置, 要求 DNS 走 Proxy 代理出站.
+ outbounds.Add(new JsonObject
+ {
+ { "protocol", "dns" },
+ { "tag", "dns-out" },
+ {
+ "settings", new JsonObject
+ {
+ { "address", "1.1.1.1" },
+ { "port", 53 },
+ { "network", "tcp" },
+ { "userLevel", 1 }
+ }
+ },
+ {
+ "proxySettings", new JsonObject
+ {
+ { "tag", outboundSetting.GetTag() },
+ { "transportLayer", true }
+ }
+ }
+ });
+
+ // ------------ Outbound 配置 ------------
+ // ------------ Routing 配置 -------------
+
+ var routingSettings = new JsonObject
+ {
+ { "domainStrategy", "AsIs" }
+ };
+ proxyConfig.Add("routing", routingSettings);
+
+ var inboundTags = new JsonArray();
+ foreach (var inboundConfig in inboundConfigs)
+ {
+ inboundTags.Add(inboundConfig.GetTag());
+ }
+
+ var rules = new JsonArray(new JsonObject
+ {
+ { "inboundTag", inboundTags },
+ { "outboundTag", "dns-out" },
+ { "type", "field" },
+ { "port", "53" }
+ })
+ {
+ new JsonObject
+ {
+ { "ip", new JsonArray("geoip:cn", "geoip:private") },
+ { "outboundTag", "bypass" },
+ { "type", "field" },
+ },
+ new JsonObject
+ {
+ { "domain", new JsonArray("geosite:cn") },
+ { "outboundTag", "bypass" },
+ { "type", "field" },
+ },
+ new JsonObject
+ {
+ { "outboundTag", "proxy" },
+ { "port", "0-65535" },
+ { "type", "field" },
+ }
+ };
+
+ routingSettings.Add("rules", rules);
+ return proxyConfig;
+ }
+
+ public Process CreateOutboundTestProcess(ushort inboundHttpPort, BaseOutboundSetting outboundSetting)
+ {
+ var configJson = GenerateXrayCoreConfigJson([
+ new HttpInboundSetting
+ {
+ Port = inboundHttpPort
+ }
+ ], outboundSetting, true);
+
+ var configFilePath = Path.Combine(Path.Combine(WorkingDirectory, "test-config"), Path.GetRandomFileName() + ".json");
+ using var configFileStream = File.CreateText(configFilePath);
+ configFileStream.Write(JsonSerializer.Serialize(configJson));
+
+ return CreateXrayCoreProcess($"run -config {configFilePath}");
+ }
+
+
}
\ No newline at end of file