#!/bin/sh # Copyright (C) 2006 OpenWrt.org # Copyright 2022-2026 sirpdboy NAME=timecontrol LOG_FILE="/var/log/timecontrol.log" DEBUG=1 # 时长数据库目录 DURATION_DIR="/var/lib/timecontrol" DURATION_DB="$DURATION_DIR/duration.db" CONNECTION_LOG="$DURATION_DIR/connections.log" # 状态文件 IDLIST="/var/$NAME.idlist" STATUS_DB="$DURATION_DIR/status.db" # 初始化目录 init_dirs() { mkdir -p "$DURATION_DIR" touch "$DURATION_DB" "$CONNECTION_LOG" "$STATUS_DB" 2>/dev/null } # 日志函数 dbg() { [ "$DEBUG" -eq 1 ] && { local d="$(date '+%Y-%m-%d %H:%M:%S')" echo "[$d] CTRL-DEBUG: $@" >> "$LOG_FILE" } } info() { local d="$(date '+%Y-%m-%d %H:%M:%S')" echo "[$d] CTRL-INFO: $@" >> "$LOG_FILE" } # 配置文件读取 config_t_get() { local index=${3:-0} local ret=$(uci -q get "${NAME}.@${1}[${index}].${2}") echo "${ret:=${4}}" } # 获取启用设备 get_enabled_devices() { uci show $NAME 2>/dev/null | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' | sort -n } # 时间检查函数 is_time_in_range() { local start_time=$1 local end_time=$2 local current_time=$(date +%H:%M) if [ "$start_time" = "$end_time" ]; then return 0 elif [ "$start_time" \< "$end_time" ]; then [ "$current_time" \> "$start_time" ] && [ "$current_time" \< "$end_time" ] && return 0 else [ "$current_time" \> "$start_time" ] || [ "$current_time" \< "$end_time" ] && return 0 fi return 1 } is_weekday_in_range() { local configured_weekdays=$1 local current_weekday=$(date +%u) [ "$configured_weekdays" = "0" ] && return 0 for ww in $(echo $configured_weekdays | sed 's/,/ /g'); do [ "$current_weekday" = "$ww" ] && return 0 done return 1 } # 时长管理 record_connection_time() { local target="$1" local action="$2" local timestamp=$(date +%s) echo "$timestamp,$target,$action" >> "$CONNECTION_LOG" if [ "$action" = "connect" ]; then if ! grep -q "^$target," "$DURATION_DB" 2>/dev/null; then echo "$target,$timestamp,0,0" >> "$DURATION_DB" dbg "初始化时长: $target" fi elif [ "$action" = "disconnect" ]; then if grep -q "^$target," "$DURATION_DB" 2>/dev/null; then local last_connect=$(grep "^$target," "$DURATION_DB" | cut -d',' -f2) local total_used=$(grep "^$target," "$DURATION_DB" | cut -d',' -f3) local last_reset=$(grep "^$target," "$DURATION_DB" | cut -d',' -f4) local session_duration=$((timestamp - last_connect)) local new_total=$((total_used + session_duration)) sed -i "/^$target,/d" "$DURATION_DB" echo "$target,$timestamp,$new_total,$last_reset" >> "$DURATION_DB" fi fi } get_connection_time() { local target="$1" local current_time=$(date +%s) if grep -q "^$target," "$DURATION_DB" 2>/dev/null; then local last_connect=$(grep "^$target," "$DURATION_DB" | cut -d',' -f2) local total_used=$(grep "^$target," "$DURATION_DB" | cut -d',' -f3) if tail -n 5 "$CONNECTION_LOG" 2>/dev/null | grep -q "^[0-9]*,$target,connect$"; then local session_duration=$((current_time - last_connect)) total_used=$((total_used + session_duration)) fi echo "$total_used" else echo "0" fi } # 重置检查 should_reset_duration() { local target="$1" local reset_cycle="$2" local last_reset_file="$DURATION_DIR/last_reset_$target" [ ! -f "$last_reset_file" ] && return 0 local last_reset=$(cat "$last_reset_file" 2>/dev/null) local current_time=$(date +%s) case "$reset_cycle" in "daily") local last_date=$(date -d "@$last_reset" +%Y%m%d 2>/dev/null || echo "0") local current_date=$(date +%Y%m%d) [ "$last_date" != "$current_date" ] ;; "weekly") local last_week=$(date -d "@$last_reset" +%Y%W 2>/dev/null || echo "0") local current_week=$(date +%Y%W) [ "$last_week" != "$current_week" ] ;; "monthly") local last_month=$(date -d "@$last_reset" +%Y%m 2>/dev/null || echo "0") local current_month=$(date +%Y%m) [ "$last_month" != "$current_month" ] ;; *) false ;; esac } reset_duration_counter() { local target="$1" local reset_cycle="$2" local current_time=$(date +%s) if grep -q "^$target," "$DURATION_DB" 2>/dev/null; then sed -i "/^$target,/d" "$DURATION_DB" fi echo "$target,$current_time,0,$current_time" >> "$DURATION_DB" echo "$current_time" > "$DURATION_DIR/last_reset_$target" info "重置时长: $target (周期: $reset_cycle)" } # 主检查函数 check_device_control() { local id="$1" local target=$(config_t_get device mac "$id") local time_mode=$(config_t_get device time_mode "$id" "period") local weekdays=$(config_t_get device week "$id" "0") # 检查星期 is_weekday_in_range "$weekdays" || { dbg "星期不允许: $target" return 1 } case "$time_mode" in "period") local start_time=$(config_t_get device timestart "$id" "00:00") local end_time=$(config_t_get device timeend "$id" "00:00") is_time_in_range "$start_time" "$end_time" || { dbg "时间段外: $target ($start_time-$end_time)" return 1 } dbg "时间段内: $target" return 0 ;; "duration") local duration=$(config_t_get device duration "$id" "60") local reset_cycle=$(config_t_get device reset_cycle "$id" "daily") # 重置检查 if should_reset_duration "$target" "$reset_cycle"; then reset_duration_counter "$target" "$reset_cycle" fi # 记录连接 local last_action=$(grep ",$target," "$CONNECTION_LOG" 2>/dev/null | tail -1 | cut -d',' -f3) if [ "$last_action" != "connect" ]; then record_connection_time "$target" "connect" fi # 检查时长 local used_seconds=$(get_connection_time "$target") local used_minutes=$((used_seconds / 60)) local max_minutes=$duration dbg "时长检查: $target 已用=${used_minutes}分钟, 限制=${max_minutes}分钟" if [ "$used_minutes" -ge "$max_minutes" ]; then dbg "已超时: $target" return 1 fi dbg "未超时: $target" return 0 ;; "combined") local start_time=$(config_t_get device timestart "$id" "00:00") local end_time=$(config_t_get device timeend "$id" "00:00") local use_duration=$(config_t_get device use_duration "$id" "0") # 时间段检查 is_time_in_range "$start_time" "$end_time" || { dbg "时间段外: $target" return 1 } # 时长检查 if [ "$use_duration" = "1" ]; then local duration=$(config_t_get device duration "$id" "60") local reset_cycle=$(config_t_get device reset_cycle "$id" "daily") if should_reset_duration "$target" "$reset_cycle"; then reset_duration_counter "$target" "$reset_cycle" fi local last_action=$(grep ",$target," "$CONNECTION_LOG" 2>/dev/null | tail -1 | cut -d',' -f3) if [ "$last_action" != "connect" ]; then record_connection_time "$target" "connect" fi local used_seconds=$(get_connection_time "$target") local used_minutes=$((used_seconds / 60)) local max_minutes=$duration if [ "$used_minutes" -ge "$max_minutes" ]; then dbg "时间段内但已超时: $target" return 1 fi fi dbg "时间段内允许: $target" return 0 ;; *) # 默认时间段控制 local start_time=$(config_t_get device timestart "$id" "00:00") local end_time=$(config_t_get device timeend "$id" "00:00") is_time_in_range "$start_time" "$end_time" return $? ;; esac } # 更新设备状态 update_device_status() { local id="$1" local should_allow="$2" # 0=允许, 1=禁止 local target=$(config_t_get device mac "$id") local comment=$(config_t_get device comment "$id" "设备$id") # 检查当前状态 local current_blocked=0 if [ -f "$IDLIST" ] && grep -q "!${id}!" "$IDLIST" 2>/dev/null; then current_blocked=1 fi dbg "设备状态: $target, 应该允许=$should_allow, 当前阻止=$current_blocked" if [ "$should_allow" -eq 0 ]; then # 应该允许 if [ "$current_blocked" -eq 1 ]; then dbg "解除阻止: $target" timecontrol del "$id" sed -i "/!$id!/d" "$IDLIST" 2>/dev/null info "允许上网: $comment ($target)" record_connection_time "$target" "disconnect" fi else # 应该阻止 if [ "$current_blocked" -eq 0 ]; then dbg "添加阻止: $target" timecontrol add "$id" if ! grep -q "!$id!" "$IDLIST" 2>/dev/null; then echo "!$id!" >> "$IDLIST" fi info "阻止上网: $comment ($target)" record_connection_time "$target" "disconnect" fi fi } # 主处理循环 main_loop() { info "时间控制守护进程启动" init_dirs while :; do dbg "开始检查设备" [ `uci show $NAME 2>/dev/null | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' | sort -n | wc -l` eq 0 ] && timecontrol stop && break for id in $(get_enabled_devices); do if check_device_control "$id"; then update_device_status "$id" 0 # 允许 else update_device_status "$id" 1 # 阻止 fi done sleep 60 done } # 启动 main_loop