mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 17:55:13 +08:00
💐 Sync 2026-09-29 21:24:56
This commit is contained in:
1 parent
6286afd6d3
commit
dd23f0e13a
87 files changed
+351
-75
No files matched your search
@@ -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
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
|
||||
? ' | <b style="color:var(--tc-warn)">📵 ' + _('WiFi blocked') + '</b> ('+escHtml(data.mac||'') + ')'
|
||||
: (data.mac ? ' | <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 = ' | <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 = ' | <b style="color:var(--tc-warn)">📵 ' + _('WiFi blocked') + '</b> ('+escHtml(data.mac||'') + ')';
|
||||
} else {
|
||||
wifiPart = data.mac ? ' | <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') + ' | '
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user