using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text; using System.Windows.Forms; namespace ClaudeProxy { class Config { public string Mode = "proxy"; // proxy | vless | awg public string Proxy = Core.DefaultProxy; public string Vless = ""; public string Awg = ""; // path to the user's AmneziaWG .conf public int LocalPort = 10808; public bool FailClosed = true; } // ---- VLESS share-link (from 3x-ui) -> Xray config ----------------------------- static class Vless { public static Dictionary Parse(string link, out string host, out int port, out string uuid, out string remark) { link = (link ?? "").Trim(); if (!link.StartsWith("vless://", StringComparison.OrdinalIgnoreCase)) throw new Exception("Ожидается vless://... ссылка (share-link из 3x-ui)."); var uri = new Uri(link); uuid = uri.UserInfo; if (string.IsNullOrEmpty(uuid)) throw new Exception("В ссылке нет UUID."); host = uri.Host; port = uri.Port; remark = Uri.UnescapeDataString(uri.Fragment.TrimStart('#')); var q = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var pair in uri.Query.TrimStart('?').Split('&')) { if (pair.Length == 0) continue; int i = pair.IndexOf('='); if (i < 0) q[pair] = ""; else q[pair.Substring(0, i)] = Uri.UnescapeDataString(pair.Substring(i + 1)); } return q; } static string J(string s) { return "\"" + (s ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; } static string GetQ(Dictionary q, string k, string d) { return q.ContainsKey(k) && q[k].Length > 0 ? q[k] : d; } public static string BuildXrayConfig(string link, int localPort) { string host, uuid, remark; int port; var q = Parse(link, out host, out port, out uuid, out remark); string type = GetQ(q, "type", "tcp"); string security = GetQ(q, "security", "none"); string flow = GetQ(q, "flow", ""); var stream = new StringBuilder(); stream.Append("\"network\":" + J(type)); stream.Append(",\"security\":" + J(security)); if (security == "reality") { if (!q.ContainsKey("pbk")) throw new Exception("Reality: в ссылке нет publicKey (pbk)."); stream.Append(",\"realitySettings\":{"); stream.Append("\"serverName\":" + J(GetQ(q, "sni", ""))); stream.Append(",\"fingerprint\":" + J(GetQ(q, "fp", "chrome"))); stream.Append(",\"publicKey\":" + J(GetQ(q, "pbk", ""))); stream.Append(",\"shortId\":" + J(GetQ(q, "sid", ""))); stream.Append(",\"spiderX\":" + J(GetQ(q, "spx", ""))); stream.Append("}"); } else if (security == "tls") { stream.Append(",\"tlsSettings\":{\"serverName\":" + J(GetQ(q, "sni", "")) + ",\"fingerprint\":" + J(GetQ(q, "fp", "chrome")) + "}"); } if (type == "grpc") stream.Append(",\"grpcSettings\":{\"serviceName\":" + J(GetQ(q, "serviceName", "")) + "}"); else if (type == "ws") stream.Append(",\"wsSettings\":{\"path\":" + J(GetQ(q, "path", "/")) + ",\"headers\":{\"Host\":" + J(GetQ(q, "host", "")) + "}}"); var user = new StringBuilder(); user.Append("{\"id\":" + J(uuid) + ",\"encryption\":\"none\""); if (flow.Length > 0) user.Append(",\"flow\":" + J(flow)); user.Append("}"); var sb = new StringBuilder(); sb.Append("{"); sb.Append("\"log\":{\"loglevel\":\"warning\"},"); sb.Append("\"inbounds\":[{\"tag\":\"socks-in\",\"listen\":\"127.0.0.1\",\"port\":" + localPort + ",\"protocol\":\"socks\",\"settings\":{\"udp\":true,\"auth\":\"noauth\"}}],"); sb.Append("\"outbounds\":[{\"tag\":\"proxy\",\"protocol\":\"vless\",\"settings\":{\"vnext\":[{\"address\":" + J(host) + ",\"port\":" + port + ",\"users\":[" + user + "]}]},\"streamSettings\":{" + stream + "}}]"); sb.Append("}"); return sb.ToString(); } // reachability of the VLESS server host:port public static bool ServerReachable(string link, out string endpoint) { endpoint = ""; try { string host, uuid, remark; int port; Parse(link, out host, out port, out uuid, out remark); endpoint = host + ":" + port; return Core.TcpReachable(host, port, 1500); } catch { endpoint = "некорректная ссылка"; return false; } } } class VerifyResult { public string Mode, Endpoint, Tunnel = ""; public int Procs; public List ViaProxy = new List(); public List Local = new List(); public List Leaks = new List(); public List Blocked = new List(); public List Lines = new List(); public string Verdict; public bool Ok; } static class Verify { class Conn { public string Remote; public int RemotePort; public string State; public int Pid; } // Parse `netstat -ano` for TCP connections owned by the given PIDs. static List TcpConns(HashSet pids) { var res = new List(); try { var psi = new ProcessStartInfo("netstat", "-ano") { UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true, StandardOutputEncoding = Encoding.ASCII }; var p = Process.Start(psi); string outp = p.StandardOutput.ReadToEnd(); p.WaitForExit(); foreach (var raw in outp.Split('\n')) { var line = raw.Trim(); if (!line.StartsWith("TCP", StringComparison.OrdinalIgnoreCase)) continue; var t = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (t.Length < 5) continue; int pid; if (!int.TryParse(t[4], out pid)) continue; if (!pids.Contains(pid)) continue; string state = t[3]; if (state != "ESTABLISHED" && state != "SYN_SENT") continue; string remote = t[2]; int rp = 0; string rip = remote; int c = remote.LastIndexOf(':'); if (c > 0) { rip = remote.Substring(0, c); int.TryParse(remote.Substring(c + 1), out rp); } rip = rip.Trim('[', ']'); res.Add(new Conn { Remote = rip, RemotePort = rp, State = state == "SYN_SENT" ? "SynSent" : "Established", Pid = pid }); } } catch { } return res; } static HashSet ClaudePids() { var set = new HashSet(); foreach (var p in Process.GetProcessesByName("claude")) try { if (p.MainModule.FileName.IndexOf("WindowsApps", StringComparison.OrdinalIgnoreCase) >= 0) set.Add(p.Id); } catch { } return set; } static HashSet ProcPids(string name) { var set = new HashSet(); foreach (var p in Process.GetProcessesByName(name)) try { if (p.MainModule.FileName.StartsWith(Core.BinDir, StringComparison.OrdinalIgnoreCase)) set.Add(p.Id); } catch { } return set; } static HashSet XrayPids() { return ProcPids("xray"); } static HashSet AwgPids() { return ProcPids("wireproxy-awg"); } public static VerifyResult Run(Config cfg) { var r = new VerifyResult { Mode = cfg.Mode }; HashSet proxySet; bool loopIsProxy; if (cfg.Mode == "vless" || cfg.Mode == "awg") { proxySet = new HashSet { "127.0.0.1", "::1" }; string lbl = cfg.Mode == "awg" ? "AmneziaWG" : "VLESS+Reality"; r.Endpoint = "127.0.0.1:" + cfg.LocalPort + " (" + lbl + ")"; loopIsProxy = true; } else { string host; int port; Core.TryParseProxy(cfg.Proxy, out host, out port); var ips = new HashSet(); try { foreach (var a in System.Net.Dns.GetHostAddresses(host)) ips.Add(a.ToString()); } catch { } proxySet = ips; r.Endpoint = host + ":" + port; loopIsProxy = false; } var pids = ClaudePids(); r.Procs = pids.Count; foreach (var c in TcpConns(pids)) { string entry = c.Remote + ":" + c.RemotePort + " [" + c.State + "]"; bool isLoop = c.Remote == "127.0.0.1" || c.Remote == "::1"; bool otherLocal = c.Remote == "0.0.0.0" || c.Remote == "::" || c.Remote.StartsWith("fe80"); if (isLoop) { if (loopIsProxy) r.ViaProxy.Add(entry); else r.Local.Add(entry); continue; } if (otherLocal) { r.Local.Add(entry); continue; } if (!loopIsProxy && proxySet.Contains(c.Remote)) { r.ViaProxy.Add(entry); continue; } if (c.State == "SynSent") { r.Blocked.Add(entry); continue; } r.Leaks.Add(entry); } if (cfg.Mode == "vless" || cfg.Mode == "awg") { string proc = cfg.Mode == "awg" ? "wireproxy-awg" : "xray"; var tp = cfg.Mode == "awg" ? AwgPids() : XrayPids(); if (tp.Count == 0) r.Tunnel = proc + " НЕ запущен"; else { var xc = TcpConns(tp).FirstOrDefault(c => c.State == "Established" && c.Remote != "127.0.0.1" && c.Remote != "::1"); r.Tunnel = xc != null ? proc + " -> " + xc.Remote + ":" + xc.RemotePort : proc + " запущен, внешнего коннекта пока нет"; } } bool tunnelDown = (cfg.Mode == "vless" || cfg.Mode == "awg") && r.Tunnel.EndsWith("НЕ запущен"); string modeLabel = cfg.Mode == "awg" ? "AmneziaWG" : (cfg.Mode == "vless" ? "VLESS+Reality" : "прямой прокси"); r.Lines.Add("Проверка трафика Claude (" + modeLabel + ")"); r.Lines.Add(" вход : " + r.Endpoint); if (cfg.Mode == "vless" || cfg.Mode == "awg") r.Lines.Add(" тоннель : " + r.Tunnel); r.Lines.Add(" процессов Claude: " + r.Procs); r.Lines.Add(" через прокси : " + r.ViaProxy.Count); r.Lines.Add(" локальные : " + r.Local.Count); r.Lines.Add(" заблокировано : " + r.Blocked.Count + " (firewall отбил обход)"); r.Lines.Add(" УТЕЧКИ : " + r.Leaks.Count); foreach (var l in r.Leaks) r.Lines.Add(" ! прямой коннект: " + l); foreach (var b in r.Blocked) r.Lines.Add(" ~ отбито: " + b); if (r.Procs == 0) r.Verdict = "НЕТ ДАННЫХ: Claude не запущен"; else if (r.Leaks.Count > 0) r.Verdict = "ПРОВАЛ: есть прямой трафик мимо прокси"; else if (tunnelDown) r.Verdict = "ПРОВАЛ: тоннель (xray) не запущен"; else if (r.ViaProxy.Count == 0) r.Verdict = "НЕОПРЕДЕЛЁННО: нет активных коннектов (подожди загрузки Claude)"; else if (cfg.Mode == "vless" || cfg.Mode == "awg") r.Verdict = "OK: весь трафик Claude идёт в тоннель (" + modeLabel + ")"; else r.Verdict = "OK: весь внешний трафик Claude идёт через прокси"; r.Lines.Add(" ВЕРДИКТ : " + r.Verdict); r.Ok = r.Leaks.Count == 0 && r.ViaProxy.Count > 0 && !tunnelDown; return r; } public static string WriteLog(VerifyResult r, string stamp) { try { System.IO.Directory.CreateDirectory(Core.ConfigDir); string path = System.IO.Path.Combine(Core.ConfigDir, "verify-" + stamp + ".log"); System.IO.File.WriteAllLines(path, r.Lines.ToArray()); return path; } catch { return ""; } } } static class Program { [STAThread] static void Main(string[] args) { // elevated firewall-helper mode (invoked by the GUI via runas). Native args arrive clean. if (args.Contains("fwapply") || args.Contains("fwremove")) { if (Core.IsAdmin()) { try { if (args.Contains("fwapply")) Core.ApplyFirewallForConfig(); else Core.RemoveFirewall(); } catch { } } return; } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); if (args.Contains("smoketest")) { var f = new MainForm(); f.StartPosition = FormStartPosition.Manual; f.Location = new System.Drawing.Point(-3000, -3000); f.Show(); Application.DoEvents(); System.Threading.Thread.Sleep(600); Application.DoEvents(); using (var bmp = new System.Drawing.Bitmap(f.ClientSize.Width, f.ClientSize.Height)) { f.DrawToBitmap(bmp, new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height)); bmp.Save(System.IO.Path.Combine(System.IO.Path.GetTempPath(), "claudeproxy-native-preview.png")); } f.Close(); return; } Application.Run(new MainForm()); } } }