diff --git a/files/etc/uci-defaults/99-parahub-mesh b/files/etc/uci-defaults/99-parahub-mesh index e8db098..13ad0ca 100755 --- a/files/etc/uci-defaults/99-parahub-mesh +++ b/files/etc/uci-defaults/99-parahub-mesh @@ -132,6 +132,13 @@ set network.vpn_tunnel.ipaddr='172.16.0.2' set network.vpn_tunnel.netmask='255.255.255.0' set network.vpn_tunnel.gateway='172.16.0.1' set network.vpn_tunnel.mtu='1400' +set network.vpn_tunnel.ip4table='100' + +# --- Policy routing: guest traffic → VPN table 100 --- +add network rule +set network.@rule[-1].src='${GUEST_SUBNET}/24' +set network.@rule[-1].lookup='100' +set network.@rule[-1].priority='100' NET_EOF uci commit network diff --git a/files/usr/bin/parahub-mullvad b/files/usr/bin/parahub-mullvad new file mode 100755 index 0000000..d91d4dc --- /dev/null +++ b/files/usr/bin/parahub-mullvad @@ -0,0 +1,297 @@ +#!/bin/sh +# Parahub Mesh — Local Mullvad WireGuard Manager +# +# Allows the node owner to run Mullvad directly on the router. +# Guest traffic routes through the local WireGuard tunnel instead of +# the default GRE6→VPS path, giving lower latency and using the +# nearest Mullvad server. +# +# Usage: +# parahub-mullvad setup [country_code] +# parahub-mullvad status +# parahub-mullvad remove + +set -e + +PARAHUB_DIR="/etc/parahub" +ACCOUNT_FILE="$PARAHUB_DIR/mullvad_account" + +# ============================================================================ +# Helpers +# ============================================================================ + +# Find the firewall forwarding index where src=guest +find_guest_forwarding() { + local idx=0 + while uci -q get "firewall.@forwarding[$idx]" >/dev/null 2>&1; do + local src=$(uci -q get "firewall.@forwarding[$idx].src") + if [ "$src" = "guest" ]; then + echo "$idx" + return 0 + fi + idx=$((idx + 1)) + done + return 1 +} + +# Find firewall zone index by name +find_zone_index() { + local name="$1" + local idx=0 + while uci -q get "firewall.@zone[$idx]" >/dev/null 2>&1; do + local zname=$(uci -q get "firewall.@zone[$idx].name") + if [ "$zname" = "$name" ]; then + echo "$idx" + return 0 + fi + idx=$((idx + 1)) + done + return 1 +} + +# ============================================================================ +# setup [country_code] +# ============================================================================ + +cmd_setup() { + local ACCOUNT="$1" + local COUNTRY="$2" + + if [ -z "$ACCOUNT" ]; then + echo "Usage: parahub-mullvad setup [country_code]" + echo "" + echo "Examples:" + echo " parahub-mullvad setup 1234567890123456 pt # Portugal" + echo " parahub-mullvad setup 1234567890123456 de # Germany" + echo " parahub-mullvad setup 1234567890123456 # Auto-detect" + echo "" + echo "Countries: us gb de nl se pt fr es ch at it jp sg br ..." + exit 1 + fi + + # Auto-detect country from WAN IP + if [ -z "$COUNTRY" ]; then + echo "Auto-detecting country from WAN IP..." + COUNTRY=$(curl -s --max-time 5 https://ipinfo.io/country 2>/dev/null | tr 'A-Z' 'a-z') + if [ -z "$COUNTRY" ] || [ ${#COUNTRY} -ne 2 ]; then + echo "Error: Could not detect country." + echo "Specify manually: parahub-mullvad setup $ACCOUNT " + exit 1 + fi + echo "Detected: $COUNTRY" + fi + + COUNTRY=$(echo "$COUNTRY" | tr 'A-Z' 'a-z') + + # --- Step 1: Generate WireGuard keys --- + echo "Generating WireGuard keys..." + umask 077 + wg genkey > "$PARAHUB_DIR/wg_private.key" + wg pubkey < "$PARAHUB_DIR/wg_private.key" > "$PARAHUB_DIR/wg_public.key" + + PRIVKEY=$(cat "$PARAHUB_DIR/wg_private.key") + PUBKEY=$(cat "$PARAHUB_DIR/wg_public.key") + + # --- Step 2: Register with Mullvad API --- + echo "Registering key with Mullvad..." + RESULT=$(curl -s --max-time 15 -X POST https://api.mullvad.net/wg/ \ + -d "account=$ACCOUNT" \ + -d "pubkey=$PUBKEY") + + if echo "$RESULT" | grep -q "^[0-9]"; then + MULLVAD_IPV4=$(echo "$RESULT" | cut -d',' -f1) + echo "Mullvad IP: $MULLVAD_IPV4" + else + echo "Error from Mullvad: $RESULT" + exit 1 + fi + + # --- Step 3: Find server in target country --- + echo "Finding Mullvad server in '$COUNTRY'..." + curl -s --max-time 15 https://api.mullvad.net/www/relays/wireguard/ | \ + tr '{' '\n' | \ + grep "\"country_code\":\"${COUNTRY}\"" | \ + grep '"active":true' | \ + head -1 > /tmp/mullvad_relay.tmp + + if [ ! -s /tmp/mullvad_relay.tmp ]; then + echo "Error: No active WireGuard server for country '$COUNTRY'" + rm -f /tmp/mullvad_relay.tmp + exit 1 + fi + + SERVER_IP=$(sed 's/.*"ipv4_addr_in":"\([^"]*\)".*/\1/' /tmp/mullvad_relay.tmp) + SERVER_PUBKEY=$(sed 's/.*"pubkey":"\([^"]*\)".*/\1/' /tmp/mullvad_relay.tmp) + SERVER_HOST=$(sed 's/.*"hostname":"\([^"]*\)".*/\1/' /tmp/mullvad_relay.tmp) + rm -f /tmp/mullvad_relay.tmp + + echo "Server: $SERVER_HOST ($SERVER_IP)" + + # --- Step 4: Clean previous config --- + uci -q delete network.mullvad_local 2>/dev/null || true + while uci -q delete network.@wireguard_mullvad_local[0] 2>/dev/null; do :; done + + # --- Step 5: Create WireGuard interface (routes in table 100) --- + echo "Configuring WireGuard..." + uci batch <<-WG_EOF +set network.mullvad_local=interface +set network.mullvad_local.proto='wireguard' +set network.mullvad_local.private_key='${PRIVKEY}' +add_list network.mullvad_local.addresses='${MULLVAD_IPV4}' +set network.mullvad_local.mtu='1420' +set network.mullvad_local.ip4table='100' + +add network wireguard_mullvad_local +set network.@wireguard_mullvad_local[-1].public_key='${SERVER_PUBKEY}' +set network.@wireguard_mullvad_local[-1].endpoint_host='${SERVER_IP}' +set network.@wireguard_mullvad_local[-1].endpoint_port='51820' +add_list network.@wireguard_mullvad_local[-1].allowed_ips='0.0.0.0/0' +set network.@wireguard_mullvad_local[-1].route_allowed_ips='1' +set network.@wireguard_mullvad_local[-1].persistent_keepalive='25' +WG_EOF + + # Disable GRE6 tunnel (WG replaces it in table 100) + uci set network.vpn_tunnel.auto='0' + uci commit network + + # --- Step 6: Firewall zone for mullvad_local --- + local zone_idx + if zone_idx=$(find_zone_index "mullvad_local"); then + uci delete "firewall.@zone[$zone_idx]" + fi + + uci batch <<-FW_EOF +add firewall zone +set firewall.@zone[-1].name='mullvad_local' +set firewall.@zone[-1].input='REJECT' +set firewall.@zone[-1].output='ACCEPT' +set firewall.@zone[-1].forward='REJECT' +set firewall.@zone[-1].masq='1' +set firewall.@zone[-1].mtu_fix='1' +add_list firewall.@zone[-1].network='mullvad_local' +FW_EOF + + # Switch guest forwarding to mullvad_local + local fwd_idx + if fwd_idx=$(find_guest_forwarding); then + uci set "firewall.@forwarding[$fwd_idx].dest=mullvad_local" + fi + uci commit firewall + + # --- Step 7: Save config --- + cat > "$ACCOUNT_FILE" <<-ACCT_EOF +MULLVAD_ACCOUNT=${ACCOUNT} +MULLVAD_COUNTRY=${COUNTRY} +MULLVAD_SERVER=${SERVER_HOST} +MULLVAD_SERVER_IP=${SERVER_IP} +MULLVAD_LOCAL_IP=${MULLVAD_IPV4} +ACCT_EOF + chmod 600 "$ACCOUNT_FILE" + + # --- Step 8: Apply --- + echo "Restarting network..." + /etc/init.d/network restart + /etc/init.d/firewall restart + + echo "" + echo "Done! Guest traffic now routes directly through Mullvad." + echo "Server: $SERVER_HOST ($COUNTRY)" + echo "Test: connect to Parahub_Free, visit https://am.i.mullvad.net" +} + +# ============================================================================ +# status +# ============================================================================ + +cmd_status() { + echo "=== Parahub Mesh VPN Status ===" + echo "" + + if [ -f "$ACCOUNT_FILE" ]; then + echo "Mode: LOCAL MULLVAD (direct)" + cat "$ACCOUNT_FILE" + echo "" + wg show mullvad_local 2>/dev/null || echo "WireGuard interface: not up" + else + echo "Mode: VPS GATEWAY (GRE6 tunnel)" + echo "VPS: 91.98.123.238 -> Mullvad Portugal" + fi + + echo "" + echo "Guest forwarding:" + local fwd_idx + if fwd_idx=$(find_guest_forwarding); then + echo " guest -> $(uci -q get "firewall.@forwarding[$fwd_idx].dest")" + fi +} + +# ============================================================================ +# remove +# ============================================================================ + +cmd_remove() { + if [ ! -f "$ACCOUNT_FILE" ]; then + echo "No local Mullvad configured. Nothing to remove." + exit 0 + fi + + echo "Removing local Mullvad, reverting to VPS gateway..." + + # Remove WireGuard interface + uci -q delete network.mullvad_local 2>/dev/null || true + while uci -q delete network.@wireguard_mullvad_local[0] 2>/dev/null; do :; done + + # Re-enable GRE6 tunnel + uci -q delete network.vpn_tunnel.auto 2>/dev/null || true + uci commit network + + # Remove firewall zone + local zone_idx + if zone_idx=$(find_zone_index "mullvad_local"); then + uci delete "firewall.@zone[$zone_idx]" + fi + + # Switch guest forwarding back to vpn_tunnel + local fwd_idx + if fwd_idx=$(find_guest_forwarding); then + uci set "firewall.@forwarding[$fwd_idx].dest=vpn_tunnel" + fi + uci commit firewall + + # Clean up files + rm -f "$PARAHUB_DIR/wg_private.key" "$PARAHUB_DIR/wg_public.key" "$ACCOUNT_FILE" + + echo "Restarting network..." + /etc/init.d/network restart + /etc/init.d/firewall restart + + echo "Done! Guest traffic now routes through VPS gateway." +} + +# ============================================================================ +# Main +# ============================================================================ + +case "${1:-}" in + setup) + cmd_setup "$2" "${3:-}" + ;; + status) + cmd_status + ;; + remove) + cmd_remove + ;; + *) + echo "Parahub Mesh — Mullvad WireGuard Manager" + echo "" + echo "Commands:" + echo " parahub-mullvad setup [country]" + echo " parahub-mullvad status" + echo " parahub-mullvad remove" + echo "" + echo "By default, guest traffic goes through the VPS gateway." + echo "With 'setup', it routes directly through your own Mullvad" + echo "account — faster, and uses the nearest server." + ;; +esac diff --git a/scripts/build.sh b/scripts/build.sh index 1f92d02..b2e530a 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -64,6 +64,11 @@ PACKAGES_CORE=( # GRE6 tunnel (guest traffic → VPS gateway) kmod-gre6 + # WireGuard (optional local Mullvad via parahub-mullvad script) + kmod-wireguard + wireguard-tools + luci-proto-wireguard + # DNS-over-HTTPS for guest privacy https-dns-proxy