Files
wireguard-admin/app/wireguard/sync.py
T
lofyerandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> 1b227c2470 Multi-interface management, topology view, performance tuning, precompiled CSS
- Interface model with per-interface subnet/port/keys; import/adopt existing wg-quick configs (key-less imported peers, optional key rotation), cascade delete
- Split wireguard.py into a package (keys via cryptography X25519, status, addressing, conf parse/render, sync, host tuning)
- ECharts horizontal topology view (interface -> peers -> site subnets)
- Advanced options: MTU, MSS clamping, FwMark/Table, custom PostUp/PostDown, per-peer keepalive override
- Runtime settings (sample interval/retention, online threshold, UI refresh) with traffic sample pruning; host tuning (UDP buffers, backlog, GRO forwarding)
- Precompiled Tailwind CSS replacing Play CDN runtime (fixes FOUC); stable table layout and diffed polling renders
- Host network mode in compose; NAT/isolation iptables moved into app sync

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-07-05 14:01:44 +08:00

119 lines
3.8 KiB
Python

import re
import subprocess
from ..models import Interface, Peer
from .conf import config_path, write_server_config
from .runner import run
from .status import InterfaceStatus, get_status
def is_managed(iface: Interface, status: InterfaceStatus) -> bool:
return status.public_key == iface.public_key
def interface_up(iface: Interface) -> None:
status = get_status(iface.name)
if status.up and not is_managed(iface, status):
raise RuntimeError(
f"Interface {iface.name} is up but uses a foreign key; "
"refusing to manage it. Import it first or pick another name."
)
if not status.up:
run(["wg-quick", "up", str(config_path(iface.name))])
sync_nat(iface)
sync_isolation(iface)
sync_mss_clamp(iface)
def interface_down(iface: Interface) -> None:
status = get_status(iface.name)
if status.up and is_managed(iface, status):
run(["wg-quick", "down", str(config_path(iface.name))])
def sync_routes(iface: Interface, peers: list[Peer]) -> None:
for peer in peers:
if not peer.extra_allowed_ips:
continue
for subnet in peer.extra_allowed_ips.split(","):
subnet = subnet.strip()
args = ["ip", "route", "replace" if peer.enabled else "del", subnet]
if peer.enabled:
args += ["dev", iface.name]
try:
run(args)
except subprocess.CalledProcessError:
pass
def sync_nat(iface: Interface) -> None:
try:
route = run(["ip", "route", "show", "default"])
except (subprocess.CalledProcessError, FileNotFoundError):
return
match = re.search(r"\bdev\s+(\S+)", route)
if not match:
return
rule = ["POSTROUTING", "-s", iface.subnet, "-o", match.group(1), "-j", "MASQUERADE"]
try:
run(["iptables", "-t", "nat", "-C", *rule])
except FileNotFoundError:
pass
except subprocess.CalledProcessError:
try:
run(["iptables", "-t", "nat", "-A", *rule])
except subprocess.CalledProcessError:
pass
def sync_isolation(iface: Interface) -> None:
rule = ["-i", iface.name, "-o", iface.name, "-j", "DROP"]
try:
if iface.peer_isolation:
try:
run(["iptables", "-C", "FORWARD", *rule])
except subprocess.CalledProcessError:
run(["iptables", "-I", "FORWARD", "1", *rule])
else:
run(["iptables", "-D", "FORWARD", *rule])
except (subprocess.CalledProcessError, FileNotFoundError):
pass
def sync_mss_clamp(iface: Interface) -> None:
rule = [
"-o", iface.name, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN",
"-j", "TCPMSS", "--clamp-mss-to-pmtu",
]
try:
if iface.mss_clamp:
try:
run(["iptables", "-t", "mangle", "-C", "FORWARD", *rule])
except subprocess.CalledProcessError:
run(["iptables", "-t", "mangle", "-A", "FORWARD", *rule])
else:
run(["iptables", "-t", "mangle", "-D", "FORWARD", *rule])
except (subprocess.CalledProcessError, FileNotFoundError):
pass
def sync_mtu(iface: Interface) -> None:
if not iface.mtu:
return
try:
run(["ip", "link", "set", "dev", iface.name, "mtu", str(iface.mtu)])
except (subprocess.CalledProcessError, FileNotFoundError):
pass
def sync_peers(iface: Interface, peers: list[Peer]) -> None:
write_server_config(iface, peers)
status = get_status(iface.name)
if status.up and is_managed(iface, status):
stripped = run(["wg-quick", "strip", str(config_path(iface.name))])
run(["wg", "syncconf", iface.name, "/dev/stdin"], input_text=stripped)
sync_routes(iface, peers)
sync_mtu(iface)
sync_isolation(iface)
sync_mss_clamp(iface)