Files
SbLauncher/tests/ImportSmoke.cs
T

122 lines
7.2 KiB
C#
Raw Normal View History

2026-09-26 14:41:04 +03:00
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Text;
using SbLauncher;
public static class ImportSmoke
{
static int failures;
static void Check(bool ok, string name)
{
Console.WriteLine((ok ? "ok " : "FAIL ") + name);
if (!ok) failures++;
}
[STAThread]
public static int Main()
{
string root = AppDomain.CurrentDomain.BaseDirectory;
Paths paths = new Paths(root);
Profiles profiles = Profiles.Load(paths.Profiles);
// Проверяем импорт на искусственных данных: тест не зависит от
// настоящего приватного ключа и сетевого адаптера пользователя.
byte[] privateBytes = new byte[32], publicBytes = new byte[32];
for (int n = 0; n < 32; n++) { privateBytes[n] = (byte)(n + 1); publicBytes[n] = (byte)(n + 33); }
LocalCfg local = new LocalCfg {
PrivateKey = Convert.ToBase64String(privateBytes),
LocalAddress = "10.10.0.2/24", Adapter = "Ethernet"
};
profiles.Wg.Server = "198.51.100.10";
profiles.Wg.PeerPublicKey = Convert.ToBase64String(publicBytes);
string id = "58863334-3b8a-4c81-9145-a18311621a7f";
string pbk = "Ot1KGILofMNTQa5Ewu5j3EoO1Ap0LEHOhYzzfANiB14";
string vmessJson = "{\"v\":\"2\",\"ps\":\"VMess WS\",\"add\":\"example.com\",\"port\":\"443\",\"id\":\"" + id + "\",\"aid\":\"0\",\"scy\":\"auto\",\"net\":\"ws\",\"host\":\"cdn.example.com\",\"path\":\"/ws\",\"tls\":\"tls\",\"sni\":\"example.com\"}";
string[] keys = {
"vless://" + id + "@example.com:443?security=reality&pbk=" + pbk + "&sid=ab&flow=xtls-rprx-vision#Reality",
"vless://" + id + "@example.com:443?security=tls&type=ws&host=cdn.example.com&path=%2Fws&alpn=h2%2Chttp%2F1.1#WebSocket",
"vless://" + id + "@example.com:443?security=tls&type=grpc&serviceName=service#GRPC",
"vless://" + id + "@example.com:443?security=tls&type=httpupgrade&host=cdn.example.com&path=%2Fup#Upgrade",
"vless://" + id + "@example.com:80?security=none#Plain",
"vmess://" + Convert.ToBase64String(Encoding.UTF8.GetBytes(vmessJson)),
"trojan://test-password@example.com:443?type=grpc&serviceName=tun&sni=example.com#Trojan",
"ss://" + Convert.ToBase64String(Encoding.UTF8.GetBytes("aes-256-gcm:password")) + "@example.com:8388#SIP002",
"ss://" + Convert.ToBase64String(Encoding.UTF8.GetBytes("aes-256-gcm:password@example.com:8388")) + "#LegacySS",
"hysteria2://password@example.com:443?sni=example.com&insecure=1&obfs=salamander&obfs-password=secret#Hy2",
"tuic://" + id + ":password@example.com:443?sni=example.com&alpn=h3#TUIC",
"anytls://password@example.com:443?sni=example.com#AnyTLS"
};
for (int i = 0; i < keys.Length; i++)
{
ServerCfg server;
string error;
bool parsed = KeyImporter.TryParseOne(keys[i], out server, out error);
Check(parsed, "parse " + i + (parsed ? " " + server.Protocol : " " + error));
if (!parsed) continue;
bool valid = KeyImporter.CheckWithSingBox(paths, server, out error);
Check(valid, "outbound " + i + (valid ? "" : " " + error));
if (!valid) continue;
foreach (string mode in new string[] { ConfigBuilder.ModeWg, ConfigBuilder.ModeDirect })
{
string config = Path.Combine(paths.RunDir, "import-smoke-" + i + "-" + mode + ".json");
try
{
BuildResult built = ConfigBuilder.Build(paths, profiles, local, server, mode, config);
if (!built.Ok) { Check(false, "build " + i + " " + mode + " " + built.Error); continue; }
string output;
bool checkedOk = SingBoxRunner.Check(paths.SbExe, config, paths.Root, out output);
Check(checkedOk, "config " + i + " " + mode + (checkedOk ? "" : " " + output));
}
finally { if (File.Exists(config)) File.Delete(config); }
}
}
ImportResult batch = KeyImporter.Parse(keys[0] + "\n" + keys[6]);
Check(batch.Servers.Count == 2 && batch.Errors.Count == 0, "multiline import");
string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(keys[0] + "\n" + keys[6]));
ImportResult subscription = KeyImporter.Parse(encoded);
Check(subscription.Servers.Count == 2 && subscription.Errors.Count == 0, "base64 subscription");
TcpListener listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
int port = ((IPEndPoint)listener.LocalEndpoint).Port;
bool accepted = false;
Task served = Task.Run(delegate
{
using (TcpClient client = listener.AcceptTcpClient())
using (NetworkStream stream = client.GetStream())
{
accepted = true;
byte[] endOfHeaders = { 13, 10, 13, 10 };
int matched = 0;
while (matched < 4)
{
int b = stream.ReadByte();
if (b < 0) break;
matched = b == endOfHeaders[matched] ? matched + 1 : 0;
}
byte[] body = Encoding.UTF8.GetBytes(encoded);
byte[] head = Encoding.ASCII.GetBytes("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: " + body.Length + "\r\nConnection: close\r\n\r\n");
stream.Write(head, 0, head.Length);
stream.Write(body, 0, body.Length);
}
});
try
{
ImportResult remote = KeyImporter.Parse("http://127.0.0.1:" + port + "/subscription");
Check(remote.Servers.Count == 2 && remote.Errors.Count == 0, "HTTP subscription " + remote.Servers.Count + " accepted=" + accepted + " served=" + served.IsCompleted + " / " + string.Join("; ", remote.Errors.ToArray()));
served.Wait(3000);
}
finally { listener.Stop(); }
ImportResult json = KeyImporter.Parse("{\"outbounds\":[{\"type\":\"trojan\",\"tag\":\"from-json\",\"server\":\"example.com\",\"server_port\":443,\"password\":\"test\",\"tls\":{\"enabled\":true}}]}");
Check(json.Servers.Count == 1 && json.Servers[0].Protocol == "trojan", "sing-box JSON");
ImportResult sameJson = KeyImporter.Parse("{\"type\":\"trojan\",\"tls\":{\"enabled\":true},\"password\":\"test\",\"server_port\":443,\"server\":\"example.com\"}");
Check(sameJson.Servers.Count == 1 && KeyImporter.Same(json.Servers[0], sameJson.Servers[0]), "dedupe reordered JSON");
ServerCfg bad; string reason;
Check(!KeyImporter.TryParseOne("vless://" + id + "@example.com:443?security=tls&type=xhttp", out bad, out reason) && reason.Contains("транспорт"), "unsupported transport rejected");
Check(!KeyImporter.TryParseOne("vless://" + id + "@example.com:443?security=reality", out bad, out reason) && reason.Contains("public key"), "incomplete Reality rejected");
Console.WriteLine("failures: " + failures);
return failures == 0 ? 0 : 1;
}
}