diff options
Diffstat (limited to 'modules/networking')
| -rw-r--r-- | modules/networking/usque/default.nix | 30 | ||||
| -rw-r--r-- | modules/networking/usque/usque-warp.sh | 223 | ||||
| -rw-r--r-- | modules/networking/usque/usque-warp.zsh | 17 |
3 files changed, 266 insertions, 4 deletions
diff --git a/modules/networking/usque/default.nix b/modules/networking/usque/default.nix index b8c25ee..9d04ab7 100644 --- a/modules/networking/usque/default.nix +++ b/modules/networking/usque/default.nix @@ -5,6 +5,24 @@ lib, ... }: +let + inherit ((import ../../lib/script.nix { inherit lib pkgs; })) mkScriptModule; + usqueWarp = mkScriptModule { + scope = [ + "networking" + "usque" + ]; + name = "usque-warp"; + path = ./usque-warp.sh; + description = "Connect/disconnect to Cloudflare WARP via usque MASQUE tunnel"; + deps = [ + config.networking.usque.package + pkgs.bash + ]; + completion.zsh = builtins.readFile ./usque-warp.zsh; + inherit config; + }; +in { options.networking.usque = { enable = lib.mkEnableOption "Enables usque."; @@ -13,9 +31,13 @@ default = pkgs.usque; description = "The usque package to use."; }; - }; + } + // usqueWarp.options.networking.usque; - config = lib.mkIf config.networking.usque.enable { - home.packages = [ config.networking.usque.package ]; - }; + config = lib.mkMerge [ + (lib.mkIf config.networking.usque.enable { + home.packages = [ config.networking.usque.package ]; + }) + usqueWarp.config + ]; } diff --git a/modules/networking/usque/usque-warp.sh b/modules/networking/usque/usque-warp.sh new file mode 100644 index 0000000..9d32872 --- /dev/null +++ b/modules/networking/usque/usque-warp.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +set -euo pipefail +CONFIG_DIR="$HOME/.cache/usque" +CONFIG="$CONFIG_DIR/config.json" +RUNTIME_DIR="${XDG_RUNTIME_DIR:-/tmp}" +PID_FILE="$RUNTIME_DIR/usque-warp.pid" +STATE_FILE="$RUNTIME_DIR/usque-warp.state" +IFACE_FILE="$RUNTIME_DIR/usque-warp.iface" +LOG_FILE="$RUNTIME_DIR/usque-warp.log" + +list_tun_ifaces() { + ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | grep -E '^tun[0-9]+$' || true +} + +detect_iface() { + if [[ -f "$IFACE_FILE" ]]; then + cat "$IFACE_FILE" + return + fi + list_tun_ifaces | head -n1 +} + +is_running() { + [[ -f "$PID_FILE" ]] || return 1 + local pid + pid=$(cat "$PID_FILE" 2>/dev/null || true) + [[ -n "$pid" ]] || return 1 + sudo kill -0 "$pid" 2>/dev/null +} + +ensure_config() { + echo "Creating $CONFIG_DIR..." + mkdir -p "$CONFIG_DIR" + echo "Registering Cloudflare WARP account..." + usque -c "$CONFIG" register < <(yes) + if [[ ! -f "$CONFIG" ]]; then + echo "Failed to create config file: $CONFIG" + exit 1 + fi + echo "Config created successfully." +} + +remove_tun_default_routes() { + local dev="$1" + while ip route show | grep -qE "^default .*dev $dev"; do + ROUTE=$(ip route show | grep -E "^default .*dev $dev" | head -n1) + echo "Removing route: $ROUTE" + sudo ip route del "$ROUTE" || break + done +} + +connect() { + sudo -v + ensure_config + if [[ -f "$PID_FILE" ]]; then + OLD_PID=$(cat "$PID_FILE") + if sudo kill -0 "$OLD_PID" 2>/dev/null; then + echo "usque-warp is already running (PID $OLD_PID)" + exit 1 + else + echo "Removing stale PID file..." + rm -f "$PID_FILE" + fi + fi + + echo "Saving current default route..." + DEFAULT_ROUTE=$(ip route show default | grep -vE 'dev tun[0-9]+' | head -n1) + if [[ -z "$DEFAULT_ROUTE" ]]; then + echo "Could not determine current default route" + exit 1 + fi + echo "$DEFAULT_ROUTE" > "$STATE_FILE" + + echo "Recording pre-existing tun interfaces..." + BEFORE_IFACES=$(list_tun_ifaces) + + echo "Starting usque..." + sudo usque nativetun -c "$CONFIG" 2>&1 | sudo tee "$LOG_FILE" >/dev/null & + echo $! > "$PID_FILE" + + echo "Waiting for MASQUE connection..." + MASQUE_IP="" + for _ in {1..30}; do + MASQUE_IP=$(grep -oP 'MASQUE connection to \K[0-9.]+(?=:443)' "$LOG_FILE" 2>/dev/null || true) + if [[ -n "$MASQUE_IP" ]]; then + break + fi + sleep 1 + done + if [[ -z "$MASQUE_IP" ]]; then + echo "Failed to detect MASQUE endpoint" + sudo kill "$(cat "$PID_FILE")" 2>/dev/null || true + rm -f "$PID_FILE" + exit 1 + fi + + echo "Waiting for usque interface..." + TUN_DEV="" + for _ in {1..30}; do + AFTER_IFACES=$(list_tun_ifaces) + TUN_DEV=$(comm -13 <(echo "$BEFORE_IFACES" | sort) <(echo "$AFTER_IFACES" | sort) | head -n1) + [[ -n "$TUN_DEV" ]] && break + sleep 1 + done + if [[ -z "$TUN_DEV" ]]; then + echo "Failed to detect usque interface" + sudo kill "$(cat "$PID_FILE")" 2>/dev/null || true + rm -f "$PID_FILE" + exit 1 + fi + echo "$TUN_DEV" > "$IFACE_FILE" + echo "Detected interface: $TUN_DEV" + + GATEWAY=$(echo "$DEFAULT_ROUTE" | awk '{for(i=1;i<=NF;i++) if($i=="via") print $(i+1)}') + INTERFACE=$(echo "$DEFAULT_ROUTE" | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}') + if [[ -z "$GATEWAY" || -z "$INTERFACE" ]]; then + echo "Cannot determine gateway/interface" + exit 1 + fi + echo "MASQUE_IP=$MASQUE_IP GATEWAY=$GATEWAY INTERFACE=$INTERFACE" >> "$STATE_FILE" + + echo "Allowing MASQUE endpoint outside tunnel..." + sudo ip route replace \ + "$MASQUE_IP" \ + via "$GATEWAY" \ + dev "$INTERFACE" + + echo "Removing old tun routes..." + remove_tun_default_routes "$TUN_DEV" + + echo "Switching default route to $TUN_DEV..." + sudo ip route add default dev "$TUN_DEV" metric 1 + echo "Connected" +} + +disconnect() { + sudo -v + echo "Disconnecting..." + local dev + if [[ -f "$IFACE_FILE" ]]; then + dev=$(cat "$IFACE_FILE") + else + dev=$(list_tun_ifaces | head -n1) + fi + + # Kill usque FIRST so the kernel tears down tun0 (and every route + # bound to it) as a single atomic operation, instead of us racing + # it by pulling routes out from under a device that's still up. + if [[ -f "$PID_FILE" ]]; then + PID=$(cat "$PID_FILE") + if sudo kill -0 "$PID" 2>/dev/null; then + echo "Stopping usque..." + sudo kill "$PID" 2>/dev/null || true + for _ in {1..20}; do + sudo kill -0 "$PID" 2>/dev/null || break + sleep 0.2 + done + fi + rm -f "$PID_FILE" + fi + + # Belt-and-braces: if the interface (or any of its routes) somehow + # survived, clean them up explicitly. No-ops if tun0 is already gone. + if [[ -n "${dev:-}" ]]; then + echo "Flushing $dev routes..." + sudo ip route flush dev "$dev" 2>/dev/null || true + remove_tun_default_routes "$dev" + fi + + if [[ -f "$STATE_FILE" ]]; then + MASQUE_IP=$(grep -oP 'MASQUE_IP=\K[0-9.]+' "$STATE_FILE" || true) + if [[ -n "$MASQUE_IP" ]]; then + echo "Removing MASQUE route: $MASQUE_IP" + sudo ip route del "$MASQUE_IP" 2>/dev/null || true + fi + + # Explicitly restore the pre-connect default route rather than + # assuming it's still intact. 'replace' is idempotent. + ORIGINAL_DEFAULT=$(head -n1 "$STATE_FILE") + if [[ "$ORIGINAL_DEFAULT" == default* ]]; then + echo "Restoring original default route..." + sudo ip route replace "$ORIGINAL_DEFAULT" \ + || echo "Warning: could not restore original default route" + fi + + rm -f "$STATE_FILE" + fi + + rm -f "$IFACE_FILE" + echo "Disconnected" +} + +status() { + local iface running=false + iface=$(detect_iface) + is_running && running=true + + if [[ -n "$iface" && "$running" == true ]]; then + printf '{"text":"%s","tooltip":"WARP connected via %s","class":"connected"}\n' "$iface" "$iface" + elif [[ -n "$iface" ]]; then + printf '{"text":"%s","tooltip":"Interface %s up, but usque-warp process not tracked","class":"connected"}\n' "$iface" "$iface" + elif [[ "$running" == true ]]; then + printf '{"text":"connecting","tooltip":"usque starting...","class":"connecting"}\n' + else + printf '{"text":"","tooltip":"WARP disconnected","class":"disconnected"}\n' + fi +} + +case "${1:-}" in + connect) + connect + ;; + disconnect) + disconnect + ;; + status) + status + ;; + *) + echo "Usage: $0 {connect|disconnect|status}" + exit 1 + ;; +esac diff --git a/modules/networking/usque/usque-warp.zsh b/modules/networking/usque/usque-warp.zsh new file mode 100644 index 0000000..fbb340e --- /dev/null +++ b/modules/networking/usque/usque-warp.zsh @@ -0,0 +1,17 @@ +#compdef usque-warp + +_arguments \ + '1: :->cmds' \ + '*::arg:->args' + +case $state in + cmds) + local -a commands + commands=( + 'connect:Connect to WARP' + 'disconnect:Disconnect from WARP' + 'status:Show connection status' + ) + _describe 'command' commands + ;; +esac |
