feat: 实现依据 InboundSetting 和 OutboundSetting 生成配置的功能.

This commit is contained in:
2025-01-10 10:00:56 +08:00
parent db43e05770
commit f802695f39
5 changed files with 247 additions and 49 deletions
@@ -20,11 +20,13 @@ public abstract class BaseOutboundSetting
/// <summary> /// <summary>
/// 发送出站数据所使用的网卡地址. /// 发送出站数据所使用的网卡地址.
/// </summary> /// </summary>
public string BindAddress { get; init; } = "0.0.0.0"; public string? BindAddress { get; init; }
/// <summary> /// <summary>
/// 出站代理协议类型. /// 出站代理协议类型.
/// </summary> /// </summary>
public OutboundProtocolType Protocol { get; init; } public OutboundProtocolType Protocol { get; init; }
public string? Address { get; init; }
public BaseStreamSetting StreamSetting { get; init; } = new RawStreamSetting(); public BaseStreamSetting StreamSetting { get; init; } = new RawStreamSetting();
@@ -41,12 +43,16 @@ public abstract class BaseOutboundSetting
{ {
var root = new JsonObject var root = new JsonObject
{ {
{ "sendThrough", BindAddress },
{ "protocol", Protocol.ToString().ToLower() }, { "protocol", Protocol.ToString().ToLower() },
{ "tag", GetTag() }, { "tag", GetTag() },
{ "settings", GetProtocolSetting() } { "settings", GetProtocolSetting() }
}; };
if (!string.IsNullOrEmpty(BindAddress))
{
root.Add("sendThrough", BindAddress);
}
if (!string.IsNullOrEmpty(NextOutboundTag)) if (!string.IsNullOrEmpty(NextOutboundTag))
{ {
root.Add("proxySettings", new JsonObject root.Add("proxySettings", new JsonObject
@@ -12,7 +12,7 @@ public class ShadowSocksOutboundSetting : BaseOutboundSetting
public string? UserEmail { get; init; } = null; public string? UserEmail { get; init; } = null;
public required string Method { get; init; } 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 int Port { get; init; }
public required string Password { get; init; } public required string Password { get; init; }
public bool EnableUdpOverTcp { get; init; } = false; public bool EnableUdpOverTcp { get; init; } = false;
+1 -1
View File
@@ -41,7 +41,7 @@ internal class Program
await xrayCoreManager.InitialXrayCoreProgram(); await xrayCoreManager.InitialXrayCoreProgram();
var subscribeManager = serviceProvider.GetRequiredService<SubscribeManager>(); var subscribeManager = serviceProvider.GetRequiredService<SubscribeManager>();
subscribeManager.StartUpdateSubscribes(); await subscribeManager.CheckAndUpdateSubscribes();
// 监听 SIGTERM 和 SIGINT 信号, 以优雅退出 // 监听 SIGTERM 和 SIGINT 信号, 以优雅退出
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
+3 -3
View File
@@ -22,7 +22,7 @@ public class SubscribeManager
_logger = logger; _logger = logger;
_config = config; _config = config;
_config.OnChange((_, _) => Task.Run(async () => await UpdateSubscribes())); _config.OnChange((_, _) => Task.Run(async () => await CheckAndUpdateSubscribes()));
} }
public delegate void SubscribeUpdatedEventHandler(IDictionary<SubscribeSource, List<BaseOutboundSetting>> subscribeOutbounds); public delegate void SubscribeUpdatedEventHandler(IDictionary<SubscribeSource, List<BaseOutboundSetting>> subscribeOutbounds);
@@ -30,7 +30,7 @@ public class SubscribeManager
private ProxdConfig Config => _config.Get(null); private ProxdConfig Config => _config.Get(null);
public async Task UpdateSubscribes(CancellationToken cancellationToken = default) public async Task CheckAndUpdateSubscribes(CancellationToken cancellationToken = default)
{ {
var httpClient = new HttpClient(); var httpClient = new HttpClient();
@@ -100,7 +100,7 @@ public class SubscribeManager
{ {
while (!cts.IsCancellationRequested) while (!cts.IsCancellationRequested)
{ {
await UpdateSubscribes(cts.Token); await CheckAndUpdateSubscribes(cts.Token);
await Task.Delay(TimeSpan.FromHours(12), cts.Token); await Task.Delay(TimeSpan.FromHours(12), cts.Token);
} }
}, cts.Token); }, cts.Token);
+234 -42
View File
@@ -4,44 +4,37 @@ using System.Net;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Proxd_man.Subscribe; using Microsoft.Extensions.Options;
using Proxd_man.Config;
using Proxd_man.Config.Outbound;
namespace Proxd_man; namespace Proxd_man;
public class XrayCoreManager public class XrayCoreManager(
ILogger<XrayCoreManager> logger,
IOptionsMonitor<ProxdConfig> config)
{ {
private const string XrayLatestReleaseApiUrl = "https://api.github.com/repos/XTLS/Xray-core/releases/latest"; private const string XrayLatestReleaseApiUrl = "https://api.github.com/repos/XTLS/Xray-core/releases/latest";
private const string XrayLatestReleaseAssetDownloadUrlPrefix = private const string XrayLatestReleaseAssetDownloadUrlPrefix =
"https://github.com/XTLS/Xray-core/releases/latest/download/"; "https://github.com/XTLS/Xray-core/releases/latest/download/";
private readonly ILogger<XrayCoreManager> _logger;
private readonly string _coreProgramDir;
private readonly string _workingDirectory;
public XrayCoreManager( private string CoreProgramDir => config.CurrentValue.XrayCoreProgramDirectory;
ILogger<XrayCoreManager> logger, private string WorkingDirectory => config.CurrentValue.XrayCoreWorkingDirectory;
SubscribeManager subscribeManager,
string coreProgramDir = "./xray-core",
string workingDirectory = "./xray-core-run")
{
_logger = logger;
_coreProgramDir = coreProgramDir;
_workingDirectory = workingDirectory;
// TODO: 看看注册订阅更新事件后, 需要进行哪些操作. // TODO: 看看注册订阅更新事件后, 需要进行哪些操作.
}
public string GetXrayCoreProgramPath() public string GetXrayCoreProgramPath()
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
return _coreProgramDir + "/xray.exe"; return CoreProgramDir + "/xray.exe";
} }
return _coreProgramDir + "/xray"; return CoreProgramDir + "/xray";
} }
public string GetXrayCoreProgramDownloadUrl() public string GetXrayCoreProgramDownloadUrl()
@@ -113,7 +106,7 @@ public class XrayCoreManager
return $"{XrayLatestReleaseAssetDownloadUrlPrefix}Xray-{os}-{arch}.zip"; return $"{XrayLatestReleaseAssetDownloadUrlPrefix}Xray-{os}-{arch}.zip";
} }
private Process CreateXrayCoreProcess(string args, bool redirectOutput = true) private Process CreateXrayCoreProcess(string args, bool redirectOutput = true)
{ {
var programPath = GetXrayCoreProgramPath(); var programPath = GetXrayCoreProgramPath();
@@ -122,11 +115,11 @@ public class XrayCoreManager
throw new Exception("Xray-Core program not found: " + programPath); 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 return new Process
{ {
StartInfo = new ProcessStartInfo StartInfo = new ProcessStartInfo
@@ -136,7 +129,7 @@ public class XrayCoreManager
RedirectStandardOutput = redirectOutput, RedirectStandardOutput = redirectOutput,
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true, CreateNoWindow = true,
WorkingDirectory = _workingDirectory WorkingDirectory = WorkingDirectory
} }
}; };
} }
@@ -188,30 +181,30 @@ public class XrayCoreManager
public async Task FetchXrayCoreProgram() public async Task FetchXrayCoreProgram()
{ {
if (!Directory.Exists(_coreProgramDir)) if (!Directory.Exists(CoreProgramDir))
{ {
Directory.CreateDirectory(_coreProgramDir); Directory.CreateDirectory(CoreProgramDir);
} }
var programArchiveUrl = GetXrayCoreProgramDownloadUrl(); var programArchiveUrl = GetXrayCoreProgramDownloadUrl();
var programArchiveDigestUrl = programArchiveUrl + ".dgst"; var programArchiveDigestUrl = programArchiveUrl + ".dgst";
_logger.LogDebug("Xray-Core 下载地址: {programArchiveUrl}", programArchiveUrl); logger.LogDebug("Xray-Core 下载地址: {programArchiveUrl}", programArchiveUrl);
_logger.LogDebug("Xray-Core 下载地址 (Digest): {programArchiveDigestUrl}", programArchiveDigestUrl); logger.LogDebug("Xray-Core 下载地址 (Digest): {programArchiveDigestUrl}", programArchiveDigestUrl);
var tempArchiveFile = Path.GetTempFileName(); var tempArchiveFile = Path.GetTempFileName();
using var httpClient = new HttpClient(); using var httpClient = new HttpClient();
_logger.LogDebug("正在下载 Xray-Core..."); logger.LogDebug("正在下载 Xray-Core...");
await using var archiveStream = await httpClient.GetStreamAsync(programArchiveUrl); await using var archiveStream = await httpClient.GetStreamAsync(programArchiveUrl);
await using (var programArchiveFileStream = new FileStream(tempArchiveFile, FileMode.Create, FileAccess.Write)) await using (var programArchiveFileStream = new FileStream(tempArchiveFile, FileMode.Create, FileAccess.Write))
{ {
await archiveStream.CopyToAsync(programArchiveFileStream); await archiveStream.CopyToAsync(programArchiveFileStream);
} }
_logger.LogDebug("正在获取 SHA-256 Digest..."); logger.LogDebug("正在获取 SHA-256 Digest...");
var digests = await httpClient.GetStringAsync(programArchiveDigestUrl); 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 值 // digests 每行都有一个不同算法的 digest HEX 值, 因此从 digests 读出开头为 SHA-256= 的行, 并提取出其右侧的 Sha256 值
var sha256Digest = var sha256Digest =
@@ -222,7 +215,7 @@ public class XrayCoreManager
throw new Exception("Failed to get SHA-256 digest."); 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 // Verify digest
using var sha256 = SHA256.Create(); using var sha256 = SHA256.Create();
@@ -230,17 +223,17 @@ public class XrayCoreManager
{ {
var localDigest = await sha256.ComputeHashAsync(localArchiveFileStream); var localDigest = await sha256.ComputeHashAsync(localArchiveFileStream);
var localDigestHex = BitConverter.ToString(localDigest).Replace("-", "").ToLower(); var localDigestHex = BitConverter.ToString(localDigest).Replace("-", "").ToLower();
_logger.LogDebug("本地 SHA-256 Digest: {localDigest}", localDigestHex); logger.LogDebug("本地 SHA-256 Digest: {localDigest}", localDigestHex);
if (localDigestHex != sha256Digest) if (localDigestHex != sha256Digest)
{ {
throw new Exception("SHA-256 digest mismatch."); 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 // Set permissions
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
@@ -258,14 +251,14 @@ public class XrayCoreManager
public async Task InitialXrayCoreProgram() public async Task InitialXrayCoreProgram()
{ {
_logger.LogInformation("正在检查 Xray-Core 安装情况以及版本..."); logger.LogInformation("正在检查 Xray-Core 安装情况以及版本...");
var currentVersion = await GetCurrentXrayCoreVersion(); var currentVersion = await GetCurrentXrayCoreVersion();
_logger.LogDebug("当前 Xray-Core 版本: {currentVersion}", currentVersion); logger.LogDebug("当前 Xray-Core 版本: {currentVersion}", currentVersion);
var requiredUpdate = false; var requiredUpdate = false;
if (currentVersion == null) if (currentVersion == null)
{ {
requiredUpdate = true; requiredUpdate = true;
_logger.LogInformation("未找到 Xray-Core, 开始下载最新版本..."); logger.LogInformation("未找到 Xray-Core, 开始下载最新版本...");
} }
else else
{ {
@@ -273,7 +266,7 @@ public class XrayCoreManager
if (currentVersion != latestXrayCoreVersion) if (currentVersion != latestXrayCoreVersion)
{ {
requiredUpdate = true; requiredUpdate = true;
_logger.LogInformation("发现新版本 Xray-Core (Version: {latestVersion}), 开始下载最新版本...", logger.LogInformation("发现新版本 Xray-Core (Version: {latestVersion}), 开始下载最新版本...",
latestXrayCoreVersion); latestXrayCoreVersion);
} }
} }
@@ -281,11 +274,11 @@ public class XrayCoreManager
if (requiredUpdate) if (requiredUpdate)
{ {
await FetchXrayCoreProgram(); await FetchXrayCoreProgram();
_logger.LogInformation("Xray-Core 更新完成."); logger.LogInformation("Xray-Core 更新完成.");
} }
else else
{ {
_logger.LogInformation("Xray-Core 已是最新版本, 无需更新."); logger.LogInformation("Xray-Core 已是最新版本, 无需更新.");
} }
} }
@@ -297,4 +290,203 @@ public class XrayCoreManager
await process.WaitForExitAsync(); await process.WaitForExitAsync();
return process.ExitCode == 0; return process.ExitCode == 0;
} }
/// <summary>
/// 生成最终的 Xray-Core 配置 JSON 对象.
///
/// 注意, 在 Xray-Core 中, Inbound 和 Outbound 的首位配置很重要.
/// </summary>
/// <param name="inboundConfigs">配置使用的入站设置.</param>
/// <param name="outboundSetting">该配置使用的出站配置.</param>
/// <param name="onlyTest"></param>
/// <returns></returns>
public JsonObject GenerateXrayCoreConfigJson(List<IInboundSetting> 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}");
}
} }