Files
2dust_v2rayN/v2rayN/v2rayN/Handler/CoreConfigSingbox.cs
T

740 lines
26 KiB
C#

using v2rayN.Base;
using v2rayN.Mode;
using v2rayN.Resx;
namespace v2rayN.Handler
{
internal class CoreConfigSingbox
{
private string SampleClient = Global.SingboxSampleClient;
private Config _config;
public CoreConfigSingbox(Config config)
{
_config = config;
}
public int GenerateClientConfigContent(ProfileItem node, out SingboxConfig? singboxConfig, out string msg)
{
singboxConfig = null;
try
{
if (node == null
|| node.port <= 0)
{
msg = ResUI.CheckServerSettings;
return -1;
}
msg = ResUI.InitialConfiguration;
string result = Utils.GetEmbedText(SampleClient);
if (Utils.IsNullOrEmpty(result))
{
msg = ResUI.FailedGetDefaultConfiguration;
return -1;
}
singboxConfig = Utils.FromJson<SingboxConfig>(result);
if (singboxConfig == null)
{
msg = ResUI.FailedGenDefaultConfiguration;
return -1;
}
log(singboxConfig);
inbound(singboxConfig);
outbound(node, singboxConfig);
routing(singboxConfig);
dns(node, singboxConfig);
statistic(singboxConfig);
msg = string.Format(ResUI.SuccessfulConfiguration, "");
}
catch (Exception ex)
{
Utils.SaveLog("GenerateClientConfig4Singbox", ex);
msg = ResUI.FailedGenDefaultConfiguration;
return -1;
}
return 0;
}
private int log(SingboxConfig singboxConfig)
{
try
{
switch (_config.coreBasicItem.loglevel)
{
case "debug":
case "info":
case "error":
singboxConfig.log.level = _config.coreBasicItem.loglevel;
break;
case "warning":
singboxConfig.log.level = "warn";
break;
default:
break;
}
if (_config.coreBasicItem.loglevel == "none")
{
singboxConfig.log.disabled = true;
}
if (_config.coreBasicItem.logEnabled)
{
var dtNow = DateTime.Now;
singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt");
}
}
catch (Exception ex)
{
Utils.SaveLog(ex.Message, ex);
}
return 0;
}
#region inbound private
private int inbound(SingboxConfig singboxConfig)
{
try
{
singboxConfig.inbounds.Clear();
if (!_config.tunModeItem.enableTun || (_config.tunModeItem.enableTun && _config.tunModeItem.enableExInbound))
{
var inbound = new Inbound4Sbox()
{
type = Global.InboundSocks,
tag = Global.InboundSocks,
listen = Global.Loopback,
};
singboxConfig.inbounds.Add(inbound);
inbound.listen_port = LazyConfig.Instance.GetLocalPort(Global.InboundSocks);
inbound.sniff = _config.inbound[0].sniffingEnabled;
inbound.sniff_override_destination = _config.inbound[0].routeOnly ? false : _config.inbound[0].sniffingEnabled;
inbound.domain_strategy = Utils.IsNullOrEmpty(_config.routingBasicItem.domainStrategy4Singbox) ? null : _config.routingBasicItem.domainStrategy4Singbox;
if (_config.routingBasicItem.enableRoutingAdvanced)
{
var routing = ConfigHandler.GetDefaultRouting(ref _config);
if (!Utils.IsNullOrEmpty(routing.domainStrategy4Singbox))
{
inbound.domain_strategy = routing.domainStrategy4Singbox;
}
}
//http
var inbound2 = GetInbound(inbound, Global.InboundHttp, 1, false);
singboxConfig.inbounds.Add(inbound2);
if (_config.inbound[0].allowLANConn)
{
if (_config.inbound[0].newPort4LAN)
{
var inbound3 = GetInbound(inbound, Global.InboundSocks2, 2, true);
inbound3.listen = "::";
singboxConfig.inbounds.Add(inbound3);
var inbound4 = GetInbound(inbound, Global.InboundHttp2, 3, false);
inbound4.listen = "::";
singboxConfig.inbounds.Add(inbound4);
//auth
if (!Utils.IsNullOrEmpty(_config.inbound[0].user) && !Utils.IsNullOrEmpty(_config.inbound[0].pass))
{
inbound3.users = new() { new() { username = _config.inbound[0].user, password = _config.inbound[0].pass } };
inbound4.users = new() { new() { username = _config.inbound[0].user, password = _config.inbound[0].pass } };
}
}
else
{
inbound.listen = "::";
inbound2.listen = "::";
}
}
}
if (_config.tunModeItem.enableTun)
{
if (_config.tunModeItem.mtu <= 0)
{
_config.tunModeItem.mtu = Convert.ToInt32(Global.TunMtus[0]);
}
if (Utils.IsNullOrEmpty(_config.tunModeItem.stack))
{
_config.tunModeItem.stack = Global.TunStacks[0];
}
var tunInbound = Utils.FromJson<Inbound4Sbox>(Utils.GetEmbedText(Global.TunSingboxInboundFileName));
tunInbound.mtu = _config.tunModeItem.mtu;
tunInbound.strict_route = _config.tunModeItem.strictRoute;
tunInbound.stack = _config.tunModeItem.stack;
singboxConfig.inbounds.Add(tunInbound);
}
}
catch (Exception ex)
{
Utils.SaveLog(ex.Message, ex);
}
return 0;
}
private Inbound4Sbox? GetInbound(Inbound4Sbox inItem, string tag, int offset, bool bSocks)
{
var inbound = Utils.DeepCopy(inItem);
inbound.tag = tag;
inbound.listen_port = inItem.listen_port + offset;
inbound.type = bSocks ? Global.InboundSocks : Global.InboundHttp;
return inbound;
}
#endregion inbound private
#region outbound private
private int outbound(ProfileItem node, SingboxConfig singboxConfig)
{
try
{
if (_config.tunModeItem.enableTun)
{
singboxConfig.outbounds.Add(new()
{
type = "dns",
tag = "dns_out"
});
}
var outbound = singboxConfig.outbounds[0];
outbound.server = node.address;
outbound.server_port = node.port;
if (node.configType == EConfigType.VMess)
{
outbound.type = Global.vmessProtocolLite;
outbound.uuid = node.id;
outbound.alter_id = node.alterId;
if (Global.vmessSecuritys.Contains(node.security))
{
outbound.security = node.security;
}
else
{
outbound.security = Global.DefaultSecurity;
}
outboundMux(node, outbound);
}
else if (node.configType == EConfigType.Shadowsocks)
{
outbound.type = Global.ssProtocolLite;
outbound.method = LazyConfig.Instance.GetShadowsocksSecuritys(node).Contains(node.security) ? node.security : "none";
outbound.password = node.id;
outboundMux(node, outbound);
}
else if (node.configType == EConfigType.Socks)
{
outbound.type = Global.socksProtocolLite;
outbound.version = "5";
if (!Utils.IsNullOrEmpty(node.security)
&& !Utils.IsNullOrEmpty(node.id))
{
outbound.username = node.security;
outbound.password = node.id;
}
}
else if (node.configType == EConfigType.VLESS)
{
outbound.type = Global.vlessProtocolLite;
outbound.uuid = node.id;
outbound.packet_encoding = "xudp";
if (Utils.IsNullOrEmpty(node.flow))
{
outboundMux(node, outbound);
}
else
{
outbound.flow = node.flow;
}
}
else if (node.configType == EConfigType.Trojan)
{
outbound.type = Global.trojanProtocolLite;
outbound.password = node.id;
outboundMux(node, outbound);
}
outboundTls(node, outbound);
outboundTransport(node, outbound);
}
catch (Exception ex)
{
Utils.SaveLog(ex.Message, ex);
}
return 0;
}
private int outboundMux(ProfileItem node, Outbound4Sbox outbound)
{
try
{
//if (_config.coreBasicItem.muxEnabled)
//{
// var mux = new Multiplex4Sbox()
// {
// enabled = true,
// protocol = _config.mux4Sbox.protocol,
// max_connections = _config.mux4Sbox.max_connections,
// min_streams = _config.mux4Sbox.min_streams,
// max_streams = _config.mux4Sbox.max_streams,
// padding = _config.mux4Sbox.padding
// };
// outbound.multiplex = mux;
//}
}
catch (Exception ex)
{
Utils.SaveLog(ex.Message, ex);
}
return 0;
}
private int outboundTls(ProfileItem node, Outbound4Sbox outbound)
{
try
{
if (node.streamSecurity == Global.StreamSecurityReality || node.streamSecurity == Global.StreamSecurity)
{
var server_name = string.Empty;
if (!string.IsNullOrWhiteSpace(node.sni))
{
server_name = node.sni;
}
else if (!string.IsNullOrWhiteSpace(node.requestHost))
{
server_name = Utils.String2List(node.requestHost)[0];
}
var tls = new Tls4Sbox()
{
enabled = true,
server_name = server_name,
insecure = Utils.ToBool(node.allowInsecure.IsNullOrEmpty() ? _config.coreBasicItem.defAllowInsecure.ToString().ToLower() : node.allowInsecure),
alpn = node.GetAlpn(),
};
if (!Utils.IsNullOrEmpty(node.fingerprint))
{
tls.utls = new Utls4Sbox()
{
enabled = true,
fingerprint = node.fingerprint.IsNullOrEmpty() ? _config.coreBasicItem.defFingerprint : node.fingerprint
};
}
if (node.streamSecurity == Global.StreamSecurityReality)
{
tls.reality = new Reality4Sbox()
{
enabled = true,
public_key = node.publicKey,
short_id = node.shortId
};
tls.insecure = false;
}
outbound.tls = tls;
}
}
catch (Exception ex)
{
Utils.SaveLog(ex.Message, ex);
}
return 0;
}
private int outboundTransport(ProfileItem node, Outbound4Sbox outbound)
{
try
{
var transport = new Transport4Sbox();
switch (node.GetNetwork())
{
case "h2":
transport.type = "http";
transport.host = Utils.IsNullOrEmpty(node.requestHost) ? null : Utils.String2List(node.requestHost);
transport.path = Utils.IsNullOrEmpty(node.path) ? null : node.path;
break;
case "ws":
transport.type = "ws";
transport.path = Utils.IsNullOrEmpty(node.path) ? null : node.path;
if (!Utils.IsNullOrEmpty(node.requestHost))
{
transport.headers = new()
{
Host = node.requestHost
};
}
break;
case "quic":
transport.type = "quic";
break;
case "grpc":
transport.type = "grpc";
transport.service_name = node.path;
transport.idle_timeout = _config.grpcItem.idle_timeout.ToString("##s");
transport.ping_timeout = _config.grpcItem.health_check_timeout.ToString("##s");
transport.permit_without_stream = _config.grpcItem.permit_without_stream;
break;
default:
transport = null;
break;
}
outbound.transport = transport;
}
catch (Exception ex)
{
Utils.SaveLog(ex.Message, ex);
}
return 0;
}
#endregion outbound private
#region routing rule private
private int routing(SingboxConfig singboxConfig)
{
try
{
if (_config.tunModeItem.enableTun)
{
singboxConfig.route.auto_detect_interface = true;
var tunRules = Utils.FromJson<List<Rule4Sbox>>(Utils.GetEmbedText(Global.TunSingboxRulesFileName));
singboxConfig.route.rules.AddRange(tunRules);
routingDirectExe(out List<string> lstDnsExe, out List<string> lstDirectExe);
singboxConfig.route.rules.Add(new()
{
port = new() { 53 },
outbound = "dns_out",
process_name = lstDnsExe
});
singboxConfig.route.rules.Add(new()
{
outbound = "direct",
process_name = lstDirectExe
});
}
if (_config.routingBasicItem.enableRoutingAdvanced)
{
var routing = ConfigHandler.GetDefaultRouting(ref _config);
if (routing != null)
{
var rules = Utils.FromJson<List<RulesItem>>(routing.ruleSet);
foreach (var item in rules!)
{
if (item.enabled)
{
routingUserRule(item, singboxConfig.route.rules);
}
}
}
}
else
{
var lockedItem = ConfigHandler.GetLockedRoutingItem(ref _config);
if (lockedItem != null)
{
var rules = Utils.FromJson<List<RulesItem>>(lockedItem.ruleSet);
foreach (var item in rules!)
{
routingUserRule(item, singboxConfig.route.rules);
}
}
}
}
catch (Exception ex)
{
Utils.SaveLog(ex.Message, ex);
}
return 0;
}
private void routingDirectExe(out List<string> lstDnsExe, out List<string> lstDirectExe)
{
lstDnsExe = new();
lstDirectExe = new();
var coreInfos = LazyConfig.Instance.GetCoreInfos();
foreach (var it in coreInfos)
{
if (it.coreType == ECoreType.v2rayN)
{
continue;
}
foreach (var it2 in it.coreExes)
{
if (!lstDnsExe.Contains(it2) && it.coreType != ECoreType.sing_box)
{
lstDnsExe.Add($"{it2}.exe");
}
if (!lstDirectExe.Contains(it2))
{
lstDirectExe.Add($"{it2}.exe");
}
}
}
}
private int routingUserRule(RulesItem item, List<Rule4Sbox> rules)
{
try
{
if (item == null)
{
return 0;
}
var rule = new Rule4Sbox()
{
outbound = item.outboundTag,
};
if (!Utils.IsNullOrEmpty(item.port))
{
if (item.port.Contains("-"))
{
rule.port_range = new List<string> { item.port.Replace("-", ":") };
}
else
{
rule.port = new List<int> { Utils.ToInt(item.port) };
}
}
if (item.protocol?.Count > 0)
{
rule.protocol = item.protocol;
}
if (item.inboundTag?.Count >= 0)
{
rule.inbound = item.inboundTag;
}
var rule2 = Utils.DeepCopy(rule);
var rule3 = Utils.DeepCopy(rule);
var hasDomainIp = false;
if (item.domain?.Count > 0)
{
foreach (var it in item.domain)
{
parseV2Domain(it, rule);
}
rules.Add(rule);
hasDomainIp = true;
}
if (item.ip?.Count > 0)
{
foreach (var it in item.ip)
{
parseV2Address(it, rule2);
}
rules.Add(rule2);
hasDomainIp = true;
}
if (_config.tunModeItem.enableTun && item.process?.Count > 0)
{
rule3.process_name = item.process;
rules.Add(rule3);
hasDomainIp = true;
}
if (!hasDomainIp)
{
rules.Add(rule);
}
}
catch (Exception ex)
{
Utils.SaveLog(ex.Message, ex);
}
return 0;
}
private void parseV2Domain(string domain, Rule4Sbox rule)
{
if (domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
{
return;
}
else if (domain.StartsWith("geosite:"))
{
if (rule.geosite is null) { rule.geosite = new(); }
rule.geosite?.Add(domain.Substring(8));
}
else if (domain.StartsWith("regexp:"))
{
if (rule.domain_regex is null) { rule.domain_regex = new(); }
rule.domain_regex?.Add(domain.Replace(Global.RoutingRuleComma, ",").Substring(7));
}
else if (domain.StartsWith("domain:"))
{
if (rule.domain is null) { rule.domain = new(); }
if (rule.domain_suffix is null) { rule.domain_suffix = new(); }
rule.domain?.Add(domain.Substring(7));
rule.domain_suffix?.Add("." + domain.Substring(7));
}
else if (domain.StartsWith("full:"))
{
if (rule.domain is null) { rule.domain = new(); }
rule.domain?.Add(domain.Substring(5));
}
else if (domain.StartsWith("keyword:"))
{
if (rule.domain_keyword is null) { rule.domain_keyword = new(); }
rule.domain_keyword?.Add(domain.Substring(8));
}
else
{
if (rule.domain_keyword is null) { rule.domain_keyword = new(); }
rule.domain_keyword?.Add(domain);
}
}
private void parseV2Address(string address, Rule4Sbox rule)
{
if (address.StartsWith("ext:") || address.StartsWith("ext-ip:"))
{
return;
}
else if (address.StartsWith("geoip:!"))
{
return;
}
else if (address.StartsWith("geoip:"))
{
if (rule.geoip is null) { rule.geoip = new(); }
rule.geoip?.Add(address.Substring(6));
}
else
{
if (rule.ip_cidr is null) { rule.ip_cidr = new(); }
rule.ip_cidr?.Add(address);
}
}
#endregion routing rule private
#region dns private
private int dns(ProfileItem node, SingboxConfig singboxConfig)
{
try
{
Dns4Sbox? dns4Sbox;
if (_config.tunModeItem.enableTun)
{
var item = LazyConfig.Instance.GetDNSItem(ECoreType.sing_box);
var tunDNS = item?.tunDNS;
if (string.IsNullOrWhiteSpace(tunDNS))
{
tunDNS = Utils.GetEmbedText(Global.TunSingboxDNSFileName);
}
dns4Sbox = Utils.FromJson<Dns4Sbox>(tunDNS);
}
else
{
var item = LazyConfig.Instance.GetDNSItem(ECoreType.sing_box);
var normalDNS = item?.normalDNS;
if (string.IsNullOrWhiteSpace(normalDNS))
{
normalDNS = "{\"servers\":[{\"address\":\"tcp://8.8.8.8\"}]}";
}
dns4Sbox = Utils.FromJson<Dns4Sbox>(normalDNS);
}
if (dns4Sbox is null)
{
return 0;
}
//Add the dns of the remote server domain
if (dns4Sbox.rules is null)
{
dns4Sbox.rules = new();
}
dns4Sbox.servers.Add(new()
{
tag = "local_local",
address = "223.5.5.5",
detour = "direct"
});
dns4Sbox.rules.Add(new()
{
server = "local_local",
outbound = "any"
});
singboxConfig.dns = dns4Sbox;
}
catch (Exception ex)
{
Utils.SaveLog(ex.Message, ex);
}
return 0;
}
#endregion dns private
private int statistic(SingboxConfig singboxConfig)
{
if (_config.guiItem.enableStatistics)
{
singboxConfig.experimental = new Experimental4Sbox()
{
//v2ray_api = new V2ray_Api4Sbox()
//{
// listen = $"{Global.Loopback}:{Global.statePort}",
// stats = new Stats4Sbox()
// {
// enabled = true,
// }
//}
clash_api = new Clash_Api4Sbox()
{
external_controller = $"{Global.Loopback}:{Global.statePort}",
store_selected = true
}
};
}
return 0;
}
}
}