- 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>
93 lines
2.6 KiB
Python
93 lines
2.6 KiB
Python
import re
|
|
import subprocess
|
|
from dataclasses import dataclass
|
|
|
|
from .runner import run
|
|
|
|
|
|
def _sysctl_get(key: str) -> str:
|
|
try:
|
|
return run(["sysctl", "-n", key])
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
return ""
|
|
|
|
|
|
def _sysctl_set(key: str, value: str) -> bool:
|
|
try:
|
|
run(["sysctl", "-w", f"{key}={value}"])
|
|
return True
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
return False
|
|
|
|
|
|
def default_out_interface() -> str:
|
|
try:
|
|
route = run(["ip", "route", "show", "default"])
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
return ""
|
|
match = re.search(r"\bdev\s+(\S+)", route)
|
|
return match.group(1) if match else ""
|
|
|
|
|
|
@dataclass
|
|
class HostTuning:
|
|
rmem_max: str = ""
|
|
wmem_max: str = ""
|
|
netdev_max_backlog: str = ""
|
|
ip_forward: str = ""
|
|
gro_forwarding: str = ""
|
|
out_interface: str = ""
|
|
|
|
|
|
def read_host_tuning() -> HostTuning:
|
|
tuning = HostTuning(
|
|
rmem_max=_sysctl_get("net.core.rmem_max"),
|
|
wmem_max=_sysctl_get("net.core.wmem_max"),
|
|
netdev_max_backlog=_sysctl_get("net.core.netdev_max_backlog"),
|
|
ip_forward=_sysctl_get("net.ipv4.ip_forward"),
|
|
out_interface=default_out_interface(),
|
|
)
|
|
if tuning.out_interface:
|
|
try:
|
|
output = run(["ethtool", "-k", tuning.out_interface])
|
|
match = re.search(r"rx-udp-gro-forwarding:\s*(\S+)", output)
|
|
if match:
|
|
tuning.gro_forwarding = match.group(1)
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
pass
|
|
return tuning
|
|
|
|
|
|
def apply_udp_buffers(size_bytes: int) -> list[str]:
|
|
errors = []
|
|
for key in ("net.core.rmem_max", "net.core.wmem_max"):
|
|
if not _sysctl_set(key, str(size_bytes)):
|
|
errors.append(f"failed to set {key}")
|
|
return errors
|
|
|
|
|
|
def apply_backlog(value: int) -> list[str]:
|
|
if _sysctl_set("net.core.netdev_max_backlog", str(value)):
|
|
return []
|
|
return ["failed to set net.core.netdev_max_backlog"]
|
|
|
|
|
|
def apply_gro_forwarding(enable: bool) -> list[str]:
|
|
device = default_out_interface()
|
|
if not device:
|
|
return ["no default route interface found"]
|
|
state = "on" if enable else "off"
|
|
try:
|
|
run(
|
|
[
|
|
"ethtool", "-K", device,
|
|
"rx-udp-gro-forwarding", state,
|
|
"rx-gro-list", "off" if enable else "on",
|
|
]
|
|
)
|
|
return []
|
|
except FileNotFoundError:
|
|
return ["ethtool not installed"]
|
|
except subprocess.CalledProcessError as exc:
|
|
return [f"ethtool failed on {device}: {exc.stderr.strip() or exc}"]
|