mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-12 04:27:51 +08:00
🔥 Sync 2026-11-04 02:13:23
This commit is contained in:
1 parent
e281fb4f73
commit
bfc1ef0244
65 files changed
+1112
-468
No files matched your search
@@ -279,11 +279,29 @@ if command -v ip >/dev/null 2>&1; then
|
||||
done
|
||||
fi
|
||||
|
||||
CONNS_OUT=$(echo "$CONNTRACK_DATA" | awk -v ip="$IP" -v pf="$PROTO_FILTER" -v oifmap="$OIF_MAP" '
|
||||
# The kernel device is not what the user named the interface: mwan3 egress is
|
||||
# "awg3", the network is "Finland" (#10). The uci name comes from the role map
|
||||
# trafficctl-ifaces.sh already memoizes in tmpfs — the overview polls it, so it
|
||||
# is normally warm. Built here only when it is missing and there is something
|
||||
# to label, never per connection.
|
||||
IFROLE_CACHE="${TCTL_IFROLE_CACHE:-/tmp/trafficctl_ifroles}"
|
||||
if [ -s "$OIF_MAP" ] && [ ! -s "$IFROLE_CACHE" ]; then
|
||||
/usr/local/bin/trafficctl-ifaces.sh >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
CONNS_OUT=$(echo "$CONNTRACK_DATA" | awk -v ip="$IP" -v pf="$PROTO_FILTER" -v oifmap="$OIF_MAP" -v ifroles="$IFROLE_CACHE" '
|
||||
BEGIN {
|
||||
n=0
|
||||
while ((getline line < oifmap) > 0) { split(line, p, " "); oifdev[p[1]] = p[2] }
|
||||
close(oifmap)
|
||||
# "dev role tunnel primary defroute enslaved label", label "-" when the
|
||||
# device has no uci interface. Header lines start with "#".
|
||||
while ((getline line < ifroles) > 0) {
|
||||
if (substr(line, 1, 1) == "#") continue
|
||||
if (split(line, f, " ") < 7 || f[7] == "-") continue
|
||||
oiflbl[f[1]] = f[7]
|
||||
}
|
||||
close(ifroles)
|
||||
}
|
||||
{
|
||||
proto=""
|
||||
@@ -325,10 +343,11 @@ BEGIN {
|
||||
else if (dport == "993") svc="imaps"
|
||||
else if (dport == "8080") svc="http-alt"
|
||||
|
||||
oif = ""
|
||||
if (dst in oifdev) oif = oifdev[dst]
|
||||
odev = ""; oif = ""
|
||||
if (dst in oifdev) { odev = oifdev[dst]; oif = odev }
|
||||
if (odev in oiflbl) oif = oiflbl[odev]
|
||||
if (n > 0) printf ","
|
||||
printf "{\"proto\":\"%s\",\"dst\":\"%s\",\"host\":\"\",\"port\":%s,\"service\":\"%s\",\"bytes\":%.0f,\"state\":\"%s\",\"oif\":\"%s\"}", proto, dst, dport, svc, bytes, state, oif
|
||||
printf "{\"proto\":\"%s\",\"dst\":\"%s\",\"host\":\"\",\"port\":%s,\"service\":\"%s\",\"bytes\":%.0f,\"state\":\"%s\",\"oif\":\"%s\",\"oif_dev\":\"%s\"}", proto, dst, dport, svc, bytes, state, oif, odev
|
||||
n++
|
||||
}
|
||||
')
|
||||
|
||||
@@ -129,7 +129,7 @@ tctl_ratelimit_add() {
|
||||
local rate_kbyte_up=$((rate_kbit_up / 8))
|
||||
[ "$rate_kbyte_up" -lt 1 ] && rate_kbyte_up=1
|
||||
|
||||
local slug dl_expr ul_expr ul6_expr mac
|
||||
local slug dl_expr ul_expr ul6_expr dl6_expr feed6_expr v6set mac
|
||||
slug=$(tctl_target_slug "$ip")
|
||||
# The two directions were already separate rules on separate hooks — they
|
||||
# were symmetric only because both took the same number.
|
||||
@@ -141,12 +141,9 @@ tctl_ratelimit_add() {
|
||||
ul_expr="ip saddr $ip limit rate over ${rate_kbyte_up} kbytes/second"
|
||||
fi
|
||||
|
||||
# Upload over IPv6, keyed on the MAC. This direction and only this one:
|
||||
# at LAN ingress the frame is still the client's own, so "ether saddr"
|
||||
# identifies it whatever address family it is using. Download over IPv6 is
|
||||
# policed at LAN egress, where the destination MAC is the next hop's — it
|
||||
# needs the device's current v6 address set and is deliberately not
|
||||
# attempted here.
|
||||
# Upload over IPv6, keyed on the MAC: at LAN ingress the frame is still
|
||||
# the client's own, so "ether saddr" identifies it whatever address family
|
||||
# it is using.
|
||||
#
|
||||
# Scoped to IPv6 on purpose: IPv4 keeps being policed by exactly the rule it
|
||||
# always was, so nothing about existing behaviour depends on whether this
|
||||
@@ -161,13 +158,41 @@ tctl_ratelimit_add() {
|
||||
# against nftables 1.1.1 / kernel 6.6). nfproto is what tctl_block_add uses,
|
||||
# because that rule lives in inet fw4. Same intent, different family, and
|
||||
# the wrong one does not silently under-match — it fails to load.
|
||||
ul6_expr=""
|
||||
#
|
||||
# Download over IPv6 cannot be keyed on the MAC: at LAN egress the L2
|
||||
# header is not built yet. It is matched on the device's v6 ADDRESSES,
|
||||
# which is only sound if the set of them can never go stale — SLAAC
|
||||
# privacy extensions rotate them, and a set that misses the current one
|
||||
# is a silent bypass. So the set is not written from a neighbour-table
|
||||
# snapshot that something would have to keep refreshing; the data path
|
||||
# fills it. Every v6 packet the client sends crosses LAN ingress with its
|
||||
# own MAC, and the feed rule there adds its source address with a timeout
|
||||
# ("update" re-arms it on every packet, so an address in use never
|
||||
# expires). A new temporary address is in the set from its first packet,
|
||||
# one that stopped being used drops out after the timeout. Download that
|
||||
# no packet from the client ever preceded is the only thing that is not
|
||||
# covered, and for TCP there is none. Link-local is left out: it never
|
||||
# crosses the router.
|
||||
#
|
||||
# Verified on kernel 6.12 / nftables 1.0.9 with a client in its own netns
|
||||
# behind a bridge: one ping from a privacy address put that address in the
|
||||
# set, and a 5 Mbit/s limit held an iperf3 download to it at ~6 Mbit/s.
|
||||
ul6_expr=""; dl6_expr=""; feed6_expr=""
|
||||
v6set="tctl_v6_$slug"
|
||||
if mac=$(tctl_target_mac "$ip"); then
|
||||
ul6_expr="meta protocol ip6 ether saddr $mac limit rate over ${rate_kbyte_up} kbytes/second"
|
||||
feed6_expr="meta protocol ip6 ether saddr $mac ip6 saddr != fe80::/10 update @$v6set { ip6 saddr }"
|
||||
dl6_expr="meta protocol ip6 ip6 daddr @$v6set limit rate over ${rate_kbyte} kbytes/second"
|
||||
fi
|
||||
|
||||
if [ "$TCTL_FW" = "nft" ]; then
|
||||
nft add table netdev tm_ratelimit 2>/dev/null
|
||||
# The set has to exist before any rule that names it. Without it the
|
||||
# v6 download half is skipped and reported as such, never half-built.
|
||||
if [ -n "$dl6_expr" ] && ! nft add set netdev tm_ratelimit "$v6set" \
|
||||
"{ type ipv6_addr; flags dynamic,timeout; timeout ${TCTL_V6SET_TIMEOUT:-1h}; }" 2>/dev/null; then
|
||||
feed6_expr=""; dl6_expr=""
|
||||
fi
|
||||
|
||||
# Download is policed at LAN EGRESS, not WAN ingress.
|
||||
#
|
||||
@@ -183,7 +208,7 @@ tctl_ratelimit_add() {
|
||||
# the IP stack, and the port-level egress hook never fires for it.
|
||||
# Verified on kernel 6.12: br-lan egress counted every packet, the
|
||||
# ports counted none.
|
||||
local dev chain dl_ok=0 wan_dev
|
||||
local dev chain dl_ok=0 dl6_ok=0 wan_dev
|
||||
for dev in $(tctl_get_lan_devices); do
|
||||
chain=$(tctl_egress_chain "$dev")
|
||||
nft add chain netdev tm_ratelimit "$chain" \
|
||||
@@ -191,15 +216,23 @@ tctl_ratelimit_add() {
|
||||
nft add rule netdev tm_ratelimit "$chain" \
|
||||
"$dl_expr counter drop comment \"$comment\"" 2>/dev/null \
|
||||
&& dl_ok=1
|
||||
[ -n "$dl6_expr" ] || continue
|
||||
nft add rule netdev tm_ratelimit "$chain" \
|
||||
"$dl6_expr counter drop comment \"${comment}_dl6\"" 2>/dev/null \
|
||||
&& dl6_ok=1
|
||||
done
|
||||
# Kernels older than 5.16 have no netdev egress hook; fall back to WAN
|
||||
# ingress, which is correct as long as the client isn't masqueraded.
|
||||
# IPv6 is not masqueraded at all, so the v6 rule is correct there too.
|
||||
if [ "$dl_ok" = "0" ] && wan_dev=$(tctl_get_wan_device); then
|
||||
nft add chain netdev tm_ratelimit dl \
|
||||
"{ type filter hook ingress device $wan_dev priority -200; policy accept; }" 2>/dev/null
|
||||
nft add rule netdev tm_ratelimit dl \
|
||||
"$dl_expr counter drop comment \"$comment\"" 2>/dev/null \
|
||||
&& dl_ok=1
|
||||
[ -n "$dl6_expr" ] && nft add rule netdev tm_ratelimit dl \
|
||||
"$dl6_expr counter drop comment \"${comment}_dl6\"" 2>/dev/null \
|
||||
&& dl6_ok=1
|
||||
fi
|
||||
# Set for the caller to inspect (see trafficctl-ratelimit.sh).
|
||||
# shellcheck disable=SC2034
|
||||
@@ -208,7 +241,7 @@ tctl_ratelimit_add() {
|
||||
# One chain per ingress device: a device that refuses the hook then
|
||||
# only loses its own chain instead of taking the whole set with it.
|
||||
local dev chain
|
||||
local ul_ok=0 ul6_ok=0
|
||||
local ul_ok=0 ul6_ok=0 feed6_ok=0
|
||||
for dev in $(tctl_ingress_devices); do
|
||||
chain=$(tctl_ingress_chain "$dev")
|
||||
nft add chain netdev tm_ratelimit "$chain" \
|
||||
@@ -217,6 +250,11 @@ tctl_ratelimit_add() {
|
||||
"$ul_expr counter drop comment \"${comment}_ul\"" 2>/dev/null \
|
||||
&& ul_ok=1
|
||||
[ -n "$ul6_expr" ] || continue
|
||||
# Feed before police, so a packet the upload limit drops still
|
||||
# keeps its address in the download set.
|
||||
[ -n "$feed6_expr" ] && nft add rule netdev tm_ratelimit "$chain" \
|
||||
"$feed6_expr comment \"${comment}_v6\"" 2>/dev/null \
|
||||
&& feed6_ok=1
|
||||
nft add rule netdev tm_ratelimit "$chain" \
|
||||
"$ul6_expr counter drop comment \"${comment}_ul6\"" 2>/dev/null \
|
||||
&& ul6_ok=1
|
||||
@@ -226,11 +264,21 @@ tctl_ratelimit_add() {
|
||||
# Set for the caller to report: "limited" means v4 only unless this is 1.
|
||||
# shellcheck disable=SC2034
|
||||
TCTL_RL_UPLOAD6_OK="$ul6_ok"
|
||||
# A download rule over a set nothing feeds matches nothing, so it only
|
||||
# counts when both halves loaded.
|
||||
# shellcheck disable=SC2034
|
||||
if [ "$dl6_ok" = "1" ] && [ "$feed6_ok" = "1" ]; then
|
||||
TCTL_RL_DOWNLOAD6_OK=1
|
||||
else
|
||||
TCTL_RL_DOWNLOAD6_OK=0
|
||||
fi
|
||||
else
|
||||
# fw3/iptables stays IPv4-only. A half-built ip6tables path would be
|
||||
# worse than a stated gap; see docs/COMPATIBILITY.md.
|
||||
# shellcheck disable=SC2034
|
||||
TCTL_RL_UPLOAD6_OK=0
|
||||
# shellcheck disable=SC2034
|
||||
TCTL_RL_DOWNLOAD6_OK=0
|
||||
iptables -t mangle -A FORWARD -d "$ip" -m hashlimit \
|
||||
--hashlimit-above "${rate_kbit}kbit/sec" --hashlimit-burst "${rate_kbit}kbit" \
|
||||
--hashlimit-mode dstip --hashlimit-name "rl_${comment}" \
|
||||
@@ -247,9 +295,11 @@ tctl_ratelimit_remove() {
|
||||
local chain h
|
||||
|
||||
if [ "$TCTL_FW" = "nft" ]; then
|
||||
# Scan the whole table once: rules for this IP live in dl (daddr), in
|
||||
# one ul_<dev> chain per ingress device (saddr), and — for a host with
|
||||
# a known MAC — a second, IPv6-scoped rule in each of those (_ul6).
|
||||
# Scan the whole table once: rules for this IP live in the dl chains
|
||||
# (daddr, plus _dl6 over the v6 address set), in one ul_<dev> chain per
|
||||
# ingress device (saddr), and — for a host with a known MAC — the
|
||||
# IPv6-scoped _ul6 rule and the _v6 rule feeding the set in each of
|
||||
# those.
|
||||
#
|
||||
# Each comment is matched in FULL, closing quote included. The suffixes
|
||||
# are prefixes of one another ("_ul" of "_ul6"), and every comment ends
|
||||
@@ -258,12 +308,15 @@ tctl_ratelimit_remove() {
|
||||
nft -a list table netdev tm_ratelimit 2>/dev/null | awk -v cmt="$comment" '
|
||||
/^[ \t]*chain [a-zA-Z0-9_]+ \{/ { chain = $2; next }
|
||||
index($0, "\"" cmt "\"") || index($0, "\"" cmt "_ul\"") ||
|
||||
index($0, "\"" cmt "_ul6\"") {
|
||||
index($0, "\"" cmt "_ul6\"") || index($0, "\"" cmt "_dl6\"") ||
|
||||
index($0, "\"" cmt "_v6\"") {
|
||||
for (i = 1; i < NF; i++)
|
||||
if ($i == "handle") { print chain, $(i+1); break }
|
||||
}' | while read -r chain h; do
|
||||
[ -n "$chain" ] && [ -n "$h" ] && nft delete rule netdev tm_ratelimit "$chain" handle "$h" 2>/dev/null
|
||||
done
|
||||
# Only once nothing references it: nft refuses to delete a set in use.
|
||||
nft delete set netdev tm_ratelimit "tctl_v6_$(tctl_target_slug "$ip")" 2>/dev/null
|
||||
else
|
||||
while iptables -t mangle -D FORWARD -d "$ip" -m comment --comment "$comment" 2>/dev/null; do :; done
|
||||
while iptables -t mangle -D FORWARD -s "$ip" -m comment --comment "${comment}_ul" 2>/dev/null; do :; done
|
||||
@@ -931,6 +984,33 @@ TCTL_SEEN_MAX=1000
|
||||
# TCTL_LEASES_FILE is set once at the top of this file (overridable for tests).
|
||||
TCTL_SHAPES_FILE="/etc/trafficctl/shapes.json"
|
||||
|
||||
# "<ip> <classid>" for every shape in shapes.json — the only way from an
|
||||
# address to its HTB class, because minors are ALLOCATED (see
|
||||
# trafficctl-shape.sh), not derived from the address. Readers that derived
|
||||
# them anyway showed one device's rate on another.
|
||||
#
|
||||
# Same record pattern as shapes_entries in trafficctl-shape.sh, optional
|
||||
# rate_kbit_up included: a pattern that misses a record form makes that shape
|
||||
# vanish from every reader. A record from before allocation carries no
|
||||
# classid, and its class is still the one the old derivation gave it
|
||||
# (legacy_classid there), so that is what it maps to.
|
||||
tctl_shape_classmap() {
|
||||
grep -oE '\{"ip":"[^"]+","rate_kbit":[0-9]+(,"classid":"1:[0-9a-f]+")?(,"rate_kbit_up":[0-9]+)?\}' \
|
||||
"$TCTL_SHAPES_FILE" 2>/dev/null | awk '
|
||||
{
|
||||
if (!match($0, /"ip":"[^"]+"/)) next
|
||||
ip = substr($0, RSTART + 6, RLENGTH - 7)
|
||||
if (match($0, /"classid":"1:[0-9a-f]+"/)) {
|
||||
print ip, substr($0, RSTART + 11, RLENGTH - 12)
|
||||
next
|
||||
}
|
||||
if (split(ip, o, ".") != 4) next
|
||||
dec = o[3] * 256 + o[4]
|
||||
if (dec < 2 || dec > 65533) next
|
||||
printf "%s 1:%x\n", ip, dec
|
||||
}'
|
||||
}
|
||||
|
||||
tctl_seen_normalize() {
|
||||
printf '%s' "$1" | tr 'A-Z' 'a-z'
|
||||
}
|
||||
|
||||
@@ -70,17 +70,24 @@ else
|
||||
TCTL_RL_DOWNLOAD_FAILED=0
|
||||
TCTL_RL_UPLOAD_FAILED=0
|
||||
TCTL_RL_UPLOAD6_OK=0
|
||||
TCTL_RL_DOWNLOAD6_OK=0
|
||||
tctl_ratelimit_add "$IP" "$RATE" "$COMMENT" "$MODE" "$RATE_UP"
|
||||
|
||||
# IPv6 coverage is partial by construction and the operator has to know
|
||||
# which part: upload is policed on the MAC, download is not policed at all
|
||||
# over v6 (see tctl_ratelimit_add). Claiming "both directions" without
|
||||
# this note is how issue #67 stayed invisible.
|
||||
if [ "$TCTL_RL_UPLOAD6_OK" = "1" ]; then
|
||||
# IPv6 coverage depends on whether the target has a MAC to key on, and
|
||||
# each half can fail to load on its own. The operator has to know which
|
||||
# part is live: claiming "both directions" without this note is how issue
|
||||
# #67 stayed invisible.
|
||||
if [ "$TCTL_RL_UPLOAD6_OK" = "1" ] && [ "$TCTL_RL_DOWNLOAD6_OK" = "1" ]; then
|
||||
V6NOTE=" [IPv6: both directions]"
|
||||
elif [ "$TCTL_RL_UPLOAD6_OK" = "1" ]; then
|
||||
V6NOTE=" [IPv6: upload only]"
|
||||
elif [ "$TCTL_RL_DOWNLOAD6_OK" = "1" ]; then
|
||||
V6NOTE=" [IPv6: download only]"
|
||||
else
|
||||
V6NOTE=" [IPv4 only]"
|
||||
fi
|
||||
V6UP=$([ "$TCTL_RL_UPLOAD6_OK" = "1" ] && echo true || echo false)
|
||||
V6DN=$([ "$TCTL_RL_DOWNLOAD6_OK" = "1" ] && echo true || echo false)
|
||||
|
||||
# A half-applied limit is a silent trap: report exactly which direction
|
||||
# is live rather than claiming success for both.
|
||||
@@ -108,10 +115,12 @@ else
|
||||
NSS="$(tctl_nss_status)"
|
||||
PERSIST=$(tctl_persist_enabled && echo true || echo false)
|
||||
if [ "$TCTL_RL_DOWNLOAD_FAILED" = "1" ]; then
|
||||
echo "{\"ok\":true,\"ipv6_upload\":$([ "$TCTL_RL_UPLOAD6_OK" = "1" ] && echo true || echo false),\"nss\":\"$NSS\",\"persist\":$PERSIST,\"msg\":\"rate limit ${RATE_UP} kbit/s applied to $IP UPLOAD ONLY — WAN device not resolvable$V6NOTE$NSSNOTE\"}"
|
||||
echo "{\"ok\":true,\"ipv6_upload\":$V6UP,\"ipv6_download\":$V6DN,\"nss\":\"$NSS\",\"persist\":$PERSIST,\"msg\":\"rate limit ${RATE_UP} kbit/s applied to $IP UPLOAD ONLY — WAN device not resolvable$V6NOTE$NSSNOTE\"}"
|
||||
elif [ "$TCTL_RL_UPLOAD_FAILED" = "1" ]; then
|
||||
echo "{\"ok\":true,\"ipv6_upload\":false,\"nss\":\"$NSS\",\"persist\":$PERSIST,\"msg\":\"rate limit ${RATE} kbit/s applied to $IP DOWNLOAD ONLY — no LAN ingress device [IPv4 only]$NSSNOTE\"}"
|
||||
# No ingress device means nothing feeds the v6 download set either, so
|
||||
# tctl_ratelimit_add has already reported that half as not live.
|
||||
echo "{\"ok\":true,\"ipv6_upload\":false,\"ipv6_download\":false,\"nss\":\"$NSS\",\"persist\":$PERSIST,\"msg\":\"rate limit ${RATE} kbit/s applied to $IP DOWNLOAD ONLY — no LAN ingress device [IPv4 only]$NSSNOTE\"}"
|
||||
else
|
||||
echo "{\"ok\":true,\"ipv6_upload\":$([ "$TCTL_RL_UPLOAD6_OK" = "1" ] && echo true || echo false),\"nss\":\"$NSS\",\"persist\":$PERSIST,\"msg\":\"rate limit $(rate_text) for $IP (both directions, $MODE)$V6NOTE$NSSNOTE\"}"
|
||||
echo "{\"ok\":true,\"ipv6_upload\":$V6UP,\"ipv6_download\":$V6DN,\"nss\":\"$NSS\",\"persist\":$PERSIST,\"msg\":\"rate limit $(rate_text) for $IP (both directions, $MODE)$V6NOTE$NSSNOTE\"}"
|
||||
fi
|
||||
fi
|
||||
@@ -21,9 +21,14 @@ fi
|
||||
# Map classid → real IP from persisted shapes. Minors are allocated, not derived
|
||||
# from the address, so this map is the only way back to an IP; classes missing
|
||||
# from it belong to another tc user and are skipped rather than mislabelled.
|
||||
CLASSMAP=$(grep -oE '\{"ip":"[^"]+","rate_kbit":[0-9]+,"classid":"1:[0-9a-f]+"\}' \
|
||||
/etc/trafficctl/shapes.json 2>/dev/null | \
|
||||
sed -n 's/{"ip":"\([^"]*\)","rate_kbit":[0-9]*,"classid":"\(1:[0-9a-f]*\)"}/\2 \1/p')
|
||||
# The pattern used to require the record to end at the classid, so every shape
|
||||
# with its own upload ceiling ("rate_kbit_up") was dropped from this output,
|
||||
# and with it from tctl_has_limit.
|
||||
#
|
||||
# Flattened to ONE "classid ip classid ip …" line: a multi-line value passed
|
||||
# through `awk -v` is rejected by some awks ("newline in string"), which left
|
||||
# this output empty. Neither field can contain a space.
|
||||
CLASSMAP=$(tctl_shape_classmap | awk '{ printf "%s %s ", $2, $1 }')
|
||||
|
||||
# Collect class stats into a temp file so we can merge with qdisc stats
|
||||
CLASS_DATA=$(tc -s class show dev "$LAN_DEV" 2>/dev/null)
|
||||
@@ -32,11 +37,9 @@ QDISC_DATA=$(tc -s qdisc show dev "$LAN_DEV" 2>/dev/null)
|
||||
# Parse class stats: emit lines "classid ip rate bytes pkts backlog drops overlimits requeues lended borrowed"
|
||||
CLASS_PARSED=$(echo "$CLASS_DATA" | awk -v classmap="$CLASSMAP" '
|
||||
BEGIN {
|
||||
n = split(classmap, cml, "\n")
|
||||
for (ci = 1; ci <= n; ci++) {
|
||||
split(cml[ci], cmf, " ")
|
||||
if (cmf[1] != "" && cmf[2] != "") ipmap[cmf[1]] = cmf[2]
|
||||
}
|
||||
n = split(classmap, cmf, " ")
|
||||
for (ci = 1; ci + 1 <= n; ci += 2)
|
||||
if (cmf[ci] != "" && cmf[ci + 1] != "") ipmap[cmf[ci]] = cmf[ci + 1]
|
||||
}
|
||||
|
||||
/class fq_codel/ { skip = 1; next }
|
||||
|
||||
@@ -242,6 +242,9 @@ if command -v tc >/dev/null 2>&1; then
|
||||
}')
|
||||
fi
|
||||
|
||||
# address → classid, from shapes.json (see tctl_shape_classmap).
|
||||
SHAPE_MAP=$(tctl_shape_classmap)
|
||||
|
||||
# Wifi ACL state, split by policy, because "listed" means opposite things in the
|
||||
# two modes: on a deny (blacklist) radio a listed MAC is blocked, on an allow
|
||||
# (whitelist) radio a listed MAC is the only kind permitted. Collected once here
|
||||
@@ -355,14 +358,14 @@ lookup_rate_limit() {
|
||||
echo "${r:-0}"
|
||||
}
|
||||
|
||||
# The classid is looked up, never derived from the address: minors are
|
||||
# allocated, so deriving put the first shaped device's rate on 192.168.x.2 and
|
||||
# showed 0 for the device actually shaped — and the Telegram bot built its
|
||||
# "Shape — Remove" button from this field.
|
||||
lookup_shape_kbit() {
|
||||
local ip="$1" o3 o4 dec hex classid r
|
||||
o3=$(echo "$ip" | cut -d. -f3)
|
||||
o4=$(echo "$ip" | cut -d. -f4)
|
||||
dec=$((o3 * 256 + o4))
|
||||
hex=$(printf "%x" "$dec")
|
||||
case "$hex" in 1|fffe) echo 0; return ;; esac
|
||||
classid="1:$hex"
|
||||
local ip="$1" classid r
|
||||
classid=$(echo "$SHAPE_MAP" | awk -v ip="$ip" '$1==ip{print $2; exit}')
|
||||
[ -n "$classid" ] || { echo 0; return; }
|
||||
r=$(echo "$TC_MAP" | awk -v c="$classid" '$1==c{print $2; exit}')
|
||||
echo "${r:-0}"
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user