mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 16:44:23 +08:00
104 lines
4.2 KiB
Bash
104 lines
4.2 KiB
Bash
#!/bin/sh
|
|
# shellcheck shell=dash
|
|
# Triggered by dnsmasq on DHCP events (add/old/del).
|
|
# Available env vars: ACTION, MACADDR, IPADDR, HOSTNAME
|
|
#
|
|
# Two consumers sit on "this MAC is new":
|
|
# * the Telegram bot, which wants a notification
|
|
# * the default-limit feature, which applies a rate limit or shape
|
|
#
|
|
# They are deliberately independent — either can be on without the other —
|
|
# and they use different notions of "known". The bot has always keyed off its
|
|
# own telegram_known.json, which the bot itself rewrites; the limiter uses the
|
|
# seen-MAC ledger in trafficctl-fw.sh, because applying a limit is not an
|
|
# action that may be repeated or misfired.
|
|
|
|
[ "$ACTION" = "add" ] || exit 0
|
|
[ -n "$MACADDR" ] || exit 0
|
|
|
|
# ── Telegram notification ──────────────────────────────────────────────────
|
|
|
|
KNOWN_FILE="/etc/trafficctl/telegram_known.json"
|
|
NOTIFY_FLAG="/tmp/trafficctl_tg_newdev"
|
|
|
|
if [ "$(uci -q get trafficctl.telegram.enabled)" = "1" ] &&
|
|
! grep -q "\"$MACADDR\"" "$KNOWN_FILE" 2>/dev/null; then
|
|
mkdir -p "$(dirname "$NOTIFY_FLAG")"
|
|
# HOSTNAME comes from the dnsmasq dhcp hotplug environment (see header),
|
|
# not a POSIX shell builtin — shellcheck's SC3028 is a false positive.
|
|
# shellcheck disable=SC3028
|
|
printf '%s\t%s\t%s\n' "$MACADDR" "${IPADDR:-?}" "${HOSTNAME:-unknown}" >> "$NOTIFY_FLAG"
|
|
fi
|
|
|
|
# ── Default limit for a device seen for the first time ─────────────────────
|
|
|
|
[ "$(uci -q get trafficctl.newdevice.enabled 2>/dev/null)" = "1" ] || exit 0
|
|
|
|
LIMIT_KBIT=$(uci -q get trafficctl.newdevice.limit_kbit 2>/dev/null)
|
|
[ -n "$LIMIT_KBIT" ] && [ "$LIMIT_KBIT" -gt 0 ] 2>/dev/null || exit 0
|
|
|
|
. /usr/local/bin/trafficctl-fw.sh
|
|
|
|
# The ledger decides what "new" means, so a run that has no ledger yet cannot
|
|
# answer the question — and the wrong answer here limits every device on the
|
|
# LAN as its lease renews.
|
|
#
|
|
# Seeding alone would not make that answer trustworthy: the seed reads the
|
|
# leases and the neighbour table, and both can be thin or empty early in boot,
|
|
# so a device missing from them would look new purely because the router had
|
|
# not noticed it yet. The first pass therefore records the baseline and
|
|
# applies nothing at all, whatever the seed happened to find.
|
|
if ! tctl_seen_ready; then
|
|
tctl_seen_seed
|
|
tctl_seen_mark "$MACADDR"
|
|
logger -t trafficctl "newdevice: seeded the seen-MAC ledger ($(tctl_seen_count) devices); no limit applied on this pass"
|
|
exit 0
|
|
fi
|
|
|
|
tctl_seen_known "$MACADDR" && exit 0
|
|
|
|
# Mark BEFORE acting. A failure while applying the limit must not leave the
|
|
# device unmarked, or every subsequent lease renewal retries — and each retry
|
|
# is another chance to race with an operator setting a limit by hand.
|
|
tctl_seen_mark "$MACADDR"
|
|
|
|
[ -n "$IPADDR" ] || {
|
|
logger -t trafficctl "newdevice: $MACADDR is new but has no address yet; no limit applied"
|
|
exit 0
|
|
}
|
|
|
|
# Never override a limit somebody set deliberately.
|
|
if tctl_has_limit "$IPADDR"; then
|
|
logger -t trafficctl "newdevice: $IPADDR ($MACADDR) already has a limit; left alone"
|
|
exit 0
|
|
fi
|
|
|
|
LIMIT_MODE=$(uci -q get trafficctl.newdevice.limit_mode 2>/dev/null)
|
|
[ -n "$LIMIT_MODE" ] || LIMIT_MODE="limiter"
|
|
|
|
case "$LIMIT_MODE" in
|
|
shaper)
|
|
TCTL_VIA=newdevice TCTL_SRC=hotplug \
|
|
/usr/local/bin/trafficctl-shape.sh add "$IPADDR" "$LIMIT_KBIT" \
|
|
"shape_$IPADDR" >/dev/null 2>&1
|
|
rc=$?
|
|
;;
|
|
*)
|
|
TCTL_VIA=newdevice TCTL_SRC=hotplug \
|
|
/usr/local/bin/trafficctl-ratelimit.sh "$IPADDR" "$LIMIT_KBIT" \
|
|
"$(tctl_ratelimit_comment "$IPADDR")" >/dev/null 2>&1
|
|
rc=$?
|
|
;;
|
|
esac
|
|
|
|
if [ "$rc" = "0" ]; then
|
|
# trafficctl-ratelimit.sh writes its own activity-log entry and picks up
|
|
# the via=newdevice/src=hotplug exported above, so the limiter path is
|
|
# already recorded. trafficctl-shape.sh logs nothing, hence this branch.
|
|
[ "$LIMIT_MODE" = "shaper" ] &&
|
|
tctl_log "shape_set" "$IPADDR" \
|
|
"${LIMIT_KBIT}kbit default for new device $MACADDR" "newdevice" "hotplug"
|
|
else
|
|
logger -t trafficctl "newdevice: failed to apply ${LIMIT_KBIT}kbit to $IPADDR ($MACADDR)"
|
|
fi
|