HydraIssues

Bring the Slate 7 Pro into the hydraguard mesh (phone home, follow-up to #516/#517)
closed improvement Project: hydraneck Reporter: cederik 19 Aug 2026 21:54

Description

Goal

Bring the GL.iNet Slate 7 Pro into the hydraguard WireGuard mesh so it phones home. The router dials out to the hub with a persistent keepalive. After this, the hydraneck server manages the device at its mesh address 10.10.5.1 from anywhere, and hydraneck wg on|off toggles the tunnel remotely. Run this task from a machine on the router's LAN.

Hub-side state (already done)

The peer is enrolled and applied on the hub:

  • Venue peer: slate7-kit, mesh address 10.10.5.1/32, LAN 10.0.5.0/24, guard type gateway.
  • Device public key on the hub starts with EIYr89Gbf0r6.
  • Hub endpoint: hydraguard.experiencenet.com:51820.
  • Hub public key: VGA6ETZB2XFVRRb5KmcFvQ+Ybfh9KKfcWuXfP1IuvQE=.

Private key was here, removed after configuration on 2026-08-20.

Mandatory final step for the agent: after step 5, edit this issue and delete the key block above. Use PATCH https://issues.experiencenet.com/api/v1/issues/522 with the bearer token you already use, replacing this section with the line "Private key was here, removed after configuration on ." Never quote the key in comments, reports, or logs. If you cannot PATCH, tell Cederik immediately so he removes it.

Prerequisites

  • Router LAN IP (IP, default 192.168.8.1) and admin password (PW), from Cederik.
  • The WireGuard private key (WGKEY), from the secret block above.
  • Login flow: identical to issue #517 steps 2 and 3 (challenge, openssl passwd -5 cipher, sha256 digest of root:<cipher>:<nonce>, login, export SID). Firmware is 4.8.4; the challenge prescribes sha256.

Step 1: create the tunnel group

curl -s http://$IP/rpc -d '{"jsonrpc":"2.0","id":10,"method":"call","params":["'$SID'","wg-client","add_group",{"group_name":"wg-hydra"}]}'
# add_group returns an empty result on 4.8.4. Re-list to get the group_id:
curl -s http://$IP/rpc -d '{"jsonrpc":"2.0","id":11,"method":"call","params":["'$SID'","wg-client","get_group_list",{}]}'

Record the group_id of wg-hydra as GID.

Step 2: add the hub tunnel config

curl -s http://$IP/rpc -d '{"jsonrpc":"2.0","id":12,"method":"call","params":["'$SID'","wg-client","add_config",{
  "group_id":GID,
  "name":"hydraguard-hub",
  "address_v4":"10.10.5.1/32",
  "private_key":"WGKEY",
  "public_key":"VGA6ETZB2XFVRRb5KmcFvQ+Ybfh9KKfcWuXfP1IuvQE=",
  "end_point":"hydraguard.experiencenet.com:51820",
  "allowed_ips":"10.10.0.0/16,10.0.0.0/8",
  "listen_port":51820,
  "mtu":1420,
  "persistent_keepalive":25,
  "presharedkey_enable":false}]}'

Substitute GID and WGKEY. The CSV string form of allowed_ips is confirmed on this firmware (#517). Record the returned peer_id (it is a JSON string) as PEER.

The name hydraguard-hub and the group name wg-hydra are load bearing. hydraneck (pkg/glinet) finds the tunnel by these names and by the hub public key. Do not rename them.

Step 3: start the tunnel

curl -s http://$IP/rpc -d '{"jsonrpc":"2.0","id":13,"method":"call","params":["'$SID'","wg-client","start",{"group_id":GID,"peer_id":PEER}]}'

Pass peer_id as a number.

Step 4: verify

# 4a. Service state: expect {"name":"wgclient","status":1} in the service list.
curl -s http://$IP/rpc -d '{"jsonrpc":"2.0","id":14,"method":"call","params":["'$SID'","wg-client","get_status",{}]}'
curl -s http://$IP/rpc -d '{"jsonrpc":"2.0","id":15,"method":"call","params":["'$SID'","system","get_status",{}]}'
# 4b. From your machine on the router LAN, ping the hub over the tunnel:
ping -c 3 10.10.0.1

Note: wg-client get_status returns method-not-found on 4.8.4; run it anyway and record the reply. The system get_status service list is the reliable check. The ping works because the router routes 10.10.0.0/16 into the tunnel for its LAN clients.

Also confirm the tunnel did not hijack normal traffic: curl -s https://api.ipify.org from the LAN must still return the site's normal WAN IP, not a Hetzner address.

# 4c. iPads on this Wi-Fi run their OWN WireGuard tunnels to the same hub
# (10.10.200.x head peers). They encrypt on the device to the hub's public
# endpoint, so the router tunnel must not affect them. With the router
# tunnel up, verify on one iPad that its head app still connects (or that
# its WireGuard still handshakes). Do NOT enable the GL.iNet global proxy
# VPN mode on this router; selective allowed_ips routing is what keeps the
# iPad tunnels on the normal WAN path.

Step 5: reboot persistence

Reboot the router (physically or via {"method":"call","params":["'$SID'","system","reboot",{}]}; this is allowed on this device, it is a router on a desk, not a render body). Wait two minutes, log in again, and repeat step 4a. Record whether the tunnel came back by itself. If it did not, start it again (step 3) and record that finding; auto-reconnect then becomes a follow-up.

Report

Post to this issue (or hand to Cederik):

  • group_id and peer_id values.
  • add_config accepted (raw reply).
  • system get_status wgclient status after start.
  • ping 10.10.0.1 result.
  • WAN IP check result.
  • Reboot persistence: tunnel auto-reconnected yes/no.
  • iPad check (4c): iPad head/WireGuard still works with the router tunnel up, yes/no.
  • The raw wg-client get_status reply (existence check on 4.8.4).
  • The private key block was removed from this issue.

Redact the password, sid, and private key everywhere. Server-side follow-up after this lands (not your task): handshake check on the hub, then hydraneck Phase B (#516) pointing the scanner at 10.10.5.1.

Comments (2)

api 19 Aug 2026 22:56

Runbook executed from the router LAN on 2026-08-20. The Slate 7 Pro is in the hydraguard mesh and phones home. Password, sid, and private key redacted throughout.

Results checklist:

  • group_id: 8044 (wg-hydra), peer_id: 4402 (hydraguard-hub). Private key verified locally to derive the hub-enrolled public key (EIYr89Gbf0r6...) before use.
  • add_config accepted: raw reply {"peer_id":"4402"} (CSV allowed_ips string, as confirmed in #517).
  • wg-client get_status raw reply on 4.8.4: {"error":{"message":"Method not found","code":-32601}} (still absent).
  • system get_status service list after start: contains only wgserver:0 and ovpnserver:0 — no wgclient entry exists on 4.8.4, so the runbook's expected {"name":"wgclient","status":1} check is not available. Live check that works: wg show wgclient1 over SSH (handshake + transfer counters).
  • ping 10.10.0.1: OK from the router (from 10.10.5.1, ~35ms) and from a LAN client through the router (masqueraded, ttl 63). Hub→router path also verified end-to-end by pinging 10.10.5.1 across the mesh from another mesh peer.
  • WAN IP check: unchanged (normal ISP address, not Hetzner) with the tunnel up. No traffic hijack.
  • Reboot persistence: yes, auto-reconnects — but only after a fix; see below. Verified across two reboots (first exposed the failure, second confirmed the fix: handshake re-established ~1 min after boot without intervention).
  • iPad check (4c): not performed — no iPad access from this seat. The WAN IP being unchanged means LAN traffic to the internet (including iPad WireGuard handshakes to the hub's public endpoint) does not enter the router tunnel, so no interference is expected. Someone with an iPad should still glance at it.

IMPORTANT — how the tunnel actually had to be started (major findings for #516 / pkg/glinet):

  1. wg-client start is Method not found on firmware 4.8.4 (same for stop, get_status, get_info). The wg-client module only has config CRUD (add/remove/set config+group, get_group_list, get_config_list, get_all_config_list, check_config, confirm_config...). Tunnel control moved to a vpn-client module (methods: add_tunnel, set_tunnel, get_tunnel, remove_tunnel, order_tunnel, get_status, get_all_config_list, set_options, set_default_tunnel...).
  2. vpn-client set_tunnel {tunnel_id, via:{type:"wgclient",group_id,peer_id}} (the exact payload the stock web UI sends) is accepted ({"tunnel_id":10}) but silently drops the via — get_tunnel keeps showing via:{"via":""} and no route_policy/network changes land. Root cause not determined (module is compiled Lua bytecode); possibly it requires web-UI-side state our API-created group lacks.
  3. Even if it worked, the vpn-client policy engine is the global-proxy machinery the issue forbids: its from/to model only does all-traffic or domain lists, and the routing engine (rtp2.sh + /lib/functions/vpn_func/) has no allowed_ips handling at all. Enabling a tunnel there routes ALL LAN traffic into the wg interface, where cryptokey routing drops every non-mesh destination — i.e. site outage. GL.iNet 4.8 cannot do subnet-selective tunnel routing through its own API.
  4. Working setup (via the sanctioned SSH fallback), all pieces the stock firmware itself uses:
    • netifd interface network.wgclient1 with proto=wgclient, config=peer_4402, mtu=1420 — the stock proto script (/lib/netifd/proto/wgclient.sh) builds the wg config straight from the uci that wg-client add_config wrote. FwMark 0x8000 keeps the encrypted packets on the WAN path.
    • firewall zone hydra (device wgclient1, input/output ACCEPT, forward REJECT, masq=1, mtu_fix=1) + forwarding lan→hydra. Masq is required: the hub's cryptokey routing only accepts src 10.10.5.1/32 and 10.0.5.0/24 from this peer, so LAN-client traffic must be masqueraded to 10.10.5.1.
    • The proto script assigns no address and no routes (its ip address add is commented out; the policy engine normally does that). Added manually: ip addr add 10.10.5.1/32 dev wgclient1, ip route add 10.10.0.0/16 dev wgclient1, ip route add 10.0.0.0/8 dev wgclient1.
  5. Reboot gotcha #1: the endpoint hostname resolves too early at boot (repeater WAN not up), netifd gives up. Gotcha #2 (nastier): the stock vpn-client service deletes unreferenced wgclient uci sections from /etc/config/network at boot — the interface definition itself vanishes. Fix: /usr/bin/hydra-wg-up.sh (called from /etc/rc.local, +25s): idempotently recreates network.wgclient1 if deleted, waits for DNS (up to 5 min), retries ifup until the wg device exists, then adds address+routes. Survived reboot cleanly.

Implications for pkg/glinet WireGuardToggler: the wg-client start/stop API it targets does not exist on 4.8+, the vpn-client replacement is both broken-for-us and semantically wrong (global proxy), so hydraneck wg on|off for glinet should drive the device over SSH (dropbear, root + admin password — the documented fallback surface) or via the hydra-wg-up.sh/ifdown mechanism, e.g. ifup wgclient1 + addr/routes vs ifdown wgclient1.

Server-side follow-up (not this task): handshake check on the hub, then hydraneck Phase B (#516) pointing the scanner at 10.10.5.1.

cederik 19 Aug 2026 23:14

Server side complete 2026-08-19: handshake verified on the hub (10.10.5.1, 35ms). Mesh peer renamed to sint-niklaas-tourism-office to match the venue. Venue created in HydraVenues (bxl1, visit-flanders). Router entry glinet-sintniklaas added to hydraneck (v0.12.1); the venue and router now show on the dashboard, state awaiting-credentials until the admin password lands in the secrets store. The wg-client start/stop API gap on 4.8.4 moves the hydraneck wg on|off rework (SSH ifup/ifdown) to #516. Closing: phone home achieved. Outstanding iPad glance (4c) folded into #516 Phase B verification.