mirror of
https://github.com/2dust/v2rayN.git
synced 2025-10-13 11:59:13 +00:00
898 lines
35 KiB
C#
898 lines
35 KiB
C#
namespace ServiceLib.Services.CoreConfig;
|
|
|
|
public partial class CoreConfigV2rayService
|
|
{
|
|
private async Task<int> GenOutbound(ProfileItem node, Outbounds4Ray outbound)
|
|
{
|
|
try
|
|
{
|
|
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
|
|
switch (node.ConfigType)
|
|
{
|
|
case EConfigType.VMess:
|
|
{
|
|
VnextItem4Ray vnextItem;
|
|
if (outbound.settings.vnext.Count <= 0)
|
|
{
|
|
vnextItem = new VnextItem4Ray();
|
|
outbound.settings.vnext.Add(vnextItem);
|
|
}
|
|
else
|
|
{
|
|
vnextItem = outbound.settings.vnext.First();
|
|
}
|
|
vnextItem.address = node.Address;
|
|
vnextItem.port = node.Port;
|
|
|
|
UsersItem4Ray usersItem;
|
|
if (vnextItem.users.Count <= 0)
|
|
{
|
|
usersItem = new UsersItem4Ray();
|
|
vnextItem.users.Add(usersItem);
|
|
}
|
|
else
|
|
{
|
|
usersItem = vnextItem.users.First();
|
|
}
|
|
|
|
usersItem.id = node.Id;
|
|
usersItem.alterId = node.AlterId;
|
|
usersItem.email = Global.UserEMail;
|
|
if (Global.VmessSecurities.Contains(node.Security))
|
|
{
|
|
usersItem.security = node.Security;
|
|
}
|
|
else
|
|
{
|
|
usersItem.security = Global.DefaultSecurity;
|
|
}
|
|
|
|
await GenOutboundMux(node, outbound, muxEnabled, muxEnabled);
|
|
|
|
outbound.settings.servers = null;
|
|
break;
|
|
}
|
|
case EConfigType.Shadowsocks:
|
|
{
|
|
ServersItem4Ray serversItem;
|
|
if (outbound.settings.servers.Count <= 0)
|
|
{
|
|
serversItem = new ServersItem4Ray();
|
|
outbound.settings.servers.Add(serversItem);
|
|
}
|
|
else
|
|
{
|
|
serversItem = outbound.settings.servers.First();
|
|
}
|
|
serversItem.address = node.Address;
|
|
serversItem.port = node.Port;
|
|
serversItem.password = node.Id;
|
|
serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : "none";
|
|
|
|
serversItem.ota = false;
|
|
serversItem.level = 1;
|
|
|
|
await GenOutboundMux(node, outbound);
|
|
|
|
outbound.settings.vnext = null;
|
|
break;
|
|
}
|
|
case EConfigType.SOCKS:
|
|
case EConfigType.HTTP:
|
|
{
|
|
ServersItem4Ray serversItem;
|
|
if (outbound.settings.servers.Count <= 0)
|
|
{
|
|
serversItem = new ServersItem4Ray();
|
|
outbound.settings.servers.Add(serversItem);
|
|
}
|
|
else
|
|
{
|
|
serversItem = outbound.settings.servers.First();
|
|
}
|
|
serversItem.address = node.Address;
|
|
serversItem.port = node.Port;
|
|
serversItem.method = null;
|
|
serversItem.password = null;
|
|
|
|
if (node.Security.IsNotEmpty()
|
|
&& node.Id.IsNotEmpty())
|
|
{
|
|
SocksUsersItem4Ray socksUsersItem = new()
|
|
{
|
|
user = node.Security,
|
|
pass = node.Id,
|
|
level = 1
|
|
};
|
|
|
|
serversItem.users = new List<SocksUsersItem4Ray>() { socksUsersItem };
|
|
}
|
|
|
|
await GenOutboundMux(node, outbound);
|
|
|
|
outbound.settings.vnext = null;
|
|
break;
|
|
}
|
|
case EConfigType.VLESS:
|
|
{
|
|
VnextItem4Ray vnextItem;
|
|
if (outbound.settings.vnext?.Count <= 0)
|
|
{
|
|
vnextItem = new VnextItem4Ray();
|
|
outbound.settings.vnext.Add(vnextItem);
|
|
}
|
|
else
|
|
{
|
|
vnextItem = outbound.settings.vnext.First();
|
|
}
|
|
vnextItem.address = node.Address;
|
|
vnextItem.port = node.Port;
|
|
|
|
UsersItem4Ray usersItem;
|
|
if (vnextItem.users.Count <= 0)
|
|
{
|
|
usersItem = new UsersItem4Ray();
|
|
vnextItem.users.Add(usersItem);
|
|
}
|
|
else
|
|
{
|
|
usersItem = vnextItem.users.First();
|
|
}
|
|
usersItem.id = node.Id;
|
|
usersItem.email = Global.UserEMail;
|
|
usersItem.encryption = node.Security;
|
|
|
|
if (node.Flow.IsNullOrEmpty())
|
|
{
|
|
await GenOutboundMux(node, outbound, muxEnabled, muxEnabled);
|
|
}
|
|
else
|
|
{
|
|
usersItem.flow = node.Flow;
|
|
await GenOutboundMux(node, outbound, false, muxEnabled);
|
|
}
|
|
outbound.settings.servers = null;
|
|
break;
|
|
}
|
|
case EConfigType.Trojan:
|
|
{
|
|
ServersItem4Ray serversItem;
|
|
if (outbound.settings.servers.Count <= 0)
|
|
{
|
|
serversItem = new ServersItem4Ray();
|
|
outbound.settings.servers.Add(serversItem);
|
|
}
|
|
else
|
|
{
|
|
serversItem = outbound.settings.servers.First();
|
|
}
|
|
serversItem.address = node.Address;
|
|
serversItem.port = node.Port;
|
|
serversItem.password = node.Id;
|
|
|
|
serversItem.ota = false;
|
|
serversItem.level = 1;
|
|
|
|
await GenOutboundMux(node, outbound);
|
|
|
|
outbound.settings.vnext = null;
|
|
break;
|
|
}
|
|
case EConfigType.WireGuard:
|
|
{
|
|
var peer = new WireguardPeer4Ray
|
|
{
|
|
publicKey = node.PublicKey,
|
|
endpoint = node.Address + ":" + node.Port.ToString()
|
|
};
|
|
var setting = new Outboundsettings4Ray
|
|
{
|
|
address = Utils.String2List(node.RequestHost),
|
|
secretKey = node.Id,
|
|
reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(),
|
|
mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(),
|
|
peers = new List<WireguardPeer4Ray> { peer }
|
|
};
|
|
outbound.settings = setting;
|
|
outbound.settings.vnext = null;
|
|
outbound.settings.servers = null;
|
|
break;
|
|
}
|
|
}
|
|
|
|
outbound.protocol = Global.ProtocolTypes[node.ConfigType];
|
|
await GenBoundStreamSettings(node, outbound);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.SaveLog(_tag, ex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private async Task<int> GenOutboundMux(ProfileItem node, Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false)
|
|
{
|
|
try
|
|
{
|
|
outbound.mux.enabled = false;
|
|
outbound.mux.concurrency = -1;
|
|
|
|
if (enabledTCP)
|
|
{
|
|
outbound.mux.enabled = true;
|
|
outbound.mux.concurrency = _config.Mux4RayItem.Concurrency;
|
|
}
|
|
else if (enabledUDP)
|
|
{
|
|
outbound.mux.enabled = true;
|
|
outbound.mux.xudpConcurrency = _config.Mux4RayItem.XudpConcurrency;
|
|
outbound.mux.xudpProxyUDP443 = _config.Mux4RayItem.XudpProxyUDP443;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.SaveLog(_tag, ex);
|
|
}
|
|
return await Task.FromResult(0);
|
|
}
|
|
|
|
private async Task<int> GenBoundStreamSettings(ProfileItem node, Outbounds4Ray outbound)
|
|
{
|
|
try
|
|
{
|
|
var streamSettings = outbound.streamSettings;
|
|
streamSettings.network = node.GetNetwork();
|
|
var host = node.RequestHost.TrimEx();
|
|
var path = node.Path.TrimEx();
|
|
var sni = node.Sni.TrimEx();
|
|
var useragent = "";
|
|
if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty())
|
|
{
|
|
try
|
|
{
|
|
useragent = Global.UserAgentTexts[_config.CoreBasicItem.DefUserAgent];
|
|
}
|
|
catch (KeyNotFoundException)
|
|
{
|
|
useragent = _config.CoreBasicItem.DefUserAgent;
|
|
}
|
|
}
|
|
|
|
//if tls
|
|
if (node.StreamSecurity == Global.StreamSecurity)
|
|
{
|
|
streamSettings.security = node.StreamSecurity;
|
|
|
|
TlsSettings4Ray tlsSettings = new()
|
|
{
|
|
allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
|
|
alpn = node.GetAlpn(),
|
|
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
|
|
};
|
|
if (sni.IsNotEmpty())
|
|
{
|
|
tlsSettings.serverName = sni;
|
|
}
|
|
else if (host.IsNotEmpty())
|
|
{
|
|
tlsSettings.serverName = Utils.String2List(host)?.First();
|
|
}
|
|
streamSettings.tlsSettings = tlsSettings;
|
|
}
|
|
|
|
//if Reality
|
|
if (node.StreamSecurity == Global.StreamSecurityReality)
|
|
{
|
|
streamSettings.security = node.StreamSecurity;
|
|
|
|
TlsSettings4Ray realitySettings = new()
|
|
{
|
|
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint,
|
|
serverName = sni,
|
|
publicKey = node.PublicKey,
|
|
shortId = node.ShortId,
|
|
spiderX = node.SpiderX,
|
|
mldsa65Verify = node.Mldsa65Verify,
|
|
show = false,
|
|
};
|
|
|
|
streamSettings.realitySettings = realitySettings;
|
|
}
|
|
|
|
//streamSettings
|
|
switch (node.GetNetwork())
|
|
{
|
|
case nameof(ETransport.kcp):
|
|
KcpSettings4Ray kcpSettings = new()
|
|
{
|
|
mtu = _config.KcpItem.Mtu,
|
|
tti = _config.KcpItem.Tti
|
|
};
|
|
|
|
kcpSettings.uplinkCapacity = _config.KcpItem.UplinkCapacity;
|
|
kcpSettings.downlinkCapacity = _config.KcpItem.DownlinkCapacity;
|
|
|
|
kcpSettings.congestion = _config.KcpItem.Congestion;
|
|
kcpSettings.readBufferSize = _config.KcpItem.ReadBufferSize;
|
|
kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize;
|
|
kcpSettings.header = new Header4Ray
|
|
{
|
|
type = node.HeaderType,
|
|
domain = host.IsNullOrEmpty() ? null : host
|
|
};
|
|
if (path.IsNotEmpty())
|
|
{
|
|
kcpSettings.seed = path;
|
|
}
|
|
streamSettings.kcpSettings = kcpSettings;
|
|
break;
|
|
//ws
|
|
case nameof(ETransport.ws):
|
|
WsSettings4Ray wsSettings = new();
|
|
wsSettings.headers = new Headers4Ray();
|
|
|
|
if (host.IsNotEmpty())
|
|
{
|
|
wsSettings.host = host;
|
|
wsSettings.headers.Host = host;
|
|
}
|
|
if (path.IsNotEmpty())
|
|
{
|
|
wsSettings.path = path;
|
|
}
|
|
if (useragent.IsNotEmpty())
|
|
{
|
|
wsSettings.headers.UserAgent = useragent;
|
|
}
|
|
streamSettings.wsSettings = wsSettings;
|
|
|
|
break;
|
|
//httpupgrade
|
|
case nameof(ETransport.httpupgrade):
|
|
HttpupgradeSettings4Ray httpupgradeSettings = new();
|
|
|
|
if (path.IsNotEmpty())
|
|
{
|
|
httpupgradeSettings.path = path;
|
|
}
|
|
if (host.IsNotEmpty())
|
|
{
|
|
httpupgradeSettings.host = host;
|
|
}
|
|
streamSettings.httpupgradeSettings = httpupgradeSettings;
|
|
|
|
break;
|
|
//xhttp
|
|
case nameof(ETransport.xhttp):
|
|
streamSettings.network = ETransport.xhttp.ToString();
|
|
XhttpSettings4Ray xhttpSettings = new();
|
|
|
|
if (path.IsNotEmpty())
|
|
{
|
|
xhttpSettings.path = path;
|
|
}
|
|
if (host.IsNotEmpty())
|
|
{
|
|
xhttpSettings.host = host;
|
|
}
|
|
if (node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(node.HeaderType))
|
|
{
|
|
xhttpSettings.mode = node.HeaderType;
|
|
}
|
|
if (node.Extra.IsNotEmpty())
|
|
{
|
|
xhttpSettings.extra = JsonUtils.ParseJson(node.Extra);
|
|
}
|
|
|
|
streamSettings.xhttpSettings = xhttpSettings;
|
|
await GenOutboundMux(node, outbound);
|
|
|
|
break;
|
|
//h2
|
|
case nameof(ETransport.h2):
|
|
HttpSettings4Ray httpSettings = new();
|
|
|
|
if (host.IsNotEmpty())
|
|
{
|
|
httpSettings.host = Utils.String2List(host);
|
|
}
|
|
httpSettings.path = path;
|
|
|
|
streamSettings.httpSettings = httpSettings;
|
|
|
|
break;
|
|
//quic
|
|
case nameof(ETransport.quic):
|
|
QuicSettings4Ray quicsettings = new()
|
|
{
|
|
security = host,
|
|
key = path,
|
|
header = new Header4Ray
|
|
{
|
|
type = node.HeaderType
|
|
}
|
|
};
|
|
streamSettings.quicSettings = quicsettings;
|
|
if (node.StreamSecurity == Global.StreamSecurity)
|
|
{
|
|
if (sni.IsNotEmpty())
|
|
{
|
|
streamSettings.tlsSettings.serverName = sni;
|
|
}
|
|
else
|
|
{
|
|
streamSettings.tlsSettings.serverName = node.Address;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case nameof(ETransport.grpc):
|
|
GrpcSettings4Ray grpcSettings = new()
|
|
{
|
|
authority = host.IsNullOrEmpty() ? null : host,
|
|
serviceName = path,
|
|
multiMode = node.HeaderType == Global.GrpcMultiMode,
|
|
idle_timeout = _config.GrpcItem.IdleTimeout,
|
|
health_check_timeout = _config.GrpcItem.HealthCheckTimeout,
|
|
permit_without_stream = _config.GrpcItem.PermitWithoutStream,
|
|
initial_windows_size = _config.GrpcItem.InitialWindowsSize,
|
|
};
|
|
streamSettings.grpcSettings = grpcSettings;
|
|
break;
|
|
|
|
default:
|
|
//tcp
|
|
if (node.HeaderType == Global.TcpHeaderHttp)
|
|
{
|
|
TcpSettings4Ray tcpSettings = new()
|
|
{
|
|
header = new Header4Ray
|
|
{
|
|
type = node.HeaderType
|
|
}
|
|
};
|
|
|
|
//request Host
|
|
string request = EmbedUtils.GetEmbedText(Global.V2raySampleHttpRequestFileName);
|
|
string[] arrHost = host.Split(',');
|
|
string host2 = string.Join(",".AppendQuotes(), arrHost);
|
|
request = request.Replace("$requestHost$", $"{host2.AppendQuotes()}");
|
|
request = request.Replace("$requestUserAgent$", $"{useragent.AppendQuotes()}");
|
|
//Path
|
|
string pathHttp = @"/";
|
|
if (path.IsNotEmpty())
|
|
{
|
|
string[] arrPath = path.Split(',');
|
|
pathHttp = string.Join(",".AppendQuotes(), arrPath);
|
|
}
|
|
request = request.Replace("$requestPath$", $"{pathHttp.AppendQuotes()}");
|
|
tcpSettings.header.request = JsonUtils.Deserialize<object>(request);
|
|
|
|
streamSettings.tcpSettings = tcpSettings;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.SaveLog(_tag, ex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private async Task<int> GenGroupOutbound(ProfileItem node, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false)
|
|
{
|
|
try
|
|
{
|
|
if (node.ConfigType is not (EConfigType.PolicyGroup or EConfigType.ProxyChain))
|
|
{
|
|
return -1;
|
|
}
|
|
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
|
|
if (profileGroupItem is null || profileGroupItem.ChildItems.IsNullOrEmpty())
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
var hasCycle = await node.HasCycle(new HashSet<string>(), new HashSet<string>());
|
|
if (hasCycle)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// remove custom nodes
|
|
// remove group nodes for proxy chain
|
|
var childProfiles = (await Task.WhenAll(
|
|
Utils.String2List(profileGroupItem.ChildItems)
|
|
.Where(p => !p.IsNullOrEmpty())
|
|
.Select(AppManager.Instance.GetProfileItem)
|
|
))
|
|
.Where(p =>
|
|
p != null &&
|
|
p.IsValid() &&
|
|
p.ConfigType != EConfigType.Custom &&
|
|
(node.ConfigType == EConfigType.PolicyGroup || p.ConfigType < EConfigType.Group)
|
|
)
|
|
.ToList();
|
|
|
|
if (childProfiles.Count <= 0)
|
|
{
|
|
return -1;
|
|
}
|
|
switch (node.ConfigType)
|
|
{
|
|
case EConfigType.PolicyGroup:
|
|
if (ignoreOriginChain)
|
|
{
|
|
await GenOutboundsList(childProfiles, v2rayConfig, baseTagName);
|
|
}
|
|
else
|
|
{
|
|
await GenOutboundsListWithChain(childProfiles, v2rayConfig, baseTagName);
|
|
}
|
|
break;
|
|
case EConfigType.ProxyChain:
|
|
await GenChainOutboundsList(childProfiles, v2rayConfig, baseTagName);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//add balancers
|
|
if (node.ConfigType == EConfigType.PolicyGroup)
|
|
{
|
|
await GenObservatory(v2rayConfig, profileGroupItem.MultipleLoad, baseTagName);
|
|
await GenBalancer(v2rayConfig, profileGroupItem.MultipleLoad, baseTagName);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.SaveLog(_tag, ex);
|
|
}
|
|
return await Task.FromResult(0);
|
|
}
|
|
|
|
private async Task<int> GenMoreOutbounds(ProfileItem node, V2rayConfig v2rayConfig)
|
|
{
|
|
//fragment proxy
|
|
if (_config.CoreBasicItem.EnableFragment
|
|
&& v2rayConfig.outbounds.First().streamSettings?.security.IsNullOrEmpty() == false)
|
|
{
|
|
var fragmentOutbound = new Outbounds4Ray
|
|
{
|
|
protocol = "freedom",
|
|
tag = $"{Global.ProxyTag}3",
|
|
settings = new()
|
|
{
|
|
fragment = new()
|
|
{
|
|
packets = _config.Fragment4RayItem?.Packets,
|
|
length = _config.Fragment4RayItem?.Length,
|
|
interval = _config.Fragment4RayItem?.Interval
|
|
}
|
|
}
|
|
};
|
|
|
|
v2rayConfig.outbounds.Add(fragmentOutbound);
|
|
v2rayConfig.outbounds.First().streamSettings.sockopt = new()
|
|
{
|
|
dialerProxy = fragmentOutbound.tag
|
|
};
|
|
return 0;
|
|
}
|
|
|
|
if (node.Subid.IsNullOrEmpty())
|
|
{
|
|
return 0;
|
|
}
|
|
try
|
|
{
|
|
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
|
if (subItem is null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
//current proxy
|
|
var outbound = v2rayConfig.outbounds.First();
|
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
|
|
|
//Previous proxy
|
|
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
|
string? prevOutboundTag = null;
|
|
if (prevNode is not null
|
|
&& Global.XraySupportConfigType.Contains(prevNode.ConfigType))
|
|
{
|
|
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
|
await GenOutbound(prevNode, prevOutbound);
|
|
prevOutboundTag = $"prev-{Global.ProxyTag}";
|
|
prevOutbound.tag = prevOutboundTag;
|
|
v2rayConfig.outbounds.Add(prevOutbound);
|
|
}
|
|
var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
|
|
|
|
if (nextOutbound is not null)
|
|
{
|
|
v2rayConfig.outbounds.Insert(0, nextOutbound);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.SaveLog(_tag, ex);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private async Task<int> GenOutboundsListWithChain(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag)
|
|
{
|
|
try
|
|
{
|
|
// Get template and initialize list
|
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
|
if (txtOutbound.IsNullOrEmpty())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var resultOutbounds = new List<Outbounds4Ray>();
|
|
var prevOutbounds = new List<Outbounds4Ray>(); // Separate list for prev outbounds and fragment
|
|
|
|
// Cache for chain proxies to avoid duplicate generation
|
|
var nextProxyCache = new Dictionary<string, Outbounds4Ray?>();
|
|
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
|
|
int prevIndex = 0; // Index for prev outbounds
|
|
|
|
// Process nodes
|
|
int index = 0;
|
|
foreach (var node in nodes)
|
|
{
|
|
index++;
|
|
|
|
if (node.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
|
|
{
|
|
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
|
|
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
var childProfiles = (await Task.WhenAll(
|
|
Utils.String2List(profileGroupItem.ChildItems)
|
|
.Where(p => !p.IsNullOrEmpty())
|
|
.Select(AppManager.Instance.GetProfileItem)
|
|
)).Where(p => p != null).ToList();
|
|
if (childProfiles.Count <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
var childBaseTagName = $"{baseTagName}-{index}";
|
|
var ret = node.ConfigType switch
|
|
{
|
|
EConfigType.PolicyGroup =>
|
|
await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName),
|
|
EConfigType.ProxyChain =>
|
|
await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
|
|
_ => throw new NotImplementedException()
|
|
};
|
|
continue;
|
|
}
|
|
|
|
// Handle proxy chain
|
|
string? prevTag = null;
|
|
var currentOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
|
var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null);
|
|
if (nextOutbound != null)
|
|
{
|
|
nextOutbound = JsonUtils.DeepCopy(nextOutbound);
|
|
}
|
|
|
|
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
|
|
|
// current proxy
|
|
await GenOutbound(node, currentOutbound);
|
|
currentOutbound.tag = $"{baseTagName}-{index}";
|
|
|
|
if (!node.Subid.IsNullOrEmpty())
|
|
{
|
|
if (prevProxyTags.TryGetValue(node.Subid, out var value))
|
|
{
|
|
prevTag = value; // maybe null
|
|
}
|
|
else
|
|
{
|
|
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
|
if (prevNode is not null
|
|
&& Global.XraySupportConfigType.Contains(prevNode.ConfigType))
|
|
{
|
|
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
|
await GenOutbound(prevNode, prevOutbound);
|
|
prevTag = $"prev-{baseTagName}-{++prevIndex}";
|
|
prevOutbound.tag = prevTag;
|
|
prevOutbounds.Add(prevOutbound);
|
|
}
|
|
prevProxyTags[node.Subid] = prevTag;
|
|
}
|
|
|
|
nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound);
|
|
if (!nextProxyCache.ContainsKey(node.Subid))
|
|
{
|
|
nextProxyCache[node.Subid] = nextOutbound;
|
|
}
|
|
}
|
|
|
|
if (nextOutbound is not null)
|
|
{
|
|
resultOutbounds.Add(nextOutbound);
|
|
}
|
|
resultOutbounds.Add(currentOutbound);
|
|
}
|
|
|
|
// Merge results: first the main chain outbounds, then other outbounds, and finally utility outbounds
|
|
resultOutbounds.AddRange(prevOutbounds);
|
|
resultOutbounds.AddRange(v2rayConfig.outbounds);
|
|
v2rayConfig.outbounds = resultOutbounds;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.SaveLog(_tag, ex);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a chained outbound configuration for the given subItem and outbound.
|
|
/// The outbound's tag must be set before calling this method.
|
|
/// Returns the next proxy's outbound configuration, which may be null if no next proxy exists.
|
|
/// </summary>
|
|
/// <param name="subItem">The subscription item containing proxy chain information.</param>
|
|
/// <param name="outbound">The current outbound configuration. Its tag must be set before calling this method.</param>
|
|
/// <param name="prevOutboundTag">The tag of the previous outbound in the chain, if any.</param>
|
|
/// <param name="nextOutbound">The outbound for the next proxy in the chain, if already created. If null, will be created inside.</param>
|
|
/// <returns>
|
|
/// The outbound configuration for the next proxy in the chain, or null if no next proxy exists.
|
|
/// </returns>
|
|
private async Task<Outbounds4Ray?> GenChainOutbounds(SubItem subItem, Outbounds4Ray outbound, string? prevOutboundTag, Outbounds4Ray? nextOutbound = null)
|
|
{
|
|
try
|
|
{
|
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
|
|
|
if (!prevOutboundTag.IsNullOrEmpty())
|
|
{
|
|
outbound.streamSettings.sockopt = new()
|
|
{
|
|
dialerProxy = prevOutboundTag
|
|
};
|
|
}
|
|
|
|
// Next proxy
|
|
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
|
if (nextNode is not null
|
|
&& Global.XraySupportConfigType.Contains(nextNode.ConfigType))
|
|
{
|
|
if (nextOutbound == null)
|
|
{
|
|
nextOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
|
await GenOutbound(nextNode, nextOutbound);
|
|
}
|
|
nextOutbound.tag = outbound.tag;
|
|
|
|
outbound.tag = $"mid-{outbound.tag}";
|
|
nextOutbound.streamSettings.sockopt = new()
|
|
{
|
|
dialerProxy = outbound.tag
|
|
};
|
|
}
|
|
return nextOutbound;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.SaveLog(_tag, ex);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag)
|
|
{
|
|
var resultOutbounds = new List<Outbounds4Ray>();
|
|
for (var i = 0; i < nodes.Count; i++)
|
|
{
|
|
var node = nodes[i];
|
|
if (node == null)
|
|
continue;
|
|
if (node.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
|
|
{
|
|
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
|
|
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
var childProfiles = (await Task.WhenAll(
|
|
Utils.String2List(profileGroupItem.ChildItems)
|
|
.Where(p => !p.IsNullOrEmpty())
|
|
.Select(AppManager.Instance.GetProfileItem)
|
|
)).Where(p => p != null).ToList();
|
|
if (childProfiles.Count <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
var childBaseTagName = $"{baseTagName}-{i + 1}";
|
|
var ret = node.ConfigType switch
|
|
{
|
|
EConfigType.PolicyGroup =>
|
|
await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName),
|
|
EConfigType.ProxyChain =>
|
|
await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
|
|
_ => throw new NotImplementedException()
|
|
};
|
|
continue;
|
|
}
|
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
|
if (txtOutbound.IsNullOrEmpty())
|
|
{
|
|
break;
|
|
}
|
|
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
|
var result = await GenOutbound(node, outbound);
|
|
if (result != 0)
|
|
{
|
|
break;
|
|
}
|
|
outbound.tag = baseTagName + (i + 1).ToString();
|
|
resultOutbounds.Add(outbound);
|
|
}
|
|
v2rayConfig.outbounds ??= new();
|
|
resultOutbounds.AddRange(v2rayConfig.outbounds);
|
|
v2rayConfig.outbounds = resultOutbounds;
|
|
return await Task.FromResult(0);
|
|
}
|
|
|
|
private async Task<int> GenChainOutboundsList(List<ProfileItem> nodes, V2rayConfig v2RayConfig, string baseTagName = Global.ProxyTag)
|
|
{
|
|
// Based on actual network flow instead of data packets
|
|
var nodesReverse = nodes.AsEnumerable().Reverse().ToList();
|
|
var resultOutbounds = new List<Outbounds4Ray>();
|
|
for (var i = 0; i < nodesReverse.Count; i++)
|
|
{
|
|
var node = nodesReverse[i];
|
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
|
if (txtOutbound.IsNullOrEmpty())
|
|
{
|
|
break;
|
|
}
|
|
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
|
var result = await GenOutbound(node, outbound);
|
|
|
|
if (result != 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (i == 0)
|
|
{
|
|
outbound.tag = baseTagName;
|
|
}
|
|
else
|
|
{
|
|
// avoid v2ray observe
|
|
outbound.tag = "chain-" + baseTagName + i.ToString();
|
|
}
|
|
|
|
if (i != nodesReverse.Count - 1)
|
|
{
|
|
outbound.streamSettings.sockopt = new()
|
|
{
|
|
dialerProxy = "chain-" + baseTagName + (i + 1).ToString()
|
|
};
|
|
}
|
|
|
|
resultOutbounds.Add(outbound);
|
|
}
|
|
v2RayConfig.outbounds ??= new();
|
|
resultOutbounds.AddRange(v2RayConfig.outbounds);
|
|
v2RayConfig.outbounds = resultOutbounds;
|
|
|
|
return await Task.FromResult(0);
|
|
}
|
|
}
|