Compare commits
2 Commits
01632034b0
...
v1.4.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d795bc6f70 | ||
|
|
c139debd8d |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
windows/build/
|
||||
|
||||
@@ -19,6 +19,11 @@ phobos-update beta # или: phobos-update dev
|
||||
|
||||
---
|
||||
|
||||
## 1.4.2 — настройка обфускатора сервера
|
||||
|
||||
- **feat:** в панели «Настройки» → «Обфускатор сервера»: masking (AUTO/STUN/NONE), max-dummy, idle — записываются во все порты сервера, обфускатор перезапускается (клиенты переподключаются за секунды).
|
||||
- **fix (Android PCA Phobos VPN 1.4.3):** конфиги других сборок Phobos с лишними ключами (`obfuscate-bytes` и т. п.) больше не роняют обфускатор («обфускатор завершился, код 1»): передаются только ключи, известные wg-obfuscator v1.6.
|
||||
|
||||
## 1.4.1 — резервный TCP-канал, «РФ напрямую»
|
||||
|
||||
- **feat:** резервный канал TCP: если провайдер режет UDP, клиент сам переходит на TCP (порт 443, иначе 8443/2096/4433). Сервер: служба `phobos-tcp` (`udptcp`), ставится и включается автоматически при установке и обновлении. Проверено реальной блокировкой UDP: переключение за ~15 с, без блокировки остаётся UDP.
|
||||
|
||||
@@ -137,8 +137,8 @@ bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/server/
|
||||
|
||||
## Приложения для Android
|
||||
|
||||
- **PCA Phobos VPN** — клиент: включает VPN одной кнопкой, импорт `phobos://` из панели, встроенный обфускатор, **раздельный трафик** (весь / только выбранные приложения / все кроме выбранных), **российские сайты напрямую** (по умолчанию), **канал Авто/UDP/TCP** (при блокировке UDP — сам на TCP), журнал, проверка скорости. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.1/PCA-Phobos-VPN.apk) · исходники `android-client/`.
|
||||
- **PCA Phobos** (админ) — список ваших панелей, открываются в приложении. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.1/PCA-Phobos.apk) · исходники `android/`.
|
||||
- **PCA Phobos VPN** — клиент: включает VPN одной кнопкой, импорт `phobos://` из панели, встроенный обфускатор, **раздельный трафик** (весь / только выбранные приложения / все кроме выбранных), **российские сайты напрямую** (по умолчанию), **канал Авто/UDP/TCP** (при блокировке UDP — сам на TCP), журнал, проверка скорости. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.2/PCA-Phobos-VPN.apk) · исходники `android-client/`.
|
||||
- **PCA Phobos** (админ) — список ваших панелей, открываются в приложении. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.2/PCA-Phobos.apk) · исходники `android/`.
|
||||
|
||||
## Клиенты для телефона
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.pca.phobos.vpn"
|
||||
android:versionCode="142"
|
||||
android:versionName="1.4.2">
|
||||
android:versionCode="143"
|
||||
android:versionName="1.4.3">
|
||||
|
||||
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="35" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
@@ -217,11 +217,16 @@ public class MainActivity extends Activity {
|
||||
|
||||
private static String getPackageNameSafe() { return "com.pca.phobos.vpn"; }
|
||||
|
||||
private static final java.util.Set<String> OBF_KEYS = new java.util.HashSet<>(java.util.Arrays.asList(
|
||||
"target", "key", "masking", "idle-timeout", "max-dummy", "static-bindings", "max-clients", "target-if", "allow-clean", "in-timeout", "resolve-interval"));
|
||||
|
||||
private String obfPart(String conf, boolean viaTcp) {
|
||||
StringBuilder sb = new StringBuilder("[instance]\nsource-if = 127.0.0.1\nsource-lport = " + LPORT + "\n");
|
||||
for (String ln : section(conf, "[instance]")) {
|
||||
String k = key(ln);
|
||||
if (k.equals("source-if") || k.equals("source-lport") || k.equals("verbose")) continue;
|
||||
// configs from other Phobos builds carry keys our wg-obfuscator v1.6 rejects (obfuscate-bytes…)
|
||||
if (!OBF_KEYS.contains(k)) continue;
|
||||
if (viaTcp && k.equals("target")) ln = "target = 127.0.0.1:" + TCP_LPORT;
|
||||
sb.append(ln).append('\n');
|
||||
}
|
||||
|
||||
66
app.py
66
app.py
@@ -37,7 +37,7 @@ WG_CONFIG = "/etc/wireguard/wg0.conf"
|
||||
# Distribution: Gitea first (GitHub account is unavailable since 23.10.2026), GitHub fallback
|
||||
PCA_GIT = "https://git.bobyrev.com/PCA/PCA_Phobos"
|
||||
PCA_GH_RAW = "https://raw.githubusercontent.com/andrey271192/PCA_Phobos"
|
||||
APK_TAG = "v1.4.1"
|
||||
APK_TAG = "v1.4.2"
|
||||
WARP_DIR = f"{PHOBOS_DIR}/server/warp"
|
||||
WARP_CUSTOM_PROFILE_FILE = f"{WARP_DIR}/custom-profile.conf"
|
||||
WARP_LICENSE_FILE = f"{WARP_DIR}/license.key"
|
||||
@@ -102,6 +102,44 @@ def save_settings(s):
|
||||
json.dump(s, f, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
SRV_OBF_KEYS = ("masking", "max-dummy", "idle-timeout")
|
||||
|
||||
|
||||
def _srv_obf_confs():
|
||||
import glob
|
||||
return sorted(glob.glob(f"{PHOBOS_DIR}/server/wg-obfuscator-*.conf"))
|
||||
|
||||
|
||||
def read_server_obf():
|
||||
"""Current server wg-obfuscator params (from the first instance config)."""
|
||||
vals = {"masking": "AUTO", "max-dummy": "50", "idle-timeout": "300"}
|
||||
confs = _srv_obf_confs()
|
||||
if confs:
|
||||
for ln in Path(confs[0]).read_text().splitlines():
|
||||
k, _, v = ln.partition("=")
|
||||
if k.strip() in SRV_OBF_KEYS:
|
||||
vals[k.strip()] = v.strip()
|
||||
return vals
|
||||
|
||||
|
||||
def write_server_obf(vals):
|
||||
"""Rewrite masking/max-dummy/idle-timeout in every server instance and restart them."""
|
||||
done = []
|
||||
for f in _srv_obf_confs():
|
||||
lines, seen = [], set()
|
||||
for ln in Path(f).read_text().splitlines():
|
||||
k = ln.partition("=")[0].strip()
|
||||
if k in vals:
|
||||
ln = f"{k} = {vals[k]}"; seen.add(k)
|
||||
lines.append(ln)
|
||||
lines += [f"{k} = {v}" for k, v in vals.items() if k not in seen]
|
||||
Path(f).write_text("\n".join(lines) + "\n")
|
||||
port = re.search(r"wg-obfuscator-(\d+)\.conf$", f).group(1)
|
||||
subprocess.run(["systemctl", "restart", f"wg-obfuscator-{port}"], timeout=30)
|
||||
done.append(port)
|
||||
return done
|
||||
|
||||
|
||||
def _load_server_env():
|
||||
"""Load server.env as dict for subprocess env."""
|
||||
env = {}
|
||||
@@ -2741,6 +2779,17 @@ def settings_page():
|
||||
except Exception:
|
||||
pass
|
||||
msg = f'<div class="alert alert-ok">{tr("Параметры обфускатора сохранены и отправлены на роутеры", "Obfuscator params saved and pushed to routers")} ({pushed})</div>'
|
||||
elif request.method == "POST" and request.form.get("action") == "save_srv_obf":
|
||||
masking = request.form.get("srv_masking", "AUTO").strip().upper()
|
||||
if masking not in ("AUTO", "STUN", "NONE"):
|
||||
masking = "AUTO"
|
||||
try:
|
||||
dummy = str(min(255, max(0, int(request.form.get("srv_max_dummy", "50")))))
|
||||
idle = str(min(3600, max(10, int(request.form.get("srv_idle", "300")))))
|
||||
except ValueError:
|
||||
dummy, idle = "50", "300"
|
||||
ports = write_server_obf({"masking": masking, "max-dummy": dummy, "idle-timeout": idle})
|
||||
msg = f'<div class="alert alert-ok">{tr("Серверный обфускатор перезапущен с новыми параметрами, порты", "Server obfuscator restarted with new params, ports")}: {", ".join(ports) or "—"}</div>'
|
||||
elif request.method == "POST":
|
||||
new_pass = request.form.get("admin_pass", "").strip()
|
||||
if new_pass:
|
||||
@@ -2758,6 +2807,7 @@ def settings_page():
|
||||
custom_warp_profile = read_custom_warp_profile()
|
||||
warp_license_key = read_warp_license_key()
|
||||
warp = warp_status()
|
||||
srv_obf = read_server_obf()
|
||||
warp_service = warp.get("service", "unknown")
|
||||
warp_state = tr("включён", "enabled") if warp.get("enabled") else tr("выключен", "disabled")
|
||||
warp_subnet = _html_attr(warp.get("subnet", server_wg_network()))
|
||||
@@ -2898,6 +2948,20 @@ def settings_page():
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>{tr("Обфускатор сервера","Server obfuscator")} {hlp("Параметры wg-obfuscator на ЭТОМ сервере (все порты). masking: AUTO принимает любой режим клиента — менять, только если знаете зачем; STUN/NONE — клиенты должны работать в том же режиме. max-dummy — мусорные байты в ответах, idle — таймаут простоя (сек). Сохранение перезапускает обфускатор: клиенты переподключатся за пару секунд.", "wg-obfuscator params on THIS server (all ports). masking: AUTO accepts any client mode — change only if you know why; with STUN/NONE clients must use the same mode. max-dummy — junk bytes in replies, idle — idle timeout (s). Saving restarts the obfuscator: clients reconnect within seconds.")}</h2>
|
||||
<form method="post" class="form-row" style="flex-wrap:wrap;gap:10px;align-items:center" onsubmit="return confirm('{tr("Перезапустить обфускатор сервера? Клиенты переподключатся за пару секунд.","Restart server obfuscator? Clients will reconnect within seconds.")}')">
|
||||
<input type="hidden" name="action" value="save_srv_obf">
|
||||
<label>masking
|
||||
<select name="srv_masking" style="background:var(--bg);border:1px solid var(--border);color:var(--text);padding:5px;border-radius:6px">
|
||||
{"".join(f'<option value="{m}"{" selected" if srv_obf["masking"].upper()==m else ""}>{m}</option>' for m in ("AUTO","STUN","NONE"))}
|
||||
</select></label>
|
||||
<label>max-dummy <input type="number" name="srv_max_dummy" value="{_html_attr(srv_obf['max-dummy'])}" min="0" max="255" style="width:70px;padding:5px"></label>
|
||||
<label>idle (s) <input type="number" name="srv_idle" value="{_html_attr(srv_obf['idle-timeout'])}" min="10" max="3600" style="width:80px;padding:5px"></label>
|
||||
<button class="btn btn-primary btn-sm" type="submit">{tr("Сохранить и перезапустить","Save & restart")}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>{tr("Информация о сервере","Server info")}</h2>
|
||||
<table>
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
// PCA Phobos — menu bar app for macOS. Drives /usr/local/bin/phobos through
|
||||
// `sudo -n` (sudoers NOPASSWD for that one root-owned script). No Network Extension.
|
||||
import AppKit
|
||||
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
private let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
private var status = ""
|
||||
|
||||
func applicationDidFinishLaunching(_ n: Notification) {
|
||||
refresh()
|
||||
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [weak self] _ in self?.refresh() }
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func phobos(_ args: [String]) -> String {
|
||||
let p = Process()
|
||||
p.executableURL = URL(fileURLWithPath: "/usr/bin/sudo")
|
||||
p.arguments = ["-n", "/usr/local/bin/phobos"] + args
|
||||
let pipe = Pipe()
|
||||
p.standardOutput = pipe
|
||||
p.standardError = pipe
|
||||
try? p.run()
|
||||
p.waitUntilExit()
|
||||
return String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
|
||||
}
|
||||
|
||||
private func refresh() {
|
||||
DispatchQueue.global().async {
|
||||
let s = self.phobos(["status"])
|
||||
DispatchQueue.main.async { self.status = s; self.rebuild() }
|
||||
}
|
||||
}
|
||||
|
||||
private var isOn: Bool { status.contains("VPN: включён") }
|
||||
|
||||
private func rebuild() {
|
||||
item.button?.title = isOn ? (status.contains("TCP") ? "🛡 TCP" : "🛡") : "◌"
|
||||
item.button?.toolTip = "PCA Phobos"
|
||||
let m = NSMenu()
|
||||
for line in status.split(separator: "\n") {
|
||||
let i = NSMenuItem(title: String(line), action: nil, keyEquivalent: "")
|
||||
i.isEnabled = false
|
||||
m.addItem(i)
|
||||
}
|
||||
m.addItem(.separator())
|
||||
m.addItem(action(isOn ? "Отключить" : "Подключить", isOn ? ["off"] : ["on"]))
|
||||
m.addItem(.separator())
|
||||
let mode = status.contains("Режим: tcp") ? "tcp" : status.contains("Режим: udp") ? "udp" : "auto"
|
||||
for (t, v) in [("Канал: Авто", "auto"), ("Канал: UDP", "udp"), ("Канал: TCP", "tcp")] {
|
||||
let i = action(t, [v]); i.state = mode == v ? .on : .off; m.addItem(i)
|
||||
}
|
||||
let ruOn = status.contains("РФ напрямую: да")
|
||||
let ru = action("Российские сайты — напрямую", ["ru", ruOn ? "off" : "on"]); ru.state = ruOn ? .on : .off
|
||||
m.addItem(ru)
|
||||
m.addItem(.separator())
|
||||
m.addItem(withTitle: "Импорт ссылки phobos:// из буфера", action: #selector(importLink), keyEquivalent: "").target = self
|
||||
m.addItem(withTitle: "Выход", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
|
||||
item.menu = m
|
||||
}
|
||||
|
||||
private func action(_ title: String, _ args: [String]) -> NSMenuItem {
|
||||
let i = NSMenuItem(title: title, action: #selector(run(_:)), keyEquivalent: "")
|
||||
i.target = self
|
||||
i.representedObject = args
|
||||
return i
|
||||
}
|
||||
|
||||
@objc private func run(_ sender: NSMenuItem) {
|
||||
guard let args = sender.representedObject as? [String] else { return }
|
||||
item.button?.title = "…"
|
||||
DispatchQueue.global().async {
|
||||
let out = self.phobos(args)
|
||||
DispatchQueue.main.async {
|
||||
if out.contains("password is required") { self.alert("Нет права sudo без пароля для /usr/local/bin/phobos") }
|
||||
self.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func importLink() {
|
||||
guard let s = NSPasteboard.general.string(forType: .string), s.hasPrefix("phobos://") else {
|
||||
alert("Скопируйте ссылку phobos:// из панели (клиент → 📱) и повторите"); return
|
||||
}
|
||||
alert(phobos(["import", s]))
|
||||
refresh()
|
||||
}
|
||||
|
||||
private func alert(_ text: String) {
|
||||
let a = NSAlert(); a.messageText = "PCA Phobos"; a.informativeText = text; a.runModal()
|
||||
}
|
||||
}
|
||||
|
||||
let app = NSApplication.shared
|
||||
let delegate = AppDelegate()
|
||||
app.delegate = delegate
|
||||
app.setActivationPolicy(.accessory)
|
||||
app.run()
|
||||
@@ -1,15 +0,0 @@
|
||||
# PCA Phobos для macOS
|
||||
|
||||
Без Network Extension и без платного аккаунта Apple: `wireguard-go` (utun) + `wg-obfuscator` + `udptcp`,
|
||||
запуск от root через `/usr/local/bin/phobos` (разрешён без пароля только он — sudoers.d/pca-phobos).
|
||||
|
||||
- `phobos on|off|status|auto|udp|tcp|ru on|off|import <phobos://...>`
|
||||
- Интернет — через сервер Phobos, **российские сайты напрямую** (ru_bypass.txt, как в Android/iOS).
|
||||
- **Авто UDP→TCP**: нет рукопожатия 15 с — обфускатор переключается на `udptcp` (TCP 443 сервера).
|
||||
- **Дом**: отдельный WireGuard до домашнего Keenetic (Wireguard1, пир `macbook-phobos-home`, 172.16.6.32),
|
||||
только 192.168.88.0/24 и 172.16.6.0/24; дома (шлюз 192.168.88.1) не поднимается. Смена сети отслеживается.
|
||||
- DNS на время работы — 1.1.1.1/8.8.8.8, при выключении возвращаются прежние.
|
||||
- `PhobosMenu.swift` — значок в строке меню (сборка: `swiftc -O -o PCAPhobos PhobosMenu.swift`, ad-hoc подпись).
|
||||
|
||||
Установка: собрать `bin/wg-obfuscator` (clang *.c) и `bin/udptcp` в `~/.pca-phobos`, положить туда profile.conf,
|
||||
home.key, home_pub, home_endpoint, home_addr, затем `sudo bash ~/.pca-phobos/install-mac.sh`.
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Install PCA Phobos for macOS from the staged folder (~/.pca-phobos). Run once: sudo bash ~/.pca-phobos/install-mac.sh
|
||||
set -euo pipefail
|
||||
[ "$(id -u)" = 0 ] || { echo "run with sudo"; exit 1; }
|
||||
U="${SUDO_USER:?run via sudo from your account}"
|
||||
S="$(eval echo "~$U")/.pca-phobos"
|
||||
D=/usr/local/pca-phobos
|
||||
|
||||
install -d -m 755 -o root -g wheel "$D" "$D/bin"
|
||||
install -m 755 -o root -g wheel "$S/bin/wg-obfuscator" "$S/bin/udptcp" "$D/bin/"
|
||||
install -m 644 -o root -g wheel "$S/ru_bypass.txt" "$D/"
|
||||
install -m 600 -o root -g wheel "$S/profile.conf" "$S/home.key" "$D/"
|
||||
for f in home_pub home_endpoint home_addr; do install -m 644 -o root -g wheel "$S/$f" "$D/"; done
|
||||
install -m 755 -o root -g wheel "$S/phobos" /usr/local/bin/phobos
|
||||
|
||||
# the menu bar app may run exactly this root-owned script without a password
|
||||
echo "$U ALL=(root) NOPASSWD: /usr/local/bin/phobos" > /etc/sudoers.d/pca-phobos
|
||||
chmod 440 /etc/sudoers.d/pca-phobos
|
||||
visudo -cf /etc/sudoers.d/pca-phobos >/dev/null || { rm -f /etc/sudoers.d/pca-phobos; echo "sudoers check failed"; exit 1; }
|
||||
|
||||
rm -rf "/Applications/PCA Phobos.app"
|
||||
cp -R "$S/PCAPhobos.app" "/Applications/PCA Phobos.app"
|
||||
LA="$(eval echo "~$U")/Library/LaunchAgents/com.bobyrev.pcaphobos.mac.plist"
|
||||
cat > "$LA" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0"><dict>
|
||||
<key>Label</key><string>com.bobyrev.pcaphobos.mac</string>
|
||||
<key>ProgramArguments</key><array><string>/Applications/PCA Phobos.app/Contents/MacOS/PCAPhobos</string></array>
|
||||
<key>RunAtLoad</key><true/>
|
||||
</dict></plist>
|
||||
EOF
|
||||
chown "$U" "$LA"
|
||||
sudo -u "$U" launchctl bootstrap "gui/$(id -u "$U")" "$LA" 2>/dev/null || sudo -u "$U" open "/Applications/PCA Phobos.app"
|
||||
echo "PCA Phobos installed: menu bar icon, or 'sudo phobos on|off|status'"
|
||||
151
macos/phobos
151
macos/phobos
@@ -1,151 +0,0 @@
|
||||
#!/bin/bash
|
||||
# PCA Phobos for macOS (no Network Extension, no paid Apple account):
|
||||
# wireguard-go (utun) -> 127.0.0.1:13255 wg-obfuscator -> server UDP, or via udptcp -> server TCP.
|
||||
# + optional "home" tunnel (plain WireGuard to the home Keenetic) for 192.168.88.0/24 when away.
|
||||
# Usage (root): phobos on | off | status | auto | udp | tcp | ru on|off | import <phobos://...>
|
||||
set -uo pipefail
|
||||
export PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
|
||||
D=/usr/local/pca-phobos
|
||||
RUN=/var/run/pca-phobos
|
||||
PROFILE=$D/profile.conf
|
||||
WGPORT=13255 TCPLOCAL=13256
|
||||
mkdir -p "$RUN"
|
||||
|
||||
say() { echo "phobos: $*"; }
|
||||
need_root() { [ "$(id -u)" = 0 ] || { echo "run with sudo"; exit 1; }; }
|
||||
opt() { cat "$D/$1" 2>/dev/null || echo "$2"; } # opt transport auto | opt ru 1
|
||||
sec() { # sec <section> <key> -> value (no "= none")
|
||||
awk -v s="[$1]" -v k="$2" 'BEGIN{IGNORECASE=1}
|
||||
/^\[/{in_s=(tolower($0)==tolower(s)); next}
|
||||
in_s && !/^#/ { split($0,a,"="); key=a[1]; gsub(/[ \t]/,"",key);
|
||||
if (tolower(key)==tolower(k)) { v=substr($0,index($0,"=")+1); gsub(/^[ \t]+|[ \t]+$/,"",v);
|
||||
if (tolower(v)!="none") print v; exit } }' "$PROFILE"
|
||||
}
|
||||
tcp_port() { grep -E '^#.*pca-tcp' "$PROFILE" | sed 's/.*=[[:space:]]*//' | head -1; }
|
||||
target() { sec instance target; }
|
||||
host() { target | sed 's/:[0-9]*$//'; }
|
||||
gateway() { route -n get default 2>/dev/null | awk '/gateway:/{print $2; exit}'; }
|
||||
at_home() { [ "$(gateway)" = "$(opt home_gw 192.168.88.1)" ] && ipconfig getifaddr en0 2>/dev/null | grep -q '^192\.168\.88\.'; }
|
||||
utun() { cat "$RUN/utun" 2>/dev/null; }
|
||||
|
||||
start_obfuscator() { # $1 = udp|tcp
|
||||
pkill -f "$D/bin/wg-obfuscator" 2>/dev/null; sleep 0.3
|
||||
local t; [ "$1" = tcp ] && t="127.0.0.1:$TCPLOCAL" || t="$(target)"
|
||||
{ echo "[instance]"; echo "source-if = 127.0.0.1"; echo "source-lport = $WGPORT"; echo "target = $t"
|
||||
awk 'BEGIN{IGNORECASE=1} /^\[/{i=(tolower($0)=="[instance]"); next}
|
||||
i && !/^#/ && !/^(source-if|source-lport|target|verbose)[ \t]*=/ && !/=[ \t]*none[ \t]*$/' "$PROFILE"
|
||||
echo "verbose = INFO"; } > "$RUN/obf.conf"
|
||||
nohup "$D/bin/wg-obfuscator" -c "$RUN/obf.conf" >> "$RUN/obf.log" 2>&1 &
|
||||
echo "$1" > "$RUN/transport"
|
||||
}
|
||||
|
||||
dns_set() {
|
||||
: > "$RUN/dns.saved"
|
||||
networksetup -listallnetworkservices | tail -n +2 | grep -v '^\*' | while IFS= read -r s; do
|
||||
printf '%s\t%s\n' "$s" "$(networksetup -getdnsservers "$s" | tr '\n' ' ')" >> "$RUN/dns.saved"
|
||||
networksetup -setdnsservers "$s" 1.1.1.1 8.8.8.8 2>/dev/null
|
||||
done
|
||||
}
|
||||
dns_restore() {
|
||||
[ -f "$RUN/dns.saved" ] || return 0
|
||||
while IFS=$'\t' read -r s v; do
|
||||
case "$v" in *"aren't any"*|"") networksetup -setdnsservers "$s" empty ;; *) networksetup -setdnsservers "$s" $v ;; esac
|
||||
done < "$RUN/dns.saved"
|
||||
rm -f "$RUN/dns.saved"
|
||||
}
|
||||
|
||||
home_up() {
|
||||
[ -f "$D/home.key" ] || return 0
|
||||
{ echo "[Interface]"; echo "PrivateKey = $(cat "$D/home.key")"; echo "Address = $(opt home_addr 172.16.6.32)/32"
|
||||
echo; echo "[Peer]"; echo "PublicKey = $(opt home_pub '')"; echo "Endpoint = $(opt home_endpoint '')"
|
||||
echo "AllowedIPs = 192.168.88.0/24, 172.16.6.0/24"; echo "PersistentKeepalive = 25"; } > "$RUN/pcahome.conf"
|
||||
chmod 600 "$RUN/pcahome.conf"
|
||||
wg-quick up "$RUN/pcahome.conf" >/dev/null 2>&1 && echo up > "$RUN/home" && say "home tunnel up"
|
||||
}
|
||||
home_down() { [ -f "$RUN/home" ] && wg-quick down "$RUN/pcahome.conf" >/dev/null 2>&1; rm -f "$RUN/home"; }
|
||||
|
||||
watch() { # background: auto UDP->TCP, gateway changes, home on/off when roaming
|
||||
local started=$(date +%s) gw=$(gateway)
|
||||
while [ -f "$RUN/utun" ]; do
|
||||
sleep 5
|
||||
local now_gw=$(gateway)
|
||||
if [ -n "$now_gw" ] && [ "$now_gw" != "$gw" ]; then
|
||||
route -n delete -host "$(host)" >/dev/null 2>&1; route -n add -host "$(host)" "$now_gw" >/dev/null 2>&1
|
||||
gw=$now_gw; started=$(date +%s)
|
||||
if at_home; then home_down; elif [ ! -f "$RUN/home" ]; then home_up; fi
|
||||
fi
|
||||
if [ "$(opt transport auto)" = auto ] && [ "$(cat "$RUN/transport")" = udp ] && [ -n "$(tcp_port)" ] \
|
||||
&& [ $(( $(date +%s) - started )) -ge 15 ]; then
|
||||
local hs=$(wg show "$(utun)" latest-handshakes 2>/dev/null | awk '{print $2; exit}')
|
||||
if [ "${hs:-0}" = 0 ]; then say "no UDP answer in 15 s -> TCP"; start_obfuscator tcp; fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
cmd_on() {
|
||||
need_root
|
||||
[ -f "$PROFILE" ] || { say "no profile: sudo phobos import 'phobos://...'"; exit 1; }
|
||||
[ -f "$RUN/utun" ] && cmd_off >/dev/null
|
||||
local gw=$(gateway) h=$(host) tp=$(tcp_port)
|
||||
[ -n "$gw" ] || { say "no network"; exit 1; }
|
||||
route -n add -host "$h" "$gw" >/dev/null 2>&1 # obfuscator/udptcp go around the tunnel
|
||||
if [ -n "$tp" ]; then nohup "$D/bin/udptcp" -c "127.0.0.1:$TCPLOCAL" "$h:$tp" >> "$RUN/tcp.log" 2>&1 & fi
|
||||
local mode=$(opt transport auto)
|
||||
start_obfuscator "$([ "$mode" = tcp ] && echo tcp || echo udp)"
|
||||
|
||||
WG_TUN_NAME_FILE="$RUN/utun" wireguard-go utun >/dev/null 2>&1
|
||||
local u=$(utun) addr=$(sec Interface Address | cut -d, -f1) mtu=$(sec Interface MTU)
|
||||
[ -n "$u" ] || { say "wireguard-go failed"; cmd_off; exit 1; }
|
||||
umask 077
|
||||
{ echo "[Interface]"; echo "PrivateKey = $(sec Interface PrivateKey)"
|
||||
echo; echo "[Peer]"; echo "PublicKey = $(sec Peer PublicKey)"
|
||||
psk=$(sec Peer PresharedKey); [ -n "$psk" ] && echo "PresharedKey = $psk"
|
||||
echo "Endpoint = 127.0.0.1:$WGPORT"; echo "AllowedIPs = 0.0.0.0/0"; echo "PersistentKeepalive = 25"; } > "$RUN/wg.conf"
|
||||
wg setconf "$u" "$RUN/wg.conf"
|
||||
ifconfig "$u" inet "${addr%/*}" "${addr%/*}" netmask 255.255.255.255 mtu "${mtu:-1420}" up
|
||||
if [ "$(opt ru 1)" = 1 ]; then # Internet minus Russian networks
|
||||
grep -v '^#' "$D/ru_bypass.txt" | tr ',' '\n' | sed 's/ //g' | grep / | while read -r n; do
|
||||
route -q -n add -inet "$n" -interface "$u" >/dev/null 2>&1; done
|
||||
else
|
||||
route -q -n add -inet 0.0.0.0/1 -interface "$u"; route -q -n add -inet 128.0.0.0/1 -interface "$u"
|
||||
fi
|
||||
dns_set
|
||||
at_home || home_up
|
||||
nohup "$0" _watch >/dev/null 2>&1 &
|
||||
echo $! > "$RUN/watch.pid"
|
||||
say "on ($(cat "$RUN/transport"), RU direct: $(opt ru 1), home: $([ -f "$RUN/home" ] && echo up || echo off))"
|
||||
}
|
||||
|
||||
cmd_off() {
|
||||
need_root
|
||||
[ -f "$RUN/watch.pid" ] && kill "$(cat "$RUN/watch.pid")" 2>/dev/null
|
||||
local u=$(utun)
|
||||
rm -f "$RUN/utun" "$RUN/watch.pid"
|
||||
[ -n "$u" ] && rm -f "/var/run/wireguard/$u.sock" # wireguard-go exits, its routes go with utun
|
||||
pkill -f "$D/bin/wg-obfuscator" 2>/dev/null; pkill -f "$D/bin/udptcp" 2>/dev/null
|
||||
[ -f "$PROFILE" ] && route -n delete -host "$(host)" >/dev/null 2>&1
|
||||
dns_restore; home_down
|
||||
say "off"
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
local u=$(utun)
|
||||
if [ -z "$u" ]; then echo "VPN: выключен"; else
|
||||
local hs=$(wg show "$u" latest-handshakes 2>/dev/null | awk '{print $2; exit}')
|
||||
echo "VPN: включён · $(cat "$RUN/transport" | tr a-z A-Z) · рукопожатие $([ "${hs:-0}" = 0 ] && echo нет || echo "$(( $(date +%s) - hs )) с назад")"
|
||||
fi
|
||||
echo "Режим: $(opt transport auto) · РФ напрямую: $([ "$(opt ru 1)" = 1 ] && echo да || echo нет) · Дом: $([ -f "$RUN/home" ] && echo подключён || ( at_home && echo 'вы дома' || echo выкл ))"
|
||||
}
|
||||
|
||||
case "${1:-status}" in
|
||||
on) cmd_on ;;
|
||||
off) cmd_off ;;
|
||||
status) cmd_status ;;
|
||||
auto|udp|tcp) need_root; echo "$1" > "$D/transport"; [ -f "$RUN/utun" ] && cmd_on >/dev/null; cmd_status ;;
|
||||
ru) need_root; [ "${2:-on}" = on ] && echo 1 > "$D/ru" || echo 0 > "$D/ru"; [ -f "$RUN/utun" ] && cmd_on >/dev/null; cmd_status ;;
|
||||
import) need_root; s="${2#phobos://}"; s="${s%%#*}"; s=$(echo "$s" | tr '_-' '/+')
|
||||
while [ $(( ${#s} % 4 )) -ne 0 ]; do s="$s="; done
|
||||
echo "$s" | base64 -D > "$PROFILE.new" && grep -q '\[instance\]' "$PROFILE.new" && mv "$PROFILE.new" "$PROFILE" && chmod 600 "$PROFILE" && say "imported $(target)" ;;
|
||||
_watch) watch ;;
|
||||
*) echo "usage: phobos on|off|status|auto|udp|tcp|ru on|off|import <phobos://...>"; exit 2 ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user