💐 Sync 2026-09-29 21:24:56

This commit is contained in:
github-actions[bot] committed 2026-09-29 21:24:56 +08:00
1 parent 6286afd6d3
commit dd23f0e13a
87 files changed
+351 -75

No files matched your search

+1 -1
View File
@@ -23,7 +23,7 @@ LUCI_DEPENDS:= \
PKG_NAME:=luci-app-homeproxy
PKG_VERSION:=28.9.1.14
PKG_RELEASE:=1
PKG_RELEASE:=2
# Only files the package actually ships belong here. The previous list named
# four paths that exist nowhere in the tree - certs/ and ruleset/ are created at
+1 -1
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-trafficctl
PKG_VERSION:=1.18.0
PKG_RELEASE:=2
PKG_RELEASE:=3
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=Denis Iusupov <yusdyr@gmail.com>
@@ -431,6 +431,21 @@ function mkEthIcon(size) {
return svg;
}
// Marks a device that the WiFi MAC filter lists as blocked. `pending` is the
// backend's wifi_block_pending: the device is on the deny list AND associated
// on a radio right now, which is proof the running hostapd ACL never got the
// block. Saying "blocked" there is the lie this whole change exists to remove,
// so that case gets its own wording and colour.
function mkWifiBlockBadge(pending) {
return E('span', {
'class': pending ? 'tc-c-err tc-fw-bold' : 'tc-c-warn tc-fw-bold',
'style': 'margin-left:4px;cursor:help;white-space:nowrap',
'title': pending
? _('On the WiFi deny list but still connected — the block is NOT in effect on the running radio. Install hostapd-utils so blocks apply immediately, or restart WiFi (disconnects every client).')
: _('Blocked from WiFi (MAC deny list)')
}, pending ? '📵⚠' : '📵');
}
function renderSparkline(history, globalMax, width, height, limitKbit) {
if (!history || history.length < 2) return null;
var maxVal = globalMax || 1;
@@ -1322,6 +1337,15 @@ function buildSummaryTable(rows, sortCol, sortDir, onSort, onSelect, speedMap, d
linkBadge = E('span', { 'class': 'tc-c-muted' }, [mkEthIcon(14), document.createTextNode(ethLabel)]);
}
cellMap.conn_type = E('div', { 'class': 'td tc-center' }, linkBadge);
// A WiFi block used to show in the row only as the line-through above,
// which lives inside the isWifi branch — so it disappeared from the row
// in the two cases that matter most: a block that WORKED (the device
// stops associating, the cell falls through to "?" or eth) and a device
// blocked while sitting on cable. Mark it in every branch, with a label
// and a tooltip rather than a text decoration nobody reads as "blocked".
if (r.wifi_blocked) {
cellMap.conn_type.appendChild(mkWifiBlockBadge(r.wifi_block_pending));
}
var appBadge;
if (r.app) {
@@ -2791,9 +2815,18 @@ return view.extend({
parts.push(_('Dropped') + ': <b style="color:var(--tc-err)">🚫 '+(Number(dm.packets) || 0)+' pkts / '+fmtBytes(dm.bytes||0)+'</b>');
}
}
var wifiPart = data.wifi_blocked
? ' &nbsp;|&nbsp; <b style="color:var(--tc-warn)">📵 ' + _('WiFi blocked') + '</b> ('+escHtml(data.mac||'') + ')'
: (data.mac ? ' &nbsp;|&nbsp; <span style="color:var(--tc-faint)">MAC: '+escHtml(data.mac)+'</span>' : '');
// Listed but still associated is not a block, so it must not
// read as one — that claim is the whole bug.
var wifiPart;
if (data.wifi_blocked && data.wifi_block_pending) {
wifiPart = ' &nbsp;|&nbsp; <b style="color:var(--tc-err)">📵⚠ ' + _('WiFi block NOT in effect') + '</b> — ' +
_('on the deny list but still connected; install hostapd-utils or restart WiFi') +
' ('+escHtml(data.mac||'') + ')';
} else if (data.wifi_blocked) {
wifiPart = ' &nbsp;|&nbsp; <b style="color:var(--tc-warn)">📵 ' + _('WiFi blocked') + '</b> ('+escHtml(data.mac||'') + ')';
} else {
wifiPart = data.mac ? ' &nbsp;|&nbsp; <span style="color:var(--tc-faint)">MAC: '+escHtml(data.mac)+'</span>' : '';
}
statsDiv.className = 'alert-message ' + (data.blocked ? 'error' : 'info');
statsDiv.innerHTML = (data.blocked
? '<b>⛔ ' + _('BLOCKED') + '</b> — '+(Number(data.block_packets) || 0)+' pkts, '+fmtBytes(Number(data.block_bytes) || 0)+' ' + _('dropped') + ' &nbsp;|&nbsp; '
@@ -2969,6 +3002,7 @@ return view.extend({
var shaped = rows.filter(function(r){return (r.shape_kbit||0) > 0;}).length;
var blocked = rows.filter(function(r){return r.blocked;}).length;
var wifiBlk = rows.filter(function(r){return r.wifi_blocked;}).length;
var wifiPending = rows.filter(function(r){return r.wifi_block_pending;}).length;
var totalDropPkts = Object.keys(self._dropMap).reduce(function(s, ip) { return s + (self._dropMap[ip].packets||0); }, 0);
var lnk = 'cursor:pointer;text-decoration:underline;text-decoration-style:dashed';
@@ -2987,6 +3021,15 @@ return view.extend({
parts.push(E('span', {}, [document.createTextNode(_('Active') + ': '), E('b', {}, String(rows.length))]));
parts.push(E('span', {}, [document.createTextNode(_('Blocked') + ': '), mkFilterVal('blocked', 'var(--tc-err)', String(blocked))]));
parts.push(E('span', {}, [document.createTextNode(_('WiFi') + ': '), mkFilterVal('wifi_blocked', 'var(--tc-warn)', String(wifiBlk))]));
// Without this the header counts a device as blocked on the strength of
// the uci maclist alone — which is how a router can report "WiFi: 2"
// while both of those devices are browsing.
if (wifiPending > 0) {
parts.push(E('span', {
'style': 'color:var(--tc-err);font-weight:700;cursor:help',
'title': _('Devices on the WiFi deny list that are still connected — those blocks are not in effect on the running radio.')
}, '⚠ ' + wifiPending + ' ' + _('not applied')));
}
if (limited > 0) parts.push(E('span', {}, [document.createTextNode(_('Limited') + ': '), mkFilterVal('limited', 'var(--tc-warn)', '⚡' + limited)]));
if (shaped > 0) parts.push(E('span', {}, [document.createTextNode(_('Shaped') + ': '), mkFilterVal('shaped', 'var(--tc-speed)', '🌊' + shaped)]));
if (totalDropPkts > 0) {
@@ -3130,7 +3173,19 @@ return view.extend({
setStatus(statusDiv, 'loading', (action==='block' ? _('Adding to') : _('Removing from')) + ' ' + _('WiFi deny list') + ': ' + name + '…');
var fn = action === 'block' ? callMacfilterAdd : callMacfilterRemove;
fn(ip).then(function(res) {
setStatus(statusDiv, (res && res.ok) ? (action==='block'?'action':'ok') : 'error', (res && res.msg) || '?');
// res.ok is now true only when the RUNNING radio was verified to
// carry the change, so a config-only write must not paint green.
// "ban" is the partial middle state (temporary hostapd ban, no
// ACL) and gets the attention colour rather than a hard error.
var state;
if (res && res.ok) {
state = (action === 'block') ? 'action' : 'ok';
} else if (res && res.enforcement === 'ban') {
state = 'action';
} else {
state = 'error';
}
setStatus(statusDiv, state, (res && res.msg) || '?');
runQuery();
}).catch(function(e) {
setStatus(statusDiv, 'error', '✗ '+e.message);
@@ -374,8 +374,13 @@ if [ "$DO_RDNS" = "1" ]; then
fi
fi
# Listed as blocked yet associated right now = the radio never got the block.
WIFI_PENDING=false
tctl_wifi_block_pending "$([ "$WIFI_BLOCKED" = "true" ] && echo 1 || echo 0)" "$CONN_TYPE" \
&& WIFI_PENDING=true
# Output final JSON
printf '{"ip":"%s","name":"%s","mac":"%s","conn_type":"%s","conn_last":"%s","timestamp":%d,"blocked":%s,"block_packets":%d,"block_bytes":%.0f,"wifi_blocked":%s,"total":%.0f,"protocols":{"tcp":%d,"udp":%d,"other":%d},"tcp_states":{"established":%d,"time_wait":%d,"syn_sent":%d,"close_wait":%d},"connections":[%s],"rate_limit_kbit":%d,"shape_kbit":%d}\n' \
printf '{"ip":"%s","name":"%s","mac":"%s","conn_type":"%s","conn_last":"%s","timestamp":%d,"blocked":%s,"block_packets":%d,"block_bytes":%.0f,"wifi_blocked":%s,"wifi_block_pending":%s,"total":%.0f,"protocols":{"tcp":%d,"udp":%d,"other":%d},"tcp_states":{"established":%d,"time_wait":%d,"syn_sent":%d,"close_wait":%d},"connections":[%s],"rate_limit_kbit":%d,"shape_kbit":%d}\n' \
"$IP" "$NAME" "$MAC" "$CONN_TYPE" "$CONN_LAST" "$TIMESTAMP" "$BLOCKED" "$BLOCK_PACKETS" "$BLOCK_BYTES" \
"$WIFI_BLOCKED" "$TOTAL" "$N_TCP" "$N_UDP" "$N_OTHER" \
"$WIFI_BLOCKED" "$WIFI_PENDING" "$TOTAL" "$N_TCP" "$N_UDP" "$N_OTHER" \
"$EST" "$TW" "$SS" "$CW" "$CONNS_OUT" "$RATE_LIM" "$SHAPE_KBIT"
@@ -418,12 +418,24 @@ tctl_get_wifi_interfaces() {
uci show wireless 2>/dev/null | grep '=wifi-iface' | cut -d. -f2 | cut -d= -f1
}
# Get running WiFi interface names (e.g. wlan0, wlan1)
# Running AP interface names, one per line (e.g. phy0-ap0), taken from the
# hostapd ubus objects.
#
# Returns non-zero when ubus itself could not be consulted. That is NOT the
# same as "no AP is running", and callers must not read an empty list as
# "nothing to enforce" unless the query actually succeeded — doing so would
# turn a broken router into a silent "blocked".
tctl_get_hostapd_ifaces() {
ubus list 2>/dev/null | grep '^hostapd\.' | cut -d. -f2
local out
command -v ubus >/dev/null 2>&1 || return 1
out=$(ubus list 2>/dev/null) || return 1
# An empty result is a valid answer -- "no AP is running" -- so the grep
# matching nothing must not be reported as a failed query. Only ubus itself
# failing, above, is that.
printf '%s\n' "$out" | grep '^hostapd\.' | cut -d. -f2-
return 0
}
# Add MAC to hostapd deny ACL at runtime + deauth the client (no wifi reload)
# Which ACL policy a wifi-iface uses: "allow" (whitelist — only listed MACs may
# associate) or "deny" (blacklist — listed MACs are rejected). Anything else,
# including unset, means no filtering is configured yet, reported as "deny"
@@ -434,51 +446,187 @@ tctl_get_wifi_filter_mode() {
[ "$mode" = "allow" ] && echo "allow" || echo "deny"
}
# Block a MAC at runtime. In deny mode that means adding it to the deny ACL; in
# allow (whitelist) mode it means dropping it from the accept ACL. Either way
# the client is deauthenticated so the change takes effect immediately.
# ── Runtime WiFi enforcement ──────────────────────────────────────────────
#
# The uci maclist is the durable half of a WiFi block; hostapd's running ACL is
# the half that decides whether the device is on the air *now*. The two drift
# apart whenever the runtime call cannot be made, and for a long time that
# drift was invisible: the calls below were fire-and-forget, their exit status
# discarded, so a router without hostapd-utils reported every block as done
# while the device kept browsing.
#
# So every entry point here reports how far enforcement actually got, as one
# of these words, and proves it by reading the state back rather than trusting
# a return code:
#
# acl - the running ACL was changed and the change was read back
# ban - no usable hostapd_cli; hostapd's ubus deauthed and banned the
# client, which expires on its own (see TCTL_WIFI_BAN_MS)
# none - nothing could be applied or verified on the running radio
# no-radio - ubus answered and no AP is running, so there is nothing to
# enforce; the uci maclist applies when wifi next starts
#
# Only "acl" and "no-radio" mean the operator's intent is in force, and the
# functions return 0 for exactly those two.
# How long a ubus ban lasts, in milliseconds. Used only where hostapd_cli is
# unavailable: hostapd's ubus object has no ACL method (del_client, list_bans,
# get_clients, reload), so a timed ban is the strongest immediate measure it
# can offer. One hour is long enough to be worth doing and short enough that
# callers must keep calling it temporary rather than done.
TCTL_WIFI_BAN_MS=3600000
# hostapd_cli exists AND this AP's control socket answers. Both halves matter:
# the package can be absent (the router this bug was found on), or present
# while hostapd is not listening, and only a PONG shows the ACL commands will
# reach anything.
tctl_hostapd_cli_alive() {
command -v hostapd_cli >/dev/null 2>&1 || return 1
hostapd_cli -i "$1" ping 2>/dev/null | grep -q PONG
}
# Does the running ACL currently keep this MAC off the air? Read back from
# hostapd instead of inferred from our own call: hostapd_cli exits 0 for
# "command delivered", and an ACL that stays empty afterwards is exactly the
# failure being guarded against. hostapd also reloads its maclist file at times
# of its own choosing, so the entry present may not be the one we added — which
# is fine, and another reason to ask rather than assume.
tctl_hostapd_acl_blocks() {
local iface="$1" mac="$2" mode="$3" acl
if [ "$mode" = "allow" ]; then
acl=$(hostapd_cli -i "$iface" accept_acl SHOW 2>/dev/null)
! printf '%s\n' "$acl" | grep -qi "$mac"
else
acl=$(hostapd_cli -i "$iface" deny_acl SHOW 2>/dev/null)
printf '%s\n' "$acl" | grep -qi "$mac"
fi
}
tctl_hostapd_ubus_banned() {
local iface="$1" mac="$2"
command -v ubus >/dev/null 2>&1 || return 1
ubus call "hostapd.$iface" list_bans 2>/dev/null | grep -qi "$mac"
}
# Fallback when hostapd_cli is unusable. del_client deauthenticates and refuses
# the client for ban_time, which is not an ACL entry — the durable half stays
# the uci maclist. The call's own exit status is deliberately ignored: it can
# fail for a client that is not currently associated while the ban still lands,
# and list_bans is the only answer worth having.
tctl_hostapd_ubus_ban() {
local iface="$1" mac="$2"
command -v ubus >/dev/null 2>&1 || return 1
ubus call "hostapd.$iface" del_client \
"{\"addr\":\"$mac\",\"reason\":1,\"deauth\":true,\"ban_time\":$TCTL_WIFI_BAN_MS}" \
>/dev/null 2>&1
tctl_hostapd_ubus_banned "$iface" "$mac"
}
# acl > ban > none, so a loop over several APs can keep the worst result.
tctl_enforce_rank() {
case "$1" in
acl) echo 3 ;;
ban) echo 2 ;;
*) echo 1 ;;
esac
}
tctl_hostapd_block_iface() {
local iface="$1" mac="$2" mode="$3"
if tctl_hostapd_cli_alive "$iface"; then
if [ "$mode" = "allow" ]; then
hostapd_cli -i "$iface" accept_acl DEL_MAC "$mac" >/dev/null 2>&1
else
hostapd_cli -i "$iface" deny_acl ADD_MAC "$mac" >/dev/null 2>&1
fi
hostapd_cli -i "$iface" deauthenticate "$mac" >/dev/null 2>&1
if tctl_hostapd_acl_blocks "$iface" "$mac" "$mode"; then
echo acl
return
fi
fi
if tctl_hostapd_ubus_ban "$iface" "$mac"; then
echo ban
return
fi
echo none
}
tctl_hostapd_unblock_iface() {
local iface="$1" mac="$2" mode="$3"
if tctl_hostapd_cli_alive "$iface"; then
if [ "$mode" = "allow" ]; then
hostapd_cli -i "$iface" accept_acl ADD_MAC "$mac" >/dev/null 2>&1
else
hostapd_cli -i "$iface" deny_acl DEL_MAC "$mac" >/dev/null 2>&1
fi
if ! tctl_hostapd_acl_blocks "$iface" "$mac" "$mode"; then
# The ACL permits the MAC again, but a ban left over from an
# earlier hostapd_cli-less block would still keep it off the air,
# and there is no ubus method to lift one.
if tctl_hostapd_ubus_banned "$iface" "$mac"; then
echo ban
else
echo acl
fi
return
fi
fi
if tctl_hostapd_ubus_banned "$iface" "$mac"; then
echo ban
return
fi
echo none
}
# Applies $2 ("allow"/"deny" ACL policy) to every running AP and echoes the
# worst per-AP outcome. Always safe to re-run: it enforces from the runtime
# state, so calling it on a MAC that uci already lists is how a block that was
# only ever written to config gets applied for real.
tctl_hostapd_apply_mac() {
local op="$1" mac="$2" mode="$3"
local ifaces iface state rank worst=3
ifaces=$(tctl_get_hostapd_ifaces) || { echo none; return 1; }
if [ -z "$ifaces" ]; then
echo no-radio
return 0
fi
for iface in $ifaces; do
case "$op" in
block) state=$(tctl_hostapd_block_iface "$iface" "$mac" "$mode") ;;
*) state=$(tctl_hostapd_unblock_iface "$iface" "$mac" "$mode") ;;
esac
rank=$(tctl_enforce_rank "$state")
[ "$rank" -lt "$worst" ] && worst="$rank"
done
case "$worst" in
3) echo acl; return 0 ;;
2) echo ban ;;
*) echo none ;;
esac
return 1
}
tctl_hostapd_block_mac() {
local mac="$1" mode="$2"
local iface
for iface in $(tctl_get_hostapd_ifaces); do
if [ "$mode" = "allow" ]; then
hostapd_cli -i "$iface" accept_acl DEL_MAC "$mac" 2>/dev/null
else
hostapd_cli -i "$iface" deny_acl ADD_MAC "$mac" 2>/dev/null
fi
hostapd_cli -i "$iface" deauthenticate "$mac" 2>/dev/null
done
tctl_hostapd_apply_mac block "$1" "$2"
}
# Unblock a MAC at runtime — the inverse of tctl_hostapd_block_mac.
tctl_hostapd_unblock_mac() {
local mac="$1" mode="$2"
local iface
for iface in $(tctl_get_hostapd_ifaces); do
if [ "$mode" = "allow" ]; then
hostapd_cli -i "$iface" accept_acl ADD_MAC "$mac" 2>/dev/null
else
hostapd_cli -i "$iface" deny_acl DEL_MAC "$mac" 2>/dev/null
fi
done
tctl_hostapd_apply_mac unblock "$1" "$2"
}
tctl_hostapd_deny_mac() {
local mac="$1"
local iface
for iface in $(tctl_get_hostapd_ifaces); do
hostapd_cli -i "$iface" deny_acl ADD_MAC "$mac" 2>/dev/null
hostapd_cli -i "$iface" deauthenticate "$mac" 2>/dev/null
done
}
# Remove MAC from hostapd deny ACL at runtime (client can reassociate immediately)
tctl_hostapd_allow_mac() {
local mac="$1"
local iface
for iface in $(tctl_get_hostapd_ifaces); do
hostapd_cli -i "$iface" deny_acl DEL_MAC "$mac" 2>/dev/null
done
# A device listed as WiFi-blocked while it is associated on a radio right now
# is proof the running ACL does not carry the block — the state a silent
# enforcement failure leaves a router in. Keyed off conn_type rather than the
# station dump so it still holds where iw is missing and the connection type
# came from the bridge port instead.
tctl_wifi_block_pending() {
local blocked="$1" conn_type="$2"
[ "$blocked" = "1" ] || return 1
case "$conn_type" in
wifi|2.4G|5G|6G) return 0 ;;
esac
return 1
}
# ── Persistence ───────────────────────────────────────────────────────────
@@ -1,7 +1,9 @@
#!/bin/sh
# shellcheck shell=dash
# Block device WiFi access by adding its MAC to deny maclist on all interfaces.
# Uses hostapd to deauth only the target client — no wifi reload needed.
# The uci maclist is the durable half; hostapd is then programmed at runtime so
# only the target client is kicked, with no wifi reload. Reports how far that
# runtime half actually got — see the enforcement words in trafficctl-fw.sh.
# Usage: trafficctl-macfilter-add.sh <ip>
. /usr/local/bin/trafficctl-fw.sh
@@ -76,11 +78,41 @@ for iface in $IFACES; do
fi
done
if [ "$CHANGED" = "1" ]; then
uci commit wireless
# Apply at runtime and deauth just this client — no wifi reload.
tctl_hostapd_block_mac "$MAC" "$MODE"
fi
[ "$CHANGED" = "1" ] && uci commit wireless
tctl_log "wifi_block" "$IP" "MAC=$MAC" "${TCTL_VIA:-cli}" "${TCTL_SRC:-local}"
echo "{\"ok\":true,\"msg\":\"MAC $MAC blocked on wifi for $IP\"}"
# Applied unconditionally, not just when uci needed editing. Config and runtime
# are two pieces of state: a router whose maclist already names the MAC but
# whose radio never got the ACL entry used to make this a no-op that still
# reported success, so pressing Block again — the obvious reaction to "it did
# not work" — could never recover it.
ENFORCE=$(tctl_hostapd_block_mac "$MAC" "$MODE")
BAN_MIN=$(( TCTL_WIFI_BAN_MS / 60000 ))
# The uci entry is kept even when the radio could not be programmed. It is the
# only durable record of the operator's intent and it does take effect at the
# next wifi restart, so discarding it would throw away the half that worked and
# leave the device unblocked forever. What made keeping it dangerous was the
# claim of success, not the entry — so the claim is what goes. A wifi reload
# would apply it now, but it disconnects every client on the radio, which is
# not something a per-device click should do behind the operator's back.
case "$ENFORCE" in
acl)
OK=true
MSG="MAC $MAC blocked on wifi for $IP"
;;
no-radio)
OK=true
MSG="MAC $MAC added to the wifi deny list for $IP; no radio is running, it applies when wifi starts"
;;
ban)
OK=false
MSG="MAC $MAC is NOT permanently blocked: hostapd_cli is missing, so the block is temporary ($BAN_MIN min). Install hostapd-utils, or restart wifi (drops all clients)"
;;
*)
OK=false
MSG="MAC $MAC saved to the wifi deny list but NOT applied to the radio, so the device stays online. Install hostapd-utils, or restart wifi (drops all clients)"
;;
esac
tctl_log "wifi_block" "$IP" "MAC=$MAC enforce=$ENFORCE" "${TCTL_VIA:-cli}" "${TCTL_SRC:-local}"
printf '{"ok":%s,"enforcement":"%s","msg":"%s"}\n' "$OK" "$ENFORCE" "$MSG"
@@ -1,7 +1,9 @@
#!/bin/sh
# shellcheck shell=dash
# Remove device WiFi MAC filter (unblock from wifi deny list).
# Uses hostapd beacon update — no wifi reload, other clients stay connected.
# Updates the uci maclist and the running hostapd ACL, so the client can
# reassociate without a wifi reload and other clients stay connected. Reports
# how far the runtime half got — see the enforcement words in trafficctl-fw.sh.
# Usage: trafficctl-macfilter-remove.sh <ip>
. /usr/local/bin/trafficctl-fw.sh
@@ -68,11 +70,32 @@ for iface in $IFACES; do
fi
done
if [ "$CHANGED" = "1" ]; then
uci commit wireless
# Update the runtime ACL — the client can reassociate immediately.
tctl_hostapd_unblock_mac "$MAC" "$MODE"
fi
[ "$CHANGED" = "1" ] && uci commit wireless
tctl_log "wifi_unblock" "$IP" "MAC=$MAC" "${TCTL_VIA:-cli}" "${TCTL_SRC:-local}"
echo "{\"ok\":true,\"msg\":\"MAC $MAC removed from wifi filter for $IP\"}"
# Unconditional for the same reason as the add path, and the stakes are higher
# here: a MAC that uci no longer lists but the radio still denies leaves a real
# person off the network while the UI says they are not blocked.
ENFORCE=$(tctl_hostapd_unblock_mac "$MAC" "$MODE")
BAN_MIN=$(( TCTL_WIFI_BAN_MS / 60000 ))
case "$ENFORCE" in
acl)
OK=true
MSG="MAC $MAC removed from wifi filter for $IP"
;;
no-radio)
OK=true
MSG="MAC $MAC removed from the wifi filter for $IP; no radio is running"
;;
ban)
OK=false
MSG="MAC $MAC removed from the wifi filter, but hostapd still bans it for up to $BAN_MIN min. Restart wifi (drops all clients) to clear it now"
;;
*)
OK=false
MSG="MAC $MAC removed from the wifi filter, but the radio ACL could not be read back: it may still be blocked. Install hostapd-utils, or restart wifi"
;;
esac
tctl_log "wifi_unblock" "$IP" "MAC=$MAC enforce=$ENFORCE" "${TCTL_VIA:-cli}" "${TCTL_SRC:-local}"
printf '{"ok":%s,"enforcement":"%s","msg":"%s"}\n' "$OK" "$ENFORCE" "$MSG"
@@ -436,16 +436,21 @@ for ip in $ACTIVE_IPS; do
fi
fi
# Needs CONN_TYPE, so it is decided here rather than next to WIFI_BLK.
WIFI_PENDING=0
tctl_wifi_block_pending "$WIFI_BLK" "$CONN_TYPE" && WIFI_PENDING=1
if [ "$FIRST" = "1" ]; then
FIRST=0
else
printf ","
fi
printf '{"ip":"%s","name":"%s","mac":"%s","conn_type":"%s","conn_last":"%s","app":"%s","conns":%d,"total":%.0f,"tcp":%.0f,"udp":%.0f,"blocked":%s,"block_bytes":%.0f,"wifi_blocked":%s,"rate_limit_kbit":%.0f,"shape_kbit":%.0f}' \
printf '{"ip":"%s","name":"%s","mac":"%s","conn_type":"%s","conn_last":"%s","app":"%s","conns":%d,"total":%.0f,"tcp":%.0f,"udp":%.0f,"blocked":%s,"block_bytes":%.0f,"wifi_blocked":%s,"wifi_block_pending":%s,"rate_limit_kbit":%.0f,"shape_kbit":%.0f}' \
"$ip" "$NAME" "$MAC" "$CONN_TYPE" "$CONN_LAST" "$APP" "$CONNS" "$TOTAL" "$TCP" "$UDP" \
"$([ "$BLOCKED" = "1" ] && echo true || echo false)" \
"$BLOCK_BYTES" \
"$([ "$WIFI_BLK" = "1" ] && echo true || echo false)" \
"$([ "$WIFI_PENDING" = "1" ] && echo true || echo false)" \
"$RATE_LIM" "$SHAPE"
done
printf "]\n"
@@ -97,6 +97,14 @@ tg_send() {
tg_answer_cb() {
local cb_id="$1" text="$2"
# The Bot API caps a callback toast at 200 characters and rejects the whole
# call when it is longer — so an over-long message does not get trimmed, it
# vanishes. Backend results carrying remediation advice can reach that
# length, and a silently missing answer is exactly the failure mode this
# plugin is being fixed for.
if [ "${#text}" -gt 200 ]; then
text="$(printf '%.196s' "$text")..."
fi
text=$(tg_json_escape "$text")
tg_api "answerCallbackQuery" \
"$(printf '{"callback_query_id":"%s","text":"%s"}' "$cb_id" "$text")" >/dev/null
+3 -3
View File
@@ -17,12 +17,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-wwand
PKG_VERSION:=1.6.8_p14
PKG_RELEASE:=33
PKG_VERSION:=1.6.8_p17
PKG_RELEASE:=34
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/ddimension/luci-app-wwand.git
PKG_SOURCE_VERSION:=5064320d9e69c3289febbc5552a4d96947d97046
PKG_SOURCE_VERSION:=376f2dc5c658f06f88cb3b917e65e9e7d58a6e85
PKG_MIRROR_HASH:=skip
PKG_LICENSE:=GPL-2.0-only
+2 -2
View File
@@ -13,8 +13,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=wwand-ipa
PKG_VERSION:=0.1.0_p6
PKG_RELEASE:=7
PKG_VERSION:=0.1.0_p7
PKG_RELEASE:=8
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/ddimension/wwand-ipa.git
+3 -3
View File
@@ -50,12 +50,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=wwand
PKG_VERSION:=1.6.8_p70
PKG_RELEASE:=62
PKG_VERSION:=1.6.8_p74
PKG_RELEASE:=63
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
PKG_SOURCE_VERSION:=99125f0c4c8162cce254a5ac56e9f4a1160bcf34
PKG_SOURCE_VERSION:=d146e6b2a30cbbb2737aa4bfb0403322623a81ad
PKG_MIRROR_HASH:=skip
PKG_LICENSE:=GPL-2.0-only