Add AmneziaWG mode (wireproxy-awg userspace SOCKS)
Third mode alongside proxy and VLESS+Reality. Bundles wireproxy-awg built from a pinned, audited source commit (fork of wireproxy over the official amneziawg-go), exposing a local SOCKS5 tunnel with no TUN/driver/admin. User picks their AmneziaWG .conf; the app appends a [Socks5] section and routes Claude through it with the same fail-closed firewall. build-wireproxy.ps1 reproducibly builds the binary (portable Go + pinned commit); it is embedded as a resource and not stored in git. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+74
-1
@@ -89,6 +89,7 @@ namespace ClaudeProxy
|
||||
case "mode": c.Mode = v.Trim(); break;
|
||||
case "proxy": c.Proxy = v.Trim(); break;
|
||||
case "vless": c.Vless = v; break;
|
||||
case "awg": c.Awg = v.Trim(); break;
|
||||
case "localPort": int.TryParse(v.Trim(), out c.LocalPort); break;
|
||||
case "failClosed": c.FailClosed = v.Trim() == "1" || v.Trim().ToLower() == "true"; break;
|
||||
}
|
||||
@@ -108,6 +109,7 @@ namespace ClaudeProxy
|
||||
sb.AppendLine("mode=" + c.Mode);
|
||||
sb.AppendLine("proxy=" + c.Proxy);
|
||||
sb.AppendLine("vless=" + (c.Vless ?? ""));
|
||||
sb.AppendLine("awg=" + (c.Awg ?? ""));
|
||||
sb.AppendLine("localPort=" + c.LocalPort);
|
||||
sb.AppendLine("failClosed=" + (c.FailClosed ? "1" : "0"));
|
||||
File.WriteAllText(ConfigPath, sb.ToString());
|
||||
@@ -271,7 +273,7 @@ namespace ClaudeProxy
|
||||
var app = GetClaudeApp();
|
||||
RemoveFirewall();
|
||||
dynamic policy = FwPolicy();
|
||||
if (cfg.Mode == "vless")
|
||||
if (cfg.Mode == "vless" || cfg.Mode == "awg")
|
||||
{
|
||||
// tunnel: block ALL external (loopback is exempt from firewall filtering)
|
||||
AddBlockRule(policy, "ClaudeProxyLock: tunnel", app.Exe, null);
|
||||
@@ -389,5 +391,76 @@ namespace ClaudeProxy
|
||||
|
||||
public static bool XrayRunning() { return Process.GetProcessesByName("xray").Any(p =>
|
||||
{ try { return p.MainModule.FileName.StartsWith(BinDir, StringComparison.OrdinalIgnoreCase); } catch { return false; } }); }
|
||||
|
||||
// ---- AmneziaWG (wireproxy-awg userspace SOCKS) ----------------------------
|
||||
public static string AwgExe { get { return Path.Combine(BinDir, "wireproxy-awg.exe"); } }
|
||||
public static string AwgRuntimeCfg { get { return Path.Combine(ConfigDir, "awg-runtime.conf"); } }
|
||||
public static bool AwgPresent() { return File.Exists(AwgExe); }
|
||||
|
||||
public static void EnsureAwg()
|
||||
{
|
||||
if (AwgPresent()) return;
|
||||
Directory.CreateDirectory(BinDir);
|
||||
var asm = Assembly.GetExecutingAssembly();
|
||||
string res = asm.GetManifestResourceNames().FirstOrDefault(n => n.EndsWith("awg.exe", StringComparison.OrdinalIgnoreCase));
|
||||
if (res == null) throw new Exception("Встроенный wireproxy-awg не найден.");
|
||||
using (var rs = asm.GetManifestResourceStream(res))
|
||||
using (var fs = File.Create(AwgExe))
|
||||
rs.CopyTo(fs);
|
||||
if (!AwgPresent()) throw new Exception("Не удалось распаковать wireproxy-awg.");
|
||||
}
|
||||
|
||||
public static void StopAwg()
|
||||
{
|
||||
foreach (var p in Process.GetProcessesByName("wireproxy-awg"))
|
||||
try { if (p.MainModule.FileName.StartsWith(BinDir, StringComparison.OrdinalIgnoreCase)) p.Kill(); } catch { }
|
||||
Thread.Sleep(300);
|
||||
}
|
||||
public static bool AwgRunning() { return Process.GetProcessesByName("wireproxy-awg").Any(p =>
|
||||
{ try { return p.MainModule.FileName.StartsWith(BinDir, StringComparison.OrdinalIgnoreCase); } catch { return false; } }); }
|
||||
|
||||
// Take the user's AmneziaWG .conf, append a [Socks5] section, run wireproxy-awg on it.
|
||||
public static void StartAwg(string confPath, int localPort)
|
||||
{
|
||||
EnsureAwg();
|
||||
if (string.IsNullOrWhiteSpace(confPath) || !File.Exists(confPath))
|
||||
throw new Exception("Файл AmneziaWG-конфига не найден: " + confPath);
|
||||
string cfg = File.ReadAllText(confPath);
|
||||
if (cfg.IndexOf("[Socks5]", StringComparison.OrdinalIgnoreCase) < 0)
|
||||
cfg = cfg.TrimEnd() + "\r\n\r\n[Socks5]\r\nBindAddress = 127.0.0.1:" + localPort + "\r\n";
|
||||
File.WriteAllText(AwgRuntimeCfg, cfg);
|
||||
StopAwg();
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = AwgExe,
|
||||
Arguments = "-s -c \"" + AwgRuntimeCfg + "\"",
|
||||
UseShellExecute = false, CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden
|
||||
};
|
||||
Process.Start(psi);
|
||||
for (int i = 0; i < 40; i++) { if (LocalSocksUp(localPort)) return; Thread.Sleep(200); }
|
||||
StopAwg();
|
||||
throw new Exception("wireproxy-awg не поднял SOCKS на 127.0.0.1:" + localPort + ". Проверь конфиг AmneziaWG.");
|
||||
}
|
||||
|
||||
// Info line for the AmneziaWG config (its Peer Endpoint). WG is UDP, so we don't TCP-ping it.
|
||||
public static bool AwgConfigInfo(string confPath, out string info)
|
||||
{
|
||||
info = "не выбран";
|
||||
if (string.IsNullOrWhiteSpace(confPath) || !File.Exists(confPath)) return false;
|
||||
try
|
||||
{
|
||||
foreach (var line in File.ReadAllLines(confPath))
|
||||
{
|
||||
var l = line.Trim();
|
||||
if (l.StartsWith("Endpoint", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
int eq = l.IndexOf('=');
|
||||
if (eq >= 0) { info = l.Substring(eq + 1).Trim(); return true; }
|
||||
}
|
||||
}
|
||||
info = "выбран"; return true;
|
||||
}
|
||||
catch { info = "ошибка чтения"; return false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user