mirror of
https://github.com/2dust/v2rayN.git
synced 2025-11-22 15:42:52 +00:00
perf: Shadowsocks (#8352)
* perf: Shadowsocks * stricter plugin name fix for SIP002 URI * Fix
This commit is contained in:
parent
d3e2e55ecf
commit
f61e6d8c63
2 changed files with 232 additions and 64 deletions
|
|
@ -41,7 +41,68 @@ public class ShadowsocksFmt : BaseFmt
|
||||||
//url = Utile.Base64Encode(url);
|
//url = Utile.Base64Encode(url);
|
||||||
//new Sip002
|
//new Sip002
|
||||||
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
|
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
|
||||||
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, null, remark);
|
|
||||||
|
// plugin
|
||||||
|
var plugin = string.Empty;
|
||||||
|
var pluginArgs = string.Empty;
|
||||||
|
|
||||||
|
if (item.Network == nameof(ETransport.tcp) && item.HeaderType == Global.TcpHeaderHttp)
|
||||||
|
{
|
||||||
|
plugin = "obfs-local";
|
||||||
|
pluginArgs = $"obfs=http;obfs-host={item.RequestHost};";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (item.Network == nameof(ETransport.ws))
|
||||||
|
{
|
||||||
|
pluginArgs += "mode=websocket;";
|
||||||
|
pluginArgs += $"host={item.RequestHost};";
|
||||||
|
pluginArgs += $"path={item.Path};";
|
||||||
|
}
|
||||||
|
else if (item.Network == nameof(ETransport.quic))
|
||||||
|
{
|
||||||
|
pluginArgs += "mode=quic;";
|
||||||
|
}
|
||||||
|
if (item.StreamSecurity == Global.StreamSecurity)
|
||||||
|
{
|
||||||
|
pluginArgs += "tls;";
|
||||||
|
var certs = CertPemManager.ParsePemChain(item.Cert);
|
||||||
|
if (certs.Count > 0)
|
||||||
|
{
|
||||||
|
var cert = certs.First();
|
||||||
|
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
|
||||||
|
const string endMarker = "\n-----END CERTIFICATE-----";
|
||||||
|
|
||||||
|
var base64Start = beginMarker.Length;
|
||||||
|
var endIndex = cert.IndexOf(endMarker, base64Start, StringComparison.Ordinal);
|
||||||
|
var base64Content = cert.Substring(base64Start, endIndex - base64Start);
|
||||||
|
|
||||||
|
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
|
||||||
|
// Equal signs and commas [and backslashes] must be escaped with a backslash.
|
||||||
|
base64Content = base64Content.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
|
||||||
|
|
||||||
|
pluginArgs += $"certRaw={base64Content};";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pluginArgs.Length > 0)
|
||||||
|
{
|
||||||
|
plugin = "v2ray-plugin";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dicQuery = new Dictionary<string, string>();
|
||||||
|
if (plugin.IsNotEmpty())
|
||||||
|
{
|
||||||
|
var pluginStr = plugin + ";" + pluginArgs;
|
||||||
|
// pluginStr remove last ';' and url encode
|
||||||
|
if (pluginStr.EndsWith(';'))
|
||||||
|
{
|
||||||
|
pluginStr = pluginStr[..^1];
|
||||||
|
}
|
||||||
|
dicQuery["plugin"] = Utils.UrlEncode(pluginStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, dicQuery, remark);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Regex UrlFinder = new(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
private static readonly Regex UrlFinder = new(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
@ -124,19 +185,81 @@ public class ShadowsocksFmt : BaseFmt
|
||||||
var queryParameters = Utils.ParseQueryString(parsedUrl.Query);
|
var queryParameters = Utils.ParseQueryString(parsedUrl.Query);
|
||||||
if (queryParameters["plugin"] != null)
|
if (queryParameters["plugin"] != null)
|
||||||
{
|
{
|
||||||
//obfs-host exists
|
var pluginStr = queryParameters["plugin"];
|
||||||
var obfsHost = queryParameters["plugin"]?.Split(';').FirstOrDefault(t => t.Contains("obfs-host"));
|
var pluginParts = pluginStr.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (queryParameters["plugin"].Contains("obfs=http") && obfsHost.IsNotEmpty())
|
|
||||||
{
|
if (pluginParts.Length == 0)
|
||||||
obfsHost = obfsHost?.Replace("obfs-host=", "");
|
|
||||||
item.Network = Global.DefaultNetwork;
|
|
||||||
item.HeaderType = Global.TcpHeaderHttp;
|
|
||||||
item.RequestHost = obfsHost ?? "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pluginName = pluginParts[0];
|
||||||
|
|
||||||
|
// A typo in https://github.com/shadowsocks/shadowsocks-org/blob/6b1c064db4129de99c516294960e731934841c94/docs/doc/sip002.md?plain=1#L15
|
||||||
|
// "simple-obfs" should be "obfs-local"
|
||||||
|
if (pluginName == "simple-obfs")
|
||||||
|
{
|
||||||
|
pluginName = "obfs-local";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse obfs-local plugin
|
||||||
|
if (pluginName == "obfs-local")
|
||||||
|
{
|
||||||
|
var obfsMode = pluginParts.FirstOrDefault(t => t.StartsWith("obfs="));
|
||||||
|
var obfsHost = pluginParts.FirstOrDefault(t => t.StartsWith("obfs-host="));
|
||||||
|
|
||||||
|
if ((!obfsMode.IsNullOrEmpty()) && obfsMode.Contains("obfs=http") && obfsHost.IsNotEmpty())
|
||||||
|
{
|
||||||
|
obfsHost = obfsHost.Replace("obfs-host=", "");
|
||||||
|
item.Network = Global.DefaultNetwork;
|
||||||
|
item.HeaderType = Global.TcpHeaderHttp;
|
||||||
|
item.RequestHost = obfsHost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Parse v2ray-plugin
|
||||||
|
else if (pluginName == "v2ray-plugin")
|
||||||
|
{
|
||||||
|
var mode = pluginParts.FirstOrDefault(t => t.StartsWith("mode="), "websocket");
|
||||||
|
var host = pluginParts.FirstOrDefault(t => t.StartsWith("host="));
|
||||||
|
var path = pluginParts.FirstOrDefault(t => t.StartsWith("path="));
|
||||||
|
var hasTls = pluginParts.Any(t => t == "tls");
|
||||||
|
var certRaw = pluginParts.FirstOrDefault(t => t.StartsWith("certRaw="));
|
||||||
|
|
||||||
|
var modeValue = mode.Replace("mode=", "");
|
||||||
|
if (modeValue == "websocket")
|
||||||
|
{
|
||||||
|
item.Network = nameof(ETransport.ws);
|
||||||
|
if (!host.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
item.RequestHost = host.Replace("host=", "");
|
||||||
|
}
|
||||||
|
if (!path.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
item.Path = path.Replace("path=", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (modeValue == "quic")
|
||||||
|
{
|
||||||
|
item.Network = nameof(ETransport.quic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasTls)
|
||||||
|
{
|
||||||
|
item.StreamSecurity = Global.StreamSecurity;
|
||||||
|
|
||||||
|
if (!certRaw.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
var certBase64 = certRaw.Replace("certRaw=", "");
|
||||||
|
|
||||||
|
certBase64 = certBase64.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\");
|
||||||
|
|
||||||
|
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
|
||||||
|
const string endMarker = "\n-----END CERTIFICATE-----";
|
||||||
|
var certPem = beginMarker + certBase64 + endMarker;
|
||||||
|
item.Cert = certPem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ public partial class CoreConfigSingboxService
|
||||||
}
|
}
|
||||||
|
|
||||||
await GenOutboundMux(node, outbound);
|
await GenOutboundMux(node, outbound);
|
||||||
|
await GenOutboundTransport(node, outbound);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EConfigType.Shadowsocks:
|
case EConfigType.Shadowsocks:
|
||||||
|
|
@ -33,6 +34,52 @@ public partial class CoreConfigSingboxService
|
||||||
outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : Global.None;
|
outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : Global.None;
|
||||||
outbound.password = node.Id;
|
outbound.password = node.Id;
|
||||||
|
|
||||||
|
if (node.Network == nameof(ETransport.tcp) && node.HeaderType == Global.TcpHeaderHttp)
|
||||||
|
{
|
||||||
|
outbound.plugin = "obfs-local";
|
||||||
|
outbound.plugin_opts = $"obfs=http;obfs-host={node.RequestHost};";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pluginArgs = string.Empty;
|
||||||
|
if (node.Network == nameof(ETransport.ws))
|
||||||
|
{
|
||||||
|
pluginArgs += "mode=websocket;";
|
||||||
|
pluginArgs += $"host={node.RequestHost};";
|
||||||
|
pluginArgs += $"path={node.Path};";
|
||||||
|
}
|
||||||
|
else if (node.Network == nameof(ETransport.quic))
|
||||||
|
{
|
||||||
|
pluginArgs += "mode=quic;";
|
||||||
|
}
|
||||||
|
if (node.StreamSecurity == Global.StreamSecurity)
|
||||||
|
{
|
||||||
|
pluginArgs += "tls;";
|
||||||
|
var certs = CertPemManager.ParsePemChain(node.Cert);
|
||||||
|
if (certs.Count > 0)
|
||||||
|
{
|
||||||
|
var cert = certs.First();
|
||||||
|
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
|
||||||
|
const string endMarker = "\n-----END CERTIFICATE-----";
|
||||||
|
|
||||||
|
var base64Start = beginMarker.Length;
|
||||||
|
var endIndex = cert.IndexOf(endMarker, base64Start, StringComparison.Ordinal);
|
||||||
|
var base64Content = cert.Substring(base64Start, endIndex - base64Start);
|
||||||
|
|
||||||
|
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
|
||||||
|
// Equal signs and commas [and backslashes] must be escaped with a backslash.
|
||||||
|
base64Content = base64Content.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
|
||||||
|
|
||||||
|
pluginArgs += $"certRaw={base64Content};";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pluginArgs.Length > 0)
|
||||||
|
{
|
||||||
|
outbound.plugin = "v2ray-plugin";
|
||||||
|
outbound.plugin_opts = pluginArgs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await GenOutboundMux(node, outbound);
|
await GenOutboundMux(node, outbound);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -71,6 +118,8 @@ public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
outbound.flow = node.Flow;
|
outbound.flow = node.Flow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await GenOutboundTransport(node, outbound);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EConfigType.Trojan:
|
case EConfigType.Trojan:
|
||||||
|
|
@ -78,6 +127,7 @@ public partial class CoreConfigSingboxService
|
||||||
outbound.password = node.Id;
|
outbound.password = node.Id;
|
||||||
|
|
||||||
await GenOutboundMux(node, outbound);
|
await GenOutboundMux(node, outbound);
|
||||||
|
await GenOutboundTransport(node, outbound);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EConfigType.Hysteria2:
|
case EConfigType.Hysteria2:
|
||||||
|
|
@ -127,8 +177,6 @@ public partial class CoreConfigSingboxService
|
||||||
}
|
}
|
||||||
|
|
||||||
await GenOutboundTls(node, outbound);
|
await GenOutboundTls(node, outbound);
|
||||||
|
|
||||||
await GenOutboundTransport(node, outbound);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -232,54 +280,59 @@ public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (node.StreamSecurity is Global.StreamSecurityReality or Global.StreamSecurity)
|
if (node.StreamSecurity is not (Global.StreamSecurityReality or Global.StreamSecurity))
|
||||||
{
|
{
|
||||||
var server_name = string.Empty;
|
return await Task.FromResult(0);
|
||||||
if (node.Sni.IsNotEmpty())
|
}
|
||||||
{
|
if (node.ConfigType is EConfigType.Shadowsocks or EConfigType.SOCKS or EConfigType.WireGuard)
|
||||||
server_name = node.Sni;
|
{
|
||||||
}
|
return await Task.FromResult(0);
|
||||||
else if (node.RequestHost.IsNotEmpty())
|
}
|
||||||
{
|
var server_name = string.Empty;
|
||||||
server_name = Utils.String2List(node.RequestHost)?.First();
|
if (node.Sni.IsNotEmpty())
|
||||||
}
|
{
|
||||||
var tls = new Tls4Sbox()
|
server_name = node.Sni;
|
||||||
|
}
|
||||||
|
else if (node.RequestHost.IsNotEmpty())
|
||||||
|
{
|
||||||
|
server_name = Utils.String2List(node.RequestHost)?.First();
|
||||||
|
}
|
||||||
|
var tls = new Tls4Sbox()
|
||||||
|
{
|
||||||
|
enabled = true,
|
||||||
|
record_fragment = _config.CoreBasicItem.EnableFragment ? true : null,
|
||||||
|
server_name = server_name,
|
||||||
|
insecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
|
||||||
|
alpn = node.GetAlpn(),
|
||||||
|
};
|
||||||
|
if (node.Fingerprint.IsNotEmpty())
|
||||||
|
{
|
||||||
|
tls.utls = new Utls4Sbox()
|
||||||
{
|
{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
record_fragment = _config.CoreBasicItem.EnableFragment ? true : null,
|
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
|
||||||
server_name = server_name,
|
|
||||||
insecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
|
|
||||||
alpn = node.GetAlpn(),
|
|
||||||
};
|
};
|
||||||
if (node.Fingerprint.IsNotEmpty())
|
}
|
||||||
|
if (node.StreamSecurity == Global.StreamSecurity)
|
||||||
|
{
|
||||||
|
var certs = CertPemManager.ParsePemChain(node.Cert);
|
||||||
|
if (certs.Count > 0)
|
||||||
{
|
{
|
||||||
tls.utls = new Utls4Sbox()
|
tls.certificate = certs;
|
||||||
{
|
|
||||||
enabled = true,
|
|
||||||
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (node.StreamSecurity == Global.StreamSecurity)
|
|
||||||
{
|
|
||||||
var certs = CertPemManager.ParsePemChain(node.Cert);
|
|
||||||
if (certs.Count > 0)
|
|
||||||
{
|
|
||||||
tls.certificate = certs;
|
|
||||||
tls.insecure = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (node.StreamSecurity == Global.StreamSecurityReality)
|
|
||||||
{
|
|
||||||
tls.reality = new Reality4Sbox()
|
|
||||||
{
|
|
||||||
enabled = true,
|
|
||||||
public_key = node.PublicKey,
|
|
||||||
short_id = node.ShortId
|
|
||||||
};
|
|
||||||
tls.insecure = false;
|
tls.insecure = false;
|
||||||
}
|
}
|
||||||
outbound.tls = tls;
|
|
||||||
}
|
}
|
||||||
|
else 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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -305,17 +358,9 @@ public partial class CoreConfigSingboxService
|
||||||
case nameof(ETransport.tcp): //http
|
case nameof(ETransport.tcp): //http
|
||||||
if (node.HeaderType == Global.TcpHeaderHttp)
|
if (node.HeaderType == Global.TcpHeaderHttp)
|
||||||
{
|
{
|
||||||
if (node.ConfigType == EConfigType.Shadowsocks)
|
transport.type = nameof(ETransport.http);
|
||||||
{
|
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
|
||||||
outbound.plugin = "obfs-local";
|
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
||||||
outbound.plugin_opts = $"obfs=http;obfs-host={node.RequestHost};";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
transport.type = nameof(ETransport.http);
|
|
||||||
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
|
|
||||||
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue