feat(wireguard): Implement Reverse MWAN WireGuard v2 Phase 1

WireGuard mesh peers as backup internet uplinks via mwan3 failover.

CLI (wgctl) uplink commands:
- uplink list/add/remove/status/test - Manage peer uplinks
- uplink failover enable/disable - Toggle automatic failover
- uplink priority/offer/withdraw - Priority and mesh advertising

Uplink Library (/usr/lib/wireguard-dashboard/uplink.sh):
- Gossip protocol integration via secubox-p2p
- WireGuard interface creation with IP allocation (172.31.x.x/16)
- mwan3 failover integration
- Connectivity testing and latency measurement

RPCD Backend (9 new methods):
- Read: uplink_status, uplinks
- Write: add_uplink, remove_uplink, test_uplink, offer_uplink,
         withdraw_uplink, set_uplink_priority, set_uplink_failover

UCI Config (/etc/config/wireguard_uplink):
- Global settings: auto_failover, failover_threshold, ping_interval
- Provider settings: offering state, bandwidth/latency advertisement
- Per-uplink config: interface, peer_pubkey, endpoint, priority

Phase 2 pending: LuCI dashboard integration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-03-01 15:43:17 +01:00
parent 0c55ef6ec1
commit 29d309649e
8 changed files with 1691 additions and 9 deletions

View File

@ -4067,3 +4067,42 @@ git checkout HEAD -- index.html
- `get_connections` - Returns all sync URLs
- `setup_mail` - Configure SMTP via LuCI
- `setup_backup_cron` - Enable scheduled backups via LuCI
60. **Reverse MWAN WireGuard v2 - Phase 1 (2026-03-01)**
- WireGuard mesh peers as backup internet uplinks via mwan3 failover
- **CLI (`wgctl`) uplink commands:**
- `uplink list` - Discover available uplink peers from mesh
- `uplink add <peer>` - Add peer as backup uplink
- `uplink remove <peer>` - Remove peer uplink
- `uplink status` - Show failover status
- `uplink test <peer>` - Test connectivity through peer
- `uplink failover enable/disable` - Toggle automatic failover
- `uplink priority <peer> <weight>` - Set peer priority
- `uplink offer` - Advertise this node as uplink provider
- `uplink withdraw` - Stop advertising as uplink
- **Uplink Library (`/usr/lib/wireguard-dashboard/uplink.sh`):**
- Gossip protocol integration via secubox-p2p
- `advertise_uplink_offer()` / `withdraw_uplink_offer()` - Mesh announcement
- `get_peer_uplink_offers()` - Query mesh for available uplinks
- `create_uplink_interface()` - WireGuard interface creation with IP allocation
- `add_to_mwan3()` / `remove_from_mwan3()` - mwan3 failover integration
- `test_uplink_connectivity()` / `measure_uplink_latency()` - Health checks
- **RPCD Backend (9 new methods):**
- Read: `uplink_status`, `uplinks`
- Write: `add_uplink`, `remove_uplink`, `test_uplink`, `offer_uplink`, `withdraw_uplink`, `set_uplink_priority`, `set_uplink_failover`
- **UCI Config (`/etc/config/wireguard_uplink`):**
- Global settings: auto_failover, failover_threshold, ping_interval
- Provider settings: offering state, bandwidth/latency advertisement
- Per-uplink config: interface, peer_pubkey, endpoint, priority, weight
- **Architecture:**
- Uplink pool uses 172.31.x.x/16 range
- mwan3 policy-based failover with configurable weights
- Gossip-based peer discovery via P2P mesh
- Phase 2 pending: LuCI dashboard with uplink column in peers table
61. **VirtualBox Image Builder Validation (2026-03-01)**
- Fresh OpenWrt 24.10.5 image boots successfully in VirtualBox
- Network connectivity confirmed (IPv6 link-local + IPv4)
- Fixed mitmproxy routing for matrix.gk2.secubox.in and alerte.gk2.secubox.in
- Identified corrupted c3box-vm images from Feb 23 - need rebuild
- ASU firmware builder working with MochaBin preseeds embedded

View File

@ -1,6 +1,6 @@
# Work In Progress (Claude)
_Last updated: 2026-02-28 (AI Gateway Deployed)_
_Last updated: 2026-03-01 (Reverse MWAN WireGuard Phase 1)_
> **Architecture Reference**: SecuBox Fanzine v3 — Les 4 Couches
@ -62,6 +62,23 @@ _Last updated: 2026-02-28 (AI Gateway Deployed)_
- Gossip-based exposure config sync via secubox-p2p
- Created `luci-app-vortex-dns` dashboard
### Just Completed (2026-03-01)
- **Reverse MWAN WireGuard v2 - Phase 1** — DONE (2026-03-01)
- WireGuard mesh peers as backup internet uplinks via mwan3 failover
- `wgctl` CLI: uplink list/add/remove/status/test/failover/priority/offer/withdraw
- Uplink library (`/usr/lib/wireguard-dashboard/uplink.sh`) with gossip integration
- RPCD backend: 9 new methods for uplink management
- UCI config (`/etc/config/wireguard_uplink`) for global and per-uplink settings
- Phase 2 pending: LuCI dashboard integration
- **Nextcloud Integration Enhancements** — DONE (2026-03-01)
- WAF-safe SSL routing via mitmproxy_inspector
- Scheduled backups with cron (hourly/daily/weekly)
- SMTP email integration (Gmail, mailserver, Mailcow)
- CalDAV/CardDAV/WebDAV connection info display
- 3 new RPCD methods: get_connections, setup_mail, setup_backup_cron
### Just Completed (2026-02-28)
- **Pre-Deploy Lint Script** — DONE (2026-02-28)
@ -1167,9 +1184,9 @@ Implementing 3 evolutions inspired by SysWarden patterns:
**Backlog / Deferred:**
- ~~Tor Shield / opkg bug~~ — FIXED (2026-02-28) - dnsmasq bypass for excluded domains
- Nextcloud self-hosted cloud storage (v2)
- ~~Nextcloud self-hosted cloud storage (v2)~~ — ENHANCED (2026-03-01) - WAF-safe SSL, scheduled backups, email, connections
- SSMTP / mail host / MX record management (v2)
- Reverse MWAN WireGuard peers (v2)
- ~~Reverse MWAN WireGuard peers (v2)~~ — Phase 1 DONE (2026-03-01) - CLI + library + RPCD; Phase 2 (LuCI) pending
---

View File

@ -486,7 +486,21 @@
"Bash(SSH_AUTH_SOCK=/run/user/1000/keyring/ssh ssh:*)",
"Bash(unset:*)",
"Bash(SSH_ASKPASS=\"\" DISPLAY=\"\" SSH_AUTH_SOCK=/run/user/1000/keyring/ssh ssh:*)",
"Bash(./secubox-tools/pre-deploy-lint.sh:*)"
"Bash(./secubox-tools/pre-deploy-lint.sh:*)",
"Bash(VBoxManage modifyvm:*)",
"Bash(# The MAC is 08:00:27:00:93:A6 - let''s search for it in ARP # First get fresh ARP entries by pinging broadcast ping -b -c 2 192.168.255.255 || true sleep 1 # Check ARP for the VM''s MAC arp -a)",
"Bash(ip neigh:*)",
"Bash(__NEW_LINE_6703df146bf1d278__ echo \"\")",
"Bash(# Check latest OpenWrt version from firmware selector API curl -s \"\"https://firmware-selector.openwrt.org/api/v1/overview\"\")",
"Bash(# Try with wget instead wget -qO- \"\"https://downloads.openwrt.org/releases/\"\")",
"Bash(# Wait for boot and get IP sleep 30 # Take a screenshot to see boot progress VBoxManage controlvm ''C3Box-SecuBox'' screenshotpng /tmp/vbox-boot.png # Check ARP for VM''s MAC echo \"\"=== Checking network ===\"\" arp -a)",
"Bash(# Wait more for boot to complete sleep 30 # Take another screenshot VBoxManage controlvm ''C3Box-SecuBox'' screenshotpng /tmp/vbox-boot2.png # Try to find VM on network ip neigh flush all || true ping -b -c 2 192.168.255.255 || true sleep 2 arp -a)",
"Bash(# Wait for full boot after GRUB sleep 45 # Take screenshot VBoxManage controlvm ''C3Box-SecuBox'' screenshotpng /tmp/vbox-booted.png # Check network again ip neigh flush all || true ping -b -c 1 192.168.255.255 || true sleep 2 # Look for VM echo \"\"=== ARP table ===\"\" arp -a)",
"WebFetch(domain:alerte.gk2.secubox.in)",
"WebFetch(domain:matrix.gk2.secubox.in)",
"Bash(# Stop the failed VM VBoxManage controlvm ''C3Box-SecuBox'' poweroff || true # Check the c3box-vm-builder for the proper build method grep -A20 \"\"build_firmware\"\" /home/reepost/CyberMindStudio/secubox-openwrt/secubox-tools/c3box-vm-builder.sh)",
"Bash(sudo umount:*)",
"Bash(__NEW_LINE_c35a46b8074eb5e8__ sudo losetup -d \"$LOOP_DEV\")"
]
}
}

View File

@ -0,0 +1,35 @@
# SecuBox WireGuard Uplink Configuration
# Reverse MWAN failover via mesh peers
# Global settings
config settings 'main'
option enabled '1'
option auto_failover '1'
option failover_threshold '3'
option ping_interval '10'
option ping_targets '8.8.8.8 1.1.1.1'
option max_uplinks '5'
option prefer_local '1'
# Local provider settings (when offering uplink to others)
config provider 'local'
option offering '0'
option public_key ''
option private_key ''
option endpoint ''
option listen_port '51821'
option bandwidth '100'
option latency '10'
# Example uplink configuration (commented out)
# config uplink 'wgup0'
# option interface 'wgup0'
# option peer_pubkey 'PEER_PUBLIC_KEY_HERE'
# option endpoint '192.168.1.1:51821'
# option local_pubkey 'LOCAL_PUBLIC_KEY_HERE'
# option enabled '1'
# option priority '10'
# option weight '1'
# option created '1709312345'
# option last_seen '1709312345'
# option status 'active'

View File

@ -0,0 +1,533 @@
#!/bin/sh
# SecuBox WireGuard Uplink Library
# Provides gossip-based peer discovery for reverse MWAN failover
# Part of the Reverse MWAN WireGuard v2 feature
# Runtime state file
UPLINK_STATE_FILE="/var/run/wireguard-uplinks.json"
UPLINK_OFFERS_FILE="/var/run/wireguard-uplink-offers.json"
P2P_SOCKET="/var/run/secubox-p2p.sock"
# UCI config section
UPLINK_UCI="wireguard_uplink"
# ============================================================================
# Gossip Protocol Integration
# ============================================================================
# Check if secubox-p2p is available
p2p_available() {
[ -S "$P2P_SOCKET" ] && command -v p2pctl >/dev/null 2>&1
}
# Get this node's identity for gossip
get_local_node_id() {
if p2p_available; then
p2pctl status 2>/dev/null | jsonfilter -e '@.node_id' 2>/dev/null
else
# Fallback to MAC-based ID
cat /sys/class/net/eth0/address 2>/dev/null | tr -d ':'
fi
}
# Advertise this node as an uplink provider via gossip
# Usage: advertise_uplink_offer <bandwidth_mbps> [latency_ms]
advertise_uplink_offer() {
local bandwidth="${1:-100}"
local latency="${2:-10}"
local node_id
local wg_pubkey
local wg_endpoint
local wg_port
node_id="$(get_local_node_id)"
[ -z "$node_id" ] && { echo "Error: Cannot determine node ID" >&2; return 1; }
# Get WireGuard public key and endpoint
wg_pubkey="$(uci -q get wireguard_uplink.local.public_key)"
if [ -z "$wg_pubkey" ]; then
# Generate keypair if not exists
local privkey
privkey="$(wg genkey)"
wg_pubkey="$(echo "$privkey" | wg pubkey)"
uci set wireguard_uplink.local=provider
uci set wireguard_uplink.local.private_key="$privkey"
uci set wireguard_uplink.local.public_key="$wg_pubkey"
uci commit wireguard_uplink
fi
# Determine endpoint (external IP or mesh address)
wg_endpoint="$(uci -q get wireguard_uplink.local.endpoint)"
if [ -z "$wg_endpoint" ]; then
# Try to detect external IP
wg_endpoint="$(wget -qO- http://ifconfig.me 2>/dev/null || ip route get 1.1.1.1 2>/dev/null | grep -oP 'src \K[^ ]+')"
fi
wg_port="$(uci -q get wireguard_uplink.local.listen_port)"
[ -z "$wg_port" ] && wg_port="51821"
# Create offer payload
local offer_json
offer_json=$(cat <<EOF
{
"type": "uplink_offer",
"node_id": "$node_id",
"wg_pubkey": "$wg_pubkey",
"wg_endpoint": "${wg_endpoint}:${wg_port}",
"bandwidth_mbps": $bandwidth,
"latency_ms": $latency,
"timestamp": $(date +%s),
"active": true
}
EOF
)
# Publish via gossip
if p2p_available; then
echo "$offer_json" | p2pctl gossip publish "uplink_offer" 2>/dev/null
local rc=$?
if [ $rc -eq 0 ]; then
# Store local state
echo "$offer_json" > "$UPLINK_OFFERS_FILE"
uci set wireguard_uplink.local.offering='1'
uci set wireguard_uplink.local.bandwidth="$bandwidth"
uci set wireguard_uplink.local.latency="$latency"
uci commit wireguard_uplink
echo "Uplink offer published successfully"
return 0
else
echo "Error: Failed to publish gossip message" >&2
return 1
fi
else
# P2P not available - store locally only
echo "$offer_json" > "$UPLINK_OFFERS_FILE"
uci set wireguard_uplink.local.offering='1'
uci commit wireguard_uplink
echo "Warning: P2P not available, offer stored locally only" >&2
return 0
fi
}
# Withdraw uplink offer
withdraw_uplink_offer() {
local node_id
node_id="$(get_local_node_id)"
# Create withdrawal message
local withdraw_json
withdraw_json=$(cat <<EOF
{
"type": "uplink_withdraw",
"node_id": "$node_id",
"timestamp": $(date +%s)
}
EOF
)
# Publish withdrawal via gossip
if p2p_available; then
echo "$withdraw_json" | p2pctl gossip publish "uplink_withdraw" 2>/dev/null
fi
# Update local state
rm -f "$UPLINK_OFFERS_FILE"
uci set wireguard_uplink.local.offering='0'
uci commit wireguard_uplink
echo "Uplink offer withdrawn"
}
# Get available uplink offers from mesh peers
# Returns JSON array of uplink offers
get_peer_uplink_offers() {
local offers="[]"
if p2p_available; then
# Query gossip for uplink_offer messages
offers=$(p2pctl gossip query "uplink_offer" 2>/dev/null)
[ -z "$offers" ] && offers="[]"
fi
# Merge with any cached offers
if [ -f "$UPLINK_STATE_FILE" ]; then
local cached
cached=$(cat "$UPLINK_STATE_FILE" 2>/dev/null)
if [ -n "$cached" ] && [ "$cached" != "[]" ]; then
# Merge and deduplicate by node_id
# For simplicity, prefer fresh gossip data
:
fi
fi
echo "$offers"
}
# Get specific peer's uplink offer
# Usage: get_peer_uplink_offer <node_id>
get_peer_uplink_offer() {
local target_node="$1"
[ -z "$target_node" ] && return 1
local offers
offers=$(get_peer_uplink_offers)
# Filter by node_id
echo "$offers" | jsonfilter -e "@[*]" 2>/dev/null | while read -r offer; do
local node_id
node_id=$(echo "$offer" | jsonfilter -e '@.node_id' 2>/dev/null)
if [ "$node_id" = "$target_node" ]; then
echo "$offer"
return 0
fi
done
}
# ============================================================================
# WireGuard Interface Management
# ============================================================================
# Find next available WireGuard interface name
get_next_wg_interface() {
local prefix="${1:-wgup}"
local i=0
while [ $i -lt 100 ]; do
local ifname="${prefix}${i}"
if ! ip link show "$ifname" >/dev/null 2>&1; then
echo "$ifname"
return 0
fi
i=$((i + 1))
done
return 1
}
# Create WireGuard interface for uplink peer
# Usage: create_uplink_interface <peer_pubkey> <endpoint> <allowed_ips>
create_uplink_interface() {
local peer_pubkey="$1"
local endpoint="$2"
local allowed_ips="${3:-0.0.0.0/0}"
[ -z "$peer_pubkey" ] && { echo "Error: peer public key required" >&2; return 1; }
[ -z "$endpoint" ] && { echo "Error: endpoint required" >&2; return 1; }
local ifname
ifname=$(get_next_wg_interface)
[ -z "$ifname" ] && { echo "Error: no available interface name" >&2; return 1; }
# Generate local keypair for this interface
local privkey pubkey
privkey=$(wg genkey)
pubkey=$(echo "$privkey" | wg pubkey)
# Allocate IP from uplink range (172.31.x.x/16)
local local_ip
local_ip=$(allocate_uplink_ip "$ifname")
# Create interface via UCI
uci set network."$ifname"=interface
uci set network."$ifname".proto='wireguard'
uci set network."$ifname".private_key="$privkey"
uci set network."$ifname".addresses="$local_ip"
uci set network."$ifname".mtu='1420'
# Add peer
local peer_section="${ifname}_peer"
uci set network."$peer_section"=wireguard_"$ifname"
uci set network."$peer_section".public_key="$peer_pubkey"
uci set network."$peer_section".endpoint_host="$(echo "$endpoint" | cut -d: -f1)"
uci set network."$peer_section".endpoint_port="$(echo "$endpoint" | cut -d: -f2)"
uci set network."$peer_section".allowed_ips="$allowed_ips"
uci set network."$peer_section".persistent_keepalive='25'
uci set network."$peer_section".route_allowed_ips='0' # We manage routing via mwan3
uci commit network
# Store metadata
uci set wireguard_uplink."$ifname"=uplink
uci set wireguard_uplink."$ifname".interface="$ifname"
uci set wireguard_uplink."$ifname".peer_pubkey="$peer_pubkey"
uci set wireguard_uplink."$ifname".endpoint="$endpoint"
uci set wireguard_uplink."$ifname".local_pubkey="$pubkey"
uci set wireguard_uplink."$ifname".created="$(date +%s)"
uci set wireguard_uplink."$ifname".enabled='1'
uci commit wireguard_uplink
echo "$ifname"
}
# Remove uplink interface
remove_uplink_interface() {
local ifname="$1"
[ -z "$ifname" ] && return 1
# Bring down interface
ip link set "$ifname" down 2>/dev/null
ip link delete "$ifname" 2>/dev/null
# Remove UCI config
uci delete network."$ifname" 2>/dev/null
uci delete network."${ifname}_peer" 2>/dev/null
uci commit network
uci delete wireguard_uplink."$ifname" 2>/dev/null
uci commit wireguard_uplink
# Release IP
release_uplink_ip "$ifname"
}
# Allocate IP from uplink pool (172.31.x.x/16)
allocate_uplink_ip() {
local ifname="$1"
local pool_file="/var/run/wireguard-uplink-pool.json"
# Simple sequential allocation
local next_octet=1
if [ -f "$pool_file" ]; then
next_octet=$(jsonfilter -i "$pool_file" -e '@.next_octet' 2>/dev/null || echo 1)
fi
local ip="172.31.0.${next_octet}/24"
# Update pool
next_octet=$((next_octet + 1))
[ $next_octet -gt 254 ] && next_octet=1
cat > "$pool_file" <<EOF
{
"next_octet": $next_octet,
"allocations": {
"$ifname": "$ip"
}
}
EOF
echo "$ip"
}
# Release allocated IP
release_uplink_ip() {
local ifname="$1"
# For now, we don't reclaim IPs (simple implementation)
:
}
# ============================================================================
# mwan3 Integration
# ============================================================================
# Add uplink interface to mwan3 failover
# Usage: add_to_mwan3 <interface> [metric] [weight]
add_to_mwan3() {
local ifname="$1"
local metric="${2:-100}"
local weight="${3:-1}"
[ -z "$ifname" ] && return 1
# Check if mwan3 is available
if ! uci -q get mwan3 >/dev/null 2>&1; then
echo "Warning: mwan3 not configured" >&2
return 0
fi
# Add interface to mwan3
uci set mwan3."$ifname"=interface
uci set mwan3."$ifname".enabled='1'
uci set mwan3."$ifname".family='ipv4'
uci set mwan3."$ifname".track_ip='8.8.8.8'
uci add_list mwan3."$ifname".track_ip='1.1.1.1'
uci set mwan3."$ifname".track_method='ping'
uci set mwan3."$ifname".reliability='1'
uci set mwan3."$ifname".count='1'
uci set mwan3."$ifname".timeout='2'
uci set mwan3."$ifname".interval='5'
uci set mwan3."$ifname".down='3'
uci set mwan3."$ifname".up='3'
# Add member for failover policy
local member="${ifname}_m1_w${weight}"
uci set mwan3."$member"=member
uci set mwan3."$member".interface="$ifname"
uci set mwan3."$member".metric="$metric"
uci set mwan3."$member".weight="$weight"
# Add to failover policy (create if not exists)
if ! uci -q get mwan3.uplink_failover >/dev/null 2>&1; then
uci set mwan3.uplink_failover=policy
uci set mwan3.uplink_failover.last_resort='default'
fi
uci add_list mwan3.uplink_failover.use_member="$member"
uci commit mwan3
# Reload mwan3
/etc/init.d/mwan3 reload 2>/dev/null
}
# Remove interface from mwan3
remove_from_mwan3() {
local ifname="$1"
[ -z "$ifname" ] && return 1
# Remove interface
uci delete mwan3."$ifname" 2>/dev/null
# Remove associated members
local members
members=$(uci show mwan3 2>/dev/null | grep "interface='$ifname'" | cut -d. -f2 | cut -d= -f1)
for member in $members; do
# Remove from policies
uci show mwan3 2>/dev/null | grep "use_member.*$member" | while read -r line; do
local policy
policy=$(echo "$line" | cut -d. -f2)
uci del_list mwan3."$policy".use_member="$member" 2>/dev/null
done
uci delete mwan3."$member" 2>/dev/null
done
uci commit mwan3
/etc/init.d/mwan3 reload 2>/dev/null
}
# Get mwan3 status for uplink interfaces
get_mwan3_status() {
if command -v mwan3 >/dev/null 2>&1; then
mwan3 interfaces 2>/dev/null
else
echo "mwan3 not available"
fi
}
# ============================================================================
# Connectivity Testing
# ============================================================================
# Test connectivity through an uplink interface
# Usage: test_uplink_connectivity <interface> [target]
test_uplink_connectivity() {
local ifname="$1"
local target="${2:-8.8.8.8}"
[ -z "$ifname" ] && return 1
# Check interface exists and is up
if ! ip link show "$ifname" 2>/dev/null | grep -q "UP"; then
echo "Interface $ifname is down"
return 1
fi
# Get interface IP
local src_ip
src_ip=$(ip -4 addr show "$ifname" 2>/dev/null | grep -oP 'inet \K[^/]+')
[ -z "$src_ip" ] && { echo "No IP on $ifname"; return 1; }
# Ping test
if ping -c 3 -W 2 -I "$ifname" "$target" >/dev/null 2>&1; then
echo "OK"
return 0
else
echo "FAIL"
return 1
fi
}
# Measure latency through uplink
measure_uplink_latency() {
local ifname="$1"
local target="${2:-8.8.8.8}"
[ -z "$ifname" ] && return 1
local result
result=$(ping -c 5 -W 2 -I "$ifname" "$target" 2>/dev/null | tail -1)
if echo "$result" | grep -q "avg"; then
# Extract average latency
echo "$result" | sed -E 's/.*= [0-9.]+\/([0-9.]+)\/.*/\1/'
else
echo "-1"
fi
}
# ============================================================================
# State Management
# ============================================================================
# Refresh uplink state from gossip
refresh_uplink_state() {
local offers
offers=$(get_peer_uplink_offers)
# Filter out expired offers (older than 5 minutes)
local now
now=$(date +%s)
local cutoff=$((now - 300))
local filtered="[]"
# Process each offer
echo "$offers" | jsonfilter -e '@[*]' 2>/dev/null | while read -r offer; do
local ts
ts=$(echo "$offer" | jsonfilter -e '@.timestamp' 2>/dev/null)
if [ -n "$ts" ] && [ "$ts" -gt "$cutoff" ]; then
# Valid offer - would add to filtered array
:
fi
done
# Update state file
echo "$offers" > "$UPLINK_STATE_FILE"
}
# Get list of active uplink interfaces
get_active_uplinks() {
uci show wireguard_uplink 2>/dev/null | grep "=uplink" | cut -d. -f2 | cut -d= -f1
}
# Get uplink statistics
get_uplink_stats() {
local stats='{"uplinks":[],"total":0,"active":0,"offering":false}'
local total=0
local active=0
for ifname in $(get_active_uplinks); do
local enabled
enabled=$(uci -q get wireguard_uplink."$ifname".enabled)
total=$((total + 1))
[ "$enabled" = "1" ] && active=$((active + 1))
done
local offering
offering=$(uci -q get wireguard_uplink.local.offering)
[ "$offering" = "1" ] && offering="true" || offering="false"
cat <<EOF
{
"total": $total,
"active": $active,
"offering": $offering,
"peer_offers": $(get_peer_uplink_offers)
}
EOF
}
# Initialize uplink subsystem
init_uplink() {
# Ensure state directory exists
mkdir -p /var/run
# Initialize state files if missing
[ ! -f "$UPLINK_STATE_FILE" ] && echo '[]' > "$UPLINK_STATE_FILE"
# Ensure UCI section exists
if ! uci -q get wireguard_uplink.local >/dev/null 2>&1; then
uci set wireguard_uplink.local=provider
uci set wireguard_uplink.local.offering='0'
uci commit wireguard_uplink
fi
}
# Initialize on source
init_uplink

View File

@ -998,6 +998,372 @@ get_peer_descriptions() {
json_dump
}
# ============================================================================
# Uplink Management (Reverse MWAN WireGuard)
# ============================================================================
# Source uplink library if available
UPLINK_LIB="/usr/lib/wireguard-dashboard/uplink.sh"
# Get uplink status
get_uplink_status() {
json_init
if [ -f "$UPLINK_LIB" ]; then
. "$UPLINK_LIB"
local enabled=$(uci -q get wireguard_uplink.main.enabled)
local offering=$(uci -q get wireguard_uplink.local.offering)
local auto_failover=$(uci -q get wireguard_uplink.main.auto_failover)
json_add_boolean "available" 1
json_add_boolean "enabled" "${enabled:-0}"
json_add_boolean "offering" "${offering:-0}"
json_add_boolean "auto_failover" "${auto_failover:-0}"
# Count active uplinks
local uplink_count=0
local active_count=0
for ifname in $(get_active_uplinks 2>/dev/null); do
uplink_count=$((uplink_count + 1))
local if_enabled=$(uci -q get wireguard_uplink.$ifname.enabled)
[ "$if_enabled" = "1" ] && active_count=$((active_count + 1))
done
json_add_int "uplink_count" "$uplink_count"
json_add_int "active_count" "$active_count"
# Get peer offers from mesh
json_add_array "peer_offers"
local offers=$(get_peer_uplink_offers 2>/dev/null)
echo "$offers" | jsonfilter -e '@[*]' 2>/dev/null | while read -r offer; do
json_add_object
local node_id=$(echo "$offer" | jsonfilter -e '@.node_id' 2>/dev/null)
local bandwidth=$(echo "$offer" | jsonfilter -e '@.bandwidth_mbps' 2>/dev/null)
local latency=$(echo "$offer" | jsonfilter -e '@.latency_ms' 2>/dev/null)
local endpoint=$(echo "$offer" | jsonfilter -e '@.wg_endpoint' 2>/dev/null)
json_add_string "node_id" "$node_id"
json_add_int "bandwidth_mbps" "${bandwidth:-0}"
json_add_int "latency_ms" "${latency:-0}"
json_add_string "endpoint" "$endpoint"
json_close_object
done
json_close_array
# mwan3 status
if command -v mwan3 >/dev/null 2>&1; then
json_add_boolean "mwan3_available" 1
local mwan3_status=$(mwan3 status 2>/dev/null | head -1)
json_add_string "mwan3_status" "$mwan3_status"
else
json_add_boolean "mwan3_available" 0
fi
else
json_add_boolean "available" 0
json_add_string "error" "Uplink library not installed"
fi
json_dump
}
# List configured uplinks
get_uplinks() {
json_init
json_add_array "uplinks"
if [ -f "$UPLINK_LIB" ]; then
. "$UPLINK_LIB"
for ifname in $(get_active_uplinks 2>/dev/null); do
json_add_object
json_add_string "interface" "$ifname"
json_add_string "peer_pubkey" "$(uci -q get wireguard_uplink.$ifname.peer_pubkey)"
json_add_string "endpoint" "$(uci -q get wireguard_uplink.$ifname.endpoint)"
json_add_boolean "enabled" "$(uci -q get wireguard_uplink.$ifname.enabled)"
json_add_int "priority" "$(uci -q get wireguard_uplink.$ifname.priority || echo 10)"
json_add_int "weight" "$(uci -q get wireguard_uplink.$ifname.weight || echo 1)"
json_add_string "created" "$(uci -q get wireguard_uplink.$ifname.created)"
# Check interface state
local state="down"
if ip link show "$ifname" 2>/dev/null | grep -q "UP"; then
state="up"
fi
json_add_string "state" "$state"
# Test connectivity
local connectivity="unknown"
if [ "$state" = "up" ]; then
if test_uplink_connectivity "$ifname" >/dev/null 2>&1; then
connectivity="ok"
else
connectivity="failed"
fi
fi
json_add_string "connectivity" "$connectivity"
json_close_object
done
fi
json_close_array
json_dump
}
# Add a new uplink from mesh peer
add_uplink() {
read input
json_load "$input"
json_get_var node_id node_id
json_get_var peer_pubkey peer_pubkey
json_get_var endpoint endpoint
json_get_var priority priority
json_get_var weight weight
json_init
if [ ! -f "$UPLINK_LIB" ]; then
json_add_boolean "success" 0
json_add_string "error" "Uplink library not installed"
json_dump
return
fi
. "$UPLINK_LIB"
if [ -z "$peer_pubkey" ] || [ -z "$endpoint" ]; then
json_add_boolean "success" 0
json_add_string "error" "Missing required fields: peer_pubkey and endpoint"
json_dump
return
fi
# Create the uplink interface
local ifname
ifname=$(create_uplink_interface "$peer_pubkey" "$endpoint")
if [ -z "$ifname" ]; then
json_add_boolean "success" 0
json_add_string "error" "Failed to create uplink interface"
json_dump
return
fi
# Set priority/weight if provided
[ -n "$priority" ] && uci set wireguard_uplink.$ifname.priority="$priority"
[ -n "$weight" ] && uci set wireguard_uplink.$ifname.weight="$weight"
[ -n "$node_id" ] && uci set wireguard_uplink.$ifname.node_id="$node_id"
uci commit wireguard_uplink
# Add to mwan3
add_to_mwan3 "$ifname" "${priority:-100}" "${weight:-1}"
# Bring up interface
ifup "$ifname" 2>/dev/null
json_add_boolean "success" 1
json_add_string "interface" "$ifname"
json_add_string "message" "Uplink added successfully"
json_dump
}
# Remove an uplink
remove_uplink() {
read input
json_load "$input"
json_get_var ifname interface
json_init
if [ ! -f "$UPLINK_LIB" ]; then
json_add_boolean "success" 0
json_add_string "error" "Uplink library not installed"
json_dump
return
fi
. "$UPLINK_LIB"
if [ -z "$ifname" ]; then
json_add_boolean "success" 0
json_add_string "error" "Missing required field: interface"
json_dump
return
fi
# Remove from mwan3 first
remove_from_mwan3 "$ifname"
# Remove interface
remove_uplink_interface "$ifname"
json_add_boolean "success" 1
json_add_string "message" "Uplink removed successfully"
json_dump
}
# Test uplink connectivity
test_uplink() {
read input
json_load "$input"
json_get_var ifname interface
json_get_var target target
json_init
if [ ! -f "$UPLINK_LIB" ]; then
json_add_boolean "success" 0
json_add_string "error" "Uplink library not installed"
json_dump
return
fi
. "$UPLINK_LIB"
if [ -z "$ifname" ]; then
json_add_boolean "success" 0
json_add_string "error" "Missing required field: interface"
json_dump
return
fi
local result
result=$(test_uplink_connectivity "$ifname" "${target:-8.8.8.8}")
local rc=$?
if [ $rc -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "result" "OK"
# Measure latency
local latency
latency=$(measure_uplink_latency "$ifname" "${target:-8.8.8.8}")
json_add_string "latency_ms" "$latency"
else
json_add_boolean "success" 0
json_add_string "result" "FAILED"
json_add_string "error" "$result"
fi
json_add_string "interface" "$ifname"
json_dump
}
# Offer this node as uplink provider
offer_uplink() {
read input
json_load "$input"
json_get_var bandwidth bandwidth
json_get_var latency latency
json_init
if [ ! -f "$UPLINK_LIB" ]; then
json_add_boolean "success" 0
json_add_string "error" "Uplink library not installed"
json_dump
return
fi
. "$UPLINK_LIB"
advertise_uplink_offer "${bandwidth:-100}" "${latency:-10}"
local rc=$?
if [ $rc -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "Now offering uplink to mesh peers"
else
json_add_boolean "success" 0
json_add_string "error" "Failed to advertise uplink offer"
fi
json_dump
}
# Withdraw uplink offer
withdraw_uplink() {
json_init
if [ ! -f "$UPLINK_LIB" ]; then
json_add_boolean "success" 0
json_add_string "error" "Uplink library not installed"
json_dump
return
fi
. "$UPLINK_LIB"
withdraw_uplink_offer
local rc=$?
if [ $rc -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "Uplink offer withdrawn"
else
json_add_boolean "success" 0
json_add_string "error" "Failed to withdraw offer"
fi
json_dump
}
# Set uplink priority
set_uplink_priority() {
read input
json_load "$input"
json_get_var ifname interface
json_get_var priority priority
json_get_var weight weight
json_init
if [ -z "$ifname" ]; then
json_add_boolean "success" 0
json_add_string "error" "Missing required field: interface"
json_dump
return
fi
# Check uplink exists
if ! uci -q get wireguard_uplink.$ifname >/dev/null 2>&1; then
json_add_boolean "success" 0
json_add_string "error" "Uplink not found: $ifname"
json_dump
return
fi
[ -n "$priority" ] && uci set wireguard_uplink.$ifname.priority="$priority"
[ -n "$weight" ] && uci set wireguard_uplink.$ifname.weight="$weight"
uci commit wireguard_uplink
# Update mwan3 if available
if [ -f "$UPLINK_LIB" ]; then
. "$UPLINK_LIB"
remove_from_mwan3 "$ifname"
add_to_mwan3 "$ifname" "${priority:-100}" "${weight:-1}"
fi
json_add_boolean "success" 1
json_add_string "message" "Priority updated"
json_dump
}
# Enable/disable uplink failover
set_uplink_failover() {
read input
json_load "$input"
json_get_var enabled enabled
json_init
uci set wireguard_uplink.main.auto_failover="${enabled:-0}"
uci commit wireguard_uplink
json_add_boolean "success" 1
json_add_string "message" "Failover $([ "$enabled" = "1" ] && echo "enabled" || echo "disabled")"
json_dump
}
# Get current bandwidth rates (requires previous call to calculate delta)
get_bandwidth_rates() {
json_init
@ -1168,7 +1534,7 @@ delete_endpoint() {
# Main dispatcher
case "$1" in
list)
echo '{"status":{},"interfaces":{},"peers":{},"traffic":{},"config":{},"generate_keys":{},"create_interface":{"name":"str","private_key":"str","listen_port":"str","addresses":"str","mtu":"str"},"add_peer":{"interface":"str","name":"str","allowed_ips":"str","public_key":"str","preshared_key":"str","endpoint":"str","persistent_keepalive":"str","private_key":"str"},"remove_peer":{"interface":"str","public_key":"str"},"generate_config":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"generate_qr":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"bandwidth_history":{},"endpoint_info":{"endpoint":"str"},"ping_peer":{"ip":"str"},"interface_control":{"interface":"str","action":"str"},"peer_descriptions":{},"bandwidth_rates":{},"get_endpoints":{},"set_endpoint":{"id":"str","name":"str","address":"str"},"set_default_endpoint":{"id":"str"},"delete_endpoint":{"id":"str"}}'
echo '{"status":{},"interfaces":{},"peers":{},"traffic":{},"config":{},"generate_keys":{},"create_interface":{"name":"str","private_key":"str","listen_port":"str","addresses":"str","mtu":"str"},"add_peer":{"interface":"str","name":"str","allowed_ips":"str","public_key":"str","preshared_key":"str","endpoint":"str","persistent_keepalive":"str","private_key":"str"},"remove_peer":{"interface":"str","public_key":"str"},"generate_config":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"generate_qr":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"bandwidth_history":{},"endpoint_info":{"endpoint":"str"},"ping_peer":{"ip":"str"},"interface_control":{"interface":"str","action":"str"},"peer_descriptions":{},"bandwidth_rates":{},"get_endpoints":{},"set_endpoint":{"id":"str","name":"str","address":"str"},"set_default_endpoint":{"id":"str"},"delete_endpoint":{"id":"str"},"uplink_status":{},"uplinks":{},"add_uplink":{"node_id":"str","peer_pubkey":"str","endpoint":"str","priority":"int","weight":"int"},"remove_uplink":{"interface":"str"},"test_uplink":{"interface":"str","target":"str"},"offer_uplink":{"bandwidth":"int","latency":"int"},"withdraw_uplink":{},"set_uplink_priority":{"interface":"str","priority":"int","weight":"int"},"set_uplink_failover":{"enabled":"bool"}}'
;;
call)
case "$2" in
@ -1235,6 +1601,33 @@ case "$1" in
delete_endpoint)
delete_endpoint
;;
uplink_status)
get_uplink_status
;;
uplinks)
get_uplinks
;;
add_uplink)
add_uplink
;;
remove_uplink)
remove_uplink
;;
test_uplink)
test_uplink
;;
offer_uplink)
offer_uplink
;;
withdraw_uplink)
withdraw_uplink
;;
set_uplink_priority)
set_uplink_priority
;;
set_uplink_failover)
set_uplink_failover
;;
*)
echo '{"error": "Unknown method"}'
;;

View File

@ -0,0 +1,641 @@
#!/bin/sh
# SecuBox WireGuard Control CLI
# Manages WireGuard interfaces and uplink failover
# Copyright (C) 2026 CyberMind.fr
. /lib/functions.sh
UPLINK_LIB="/usr/lib/wireguard-dashboard/uplink.sh"
UPLINK_CONFIG="wireguard_uplink"
RUNTIME_STATE="/var/run/wireguard-uplinks.json"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# Load uplink library if available
load_uplink_lib() {
if [ -f "$UPLINK_LIB" ]; then
. "$UPLINK_LIB"
return 0
fi
return 1
}
# UCI helpers
uci_get() { uci -q get ${UPLINK_CONFIG}.$1; }
uci_set() { uci set ${UPLINK_CONFIG}.$1="$2" && uci commit ${UPLINK_CONFIG}; }
usage() {
cat <<'EOF'
SecuBox WireGuard Control CLI
Usage: wgctl <command> [options]
Interface Commands:
status Show all WireGuard interfaces
show <iface> Show details for interface
up <iface> Bring interface up
down <iface> Bring interface down
Uplink Commands (Multi-WAN Failover):
uplink list List available uplink peers from mesh
uplink add <peer> Add peer as backup uplink
uplink remove <peer> Remove peer uplink
uplink status Show uplink failover status
uplink test <peer> Test connectivity through peer
uplink failover <enable|disable>
Enable/disable automatic failover
uplink priority <peer> <weight>
Set peer priority (lower = less preferred)
uplink offer Advertise this node as uplink provider
uplink withdraw Stop advertising as uplink provider
Examples:
wgctl status
wgctl uplink list
wgctl uplink add nodeA
wgctl uplink failover enable
Configuration:
/etc/config/wireguard_uplink
EOF
}
# ============================================================
# Interface Commands
# ============================================================
cmd_status() {
if ! command -v wg >/dev/null 2>&1; then
log_error "wireguard-tools not installed"
exit 1
fi
local interfaces=$(wg show interfaces 2>/dev/null)
if [ -z "$interfaces" ]; then
echo "No WireGuard interfaces configured"
return 0
fi
echo "WireGuard Interfaces"
echo "===================="
echo ""
for iface in $interfaces; do
local pubkey=$(wg show "$iface" public-key 2>/dev/null)
local port=$(wg show "$iface" listen-port 2>/dev/null)
local peers=$(wg show "$iface" peers 2>/dev/null | wc -l)
local state="down"
ip link show "$iface" 2>/dev/null | grep -q "UP" && state="up"
local ip=$(ip -4 addr show "$iface" 2>/dev/null | grep -oE 'inet [0-9.]+' | cut -d' ' -f2)
printf "${BLUE}%s${NC}\n" "$iface"
printf " State: %s\n" "$([ "$state" = "up" ] && echo -e "${GREEN}UP${NC}" || echo -e "${RED}DOWN${NC}")"
printf " Address: %s\n" "${ip:-N/A}"
printf " Listen Port: %s\n" "${port:-N/A}"
printf " Public Key: %s\n" "${pubkey:0:20}..."
printf " Peers: %d\n" "$peers"
# Check if this is an uplink interface
local is_uplink=$(uci -q get network.${iface}.secubox_uplink)
if [ "$is_uplink" = "1" ]; then
local peer_id=$(uci -q get network.${iface}.secubox_peer_id)
printf " ${YELLOW}Uplink:${NC} %s\n" "${peer_id:-yes}"
fi
echo ""
done
# Show uplink summary if enabled
local uplink_enabled=$(uci_get uplink.enabled)
if [ "$uplink_enabled" = "1" ]; then
echo "Uplink Failover: ${GREEN}ENABLED${NC}"
local active_uplinks=$(cat "$RUNTIME_STATE" 2>/dev/null | jsonfilter -e '@.active_uplinks' 2>/dev/null || echo "0")
echo "Active Uplinks: $active_uplinks"
fi
}
cmd_show() {
local iface="$1"
if [ -z "$iface" ]; then
log_error "Usage: wgctl show <interface>"
exit 1
fi
if ! wg show "$iface" >/dev/null 2>&1; then
log_error "Interface $iface not found"
exit 1
fi
wg show "$iface"
}
cmd_up() {
local iface="$1"
if [ -z "$iface" ]; then
log_error "Usage: wgctl up <interface>"
exit 1
fi
log_info "Bringing up $iface..."
ifup "$iface" 2>/dev/null || ip link set "$iface" up
log_info "Interface $iface is up"
}
cmd_down() {
local iface="$1"
if [ -z "$iface" ]; then
log_error "Usage: wgctl down <interface>"
exit 1
fi
log_info "Bringing down $iface..."
ifdown "$iface" 2>/dev/null || ip link set "$iface" down
log_info "Interface $iface is down"
}
# ============================================================
# Uplink Commands
# ============================================================
uplink_list() {
load_uplink_lib || {
log_error "Uplink library not found"
exit 1
}
echo "Available Uplink Peers"
echo "======================"
echo ""
# Get peers from gossip/P2P discovery
local peers_file="/var/run/p2p-uplink-offers.json"
if [ ! -f "$peers_file" ]; then
# Try to fetch from P2P gossip
if command -v p2pctl >/dev/null 2>&1; then
p2pctl gossip get uplink_offers > "$peers_file" 2>/dev/null
fi
fi
if [ -f "$peers_file" ] && [ -s "$peers_file" ]; then
# Parse JSON offers
local count=0
jsonfilter -i "$peers_file" -e '@[*]' 2>/dev/null | while read -r offer; do
local peer_id=$(echo "$offer" | jsonfilter -e '@.peer_id' 2>/dev/null)
local bandwidth=$(echo "$offer" | jsonfilter -e '@.capabilities.bandwidth_mbps' 2>/dev/null)
local latency=$(echo "$offer" | jsonfilter -e '@.capabilities.latency_ms' 2>/dev/null)
local wan_type=$(echo "$offer" | jsonfilter -e '@.capabilities.wan_type' 2>/dev/null)
local available=$(echo "$offer" | jsonfilter -e '@.capabilities.available' 2>/dev/null)
[ -z "$peer_id" ] && continue
count=$((count + 1))
local status="${GREEN}available${NC}"
[ "$available" != "true" ] && status="${RED}unavailable${NC}"
printf "%2d. ${BLUE}%s${NC}\n" "$count" "$peer_id"
printf " Status: %b\n" "$status"
printf " Bandwidth: %s Mbps\n" "${bandwidth:-?}"
printf " Latency: %s ms\n" "${latency:-?}"
printf " WAN Type: %s\n" "${wan_type:-unknown}"
echo ""
done
else
echo "No uplink offers discovered yet."
echo ""
echo "Peers advertise uplink capability via gossip protocol."
echo "Make sure P2P mesh is connected and peers have enabled uplink sharing."
fi
# Also show locally configured uplinks
echo ""
echo "Configured Uplinks"
echo "=================="
local found=0
config_load network
config_foreach _show_uplink_iface interface
[ $found -eq 0 ] && echo "No uplinks configured. Use: wgctl uplink add <peer_id>"
}
_show_uplink_iface() {
local cfg="$1"
local is_uplink
config_get is_uplink "$cfg" secubox_uplink "0"
[ "$is_uplink" != "1" ] && return
local peer_id proto
config_get peer_id "$cfg" secubox_peer_id
config_get proto "$cfg" proto
[ "$proto" != "wireguard" ] && return
found=1
printf " - %s (peer: %s)\n" "$cfg" "${peer_id:-unknown}"
}
uplink_add() {
local peer_id="$1"
if [ -z "$peer_id" ]; then
log_error "Usage: wgctl uplink add <peer_id>"
exit 1
fi
load_uplink_lib || {
log_error "Uplink library not found"
exit 1
}
log_info "Adding uplink peer: $peer_id"
# Get peer info from gossip
local peer_info
peer_info=$(get_peer_uplink_offer "$peer_id")
if [ -z "$peer_info" ]; then
log_error "Peer $peer_id not found in uplink offers"
log_info "Make sure the peer is advertising uplink capability"
exit 1
fi
# Extract endpoint info
local endpoint_host=$(echo "$peer_info" | jsonfilter -e '@.endpoint.host' 2>/dev/null)
local endpoint_port=$(echo "$peer_info" | jsonfilter -e '@.endpoint.port' 2>/dev/null)
local public_key=$(echo "$peer_info" | jsonfilter -e '@.endpoint.public_key' 2>/dev/null)
if [ -z "$public_key" ]; then
log_error "Could not get public key for peer $peer_id"
exit 1
fi
# Create WireGuard interface for uplink
local iface_name="wg_uplink_${peer_id}"
log_info "Creating interface $iface_name..."
# Generate private key if needed
local priv_key=$(wg genkey)
local pub_key=$(echo "$priv_key" | wg pubkey)
# Create network interface
uci set network.${iface_name}=interface
uci set network.${iface_name}.proto='wireguard'
uci set network.${iface_name}.private_key="$priv_key"
uci add_list network.${iface_name}.addresses='10.99.0.2/24'
uci set network.${iface_name}.secubox_uplink='1'
uci set network.${iface_name}.secubox_peer_id="$peer_id"
# Add peer config
local peer_section=$(uci add network wireguard_${iface_name})
uci set network.${peer_section}.public_key="$public_key"
uci set network.${peer_section}.endpoint_host="$endpoint_host"
uci set network.${peer_section}.endpoint_port="${endpoint_port:-51820}"
uci add_list network.${peer_section}.allowed_ips='0.0.0.0/0'
uci add_list network.${peer_section}.allowed_ips='::/0'
uci set network.${peer_section}.persistent_keepalive='25'
uci set network.${peer_section}.route_allowed_ips='0'
uci commit network
# Add to mwan3 if enabled
local auto_failover=$(uci_get uplink.auto_failover)
if [ "$auto_failover" = "1" ]; then
add_uplink_to_mwan3 "$iface_name" "$peer_id"
fi
# Bring up interface
ifup "$iface_name"
log_info "Uplink $peer_id added successfully"
log_info "Your public key (share with peer): $pub_key"
}
uplink_remove() {
local peer_id="$1"
if [ -z "$peer_id" ]; then
log_error "Usage: wgctl uplink remove <peer_id>"
exit 1
fi
local iface_name="wg_uplink_${peer_id}"
log_info "Removing uplink peer: $peer_id"
# Bring down interface
ifdown "$iface_name" 2>/dev/null
# Remove from mwan3
remove_uplink_from_mwan3 "$iface_name"
# Remove network config
uci delete network.${iface_name} 2>/dev/null
# Remove peer configs
local idx=0
while uci -q get network.@wireguard_${iface_name}[$idx] >/dev/null 2>&1; do
uci delete network.@wireguard_${iface_name}[$idx]
done
uci commit network
log_info "Uplink $peer_id removed"
}
uplink_status() {
load_uplink_lib || {
log_error "Uplink library not found"
exit 1
}
echo "Uplink Failover Status"
echo "======================"
echo ""
local enabled=$(uci_get uplink.enabled)
local auto_failover=$(uci_get uplink.auto_failover)
local check_interval=$(uci_get uplink.check_interval)
printf "Enabled: %s\n" "$([ "$enabled" = "1" ] && echo -e "${GREEN}yes${NC}" || echo -e "${RED}no${NC}")"
printf "Auto Failover: %s\n" "$([ "$auto_failover" = "1" ] && echo -e "${GREEN}yes${NC}" || echo -e "${RED}no${NC}")"
printf "Check Interval: %ss\n" "${check_interval:-30}"
echo ""
# Show primary WAN status
echo "Primary WAN:"
local wan_status=$(ping -c 1 -W 2 8.8.8.8 >/dev/null 2>&1 && echo "up" || echo "down")
printf " Status: %s\n" "$([ "$wan_status" = "up" ] && echo -e "${GREEN}UP${NC}" || echo -e "${RED}DOWN${NC}")"
echo ""
# Show uplink interfaces
echo "Uplink Interfaces:"
config_load network
local uplink_count=0
for iface in $(uci show network 2>/dev/null | grep 'secubox_uplink=.1' | cut -d'.' -f2); do
uplink_count=$((uplink_count + 1))
local peer_id=$(uci -q get network.${iface}.secubox_peer_id)
local state="down"
ip link show "$iface" 2>/dev/null | grep -q "UP" && state="up"
# Test connectivity through this uplink
local reachable="no"
if [ "$state" = "up" ]; then
# Ping through specific interface
local gw=$(ip route show dev "$iface" 2>/dev/null | grep default | awk '{print $3}')
if [ -n "$gw" ]; then
ping -c 1 -W 2 -I "$iface" 8.8.8.8 >/dev/null 2>&1 && reachable="yes"
fi
fi
printf " %s (%s):\n" "$iface" "${peer_id:-unknown}"
printf " State: %s\n" "$([ "$state" = "up" ] && echo -e "${GREEN}UP${NC}" || echo -e "${RED}DOWN${NC}")"
printf " Reachable: %s\n" "$([ "$reachable" = "yes" ] && echo -e "${GREEN}yes${NC}" || echo -e "${RED}no${NC}")"
done
[ $uplink_count -eq 0 ] && echo " No uplinks configured"
echo ""
# Show current routing
if command -v mwan3 >/dev/null 2>&1; then
echo "mwan3 Status:"
mwan3 status 2>/dev/null | head -20
fi
}
uplink_test() {
local peer_id="$1"
if [ -z "$peer_id" ]; then
log_error "Usage: wgctl uplink test <peer_id>"
exit 1
fi
local iface_name="wg_uplink_${peer_id}"
log_info "Testing connectivity through $peer_id..."
# Check interface exists
if ! ip link show "$iface_name" >/dev/null 2>&1; then
log_error "Interface $iface_name not found"
exit 1
fi
# Ping through uplink
echo "Pinging 8.8.8.8 through $iface_name..."
if ping -c 3 -W 5 -I "$iface_name" 8.8.8.8; then
echo ""
log_info "Uplink $peer_id is ${GREEN}WORKING${NC}"
else
echo ""
log_error "Uplink $peer_id is ${RED}NOT REACHABLE${NC}"
exit 1
fi
}
uplink_failover() {
local action="$1"
case "$action" in
enable)
log_info "Enabling uplink failover..."
uci_set uplink.enabled '1'
uci_set uplink.auto_failover '1'
# Setup mwan3 for all configured uplinks
setup_mwan3_failover
log_info "Uplink failover enabled"
;;
disable)
log_info "Disabling uplink failover..."
uci_set uplink.enabled '0'
uci_set uplink.auto_failover '0'
# Remove mwan3 config for uplinks
cleanup_mwan3_failover
log_info "Uplink failover disabled"
;;
*)
log_error "Usage: wgctl uplink failover <enable|disable>"
exit 1
;;
esac
}
uplink_priority() {
local peer_id="$1"
local weight="$2"
if [ -z "$peer_id" ] || [ -z "$weight" ]; then
log_error "Usage: wgctl uplink priority <peer_id> <weight>"
log_info "Lower weight = lower priority (backup)"
exit 1
fi
local iface_name="wg_uplink_${peer_id}"
# Update mwan3 member weight
local member="${iface_name}_member"
uci set mwan3.${member}.weight="$weight"
uci commit mwan3
# Reload mwan3
/etc/init.d/mwan3 reload 2>/dev/null
log_info "Priority for $peer_id set to weight $weight"
}
uplink_offer() {
load_uplink_lib || {
log_error "Uplink library not found"
exit 1
}
log_info "Advertising this node as uplink provider..."
advertise_uplink_offer
log_info "Uplink offer advertised via gossip"
}
uplink_withdraw() {
load_uplink_lib || {
log_error "Uplink library not found"
exit 1
}
log_info "Withdrawing uplink offer..."
withdraw_uplink_offer
log_info "Uplink offer withdrawn"
}
# ============================================================
# mwan3 Integration Helpers
# ============================================================
add_uplink_to_mwan3() {
local iface="$1"
local peer_id="$2"
# Check if mwan3 is available
[ ! -f /etc/config/mwan3 ] && return
log_info "Adding $iface to mwan3..."
# Add interface
uci set mwan3.${iface}=interface
uci set mwan3.${iface}.enabled='1'
uci set mwan3.${iface}.family='ipv4'
uci set mwan3.${iface}.reliability='1'
uci set mwan3.${iface}.track_method='ping'
uci add_list mwan3.${iface}.track_ip='8.8.8.8'
# Add member with low weight (backup)
local member="${iface}_member"
uci set mwan3.${member}=member
uci set mwan3.${member}.interface="$iface"
uci set mwan3.${member}.weight='10'
uci set mwan3.${member}.metric='10'
# Add to failover policy
uci add_list mwan3.failover_policy.use_member="$member"
uci commit mwan3
/etc/init.d/mwan3 reload 2>/dev/null
}
remove_uplink_from_mwan3() {
local iface="$1"
[ ! -f /etc/config/mwan3 ] && return
local member="${iface}_member"
uci delete mwan3.${iface} 2>/dev/null
uci delete mwan3.${member} 2>/dev/null
uci del_list mwan3.failover_policy.use_member="$member" 2>/dev/null
uci commit mwan3
/etc/init.d/mwan3 reload 2>/dev/null
}
setup_mwan3_failover() {
[ ! -f /etc/config/mwan3 ] && return
# Ensure failover policy exists
if ! uci -q get mwan3.failover_policy >/dev/null; then
uci set mwan3.failover_policy=policy
uci add_list mwan3.failover_policy.use_member='wan_member'
fi
# Add all uplink interfaces
config_load network
for iface in $(uci show network 2>/dev/null | grep 'secubox_uplink=.1' | cut -d'.' -f2); do
local peer_id=$(uci -q get network.${iface}.secubox_peer_id)
add_uplink_to_mwan3 "$iface" "$peer_id"
done
}
cleanup_mwan3_failover() {
[ ! -f /etc/config/mwan3 ] && return
# Remove all uplink interfaces from mwan3
for iface in $(uci show network 2>/dev/null | grep 'secubox_uplink=.1' | cut -d'.' -f2); do
remove_uplink_from_mwan3 "$iface"
done
}
# ============================================================
# Main
# ============================================================
case "${1:-}" in
status)
shift; cmd_status "$@" ;;
show)
shift; cmd_show "$@" ;;
up)
shift; cmd_up "$@" ;;
down)
shift; cmd_down "$@" ;;
uplink)
shift
case "${1:-}" in
list) shift; uplink_list "$@" ;;
add) shift; uplink_add "$@" ;;
remove) shift; uplink_remove "$@" ;;
status) shift; uplink_status "$@" ;;
test) shift; uplink_test "$@" ;;
failover) shift; uplink_failover "$@" ;;
priority) shift; uplink_priority "$@" ;;
offer) shift; uplink_offer "$@" ;;
withdraw) shift; uplink_withdraw "$@" ;;
*) log_error "Unknown uplink command: $1"; usage; exit 1 ;;
esac
;;
-h|--help|help)
usage ;;
*)
usage ;;
esac

View File

@ -13,15 +13,18 @@
"bandwidth_rates",
"bandwidth_history",
"endpoint_info",
"get_endpoints"
"get_endpoints",
"uplink_status",
"uplinks"
],
"system": [ "info", "board" ],
"file": [ "read", "stat", "exec" ]
},
"uci": [ "network", "wireguard_dashboard" ],
"uci": [ "network", "wireguard_dashboard", "wireguard_uplink", "mwan3" ],
"file": {
"/etc/config/network": [ "read" ],
"/etc/config/wireguard_dashboard": [ "read" ],
"/etc/config/wireguard_uplink": [ "read" ],
"/usr/bin/wg": [ "exec" ]
}
},
@ -38,10 +41,17 @@
"ping_peer",
"set_endpoint",
"set_default_endpoint",
"delete_endpoint"
"delete_endpoint",
"add_uplink",
"remove_uplink",
"test_uplink",
"offer_uplink",
"withdraw_uplink",
"set_uplink_priority",
"set_uplink_failover"
]
},
"uci": [ "wireguard_dashboard", "network" ]
"uci": [ "wireguard_dashboard", "network", "wireguard_uplink", "mwan3" ]
}
}
}