mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 05:53:56 +08:00
609 lines
23 KiB
Bash
Executable File
609 lines
23 KiB
Bash
Executable File
#!/bin/sh
|
|
# shellcheck shell=dash
|
|
# rpcd/ubus backend for luci-app-trafficctl.
|
|
# Provides ubus object "luci.trafficctl" with methods for traffic control.
|
|
|
|
. /usr/local/bin/trafficctl-fw.sh
|
|
|
|
SCRIPTS="/usr/local/bin"
|
|
|
|
# Strip unsafe chars from label (allow alphanumeric, dash, underscore, dot)
|
|
sanitize_label() { echo "$1" | tr -cd 'a-zA-Z0-9_.-'; }
|
|
|
|
# The config holds the Telegram bot token and the metrics token. uci rewrites the
|
|
# file at the default mode on every commit, so the restriction is re-applied here
|
|
# rather than at the one call site that first set it.
|
|
tctl_commit() {
|
|
uci commit trafficctl
|
|
chmod 0600 /etc/config/trafficctl 2>/dev/null
|
|
}
|
|
|
|
# Detect LuCI session peer IP
|
|
get_peer_ip() {
|
|
local sid
|
|
sid=$(echo "$HTTP_SID" | tr -cd 'a-f0-9')
|
|
if [ -n "$sid" ]; then
|
|
ubus call session info "{\"ubus_rpc_session\":\"$sid\"}" 2>/dev/null | \
|
|
jsonfilter -e '@.data.peer' 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
export TCTL_VIA="luci"
|
|
export TCTL_SRC="${REMOTE_ADDR:-$(get_peer_ip)}"
|
|
export TCTL_USER="${USERNAME:-root}"
|
|
|
|
case "$1" in
|
|
list)
|
|
printf '{'
|
|
printf '"summary":{},'
|
|
printf '"device":{"ip":"str","proto":"str"},'
|
|
printf '"bytes":{},'
|
|
printf '"ifaces":{},'
|
|
printf '"block":{"ip":"str","label":"str"},'
|
|
printf '"unblock":{"ip":"str","label":"str"},'
|
|
printf '"macfilter_add":{"ip":"str"},'
|
|
printf '"macfilter_remove":{"ip":"str"},'
|
|
printf '"ratelimit":{"ip":"str","rate_kbit":"int","label":"str","mode":"str","rate_kbit_up":"int"},'
|
|
printf '"ratelimit_stats":{},'
|
|
printf '"subnets":{},'
|
|
printf '"shape_add":{"ip":"str","rate_kbit":"int","label":"str","rate_kbit_up":"int"},'
|
|
printf '"shape_remove":{"ip":"str","label":"str"},'
|
|
printf '"shape_status":{"ip":"str"},'
|
|
printf '"shape_stats":{},'
|
|
printf '"rdns":{"ip":"str"},'
|
|
printf '"netify_status":{},'
|
|
printf '"netify_list":{},'
|
|
printf '"netify_collect":{"secs":"int"},'
|
|
printf '"names_list":{},'
|
|
printf '"name_set":{"ip":"str","name":"str"},'
|
|
printf '"name_clear":{"ip":"str"},'
|
|
printf '"portfw_list":{},'
|
|
printf '"portfw_ctl":{"action":"str","scope":"str","proto":"str","ip":"str","port":"str","rate_kbit":"int"},'
|
|
printf '"config_get":{},'
|
|
printf '"config_set":{"enabled":"bool","default_mode":"str","sw":"bool","hw":"bool","poll_interval":"int","avg_window":"int"},'
|
|
printf '"telegram_config_get":{},'
|
|
printf '"telegram_config_set":{"enabled":"bool","bot_token":"str","chat_id":"str","poll_interval":"int","notify_new_device":"bool","notify_known_device":"bool","control_enabled":"bool","notify_template":"str","btn_block_inet":"bool","btn_block_wifi":"bool","btn_limiter":"bool","btn_shaper":"bool"},'
|
|
printf '"telegram_test":{"bot_token":"str","chat_id":"str","message":"str"},'
|
|
printf '"newdevice_config_get":{},'
|
|
printf '"newdevice_config_set":{"enabled":"bool","limit_kbit":"int","limit_mode":"str"},'
|
|
printf '"logging_config_get":{},'
|
|
printf '"logging_config_set":{"enabled":"bool","log_file":"str","max_lines":"int","syslog":"bool","log_blocks":"bool","log_ratelimits":"bool","log_shapes":"bool","log_telegram":"bool","log_config":"bool"},'
|
|
printf '"cut_status":{},'
|
|
printf '"cut_set":{"active":"bool","duration":"int","persist":"bool"},'
|
|
printf '"activity_log":{"lines":"int"},'
|
|
printf '"version":{}'
|
|
printf '}'
|
|
;;
|
|
call)
|
|
case "$2" in
|
|
summary)
|
|
printf '{"result":'
|
|
"$SCRIPTS/trafficctl-summary.sh"
|
|
printf '}'
|
|
;;
|
|
device)
|
|
read -r input
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
proto=$(echo "$input" | jsonfilter -e '@.proto' 2>/dev/null)
|
|
if [ -z "$ip" ]; then
|
|
echo '{"ok":false,"msg":"ip required"}'
|
|
else
|
|
case "$proto" in
|
|
tcp|udp) "$SCRIPTS/trafficctl-device.sh" "$ip" --proto "$proto" ;;
|
|
*) "$SCRIPTS/trafficctl-device.sh" "$ip" ;;
|
|
esac
|
|
fi
|
|
;;
|
|
bytes)
|
|
# trafficctl-totals.sh wraps trafficctl-bytes.sh: same array, same
|
|
# bytes_in/bytes_out the speed graph differentiates, plus the lifetime
|
|
# *_total fields the Bytes/TCP/UDP columns show. Folded into this one
|
|
# call rather than given a method of its own so the accumulator sees
|
|
# exactly one sample per poll — a second method would mean a second
|
|
# /proc/net/nf_conntrack read every two seconds.
|
|
#
|
|
# Note this makes a read-ACL method write the tmpfs accumulator as a
|
|
# side effect. That is what keeps the counter advancing while somebody
|
|
# is watching the page; a metrics scrape has always done the same.
|
|
printf '{"result":'
|
|
"$SCRIPTS/trafficctl-totals.sh"
|
|
printf '}'
|
|
;;
|
|
ifaces)
|
|
printf '{"result":'
|
|
"$SCRIPTS/trafficctl-ifaces.sh"
|
|
printf '}'
|
|
;;
|
|
block)
|
|
read -r input
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
label=$(sanitize_label "$(echo "$input" | jsonfilter -e '@.label' 2>/dev/null)")
|
|
if [ -z "$ip" ]; then
|
|
echo '{"ok":false,"msg":"ip required"}'
|
|
else
|
|
"$SCRIPTS/trafficctl-block.sh" "$ip" "$label"
|
|
fi
|
|
;;
|
|
unblock)
|
|
read -r input
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
label=$(sanitize_label "$(echo "$input" | jsonfilter -e '@.label' 2>/dev/null)")
|
|
if [ -z "$ip" ]; then
|
|
echo '{"ok":false,"msg":"ip required"}'
|
|
else
|
|
"$SCRIPTS/trafficctl-unblock.sh" "$ip" "$label"
|
|
fi
|
|
;;
|
|
macfilter_add)
|
|
read -r input
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
if [ -z "$ip" ]; then
|
|
echo '{"ok":false,"msg":"ip required"}'
|
|
else
|
|
"$SCRIPTS/trafficctl-macfilter-add.sh" "$ip"
|
|
fi
|
|
;;
|
|
macfilter_remove)
|
|
read -r input
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
if [ -z "$ip" ]; then
|
|
echo '{"ok":false,"msg":"ip required"}'
|
|
else
|
|
"$SCRIPTS/trafficctl-macfilter-remove.sh" "$ip"
|
|
fi
|
|
;;
|
|
ratelimit)
|
|
read -r input
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
rate=$(echo "$input" | jsonfilter -e '@.rate_kbit' 2>/dev/null)
|
|
label=$(sanitize_label "$(echo "$input" | jsonfilter -e '@.label' 2>/dev/null)")
|
|
mode=$(echo "$input" | jsonfilter -e '@.mode' 2>/dev/null)
|
|
case "$mode" in each|shared) ;; *) mode="" ;; esac
|
|
rate_up=$(echo "$input" | jsonfilter -e '@.rate_kbit_up' 2>/dev/null)
|
|
# A caller that does not send the field — any UI older than this
|
|
# method signature — means "symmetric", NOT "no upload allowed". Zero
|
|
# is normalised away for the same reason: a zero ceiling polices every
|
|
# packet, which is a total upload block reporting itself as a limit.
|
|
[ "$rate_up" = "0" ] && rate_up=""
|
|
if [ -z "$ip" ]; then
|
|
echo '{"ok":false,"msg":"ip required"}'
|
|
else
|
|
"$SCRIPTS/trafficctl-ratelimit.sh" "$ip" "${rate:-0}" "$label" "$mode" "$rate_up"
|
|
fi
|
|
;;
|
|
ratelimit_stats)
|
|
printf '{"result":'
|
|
"$SCRIPTS/trafficctl-ratelimit-stats.sh"
|
|
printf '}'
|
|
;;
|
|
subnets)
|
|
printf '{"result":'
|
|
"$SCRIPTS/trafficctl-subnets.sh"
|
|
printf '}'
|
|
;;
|
|
shape_add)
|
|
read -r input
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
rate=$(echo "$input" | jsonfilter -e '@.rate_kbit' 2>/dev/null)
|
|
label=$(sanitize_label "$(echo "$input" | jsonfilter -e '@.label' 2>/dev/null)")
|
|
rate_up=$(echo "$input" | jsonfilter -e '@.rate_kbit_up' 2>/dev/null)
|
|
# Same reading as the limiter: absent or zero means "same as download".
|
|
[ "$rate_up" = "0" ] && rate_up=""
|
|
if [ -z "$ip" ] || [ -z "$rate" ]; then
|
|
echo '{"ok":false,"msg":"ip and rate_kbit required"}'
|
|
else
|
|
"$SCRIPTS/trafficctl-shape.sh" add "$ip" "$rate" "$label" "$rate_up"
|
|
fi
|
|
;;
|
|
shape_remove)
|
|
read -r input
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
label=$(echo "$input" | jsonfilter -e '@.label' 2>/dev/null)
|
|
if [ -z "$ip" ]; then
|
|
echo '{"ok":false,"msg":"ip required"}'
|
|
else
|
|
"$SCRIPTS/trafficctl-shape.sh" remove "$ip"
|
|
fi
|
|
;;
|
|
shape_status)
|
|
read -r input
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
if [ -z "$ip" ]; then
|
|
echo '{"ok":false,"msg":"ip required"}'
|
|
else
|
|
"$SCRIPTS/trafficctl-shape.sh" status "$ip"
|
|
fi
|
|
;;
|
|
shape_stats)
|
|
printf '{"result":'
|
|
"$SCRIPTS/trafficctl-shape-stats.sh"
|
|
printf '}'
|
|
;;
|
|
rdns)
|
|
read -r input
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
if [ -z "$ip" ]; then
|
|
echo '{"ok":false,"msg":"ip required"}'
|
|
else
|
|
"$SCRIPTS/trafficctl-rdns.sh" "$ip"
|
|
fi
|
|
;;
|
|
netify_status)
|
|
"$SCRIPTS/trafficctl-netify.sh" status
|
|
;;
|
|
netify_list)
|
|
printf '{"result":'
|
|
"$SCRIPTS/trafficctl-netify.sh" list
|
|
printf '}'
|
|
;;
|
|
netify_collect)
|
|
read -r input
|
|
secs=$(echo "$input" | jsonfilter -e '@.secs' 2>/dev/null)
|
|
"$SCRIPTS/trafficctl-netify.sh" collect "${secs:-3}"
|
|
;;
|
|
names_list)
|
|
printf '{"result":'
|
|
"$SCRIPTS/trafficctl-names.sh" list
|
|
printf '}'
|
|
;;
|
|
name_set)
|
|
read -r input
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null)
|
|
if [ -z "$ip" ]; then
|
|
echo '{"ok":false,"msg":"ip required"}'
|
|
elif [ -z "$name" ]; then
|
|
# Empty name means "forget the alias" so the UI can clear a name
|
|
# by submitting a blank field.
|
|
"$SCRIPTS/trafficctl-names.sh" remove "$ip"
|
|
else
|
|
"$SCRIPTS/trafficctl-names.sh" set "$ip" "$name"
|
|
fi
|
|
;;
|
|
name_clear)
|
|
read -r input
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
if [ -z "$ip" ]; then
|
|
echo '{"ok":false,"msg":"ip required"}'
|
|
else
|
|
"$SCRIPTS/trafficctl-names.sh" remove "$ip"
|
|
fi
|
|
;;
|
|
portfw_list)
|
|
printf '{"result":'
|
|
"$SCRIPTS/trafficctl-portfw.sh" list
|
|
printf '}'
|
|
;;
|
|
portfw_ctl)
|
|
read -r input
|
|
action=$(echo "$input" | jsonfilter -e '@.action' 2>/dev/null)
|
|
scope=$(echo "$input" | jsonfilter -e '@.scope' 2>/dev/null)
|
|
proto=$(echo "$input" | jsonfilter -e '@.proto' 2>/dev/null)
|
|
ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null)
|
|
port=$(echo "$input" | jsonfilter -e '@.port' 2>/dev/null)
|
|
rate=$(echo "$input" | jsonfilter -e '@.rate_kbit' 2>/dev/null)
|
|
case "$action" in
|
|
pause|resume)
|
|
"$SCRIPTS/trafficctl-portfw.sh" "$action" "$scope" "$proto" "${ip:--}" "$port"
|
|
;;
|
|
limit)
|
|
"$SCRIPTS/trafficctl-portfw.sh" limit "$scope" "$proto" "${ip:--}" "$port" "${rate:-0}"
|
|
;;
|
|
*)
|
|
echo '{"ok":false,"msg":"action must be pause, resume or limit"}'
|
|
;;
|
|
esac
|
|
;;
|
|
config_get)
|
|
enabled=$(uci -q get trafficctl.main.enabled)
|
|
mode=$(uci -q get trafficctl.main.default_mode)
|
|
offload=$(tctl_get_offload_mode)
|
|
sw=$(uci -q get firewall.@defaults[0].flow_offloading)
|
|
hw=$(uci -q get firewall.@defaults[0].flow_offloading_hw)
|
|
poll=$(uci -q get trafficctl.main.poll_interval)
|
|
window=$(uci -q get trafficctl.main.avg_window)
|
|
printf '{"enabled":%s,"default_mode":"%s","offload_mode":"%s","sw":%s,"hw":%s,"poll_interval":%d,"avg_window":%d}' \
|
|
"$([ "$enabled" = "1" ] && echo true || echo false)" \
|
|
"${mode:-limiter}" \
|
|
"$offload" \
|
|
"$([ "$sw" = "1" ] && echo true || echo false)" \
|
|
"$([ "$hw" = "1" ] && echo true || echo false)" \
|
|
"${poll:-2}" \
|
|
"${window:-15}"
|
|
;;
|
|
config_set)
|
|
read -r input
|
|
enabled=$(echo "$input" | jsonfilter -e '@.enabled' 2>/dev/null)
|
|
mode=$(echo "$input" | jsonfilter -e '@.default_mode' 2>/dev/null)
|
|
sw=$(echo "$input" | jsonfilter -e '@.sw' 2>/dev/null)
|
|
hw=$(echo "$input" | jsonfilter -e '@.hw' 2>/dev/null)
|
|
poll=$(echo "$input" | jsonfilter -e '@.poll_interval' 2>/dev/null)
|
|
window=$(echo "$input" | jsonfilter -e '@.avg_window' 2>/dev/null)
|
|
[ -n "$enabled" ] && uci set trafficctl.main.enabled="$([ "$enabled" = "true" ] && echo 1 || echo 0)"
|
|
[ -n "$mode" ] && uci set trafficctl.main.default_mode="$mode"
|
|
# Bounded rather than free-form. 0 means "do not poll"; below a second
|
|
# the browser cannot keep up and the router pays a conntrack read per
|
|
# tick for frames nobody sees. The upper bound keeps a "live" dashboard
|
|
# recognisably live.
|
|
if [ -n "$poll" ]; then
|
|
case "$poll" in
|
|
''|*[!0-9]*) echo '{"ok":false,"msg":"poll_interval must be a number"}'; exit 0 ;;
|
|
esac
|
|
if [ "$poll" != "0" ] && { [ "$poll" -lt 1 ] || [ "$poll" -gt 300 ]; }; then
|
|
echo '{"ok":false,"msg":"poll_interval must be 0 (off) or between 1 and 300 seconds"}'
|
|
exit 0
|
|
fi
|
|
uci set trafficctl.main.poll_interval="$poll"
|
|
fi
|
|
# The window is what the speed is averaged over, and the per-device
|
|
# sample history is sized from it — so an unbounded value is an
|
|
# unbounded array in every open browser, one per device.
|
|
if [ -n "$window" ]; then
|
|
case "$window" in
|
|
''|*[!0-9]*) echo '{"ok":false,"msg":"avg_window must be a number"}'; exit 0 ;;
|
|
esac
|
|
if [ "$window" -lt 2 ] || [ "$window" -gt 3600 ]; then
|
|
echo '{"ok":false,"msg":"avg_window must be between 2 and 3600 seconds"}'
|
|
exit 0
|
|
fi
|
|
uci set trafficctl.main.avg_window="$window"
|
|
fi
|
|
tctl_commit
|
|
if [ -n "$sw" ] || [ -n "$hw" ]; then
|
|
[ -n "$sw" ] && uci set "firewall.@defaults[0].flow_offloading=$([ "$sw" = "true" ] && echo 1 || echo 0)"
|
|
[ -n "$hw" ] && uci set "firewall.@defaults[0].flow_offloading_hw=$([ "$hw" = "true" ] && echo 1 || echo 0)"
|
|
uci commit firewall
|
|
/etc/init.d/firewall reload >/dev/null 2>&1 &
|
|
fi
|
|
echo '{"ok":true}'
|
|
;;
|
|
telegram_config_get)
|
|
uci -q get trafficctl.telegram >/dev/null 2>&1 || {
|
|
echo '{"enabled":false,"bot_token":"","bot_token_set":false,"chat_id":"","poll_interval":3,"notify_new_device":true,"notify_known_device":false,"control_enabled":true,"notify_template":"","btn_block_inet":true,"btn_block_wifi":true,"btn_limiter":true,"btn_shaper":true,"bot_running":false}'
|
|
exit 0
|
|
}
|
|
tg_enabled=$(uci -q get trafficctl.telegram.enabled)
|
|
tg_token=$(uci -q get trafficctl.telegram.bot_token)
|
|
tg_chat_id=$(uci -q get trafficctl.telegram.chat_id)
|
|
tg_poll=$(uci -q get trafficctl.telegram.poll_interval)
|
|
tg_nn=$(uci -q get trafficctl.telegram.notify_new_device)
|
|
tg_nk=$(uci -q get trafficctl.telegram.notify_known_device)
|
|
tg_ctrl=$(uci -q get trafficctl.telegram.control_enabled)
|
|
tg_tmpl=$(uci -q get trafficctl.telegram.notify_template)
|
|
tg_bi=$(uci -q get trafficctl.telegram.btn_block_inet)
|
|
tg_bw=$(uci -q get trafficctl.telegram.btn_block_wifi)
|
|
tg_bl=$(uci -q get trafficctl.telegram.btn_limiter)
|
|
tg_bs=$(uci -q get trafficctl.telegram.btn_shaper)
|
|
tg_token_set=false
|
|
tg_token_display=""
|
|
if [ -n "$tg_token" ]; then
|
|
tg_token_set=true
|
|
tg_token_display="***"
|
|
fi
|
|
tg_running=false
|
|
pgrep -f "trafficctl-telegram.sh" >/dev/null 2>&1 && tg_running=true
|
|
tg_tmpl_esc=$(printf '%s' "${tg_tmpl:-}" | sed 's/\\/\\\\/g;s/"/\\"/g;s/ /\\t/g')
|
|
printf '{"enabled":%s,"bot_token":"%s","bot_token_set":%s,"chat_id":"%s","poll_interval":%d,"notify_new_device":%s,"notify_known_device":%s,"control_enabled":%s,"notify_template":"%s","btn_block_inet":%s,"btn_block_wifi":%s,"btn_limiter":%s,"btn_shaper":%s,"bot_running":%s}' \
|
|
"$([ "$tg_enabled" = "1" ] && echo true || echo false)" \
|
|
"$tg_token_display" "$tg_token_set" "${tg_chat_id:-}" \
|
|
"${tg_poll:-3}" \
|
|
"$([ "${tg_nn:-1}" != "0" ] && echo true || echo false)" \
|
|
"$([ "$tg_nk" = "1" ] && echo true || echo false)" \
|
|
"$([ "${tg_ctrl:-1}" != "0" ] && echo true || echo false)" \
|
|
"$tg_tmpl_esc" \
|
|
"$([ "${tg_bi:-1}" != "0" ] && echo true || echo false)" \
|
|
"$([ "${tg_bw:-1}" != "0" ] && echo true || echo false)" \
|
|
"$([ "${tg_bl:-1}" != "0" ] && echo true || echo false)" \
|
|
"$([ "${tg_bs:-1}" != "0" ] && echo true || echo false)" \
|
|
"$tg_running"
|
|
;;
|
|
telegram_config_set)
|
|
read -r input
|
|
uci -q get trafficctl.telegram >/dev/null 2>&1 || uci set trafficctl.telegram=telegram
|
|
val=$(echo "$input" | jsonfilter -e '@.enabled' 2>/dev/null)
|
|
[ -n "$val" ] && uci set trafficctl.telegram.enabled="$([ "$val" = "true" ] && echo 1 || echo 0)"
|
|
val=$(echo "$input" | jsonfilter -e '@.bot_token' 2>/dev/null)
|
|
if [ -n "$val" ] && [ "$val" != "***" ]; then
|
|
uci set trafficctl.telegram.bot_token="$val"
|
|
elif [ -z "$val" ]; then
|
|
uci -q delete trafficctl.telegram.bot_token
|
|
fi
|
|
val=$(echo "$input" | jsonfilter -e '@.chat_id' 2>/dev/null)
|
|
[ -n "$val" ] && uci set trafficctl.telegram.chat_id="$val"
|
|
val=$(echo "$input" | jsonfilter -e '@.poll_interval' 2>/dev/null)
|
|
[ -n "$val" ] && uci set trafficctl.telegram.poll_interval="$val"
|
|
val=$(echo "$input" | jsonfilter -e '@.notify_template' 2>/dev/null)
|
|
uci set trafficctl.telegram.notify_template="$val"
|
|
for opt in notify_new_device notify_known_device control_enabled btn_block_inet btn_block_wifi btn_limiter btn_shaper; do
|
|
val=$(echo "$input" | jsonfilter -e "@.$opt" 2>/dev/null)
|
|
[ -n "$val" ] && uci set "trafficctl.telegram.$opt=$([ "$val" = "true" ] && echo 1 || echo 0)"
|
|
done
|
|
tctl_commit
|
|
tctl_log "telegram_config_change" "" "" "luci" "${TCTL_SRC:-local}"
|
|
/etc/init.d/trafficctl-telegram reload 2>/dev/null &
|
|
echo '{"ok":true}'
|
|
;;
|
|
telegram_test)
|
|
read -r input
|
|
token=$(echo "$input" | jsonfilter -e '@.bot_token' 2>/dev/null)
|
|
chat_id=$(echo "$input" | jsonfilter -e '@.chat_id' 2>/dev/null)
|
|
msg=$(echo "$input" | jsonfilter -e '@.message' 2>/dev/null)
|
|
if [ "$token" = "***" ] || [ -z "$token" ]; then
|
|
token=$(uci -q get trafficctl.telegram.bot_token)
|
|
fi
|
|
if [ -z "$token" ] || [ -z "$chat_id" ]; then
|
|
echo '{"ok":false,"msg":"token and chat_id required"}'
|
|
else
|
|
TCTL_TG_TOKEN="$token" "$SCRIPTS/trafficctl-telegram-test.sh" - "$chat_id" "$msg"
|
|
fi
|
|
;;
|
|
logging_config_get)
|
|
log_enabled=$(uci -q get trafficctl.logging.enabled)
|
|
log_file=$(uci -q get trafficctl.logging.log_file)
|
|
max_lines=$(uci -q get trafficctl.logging.max_lines)
|
|
log_syslog=$(uci -q get trafficctl.logging.syslog)
|
|
log_blocks=$(uci -q get trafficctl.logging.log_blocks)
|
|
log_ratelimits=$(uci -q get trafficctl.logging.log_ratelimits)
|
|
log_shapes=$(uci -q get trafficctl.logging.log_shapes)
|
|
log_telegram=$(uci -q get trafficctl.logging.log_telegram)
|
|
log_config=$(uci -q get trafficctl.logging.log_config)
|
|
persist=$(uci -q get trafficctl.main.persist_rules)
|
|
printf '{"enabled":%s,"log_file":"%s","max_lines":%d,"syslog":%s,"log_blocks":%s,"log_ratelimits":%s,"log_shapes":%s,"log_telegram":%s,"log_config":%s,"persist_rules":%s}' \
|
|
"$([ "${log_enabled:-1}" = "1" ] && echo true || echo false)" \
|
|
"${log_file:-/tmp/trafficctl/activity.log}" \
|
|
"${max_lines:-500}" \
|
|
"$([ "${log_syslog:-1}" = "1" ] && echo true || echo false)" \
|
|
"$([ "${log_blocks:-1}" != "0" ] && echo true || echo false)" \
|
|
"$([ "${log_ratelimits:-1}" != "0" ] && echo true || echo false)" \
|
|
"$([ "${log_shapes:-1}" != "0" ] && echo true || echo false)" \
|
|
"$([ "${log_telegram:-1}" != "0" ] && echo true || echo false)" \
|
|
"$([ "${log_config:-1}" != "0" ] && echo true || echo false)" \
|
|
"$([ "$persist" = "1" ] && echo true || echo false)"
|
|
;;
|
|
logging_config_set)
|
|
read -r input
|
|
uci -q get trafficctl.logging >/dev/null 2>&1 || uci set trafficctl.logging=logging
|
|
for opt in enabled syslog log_blocks log_ratelimits log_shapes log_telegram log_config; do
|
|
val=$(echo "$input" | jsonfilter -e "@.$opt" 2>/dev/null)
|
|
[ -n "$val" ] && uci set "trafficctl.logging.$opt=$([ "$val" = "true" ] && echo 1 || echo 0)"
|
|
done
|
|
val=$(echo "$input" | jsonfilter -e '@.log_file' 2>/dev/null)
|
|
if [ -n "$val" ]; then
|
|
if ! tctl_validate_log_file "$val"; then
|
|
echo '{"ok":false,"msg":"log_file must be under /tmp/trafficctl/ or /var/log/"}'
|
|
exit 0
|
|
fi
|
|
uci set trafficctl.logging.log_file="$val"
|
|
fi
|
|
val=$(echo "$input" | jsonfilter -e '@.max_lines' 2>/dev/null)
|
|
if [ -n "$val" ]; then
|
|
case "$val" in
|
|
''|*[!0-9]*) echo '{"ok":false,"msg":"max_lines must be a number"}'; exit 0 ;;
|
|
esac
|
|
if [ "$val" -lt 20 ] || [ "$val" -gt 100000 ]; then
|
|
echo '{"ok":false,"msg":"max_lines must be between 20 and 100000"}'
|
|
exit 0
|
|
fi
|
|
uci set trafficctl.logging.max_lines="$val"
|
|
fi
|
|
val=$(echo "$input" | jsonfilter -e '@.persist_rules' 2>/dev/null)
|
|
[ -n "$val" ] && uci set "trafficctl.main.persist_rules=$([ "$val" = "true" ] && echo 1 || echo 0)"
|
|
tctl_commit
|
|
tctl_log "config_change" "logging" "" "luci" "${TCTL_SRC:-local}"
|
|
echo '{"ok":true}'
|
|
;;
|
|
newdevice_config_get)
|
|
nd_enabled=$(uci -q get trafficctl.newdevice.enabled)
|
|
nd_kbit=$(uci -q get trafficctl.newdevice.limit_kbit)
|
|
nd_mode=$(uci -q get trafficctl.newdevice.limit_mode)
|
|
# seen_count lets the UI say how large the baseline is, so an operator
|
|
# can tell "the ledger knows my 23 devices" from "the ledger is empty
|
|
# and everything is about to look new".
|
|
printf '{"enabled":%s,"limit_kbit":%d,"limit_mode":"%s","seen_count":%d,"seeded":%s}' \
|
|
"$([ "$nd_enabled" = "1" ] && echo true || echo false)" \
|
|
"${nd_kbit:-0}" \
|
|
"${nd_mode:-limiter}" \
|
|
"$(tctl_seen_count)" \
|
|
"$(tctl_seen_ready && echo true || echo false)"
|
|
;;
|
|
newdevice_config_set)
|
|
read -r input
|
|
uci -q get trafficctl.newdevice >/dev/null 2>&1 || uci set trafficctl.newdevice=newdevice
|
|
val=$(echo "$input" | jsonfilter -e '@.limit_kbit' 2>/dev/null)
|
|
if [ -n "$val" ]; then
|
|
case "$val" in
|
|
''|*[!0-9]*) echo '{"ok":false,"msg":"limit_kbit must be a number"}'; exit 0 ;;
|
|
esac
|
|
uci set trafficctl.newdevice.limit_kbit="$val"
|
|
fi
|
|
val=$(echo "$input" | jsonfilter -e '@.limit_mode' 2>/dev/null)
|
|
if [ -n "$val" ]; then
|
|
case "$val" in
|
|
limiter|shaper) uci set trafficctl.newdevice.limit_mode="$val" ;;
|
|
*) echo '{"ok":false,"msg":"limit_mode must be limiter or shaper"}'; exit 0 ;;
|
|
esac
|
|
fi
|
|
val=$(echo "$input" | jsonfilter -e '@.enabled' 2>/dev/null)
|
|
if [ -n "$val" ]; then
|
|
# Seed the ledger BEFORE the feature can fire. Done here rather
|
|
# than in the hotplug because this runs on an operator action,
|
|
# when the leases and the neighbour table are populated; seeding
|
|
# early in boot would record an empty LAN and then treat every
|
|
# device as new.
|
|
[ "$val" = "true" ] && tctl_seen_seed
|
|
uci set "trafficctl.newdevice.enabled=$([ "$val" = "true" ] && echo 1 || echo 0)"
|
|
fi
|
|
tctl_commit
|
|
tctl_log "config_change" "newdevice" "" "luci" "${TCTL_SRC:-local}"
|
|
echo '{"ok":true}'
|
|
;;
|
|
cut_status)
|
|
# A write method, like activity_log, because it is not purely
|
|
# observational: it enforces the auto-revert deadline and starts a
|
|
# replacement keeper if one died. That reconciliation is what stops
|
|
# the UI reporting a cut as ON after it has lapsed or run out, which
|
|
# is the one direction in which a wrong answer is dangerous.
|
|
"$SCRIPTS/trafficctl-cut.sh" status
|
|
;;
|
|
cut_set)
|
|
read -r input
|
|
active=$(echo "$input" | jsonfilter -e '@.active' 2>/dev/null)
|
|
duration=$(echo "$input" | jsonfilter -e '@.duration' 2>/dev/null)
|
|
persist=$(echo "$input" | jsonfilter -e '@.persist' 2>/dev/null)
|
|
if [ "$active" = "true" ]; then
|
|
case "$duration" in
|
|
''|*[!0-9]*) echo '{"ok":false,"msg":"duration must be a number of seconds (0 = indefinite)"}'; exit 0 ;;
|
|
esac
|
|
pflag=0
|
|
[ "$persist" = "true" ] && pflag=1
|
|
# Remember what the operator picked so the control comes back the
|
|
# way they left it. PREFERENCES only: the engaged state never
|
|
# touches uci, because /etc/config survives a reboot and this
|
|
# control must not.
|
|
uci -q get trafficctl.cut >/dev/null 2>&1 || uci set trafficctl.cut=cut
|
|
uci set trafficctl.cut.default_duration="$duration"
|
|
uci set trafficctl.cut.persist="$pflag"
|
|
tctl_commit
|
|
"$SCRIPTS/trafficctl-cut.sh" engage "$duration" "$pflag"
|
|
elif [ "$active" = "false" ]; then
|
|
"$SCRIPTS/trafficctl-cut.sh" release
|
|
else
|
|
echo '{"ok":false,"msg":"active must be true or false"}'
|
|
fi
|
|
;;
|
|
activity_log)
|
|
read -r input
|
|
lines=$(echo "$input" | jsonfilter -e '@.lines' 2>/dev/null)
|
|
lines="${lines:-50}"
|
|
case "$lines" in
|
|
''|*[!0-9]*) lines=50 ;;
|
|
esac
|
|
[ "$lines" -gt 1000 ] && lines=1000
|
|
log_file=$(tctl_log_file)
|
|
if [ -f "$log_file" ]; then
|
|
printf '{"ok":true,"lines":['
|
|
tail -n "$lines" "$log_file" | awk '
|
|
BEGIN { first=1 }
|
|
{
|
|
gsub(/\\/, "\\\\"); gsub(/"/, "\\\"")
|
|
if (!first) printf ","
|
|
printf "\"%s\"", $0
|
|
first=0
|
|
}'
|
|
printf ']}'
|
|
else
|
|
echo '{"ok":true,"lines":[]}'
|
|
fi
|
|
;;
|
|
version)
|
|
ver=$(opkg info luci-app-trafficctl 2>/dev/null | awk '/^Version:/{print $2}')
|
|
[ -z "$ver" ] && ver="dev"
|
|
printf '{"version":"%s"}' "$ver"
|
|
;;
|
|
*)
|
|
echo '{"error":"unknown method"}'
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|