From ef1b9c10ea114e46aa64934216758bc7e2a66fc7 Mon Sep 17 00:00:00 2001 From: Parahub AI Date: Thu, 5 Feb 2026 22:35:16 +0000 Subject: [PATCH] feat: Replace SQM with per-client speed control for paid WiFi upgrade Add parahub-speed-control script (nftables set + tc HTB) for per-IP speed shaping. Free tier 512kbps, paid tier unlimited. Heartbeat now parses paid_clients from API response and syncs nftables set. Replaced sqm-scripts/kmod-sched-cake/luci-app-sqm packages with tc-full/kmod-ifb/kmod-sched-htb. Section 8 of uci-defaults creates init.d service for speed control instead of SQM config. Co-Authored-By: Claude Opus 4.6 --- files/etc/uci-defaults/99-parahub-mesh | 30 ++++++----- files/usr/bin/parahub-heartbeat | 24 ++++++--- files/usr/bin/parahub-speed-control | 74 ++++++++++++++++++++++++++ scripts/build.sh | 6 +-- 4 files changed, 112 insertions(+), 22 deletions(-) create mode 100755 files/usr/bin/parahub-speed-control diff --git a/files/etc/uci-defaults/99-parahub-mesh b/files/etc/uci-defaults/99-parahub-mesh index fab5d99..8b295d3 100755 --- a/files/etc/uci-defaults/99-parahub-mesh +++ b/files/etc/uci-defaults/99-parahub-mesh @@ -525,22 +525,26 @@ fi uci commit dhcp # ============================================================================ -# 8. SQM TRAFFIC SHAPING (Bumblebee only — guest 512 kbps limit) +# 8. SPEED CONTROL (Bumblebee only — per-client shaping, paid Lightning upgrade) # ============================================================================ if [ "$ROLE" != "bee" ]; then - uci batch <<-SQM_EOF -set sqm.guest=queue -set sqm.guest.enabled='1' -set sqm.guest.interface='br-guest' -set sqm.guest.download='512' -set sqm.guest.upload='512' -set sqm.guest.qdisc='cake' -set sqm.guest.script='piece_of_cake.qos' -set sqm.guest.linklayer='ethernet' -set sqm.guest.overhead='44' -SQM_EOF - uci commit sqm + chmod +x /usr/bin/parahub-speed-control + + # Create init.d service for speed control + cat > /etc/init.d/parahub-speed <<'INITEOF' +#!/bin/sh /etc/rc.common +START=99 +start() { /usr/bin/parahub-speed-control init; } +stop() { + tc qdisc del dev br-guest root 2>/dev/null + tc qdisc del dev br-guest ingress 2>/dev/null + ip link del ifb-guest 2>/dev/null + nft delete table inet parahub 2>/dev/null +} +INITEOF + chmod +x /etc/init.d/parahub-speed + /etc/init.d/parahub-speed enable fi # ============================================================================ diff --git a/files/usr/bin/parahub-heartbeat b/files/usr/bin/parahub-heartbeat index b61c2c2..94183d0 100755 --- a/files/usr/bin/parahub-heartbeat +++ b/files/usr/bin/parahub-heartbeat @@ -34,26 +34,38 @@ esac PAYLOAD="{\"mac\":\"${MAC}\",\"hostname\":\"${HOSTNAME}\",\"yggdrasil_address\":\"${YGG_ADDR}\",\"firmware_version\":\"25.12.0-rc4\",\"hardware_profile\":\"${HW}\",\"uptime\":${UPTIME},\"private_ssid\":\"${SSID}\",\"firmware_role\":\"${ROLE}\",\"mesh_ip\":\"${MESH_IP}\"}" +RESPONSE="" + if [ "$ROLE" = "bee" ]; then # Bee: no yggdrasil, use public URL only - curl -s -m 10 -X POST \ + RESPONSE=$(curl -s -m 10 -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${HEARTBEAT_KEY}" \ -d "$PAYLOAD" \ - "${PARAHUB_API_PUBLIC}" >/dev/null 2>&1 + "${PARAHUB_API_PUBLIC}" 2>/dev/null) else # Bumblebee: try yggdrasil first, fallback to public if ping6 -c 1 -W 3 200:abb9:5810:37d3:8a4c:98a6:b82b:969a >/dev/null 2>&1; then - curl -s -m 10 -X POST \ + RESPONSE=$(curl -s -m 10 -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${HEARTBEAT_KEY}" \ -d "$PAYLOAD" \ - "${PARAHUB_API_YGG}" >/dev/null 2>&1 + "${PARAHUB_API_YGG}" 2>/dev/null) else - curl -s -m 10 -X POST \ + RESPONSE=$(curl -s -m 10 -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${HEARTBEAT_KEY}" \ -d "$PAYLOAD" \ - "${PARAHUB_API_PUBLIC}" >/dev/null 2>&1 + "${PARAHUB_API_PUBLIC}" 2>/dev/null) fi fi + +# Sync paid_clients to speed control (Bumblebee only) +if [ "$ROLE" != "bee" ] && [ -x /usr/bin/parahub-speed-control ] && [ -n "$RESPONSE" ]; then + PAID_IPS=$(echo "$RESPONSE" | jsonfilter -e '$.paid_clients[*]' 2>/dev/null) + # Flush and re-add all paid IPs + parahub-speed-control flush + for IP in $PAID_IPS; do + parahub-speed-control add "$IP" + done +fi diff --git a/files/usr/bin/parahub-speed-control b/files/usr/bin/parahub-speed-control new file mode 100755 index 0000000..8dfb286 --- /dev/null +++ b/files/usr/bin/parahub-speed-control @@ -0,0 +1,74 @@ +#!/bin/sh +# Parahub Mesh — Per-client speed control +# Free tier: 512kbps (default), Paid tier: full speed (via nftables set + tc HTB) +# +# Usage: parahub-speed-control init|add |remove |list|flush + +IFACE="br-guest" +SLOW_RATE="512kbit" + +case "$1" in + init) + # Remove SQM if active + /etc/init.d/sqm stop 2>/dev/null + /etc/init.d/sqm disable 2>/dev/null + + # --- Egress shaping (router → client, i.e. download for client) --- + tc qdisc del dev $IFACE root 2>/dev/null + tc qdisc add dev $IFACE root handle 1: htb default 10 + tc class add dev $IFACE parent 1: classid 1:1 htb rate 1000mbit + tc class add dev $IFACE parent 1:1 classid 1:10 htb rate $SLOW_RATE ceil $SLOW_RATE # free tier + tc class add dev $IFACE parent 1:1 classid 1:20 htb rate 1000mbit # paid tier + tc qdisc add dev $IFACE parent 1:10 fq_codel + tc qdisc add dev $IFACE parent 1:20 fq_codel + + # tc filter: packets with mark 0x20 → paid class + tc filter add dev $IFACE parent 1: protocol ip handle 0x20 fw flowid 1:20 + + # --- Ingress shaping (client → router, i.e. upload for client) via IFB --- + ip link add ifb-guest type ifb 2>/dev/null + ip link set ifb-guest up + tc qdisc del dev $IFACE ingress 2>/dev/null + tc qdisc add dev $IFACE ingress + tc filter add dev $IFACE parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb-guest + + tc qdisc del dev ifb-guest root 2>/dev/null + tc qdisc add dev ifb-guest root handle 1: htb default 10 + tc class add dev ifb-guest parent 1: classid 1:1 htb rate 1000mbit + tc class add dev ifb-guest parent 1:1 classid 1:10 htb rate $SLOW_RATE ceil $SLOW_RATE + tc class add dev ifb-guest parent 1:1 classid 1:20 htb rate 1000mbit + tc qdisc add dev ifb-guest parent 1:10 fq_codel + tc qdisc add dev ifb-guest parent 1:20 fq_codel + tc filter add dev ifb-guest parent 1: protocol ip handle 0x20 fw flowid 1:20 + + # --- nftables: paid_clients set + mark rules --- + nft add table inet parahub 2>/dev/null + nft flush table inet parahub 2>/dev/null + nft add set inet parahub paid_clients '{ type ipv4_addr; }' + nft add chain inet parahub speed_mark '{ type filter hook forward priority -150; }' + nft add rule inet parahub speed_mark ip daddr @paid_clients meta mark set 0x20 + nft add rule inet parahub speed_mark ip saddr @paid_clients meta mark set 0x20 + + logger -t parahub-speed "Speed control initialized: free=${SLOW_RATE}, paid=unlimited" + ;; + add) + [ -z "$2" ] && echo "Usage: $0 add " && exit 1 + nft add element inet parahub paid_clients "{ $2 }" 2>/dev/null + logger -t parahub-speed "Added paid client: $2" + ;; + remove) + [ -z "$2" ] && echo "Usage: $0 remove " && exit 1 + nft delete element inet parahub paid_clients "{ $2 }" 2>/dev/null + logger -t parahub-speed "Removed paid client: $2" + ;; + list) + nft list set inet parahub paid_clients 2>/dev/null + ;; + flush) + nft flush set inet parahub paid_clients 2>/dev/null + ;; + *) + echo "Usage: $0 {init|add |remove |list|flush}" + exit 1 + ;; +esac diff --git a/scripts/build.sh b/scripts/build.sh index 5121159..baf3224 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -86,10 +86,10 @@ PACKAGES_BUMBLEBEE=( wireguard-tools luci-proto-wireguard https-dns-proxy - sqm-scripts - kmod-sched-cake + tc-full + kmod-ifb + kmod-sched-htb luci - luci-app-sqm tcpdump iperf3 iwinfo