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:
+71
-21
@@ -23,7 +23,7 @@ namespace ClaudeProxy
|
||||
fPill = new Font("Segoe UI Semibold", 12f), fMono = new Font("Consolas", 8.5f);
|
||||
|
||||
bool switchOn = false, busy = false;
|
||||
RadioButton rbProxy, rbVless;
|
||||
RadioButton rbProxy, rbVless, rbAwg;
|
||||
BufferedPanel mark, tgl, card;
|
||||
Label title, subtitle, lblFieldCap, lblReach, rDot, lblFwCap, lblFw, lblClCap, lblCl, lblXrayCap, lblXray;
|
||||
TextBox txtField, log;
|
||||
@@ -32,7 +32,7 @@ namespace ClaudeProxy
|
||||
Panel sep;
|
||||
Timer timer;
|
||||
|
||||
string Mode { get { return rbVless.Checked ? "vless" : "proxy"; } }
|
||||
string Mode { get { return rbAwg.Checked ? "awg" : (rbVless.Checked ? "vless" : "proxy"); } }
|
||||
|
||||
public MainForm()
|
||||
{
|
||||
@@ -53,9 +53,10 @@ namespace ClaudeProxy
|
||||
subtitle = MkLabel("весь трафик Claude через прокси", fSub, Muted, 84, 48);
|
||||
subtitle.AutoSize = false; subtitle.Size = new Size(196, 18); Controls.Add(subtitle);
|
||||
|
||||
rbProxy = MkRadio("Прямой прокси", 292, 18);
|
||||
rbVless = MkRadio("VLESS + Reality", 292, 42);
|
||||
Controls.Add(rbProxy); Controls.Add(rbVless);
|
||||
rbProxy = MkRadio("Прямой прокси", 292, 14);
|
||||
rbVless = MkRadio("VLESS + Reality", 292, 36);
|
||||
rbAwg = MkRadio("AmneziaWG", 292, 58);
|
||||
Controls.Add(rbProxy); Controls.Add(rbVless); Controls.Add(rbAwg);
|
||||
|
||||
tgl = new BufferedPanel { Size = new Size(172, 58), Location = new Point(24, 88), BackColor = Cream, Cursor = Cursors.Hand };
|
||||
tgl.Paint += (s, e) => PaintToggle(e.Graphics, tgl.Width, tgl.Height);
|
||||
@@ -83,7 +84,7 @@ namespace ClaudeProxy
|
||||
|
||||
lblXrayCap = MkLabel("Xray-core", fReg, Muted, 18, 68); lblXrayCap.AutoSize = true; card.Controls.Add(lblXrayCap);
|
||||
lblXray = MkLabel("", fSemi, Ink, 120, 68); lblXray.AutoSize = true; card.Controls.Add(lblXray);
|
||||
btnXray = MkButton("Распаковать", 300, 64, 96, 26, false); btnXray.Click += (s, e) => OnXray(); card.Controls.Add(btnXray);
|
||||
btnXray = MkButton("Распаковать", 300, 64, 96, 26, false); btnXray.Click += (s, e) => { if (Mode == "awg") OnBrowseAwg(); else OnXray(); }; card.Controls.Add(btnXray);
|
||||
|
||||
sep = new Panel { Location = new Point(18, 86), Size = new Size(378, 1), BackColor = Border }; card.Controls.Add(sep);
|
||||
lblFwCap = MkLabel("Firewall-лок", fReg, Muted, 18, 98); lblFwCap.AutoSize = true; card.Controls.Add(lblFwCap);
|
||||
@@ -102,7 +103,8 @@ namespace ClaudeProxy
|
||||
log = new TextBox { Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Vertical, Location = new Point(20, 430), Size = new Size(412, 66), BorderStyle = BorderStyle.FixedSingle, BackColor = Card, ForeColor = Muted, Font = fMono };
|
||||
Controls.Add(log);
|
||||
|
||||
rbVless.CheckedChanged += (s, e) => { ApplyModeLayout(); var c = Core.LoadConfig(); c.Mode = Mode; Core.SaveConfig(c); UpdateUi(true); };
|
||||
EventHandler modeCh = (s, e) => { if (!((RadioButton)s).Checked) return; ApplyModeLayout(); var c = Core.LoadConfig(); c.Mode = Mode; Core.SaveConfig(c); UpdateUi(true); };
|
||||
rbVless.CheckedChanged += modeCh; rbAwg.CheckedChanged += modeCh; rbProxy.CheckedChanged += modeCh;
|
||||
|
||||
timer = new Timer { Interval = 4000 };
|
||||
timer.Tick += (s, e) => { if (!busy) UpdateUi(false); };
|
||||
@@ -111,7 +113,7 @@ namespace ClaudeProxy
|
||||
Load += (s, e) => {
|
||||
var cfg = Core.LoadConfig();
|
||||
chkFail.Checked = cfg.FailClosed;
|
||||
if (cfg.Mode == "vless") rbVless.Checked = true; else rbProxy.Checked = true;
|
||||
if (cfg.Mode == "awg") rbAwg.Checked = true; else if (cfg.Mode == "vless") rbVless.Checked = true; else rbProxy.Checked = true;
|
||||
ApplyModeLayout();
|
||||
Log("готово. рубильник управляет проксированием Claude.");
|
||||
UpdateUi(true);
|
||||
@@ -182,20 +184,27 @@ namespace ClaudeProxy
|
||||
void ApplyModeLayout()
|
||||
{
|
||||
var cfg = Core.LoadConfig();
|
||||
bool tunnel = Mode == "vless" || Mode == "awg";
|
||||
lblXrayCap.Visible = lblXray.Visible = btnXray.Visible = tunnel;
|
||||
if (tunnel) { sep.Top = 100; lblFwCap.Top = lblFw.Top = 112; lblClCap.Top = lblCl.Top = 134; }
|
||||
else { sep.Top = 74; lblFwCap.Top = lblFw.Top = 86; lblClCap.Top = lblCl.Top = 108; }
|
||||
|
||||
if (Mode == "vless")
|
||||
{
|
||||
lblFieldCap.Text = "VLESS"; subtitle.Text = "через VLESS + Reality тоннель";
|
||||
txtField.UseSystemPasswordChar = true; txtField.Text = cfg.Vless ?? "";
|
||||
lblXrayCap.Visible = lblXray.Visible = btnXray.Visible = true;
|
||||
sep.Top = 100; lblFwCap.Top = lblFw.Top = 112; lblClCap.Top = lblCl.Top = 134;
|
||||
UpdateXrayStatus();
|
||||
lblXrayCap.Text = "Xray-core"; UpdateXrayStatus();
|
||||
}
|
||||
else if (Mode == "awg")
|
||||
{
|
||||
lblFieldCap.Text = "Конфиг"; subtitle.Text = "через AmneziaWG тоннель";
|
||||
txtField.UseSystemPasswordChar = false; txtField.Text = cfg.Awg ?? "";
|
||||
lblXrayCap.Text = "AmneziaWG"; UpdateAwgStatus();
|
||||
}
|
||||
else
|
||||
{
|
||||
lblFieldCap.Text = "Прокси"; subtitle.Text = "весь трафик Claude через прокси";
|
||||
txtField.UseSystemPasswordChar = false; txtField.Text = cfg.Proxy ?? "";
|
||||
lblXrayCap.Visible = lblXray.Visible = btnXray.Visible = false;
|
||||
sep.Top = 74; lblFwCap.Top = lblFw.Top = 86; lblClCap.Top = lblCl.Top = 108;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +212,14 @@ namespace ClaudeProxy
|
||||
{
|
||||
if (Core.XrayPresent()) { lblXray.Text = "готов (" + Core.XrayVersion + ")"; lblXray.ForeColor = OkC; btnXray.Text = "Переустановить"; }
|
||||
else { lblXray.Text = "встроен (" + Core.XrayVersion + ")"; lblXray.ForeColor = OkC; btnXray.Text = "Распаковать"; }
|
||||
btnXray.Width = 96; btnXray.Left = 300;
|
||||
}
|
||||
|
||||
void UpdateAwgStatus()
|
||||
{
|
||||
bool ok = !string.IsNullOrWhiteSpace(txtField.Text) && System.IO.File.Exists(txtField.Text.Trim());
|
||||
lblXray.Text = ok ? "конфиг выбран" : "не выбран"; lblXray.ForeColor = ok ? OkC : RedC;
|
||||
btnXray.Text = "Обзор..."; btnXray.Width = 96; btnXray.Left = 300;
|
||||
}
|
||||
|
||||
// ---- helpers ----
|
||||
@@ -216,13 +233,15 @@ namespace ClaudeProxy
|
||||
}
|
||||
|
||||
// Mode can't be switched while the lock is active (ON) or during an operation.
|
||||
void UpdateModeLock() { rbProxy.Enabled = rbVless.Enabled = !switchOn && !busy; }
|
||||
void UpdateModeLock() { rbProxy.Enabled = rbVless.Enabled = rbAwg.Enabled = !switchOn && !busy; }
|
||||
|
||||
Config CaptureConfig()
|
||||
{
|
||||
var c = Core.LoadConfig();
|
||||
c.Mode = Mode; c.FailClosed = chkFail.Checked;
|
||||
if (Mode == "vless") c.Vless = txtField.Text; else c.Proxy = txtField.Text;
|
||||
if (Mode == "vless") c.Vless = txtField.Text;
|
||||
else if (Mode == "awg") c.Awg = txtField.Text;
|
||||
else c.Proxy = txtField.Text;
|
||||
Core.SaveConfig(c); return c;
|
||||
}
|
||||
|
||||
@@ -246,13 +265,15 @@ namespace ClaudeProxy
|
||||
|
||||
if (checkReach)
|
||||
{
|
||||
bool ok; string ep;
|
||||
if (Mode == "vless") ok = Vless.ServerReachable(txtField.Text, out ep);
|
||||
else { string h; int p; if (Core.TryParseProxy(txtField.Text, out h, out p)) { ep = h + ":" + p; ok = Core.TcpReachable(h, p, 1500); } else { ep = "некорректный адрес"; ok = false; } }
|
||||
bool ok; string text;
|
||||
if (Mode == "vless") { string ep; ok = Vless.ServerReachable(txtField.Text, out ep); text = (ok ? "сервер доступен " : "не отвечает ") + ep; }
|
||||
else if (Mode == "awg") { string ep; ok = Core.AwgConfigInfo(txtField.Text, out ep); text = ok ? ("конфиг: " + ep) : "конфиг не выбран"; }
|
||||
else { string h; int p; if (Core.TryParseProxy(txtField.Text, out h, out p)) { ok = Core.TcpReachable(h, p, 1500); text = (ok ? "сервер доступен " : "не отвечает ") + h + ":" + p; } else { ok = false; text = "некорректный адрес"; } }
|
||||
rDot.ForeColor = ok ? OkC : RedC;
|
||||
lblReach.Text = (ok ? "сервер доступен " : "не отвечает ") + ep;
|
||||
lblReach.Text = text;
|
||||
}
|
||||
if (Mode == "vless") UpdateXrayStatus();
|
||||
else if (Mode == "awg") UpdateAwgStatus();
|
||||
}
|
||||
catch (Exception ex) { Log("ошибка обновления: " + ex.Message); }
|
||||
}
|
||||
@@ -275,6 +296,18 @@ namespace ClaudeProxy
|
||||
string ep; if (!Vless.ServerReachable(txtField.Text, out ep))
|
||||
if (MessageBox.Show("Сервер " + ep + " не отвечает. Всё равно продолжить?", "Сервер недоступен", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return false;
|
||||
}
|
||||
else if (Mode == "awg")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(txtField.Text) || !System.IO.File.Exists(txtField.Text.Trim()))
|
||||
{ MessageBox.Show("Выбери файл AmneziaWG .conf (кнопка «Обзор...»).", "Нужен конфиг", MessageBoxButtons.OK, MessageBoxIcon.Warning); return false; }
|
||||
if (!Core.AwgPresent())
|
||||
{
|
||||
SetBusy(true);
|
||||
try { Core.EnsureAwg(); UpdateAwgStatus(); }
|
||||
catch (Exception ex) { Log("ОШИБКА AmneziaWG: " + ex.Message); MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); SetBusy(false); return false; }
|
||||
SetBusy(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string h; int p;
|
||||
@@ -291,10 +324,11 @@ namespace ClaudeProxy
|
||||
var cfg = Core.LoadConfig();
|
||||
string proxyArg;
|
||||
if (cfg.Mode == "vless") { Core.EnsureXray(); Core.StartXray(cfg.Vless, cfg.LocalPort); proxyArg = "socks5://127.0.0.1:" + cfg.LocalPort; }
|
||||
else if (cfg.Mode == "awg") { Core.EnsureAwg(); Core.StartAwg(cfg.Awg, cfg.LocalPort); proxyArg = "socks5://127.0.0.1:" + cfg.LocalPort; }
|
||||
else proxyArg = Core.ProxyArg(cfg.Proxy);
|
||||
|
||||
if (cfg.FailClosed)
|
||||
if (!Core.ElevateSelf("fwapply")) { if (cfg.Mode == "vless") Core.StopXray(); throw new Exception("Установка firewall отменена в UAC."); }
|
||||
if (!Core.ElevateSelf("fwapply")) { Core.StopXray(); Core.StopAwg(); throw new Exception("Установка firewall отменена в UAC."); }
|
||||
|
||||
if (restart) { Core.StopClaude(); string a = "--proxy-server=" + proxyArg; Core.LaunchClaude(a); }
|
||||
}
|
||||
@@ -304,7 +338,7 @@ namespace ClaudeProxy
|
||||
string exe = null; try { exe = Core.GetClaudeApp().Exe; } catch { }
|
||||
if (Core.FirewallState(exe) != "absent")
|
||||
if (!Core.ElevateSelf("fwremove")) throw new Exception("Снятие firewall отменено в UAC (лок остался).");
|
||||
Core.StopXray();
|
||||
Core.StopXray(); Core.StopAwg();
|
||||
if (restart) { Core.StopClaude(); Core.LaunchClaude(""); }
|
||||
}
|
||||
|
||||
@@ -368,6 +402,7 @@ namespace ClaudeProxy
|
||||
try
|
||||
{
|
||||
if (Mode == "vless") { string h, u, rm; int p; Vless.Parse(txtField.Text, out h, out p, out u, out rm); CaptureConfig(); Log("VLESS сохранён: " + h + ":" + p + " [" + rm + "]"); }
|
||||
else if (Mode == "awg") { if (string.IsNullOrWhiteSpace(txtField.Text) || !System.IO.File.Exists(txtField.Text.Trim())) throw new Exception("Файл AmneziaWG-конфига не найден."); CaptureConfig(); Log("AmneziaWG-конфиг сохранён: " + txtField.Text.Trim()); }
|
||||
else { string h; int p; if (!Core.TryParseProxy(txtField.Text, out h, out p)) throw new Exception("Некорректный адрес."); CaptureConfig(); Log("прокси сохранён: " + Core.ProxyArg(txtField.Text)); }
|
||||
if (switchOn) Log("ВНИМАНИЕ: режим активен со старой настройкой — переключи рубильник, чтобы применить.");
|
||||
UpdateUi(true);
|
||||
@@ -387,5 +422,20 @@ namespace ClaudeProxy
|
||||
catch (Exception ex) { Log("ОШИБКА: " + ex.Message); MessageBox.Show(ex.Message, "Ошибка Xray", MessageBoxButtons.OK, MessageBoxIcon.Error); }
|
||||
finally { SetBusy(false); }
|
||||
}
|
||||
|
||||
void OnBrowseAwg()
|
||||
{
|
||||
using (var dlg = new OpenFileDialog())
|
||||
{
|
||||
dlg.Title = "Выбери AmneziaWG .conf";
|
||||
dlg.Filter = "AmneziaWG config (*.conf)|*.conf|Все файлы (*.*)|*.*";
|
||||
if (dlg.ShowDialog() != DialogResult.OK) return;
|
||||
txtField.Text = dlg.FileName;
|
||||
CaptureConfig();
|
||||
UpdateAwgStatus();
|
||||
Log("конфиг выбран: " + dlg.FileName);
|
||||
UpdateUi(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user