#!/bin/sh # Bandwidth Manager RPCD Backend # Provides QoS rules, client quotas, and traffic statistics . /lib/functions.sh . /usr/share/libubox/jshn.sh # Configuration paths CONFIG_FILE="/etc/config/bandwidth" USAGE_DB="/tmp/bandwidth_usage.db" IPTABLES_CHAIN="BW_TRACKING" # Initialize usage database init_usage_db() { if [ ! -f "$USAGE_DB" ]; then cat > "$USAGE_DB" << 'EOF' # MAC|Timestamp|RX_Bytes|TX_Bytes EOF fi } # Get system status and global stats status() { json_init local enabled interface sqm_enabled config_load bandwidth config_get enabled global enabled "0" config_get interface global interface "br-lan" config_get sqm_enabled global sqm_enabled "0" json_add_boolean "enabled" "$enabled" json_add_string "interface" "$interface" json_add_boolean "sqm_enabled" "$sqm_enabled" # Check QoS status local qos_active=0 tc qdisc show dev "$interface" 2>/dev/null | grep -qE "(cake|htb|fq_codel)" && qos_active=1 json_add_boolean "qos_active" "$qos_active" # Get interface stats if [ -d "/sys/class/net/$interface" ]; then local rx_bytes=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0) local tx_bytes=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0) local rx_packets=$(cat /sys/class/net/$interface/statistics/rx_packets 2>/dev/null || echo 0) local tx_packets=$(cat /sys/class/net/$interface/statistics/tx_packets 2>/dev/null || echo 0) json_add_object "stats" json_add_int "rx_bytes" "$rx_bytes" json_add_int "tx_bytes" "$tx_bytes" json_add_int "rx_packets" "$rx_packets" json_add_int "tx_packets" "$tx_packets" json_close_object fi # Count rules and quotas local rule_count=0 local quota_count=0 config_foreach count_section rule && rule_count=$? config_foreach count_section quota && quota_count=$? json_add_int "rule_count" "$rule_count" json_add_int "quota_count" "$quota_count" json_dump } count_section() { return $(( $? + 1 )) } # List all QoS rules list_rules() { config_load bandwidth json_init json_add_array "rules" _add_rule() { local name type target limit_down limit_up priority enabled schedule config_get name "$1" name "" config_get type "$1" type "application" config_get target "$1" target "" config_get limit_down "$1" limit_down "0" config_get limit_up "$1" limit_up "0" config_get priority "$1" priority "5" config_get enabled "$1" enabled "1" config_get schedule "$1" schedule "" json_add_object "" json_add_string "id" "$1" json_add_string "name" "$name" json_add_string "type" "$type" json_add_string "target" "$target" json_add_int "limit_down" "$limit_down" json_add_int "limit_up" "$limit_up" json_add_int "priority" "$priority" json_add_boolean "enabled" "$enabled" json_add_string "schedule" "$schedule" json_close_object } config_foreach _add_rule rule json_close_array json_dump } # Add new QoS rule add_rule() { read -r input json_load "$input" local name type target limit_down limit_up priority json_get_var name name json_get_var type type "application" json_get_var target target json_get_var limit_down limit_down "0" json_get_var limit_up limit_up "0" json_get_var priority priority "5" json_cleanup if [ -z "$name" ] || [ -z "$target" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Name and target are required" json_dump return 1 fi # Generate unique ID local rule_id="rule_$(date +%s)" # Add to UCI config uci -q batch << EOF set bandwidth.$rule_id=rule set bandwidth.$rule_id.name='$name' set bandwidth.$rule_id.type='$type' set bandwidth.$rule_id.target='$target' set bandwidth.$rule_id.limit_down='$limit_down' set bandwidth.$rule_id.limit_up='$limit_up' set bandwidth.$rule_id.priority='$priority' set bandwidth.$rule_id.enabled='1' commit bandwidth EOF json_init json_add_boolean "success" 1 json_add_string "rule_id" "$rule_id" json_add_string "message" "Rule created successfully" json_dump } # Delete QoS rule delete_rule() { read -r input json_load "$input" local rule_id json_get_var rule_id rule_id json_cleanup if [ -z "$rule_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Rule ID is required" json_dump return 1 fi # Check if rule exists if ! uci -q get bandwidth.$rule_id >/dev/null 2>&1; then json_init json_add_boolean "success" 0 json_add_string "message" "Rule not found" json_dump return 1 fi uci -q delete bandwidth.$rule_id uci -q commit bandwidth json_init json_add_boolean "success" 1 json_add_string "message" "Rule deleted successfully" json_dump } # List all client quotas list_quotas() { config_load bandwidth json_init json_add_array "quotas" _add_quota() { local mac name limit_mb used_mb action reset_day enabled config_get mac "$1" mac "" config_get name "$1" name "" config_get limit_mb "$1" limit_mb "0" config_get action "$1" action "throttle" config_get reset_day "$1" reset_day "1" config_get enabled "$1" enabled "1" # Get current usage used_mb=$(get_mac_usage "$mac") local percent=0 if [ "$limit_mb" -gt 0 ]; then percent=$(( (used_mb * 100) / limit_mb )) fi json_add_object "" json_add_string "id" "$1" json_add_string "mac" "$mac" json_add_string "name" "$name" json_add_int "limit_mb" "$limit_mb" json_add_int "used_mb" "$used_mb" json_add_int "percent" "$percent" json_add_string "action" "$action" json_add_int "reset_day" "$reset_day" json_add_boolean "enabled" "$enabled" json_close_object } config_foreach _add_quota quota json_close_array json_dump } # Get usage for a specific MAC address get_mac_usage() { local mac="$1" local total_bytes=0 # Get from iptables counters if iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -qi "$mac"; then local bytes=$(iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -i "$mac" | awk '{sum+=$2} END {print sum}') total_bytes=${bytes:-0} fi # Convert to MB echo $(( total_bytes / 1024 / 1024 )) } # Get quota details for specific client get_quota() { read -r input json_load "$input" local mac json_get_var mac mac json_cleanup if [ -z "$mac" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "MAC address is required" json_dump return 1 fi config_load bandwidth local found=0 local quota_id name limit_mb action reset_day _find_quota() { local this_mac config_get this_mac "$1" mac "" if [ "$this_mac" = "$mac" ]; then quota_id="$1" config_get name "$1" name "" config_get limit_mb "$1" limit_mb "0" config_get action "$1" action "throttle" config_get reset_day "$1" reset_day "1" found=1 fi } config_foreach _find_quota quota if [ "$found" -eq 0 ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Quota not found for this MAC" json_dump return 1 fi local used_mb=$(get_mac_usage "$mac") local remaining_mb=$(( limit_mb - used_mb )) [ "$remaining_mb" -lt 0 ] && remaining_mb=0 local percent=0 if [ "$limit_mb" -gt 0 ]; then percent=$(( (used_mb * 100) / limit_mb )) fi json_init json_add_boolean "success" 1 json_add_string "quota_id" "$quota_id" json_add_string "mac" "$mac" json_add_string "name" "$name" json_add_int "limit_mb" "$limit_mb" json_add_int "used_mb" "$used_mb" json_add_int "remaining_mb" "$remaining_mb" json_add_int "percent" "$percent" json_add_string "action" "$action" json_add_int "reset_day" "$reset_day" json_dump } # Set or update quota set_quota() { read -r input json_load "$input" local mac name limit_mb action reset_day json_get_var mac mac json_get_var name name "" json_get_var limit_mb limit_mb "0" json_get_var action action "throttle" json_get_var reset_day reset_day "1" json_cleanup if [ -z "$mac" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "MAC address is required" json_dump return 1 fi # Check if quota exists for this MAC config_load bandwidth local quota_id="" _find_existing() { local this_mac config_get this_mac "$1" mac "" if [ "$this_mac" = "$mac" ]; then quota_id="$1" fi } config_foreach _find_existing quota if [ -z "$quota_id" ]; then # Create new quota quota_id="quota_$(date +%s)" uci -q batch << EOF set bandwidth.$quota_id=quota set bandwidth.$quota_id.mac='$mac' set bandwidth.$quota_id.name='$name' set bandwidth.$quota_id.limit_mb='$limit_mb' set bandwidth.$quota_id.action='$action' set bandwidth.$quota_id.reset_day='$reset_day' set bandwidth.$quota_id.enabled='1' commit bandwidth EOF local msg="Quota created successfully" else # Update existing quota uci -q batch << EOF set bandwidth.$quota_id.name='$name' set bandwidth.$quota_id.limit_mb='$limit_mb' set bandwidth.$quota_id.action='$action' set bandwidth.$quota_id.reset_day='$reset_day' commit bandwidth EOF local msg="Quota updated successfully" fi json_init json_add_boolean "success" 1 json_add_string "quota_id" "$quota_id" json_add_string "message" "$msg" json_dump } # Reset quota counter for a client reset_quota() { read -r input json_load "$input" local mac json_get_var mac mac json_cleanup if [ -z "$mac" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "MAC address is required" json_dump return 1 fi # Reset iptables counters for this MAC iptables -Z $IPTABLES_CHAIN 2>/dev/null # Remove from usage DB if [ -f "$USAGE_DB" ]; then sed -i "/^${mac}|/d" "$USAGE_DB" fi json_init json_add_boolean "success" 1 json_add_string "message" "Quota counter reset for $mac" json_dump } # Get real-time usage for all clients get_usage_realtime() { json_init json_add_array "clients" # Parse DHCP leases for active clients if [ -f /tmp/dhcp.leases ]; then while read -r expires mac ip hostname clientid; do local rx_bytes=0 tx_bytes=0 # Get current bytes from iptables if iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -qi "$mac"; then rx_bytes=$(iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -i "$mac" | awk '{print $2}' | head -1) tx_bytes=$(iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -i "$mac" | awk '{print $2}' | tail -1) fi # Get quota info if exists config_load bandwidth local has_quota=0 limit_mb=0 used_mb=0 _check_quota() { local this_mac config_get this_mac "$1" mac "" if [ "$this_mac" = "$mac" ]; then has_quota=1 config_get limit_mb "$1" limit_mb "0" used_mb=$(get_mac_usage "$mac") fi } config_foreach _check_quota quota json_add_object "" json_add_string "mac" "$mac" json_add_string "ip" "$ip" json_add_string "hostname" "${hostname:-unknown}" json_add_int "rx_bytes" "${rx_bytes:-0}" json_add_int "tx_bytes" "${tx_bytes:-0}" json_add_boolean "has_quota" "$has_quota" if [ "$has_quota" -eq 1 ]; then json_add_int "limit_mb" "$limit_mb" json_add_int "used_mb" "$used_mb" fi json_close_object done < /tmp/dhcp.leases fi json_close_array json_dump } # Get usage history get_usage_history() { read -r input json_load "$input" local timeframe mac json_get_var timeframe timeframe "24h" json_get_var mac mac "" json_cleanup init_usage_db json_init json_add_array "history" # Calculate time threshold local now=$(date +%s) local threshold=0 case "$timeframe" in "1h") threshold=$(( now - 3600 )) ;; "6h") threshold=$(( now - 21600 )) ;; "24h") threshold=$(( now - 86400 )) ;; "7d") threshold=$(( now - 604800 )) ;; "30d") threshold=$(( now - 2592000 )) ;; *) threshold=$(( now - 86400 )) ;; esac # Read usage database if [ -f "$USAGE_DB" ]; then while IFS='|' read -r db_mac timestamp rx_bytes tx_bytes; do # Skip header line [ "$db_mac" = "# MAC" ] && continue # Filter by MAC if specified if [ -n "$mac" ] && [ "$db_mac" != "$mac" ]; then continue fi # Filter by timeframe if [ "$timestamp" -lt "$threshold" ]; then continue fi json_add_object "" json_add_string "mac" "$db_mac" json_add_int "timestamp" "$timestamp" json_add_int "rx_bytes" "$rx_bytes" json_add_int "tx_bytes" "$tx_bytes" json_close_object done < "$USAGE_DB" fi json_close_array json_dump } # Get media traffic types for classification get_media() { json_init json_add_array "media" # VoIP json_add_object "" json_add_string "id" "voip" json_add_string "name" "VoIP" json_add_string "class" "1 (Highest)" json_add_string "description" "Voice over IP (SIP, RTP, VoLTE)" json_add_string "ports" "5060-5061,5004,16384-32767" json_close_object # Gaming json_add_object "" json_add_string "id" "gaming" json_add_string "name" "Gaming" json_add_string "class" "2 (High)" json_add_string "description" "Online gaming traffic" json_add_string "ports" "3074,27015-27030,3478-3479" json_close_object # Streaming json_add_object "" json_add_string "id" "streaming" json_add_string "name" "Video Streaming" json_add_string "class" "4 (Normal)" json_add_string "description" "YouTube, Netflix, Twitch" json_add_string "ports" "443,80" json_add_string "domains" "youtube.com,netflix.com,twitch.tv" json_close_object # Downloads json_add_object "" json_add_string "id" "download" json_add_string "name" "Downloads" json_add_string "class" "7 (Low)" json_add_string "description" "HTTP/HTTPS downloads, torrents" json_add_string "ports" "6881-6889,51413" json_close_object # Social Media json_add_object "" json_add_string "id" "social" json_add_string "name" "Social Media" json_add_string "class" "5 (Normal)" json_add_string "description" "Facebook, Twitter, Instagram" json_add_string "ports" "443,80" json_close_object # Work/Business json_add_object "" json_add_string "id" "work" json_add_string "name" "Work / Business" json_add_string "class" "3" json_add_string "description" "Email, VPN, remote desktop" json_add_string "ports" "22,3389,1194,1723" json_close_object json_close_array json_dump } # ============================================ # Smart QoS - DPI Integration with nDPId # ============================================ NDPID_FLOWS="/tmp/ndpid-flows.json" DPI_RULES_FILE="/tmp/bandwidth_dpi_rules.json" # Get DPI detected applications from nDPId get_dpi_applications() { json_init json_add_array "applications" if [ -f "$NDPID_FLOWS" ] && pgrep ndpid >/dev/null 2>&1; then # Extract unique applications from nDPId flows jq -r '.flows[]? | .app // "Unknown"' "$NDPID_FLOWS" 2>/dev/null | sort -u | while read app; do [ -z "$app" ] && continue local count=$(jq -r ".flows[] | select(.app == \"$app\") | .src_ip" "$NDPID_FLOWS" 2>/dev/null | wc -l) local bytes=$(jq -r ".flows[] | select(.app == \"$app\") | .bytes_rx + .bytes_tx" "$NDPID_FLOWS" 2>/dev/null | awk '{sum+=$1} END {print sum}') json_add_object "" json_add_string "name" "$app" json_add_int "flow_count" "$count" json_add_int "total_bytes" "${bytes:-0}" json_close_object done fi json_close_array # Check DPI source status local dpi_source="none" if [ -f "$NDPID_FLOWS" ] && pgrep ndpid >/dev/null 2>&1; then dpi_source="ndpid" elif [ -f "/var/run/netifyd/status.json" ] && pgrep netifyd >/dev/null 2>&1; then dpi_source="netifyd" fi json_add_string "dpi_source" "$dpi_source" json_dump } # Get smart QoS suggestions based on detected traffic get_smart_suggestions() { json_init json_add_array "suggestions" if [ -f "$NDPID_FLOWS" ]; then # Check for gaming traffic local gaming_count=$(jq -r '.flows[]? | select(.category == "Game" or .app | test("Steam|Xbox|PlayStation|Nintendo|Epic|Riot"; "i")) | .src_ip' "$NDPID_FLOWS" 2>/dev/null | sort -u | wc -l) if [ "$gaming_count" -gt 0 ]; then json_add_object "" json_add_string "type" "gaming" json_add_string "title" "Gaming Traffic Detected" json_add_string "description" "Detected $gaming_count devices with gaming traffic. Recommend priority class 1-2 for low latency." json_add_int "priority" 2 json_add_int "affected_devices" "$gaming_count" json_close_object fi # Check for streaming traffic local streaming_count=$(jq -r '.flows[]? | select(.app | test("YouTube|Netflix|Twitch|Disney|HBO|Amazon.*Video|Spotify"; "i")) | .src_ip' "$NDPID_FLOWS" 2>/dev/null | sort -u | wc -l) if [ "$streaming_count" -gt 0 ]; then json_add_object "" json_add_string "type" "streaming" json_add_string "title" "Streaming Traffic Detected" json_add_string "description" "Detected $streaming_count devices streaming video/audio. Recommend priority class 4 for consistent quality." json_add_int "priority" 4 json_add_int "affected_devices" "$streaming_count" json_close_object fi # Check for video conferencing local video_conf_count=$(jq -r '.flows[]? | select(.app | test("Zoom|Teams|Meet|WebEx|Skype"; "i")) | .src_ip' "$NDPID_FLOWS" 2>/dev/null | sort -u | wc -l) if [ "$video_conf_count" -gt 0 ]; then json_add_object "" json_add_string "type" "videoconf" json_add_string "title" "Video Conferencing Detected" json_add_string "description" "Detected $video_conf_count devices in video calls. Recommend priority class 1 for real-time communication." json_add_int "priority" 1 json_add_int "affected_devices" "$video_conf_count" json_close_object fi # Check for heavy downloaders local download_bytes=$(jq -r '.flows[]? | select(.bytes_rx > 100000000) | "\(.src_ip) \(.bytes_rx)"' "$NDPID_FLOWS" 2>/dev/null | sort -t' ' -k2 -nr | head -3) if [ -n "$download_bytes" ]; then local heavy_users=$(echo "$download_bytes" | wc -l) json_add_object "" json_add_string "type" "downloads" json_add_string "title" "Heavy Bandwidth Users" json_add_string "description" "Detected $heavy_users devices downloading heavily. Consider priority class 6-7 or quotas." json_add_int "priority" 7 json_add_int "affected_devices" "$heavy_users" json_close_object fi fi json_close_array json_dump } # Apply smart QoS rule based on app detection apply_dpi_rule() { read -r input json_load "$input" local app_name priority limit_down limit_up json_get_var app_name app_name json_get_var priority priority "5" json_get_var limit_down limit_down "0" json_get_var limit_up limit_up "0" json_cleanup if [ -z "$app_name" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Application name is required" json_dump return 1 fi # Create DPI-based QoS rule local rule_id="dpi_rule_$(echo "$app_name" | tr ' ' '_' | tr '[:upper:]' '[:lower:]')_$(date +%s)" uci -q batch << EOF set bandwidth.$rule_id=rule set bandwidth.$rule_id.name='DPI: $app_name' set bandwidth.$rule_id.type='dpi_app' set bandwidth.$rule_id.target='$app_name' set bandwidth.$rule_id.limit_down='$limit_down' set bandwidth.$rule_id.limit_up='$limit_up' set bandwidth.$rule_id.priority='$priority' set bandwidth.$rule_id.enabled='1' set bandwidth.$rule_id.dpi_match='1' commit bandwidth EOF json_init json_add_boolean "success" 1 json_add_string "rule_id" "$rule_id" json_add_string "message" "DPI rule created for $app_name" json_dump } # ============================================ # Device Groups Management # ============================================ # List all device groups list_groups() { config_load bandwidth json_init json_add_array "groups" _add_group() { local name description quota_mb priority members enabled config_get name "$1" name "" config_get description "$1" description "" config_get quota_mb "$1" quota_mb "0" config_get priority "$1" priority "5" config_get members "$1" members "" config_get enabled "$1" enabled "1" # Calculate group usage local total_used=0 local member_count=0 for mac in $members; do member_count=$(( member_count + 1 )) local used=$(get_mac_usage "$mac") total_used=$(( total_used + used )) done json_add_object "" json_add_string "id" "$1" json_add_string "name" "$name" json_add_string "description" "$description" json_add_int "quota_mb" "$quota_mb" json_add_int "priority" "$priority" json_add_int "member_count" "$member_count" json_add_int "used_mb" "$total_used" json_add_boolean "enabled" "$enabled" json_close_object } config_foreach _add_group group json_close_array json_dump } # Get group details with members get_group() { read -r input json_load "$input" local group_id json_get_var group_id group_id json_cleanup if [ -z "$group_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Group ID is required" json_dump return 1 fi config_load bandwidth local name description quota_mb priority members enabled config_get name "$group_id" name "" config_get description "$group_id" description "" config_get quota_mb "$group_id" quota_mb "0" config_get priority "$group_id" priority "5" config_get members "$group_id" members "" config_get enabled "$group_id" enabled "1" if [ -z "$name" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Group not found" json_dump return 1 fi json_init json_add_boolean "success" 1 json_add_string "id" "$group_id" json_add_string "name" "$name" json_add_string "description" "$description" json_add_int "quota_mb" "$quota_mb" json_add_int "priority" "$priority" json_add_boolean "enabled" "$enabled" # Add members with details json_add_array "members" for mac in $members; do local hostname ip used_mb hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}') ip=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $3}') used_mb=$(get_mac_usage "$mac") json_add_object "" json_add_string "mac" "$mac" json_add_string "hostname" "${hostname:-unknown}" json_add_string "ip" "${ip:-unknown}" json_add_int "used_mb" "$used_mb" json_close_object done json_close_array json_dump } # Create device group create_group() { read -r input json_load "$input" local name description quota_mb priority members json_get_var name name json_get_var description description "" json_get_var quota_mb quota_mb "0" json_get_var priority priority "5" json_get_var members members "" json_cleanup if [ -z "$name" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Group name is required" json_dump return 1 fi local group_id="group_$(date +%s)" uci -q batch << EOF set bandwidth.$group_id=group set bandwidth.$group_id.name='$name' set bandwidth.$group_id.description='$description' set bandwidth.$group_id.quota_mb='$quota_mb' set bandwidth.$group_id.priority='$priority' set bandwidth.$group_id.members='$members' set bandwidth.$group_id.enabled='1' commit bandwidth EOF json_init json_add_boolean "success" 1 json_add_string "group_id" "$group_id" json_add_string "message" "Group created successfully" json_dump } # Update device group update_group() { read -r input json_load "$input" local group_id name description quota_mb priority members json_get_var group_id group_id json_get_var name name json_get_var description description "" json_get_var quota_mb quota_mb "0" json_get_var priority priority "5" json_get_var members members "" json_cleanup if [ -z "$group_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Group ID is required" json_dump return 1 fi # Check if group exists config_load bandwidth local existing_name config_get existing_name "$group_id" name "" if [ -z "$existing_name" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Group not found" json_dump return 1 fi uci -q batch << EOF set bandwidth.$group_id.name='$name' set bandwidth.$group_id.description='$description' set bandwidth.$group_id.quota_mb='$quota_mb' set bandwidth.$group_id.priority='$priority' set bandwidth.$group_id.members='$members' commit bandwidth EOF json_init json_add_boolean "success" 1 json_add_string "message" "Group updated successfully" json_dump } # Delete device group delete_group() { read -r input json_load "$input" local group_id json_get_var group_id group_id json_cleanup if [ -z "$group_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Group ID is required" json_dump return 1 fi # Check if group exists if ! uci -q get bandwidth.$group_id >/dev/null 2>&1; then json_init json_add_boolean "success" 0 json_add_string "message" "Group not found" json_dump return 1 fi uci -q delete bandwidth.$group_id uci -q commit bandwidth json_init json_add_boolean "success" 1 json_add_string "message" "Group deleted successfully" json_dump } # Add device to group add_to_group() { read -r input json_load "$input" local group_id mac json_get_var group_id group_id json_get_var mac mac json_cleanup if [ -z "$group_id" ] || [ -z "$mac" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Group ID and MAC address are required" json_dump return 1 fi config_load bandwidth local members config_get members "$group_id" members "" # Check if already in group if echo "$members" | grep -qi "$mac"; then json_init json_add_boolean "success" 0 json_add_string "message" "Device already in group" json_dump return 1 fi members="$members $mac" uci -q set bandwidth.$group_id.members="$members" uci -q commit bandwidth json_init json_add_boolean "success" 1 json_add_string "message" "Device added to group" json_dump } # Remove device from group remove_from_group() { read -r input json_load "$input" local group_id mac json_get_var group_id group_id json_get_var mac mac json_cleanup if [ -z "$group_id" ] || [ -z "$mac" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Group ID and MAC address are required" json_dump return 1 fi config_load bandwidth local members config_get members "$group_id" members "" # Remove MAC from members list members=$(echo "$members" | sed "s/$mac//gi" | tr -s ' ') uci -q set bandwidth.$group_id.members="$members" uci -q commit bandwidth json_init json_add_boolean "success" 1 json_add_string "message" "Device removed from group" json_dump } # ============================================ # Device Profiles # ============================================ PROFILES_CACHE="/tmp/bandwidth_profiles.cache" # Get built-in profile definitions get_builtin_profiles() { json_init json_add_array "profiles" # Gaming profile json_add_object "" json_add_string "id" "builtin_gaming" json_add_string "name" "Gaming" json_add_string "description" "Low latency, high priority for gaming" json_add_string "icon" "gamepad" json_add_string "color" "#8b5cf6" json_add_int "priority" 1 json_add_int "limit_down" 0 json_add_int "limit_up" 0 json_add_string "latency_mode" "ultra" json_add_boolean "builtin" 1 json_close_object # Streaming profile json_add_object "" json_add_string "id" "builtin_streaming" json_add_string "name" "Streaming" json_add_string "description" "Optimized for video streaming" json_add_string "icon" "play" json_add_string "color" "#06b6d4" json_add_int "priority" 4 json_add_int "limit_down" 25000 json_add_int "limit_up" 5000 json_add_string "latency_mode" "normal" json_add_boolean "builtin" 1 json_close_object # IoT profile json_add_object "" json_add_string "id" "builtin_iot" json_add_string "name" "IoT Devices" json_add_string "description" "Low bandwidth, network isolated" json_add_string "icon" "cpu" json_add_string "color" "#10b981" json_add_int "priority" 7 json_add_int "limit_down" 1000 json_add_int "limit_up" 500 json_add_string "latency_mode" "normal" json_add_boolean "isolate" 1 json_add_boolean "builtin" 1 json_close_object # Work profile json_add_object "" json_add_string "id" "builtin_work" json_add_string "name" "Work" json_add_string "description" "Business-critical apps prioritized" json_add_string "icon" "briefcase" json_add_string "color" "#3b82f6" json_add_int "priority" 3 json_add_int "limit_down" 0 json_add_int "limit_up" 0 json_add_string "latency_mode" "normal" json_add_boolean "builtin" 1 json_close_object # Kids profile json_add_object "" json_add_string "id" "builtin_kids" json_add_string "name" "Kids" json_add_string "description" "Parental controls, content filtered" json_add_string "icon" "child" json_add_string "color" "#f59e0b" json_add_int "priority" 5 json_add_int "limit_down" 10000 json_add_int "limit_up" 2000 json_add_string "latency_mode" "normal" json_add_string "content_filter" "kids_safe" json_add_boolean "builtin" 1 json_close_object json_close_array json_dump } # List all profiles (custom + builtin) list_profiles() { config_load bandwidth json_init json_add_array "profiles" _add_profile() { local name description icon color priority limit_down limit_up latency_mode content_filter isolate schedule enabled config_get name "$1" name "" config_get description "$1" description "" config_get icon "$1" icon "tag" config_get color "$1" color "#6366f1" config_get priority "$1" priority "5" config_get limit_down "$1" limit_down "0" config_get limit_up "$1" limit_up "0" config_get latency_mode "$1" latency_mode "normal" config_get content_filter "$1" content_filter "" config_get isolate "$1" isolate "0" config_get schedule "$1" schedule "" config_get enabled "$1" enabled "1" # Count assigned devices local assigned_count=0 _count_assigned() { local profile config_get profile "$1" profile "" [ "$profile" = "$2" ] && assigned_count=$(( assigned_count + 1 )) } config_foreach _count_assigned device_profile "$1" json_add_object "" json_add_string "id" "$1" json_add_string "name" "$name" json_add_string "description" "$description" json_add_string "icon" "$icon" json_add_string "color" "$color" json_add_int "priority" "$priority" json_add_int "limit_down" "$limit_down" json_add_int "limit_up" "$limit_up" json_add_string "latency_mode" "$latency_mode" json_add_string "content_filter" "$content_filter" json_add_boolean "isolate" "$isolate" json_add_string "schedule" "$schedule" json_add_boolean "enabled" "$enabled" json_add_int "assigned_count" "$assigned_count" json_add_boolean "builtin" 0 json_close_object } config_foreach _add_profile profile json_close_array json_dump } # Get single profile details get_profile() { read -r input json_load "$input" local profile_id json_get_var profile_id profile_id json_cleanup if [ -z "$profile_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Profile ID is required" json_dump return 1 fi config_load bandwidth local name description icon color priority limit_down limit_up latency_mode content_filter isolate schedule enabled config_get name "$profile_id" name "" config_get description "$profile_id" description "" config_get icon "$profile_id" icon "tag" config_get color "$profile_id" color "#6366f1" config_get priority "$profile_id" priority "5" config_get limit_down "$profile_id" limit_down "0" config_get limit_up "$profile_id" limit_up "0" config_get latency_mode "$profile_id" latency_mode "normal" config_get content_filter "$profile_id" content_filter "" config_get isolate "$profile_id" isolate "0" config_get schedule "$profile_id" schedule "" config_get enabled "$profile_id" enabled "1" if [ -z "$name" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Profile not found" json_dump return 1 fi json_init json_add_boolean "success" 1 json_add_string "id" "$profile_id" json_add_string "name" "$name" json_add_string "description" "$description" json_add_string "icon" "$icon" json_add_string "color" "$color" json_add_int "priority" "$priority" json_add_int "limit_down" "$limit_down" json_add_int "limit_up" "$limit_up" json_add_string "latency_mode" "$latency_mode" json_add_string "content_filter" "$content_filter" json_add_boolean "isolate" "$isolate" json_add_string "schedule" "$schedule" json_add_boolean "enabled" "$enabled" # Get assigned devices json_add_array "assigned_devices" _add_assigned() { local mac profile custom_limit_down custom_limit_up config_get mac "$1" mac "" config_get profile "$1" profile "" config_get custom_limit_down "$1" custom_limit_down "0" config_get custom_limit_up "$1" custom_limit_up "0" if [ "$profile" = "$profile_id" ]; then local hostname ip hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}') ip=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $3}') json_add_object "" json_add_string "mac" "$mac" json_add_string "hostname" "${hostname:-unknown}" json_add_string "ip" "${ip:-}" json_add_int "custom_limit_down" "$custom_limit_down" json_add_int "custom_limit_up" "$custom_limit_up" json_close_object fi } config_foreach _add_assigned device_profile json_close_array json_dump } # Create new profile create_profile() { read -r input json_load "$input" local name description icon color priority limit_down limit_up latency_mode content_filter isolate schedule json_get_var name name json_get_var description description "" json_get_var icon icon "tag" json_get_var color color "#6366f1" json_get_var priority priority "5" json_get_var limit_down limit_down "0" json_get_var limit_up limit_up "0" json_get_var latency_mode latency_mode "normal" json_get_var content_filter content_filter "" json_get_var isolate isolate "0" json_get_var schedule schedule "" json_cleanup if [ -z "$name" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Profile name is required" json_dump return 1 fi local profile_id="profile_$(date +%s)" uci -q batch << EOF set bandwidth.$profile_id=profile set bandwidth.$profile_id.name='$name' set bandwidth.$profile_id.description='$description' set bandwidth.$profile_id.icon='$icon' set bandwidth.$profile_id.color='$color' set bandwidth.$profile_id.priority='$priority' set bandwidth.$profile_id.limit_down='$limit_down' set bandwidth.$profile_id.limit_up='$limit_up' set bandwidth.$profile_id.latency_mode='$latency_mode' set bandwidth.$profile_id.content_filter='$content_filter' set bandwidth.$profile_id.isolate='$isolate' set bandwidth.$profile_id.schedule='$schedule' set bandwidth.$profile_id.enabled='1' commit bandwidth EOF json_init json_add_boolean "success" 1 json_add_string "profile_id" "$profile_id" json_add_string "message" "Profile created successfully" json_dump } # Update profile update_profile() { read -r input json_load "$input" local profile_id name description icon color priority limit_down limit_up latency_mode content_filter isolate schedule enabled json_get_var profile_id profile_id json_get_var name name json_get_var description description "" json_get_var icon icon "tag" json_get_var color color "#6366f1" json_get_var priority priority "5" json_get_var limit_down limit_down "0" json_get_var limit_up limit_up "0" json_get_var latency_mode latency_mode "normal" json_get_var content_filter content_filter "" json_get_var isolate isolate "0" json_get_var schedule schedule "" json_get_var enabled enabled "1" json_cleanup if [ -z "$profile_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Profile ID is required" json_dump return 1 fi # Check if profile exists config_load bandwidth local existing_name config_get existing_name "$profile_id" name "" if [ -z "$existing_name" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Profile not found" json_dump return 1 fi uci -q batch << EOF set bandwidth.$profile_id.name='$name' set bandwidth.$profile_id.description='$description' set bandwidth.$profile_id.icon='$icon' set bandwidth.$profile_id.color='$color' set bandwidth.$profile_id.priority='$priority' set bandwidth.$profile_id.limit_down='$limit_down' set bandwidth.$profile_id.limit_up='$limit_up' set bandwidth.$profile_id.latency_mode='$latency_mode' set bandwidth.$profile_id.content_filter='$content_filter' set bandwidth.$profile_id.isolate='$isolate' set bandwidth.$profile_id.schedule='$schedule' set bandwidth.$profile_id.enabled='$enabled' commit bandwidth EOF # Apply QoS rules for assigned devices apply_profile_qos "$profile_id" json_init json_add_boolean "success" 1 json_add_string "message" "Profile updated successfully" json_dump } # Delete profile delete_profile() { read -r input json_load "$input" local profile_id json_get_var profile_id profile_id json_cleanup if [ -z "$profile_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Profile ID is required" json_dump return 1 fi # Check if profile exists if ! uci -q get bandwidth.$profile_id >/dev/null 2>&1; then json_init json_add_boolean "success" 0 json_add_string "message" "Profile not found" json_dump return 1 fi # Remove profile assignments first config_load bandwidth _remove_assignments() { local profile config_get profile "$1" profile "" [ "$profile" = "$profile_id" ] && uci -q delete bandwidth.$1 } config_foreach _remove_assignments device_profile uci -q delete bandwidth.$profile_id uci -q commit bandwidth json_init json_add_boolean "success" 1 json_add_string "message" "Profile deleted successfully" json_dump } # Clone an existing profile clone_profile() { read -r input json_load "$input" local source_id new_name json_get_var source_id source_id json_get_var new_name new_name json_cleanup if [ -z "$source_id" ] || [ -z "$new_name" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Source profile ID and new name are required" json_dump return 1 fi config_load bandwidth local description icon color priority limit_down limit_up latency_mode content_filter isolate schedule config_get description "$source_id" description "" config_get icon "$source_id" icon "tag" config_get color "$source_id" color "#6366f1" config_get priority "$source_id" priority "5" config_get limit_down "$source_id" limit_down "0" config_get limit_up "$source_id" limit_up "0" config_get latency_mode "$source_id" latency_mode "normal" config_get content_filter "$source_id" content_filter "" config_get isolate "$source_id" isolate "0" config_get schedule "$source_id" schedule "" local profile_id="profile_$(date +%s)" uci -q batch << EOF set bandwidth.$profile_id=profile set bandwidth.$profile_id.name='$new_name' set bandwidth.$profile_id.description='$description (copy)' set bandwidth.$profile_id.icon='$icon' set bandwidth.$profile_id.color='$color' set bandwidth.$profile_id.priority='$priority' set bandwidth.$profile_id.limit_down='$limit_down' set bandwidth.$profile_id.limit_up='$limit_up' set bandwidth.$profile_id.latency_mode='$latency_mode' set bandwidth.$profile_id.content_filter='$content_filter' set bandwidth.$profile_id.isolate='$isolate' set bandwidth.$profile_id.schedule='$schedule' set bandwidth.$profile_id.enabled='1' commit bandwidth EOF json_init json_add_boolean "success" 1 json_add_string "profile_id" "$profile_id" json_add_string "message" "Profile cloned successfully" json_dump } # Assign profile to device assign_profile_to_device() { read -r input json_load "$input" local mac profile_id custom_limit_down custom_limit_up json_get_var mac mac json_get_var profile_id profile_id json_get_var custom_limit_down custom_limit_down "0" json_get_var custom_limit_up custom_limit_up "0" json_cleanup if [ -z "$mac" ] || [ -z "$profile_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "MAC address and profile ID are required" json_dump return 1 fi # Check if device already has an assignment config_load bandwidth local existing_assignment="" _find_assignment() { local this_mac config_get this_mac "$1" mac "" [ "$this_mac" = "$mac" ] && existing_assignment="$1" } config_foreach _find_assignment device_profile if [ -n "$existing_assignment" ]; then # Update existing uci -q batch << EOF set bandwidth.$existing_assignment.profile='$profile_id' set bandwidth.$existing_assignment.custom_limit_down='$custom_limit_down' set bandwidth.$existing_assignment.custom_limit_up='$custom_limit_up' commit bandwidth EOF else # Create new local dp_id="dp_$(date +%s)_$(echo "$mac" | tr ':' '' | tail -c 7)" uci -q batch << EOF set bandwidth.$dp_id=device_profile set bandwidth.$dp_id.mac='$mac' set bandwidth.$dp_id.profile='$profile_id' set bandwidth.$dp_id.custom_limit_down='$custom_limit_down' set bandwidth.$dp_id.custom_limit_up='$custom_limit_up' commit bandwidth EOF fi # Apply QoS apply_device_qos "$mac" "$profile_id" "$custom_limit_down" "$custom_limit_up" json_init json_add_boolean "success" 1 json_add_string "message" "Profile assigned to device" json_dump } # Assign profile to group assign_profile_to_group() { read -r input json_load "$input" local group_id profile_id json_get_var group_id group_id json_get_var profile_id profile_id json_cleanup if [ -z "$group_id" ] || [ -z "$profile_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Group ID and profile ID are required" json_dump return 1 fi config_load bandwidth local members config_get members "$group_id" members "" local assigned=0 for mac in $members; do local dp_id="dp_$(date +%s)_$(echo "$mac" | tr ':' '' | tail -c 7)" # Check existing local existing="" _find_dev() { local this_mac config_get this_mac "$1" mac "" [ "$this_mac" = "$mac" ] && existing="$1" } config_foreach _find_dev device_profile if [ -n "$existing" ]; then uci -q set bandwidth.$existing.profile="$profile_id" else uci -q batch << EOF set bandwidth.$dp_id=device_profile set bandwidth.$dp_id.mac='$mac' set bandwidth.$dp_id.profile='$profile_id' set bandwidth.$dp_id.custom_limit_down='0' set bandwidth.$dp_id.custom_limit_up='0' EOF fi assigned=$(( assigned + 1 )) done uci -q commit bandwidth json_init json_add_boolean "success" 1 json_add_int "assigned_count" "$assigned" json_add_string "message" "Profile assigned to $assigned devices in group" json_dump } # Remove profile assignment remove_profile_assignment() { read -r input json_load "$input" local mac json_get_var mac mac json_cleanup if [ -z "$mac" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "MAC address is required" json_dump return 1 fi config_load bandwidth local found="" _find_dp() { local this_mac config_get this_mac "$1" mac "" [ "$this_mac" = "$mac" ] && found="$1" } config_foreach _find_dp device_profile if [ -n "$found" ]; then uci -q delete bandwidth.$found uci -q commit bandwidth # Remove QoS rules for this device remove_device_qos "$mac" fi json_init json_add_boolean "success" 1 json_add_string "message" "Profile assignment removed" json_dump } # List profile assignments list_profile_assignments() { config_load bandwidth json_init json_add_array "assignments" _add_assignment() { local mac profile custom_limit_down custom_limit_up config_get mac "$1" mac "" config_get profile "$1" profile "" config_get custom_limit_down "$1" custom_limit_down "0" config_get custom_limit_up "$1" custom_limit_up "0" local hostname ip hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}') ip=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $3}') # Get profile name local profile_name config_get profile_name "$profile" name "" json_add_object "" json_add_string "id" "$1" json_add_string "mac" "$mac" json_add_string "hostname" "${hostname:-unknown}" json_add_string "ip" "${ip:-}" json_add_string "profile" "$profile" json_add_string "profile_name" "$profile_name" json_add_int "custom_limit_down" "$custom_limit_down" json_add_int "custom_limit_up" "$custom_limit_up" json_close_object } config_foreach _add_assignment device_profile json_close_array json_dump } # Apply QoS for profile (internal) apply_profile_qos() { local profile_id="$1" config_load bandwidth local priority limit_down limit_up config_get priority "$profile_id" priority "5" config_get limit_down "$profile_id" limit_down "0" config_get limit_up "$profile_id" limit_up "0" # Apply to all assigned devices _apply_device() { local mac profile custom_down custom_up config_get mac "$1" mac "" config_get profile "$1" profile "" config_get custom_down "$1" custom_limit_down "0" config_get custom_up "$1" custom_limit_up "0" if [ "$profile" = "$profile_id" ]; then local eff_down=$(( custom_down > 0 ? custom_down : limit_down )) local eff_up=$(( custom_up > 0 ? custom_up : limit_up )) apply_device_qos "$mac" "$profile_id" "$eff_down" "$eff_up" fi } config_foreach _apply_device device_profile } # Apply QoS for single device (internal) apply_device_qos() { local mac="$1" local profile_id="$2" local limit_down="$3" local limit_up="$4" # Get profile priority config_load bandwidth local priority config_get priority "$profile_id" priority "5" local interface config_get interface global interface "br-lan" # Apply tc rules if limits are set if [ "$limit_down" -gt 0 ] || [ "$limit_up" -gt 0 ]; then # Mark packets from this MAC for QoS class iptables -t mangle -D PREROUTING -m mac --mac-source "$mac" -j MARK --set-mark "$priority" 2>/dev/null iptables -t mangle -A PREROUTING -m mac --mac-source "$mac" -j MARK --set-mark "$priority" # Could add tc filter here for rate limiting # tc filter add dev $interface parent 1: protocol ip prio $priority handle $priority fw flowid 1:$priority fi } # Remove QoS for device (internal) remove_device_qos() { local mac="$1" # Remove iptables rules for i in 1 2 3 4 5 6 7 8; do iptables -t mangle -D PREROUTING -m mac --mac-source "$mac" -j MARK --set-mark "$i" 2>/dev/null done } # ============================================ # Parental Controls # ============================================ # List parental schedules list_parental_schedules() { config_load bandwidth json_init json_add_array "schedules" _add_parental_schedule() { local name enabled target_type target action start_time end_time days config_get name "$1" name "" config_get enabled "$1" enabled "1" config_get target_type "$1" target_type "device" config_get target "$1" target "" config_get action "$1" action "block" config_get start_time "$1" start_time "21:00" config_get end_time "$1" end_time "07:00" config_get days "$1" days "" # Check if currently active local active=0 local current_day=$(date +%a | tr '[:upper:]' '[:lower:]') local current_time=$(date +%H:%M) if [ "$enabled" = "1" ] && echo "$days" | grep -qi "$current_day"; then if [ "$start_time" \< "$end_time" ]; then [ "$current_time" \> "$start_time" ] && [ "$current_time" \< "$end_time" ] && active=1 else # Overnight schedule [ "$current_time" \> "$start_time" ] || [ "$current_time" \< "$end_time" ] && active=1 fi fi json_add_object "" json_add_string "id" "$1" json_add_string "name" "$name" json_add_boolean "enabled" "$enabled" json_add_string "target_type" "$target_type" json_add_string "target" "$target" json_add_string "action" "$action" json_add_string "start_time" "$start_time" json_add_string "end_time" "$end_time" json_add_string "days" "$days" json_add_boolean "active" "$active" json_close_object } config_foreach _add_parental_schedule parental_schedule json_close_array json_dump } # Create parental schedule create_parental_schedule() { read -r input json_load "$input" local name target_type target action start_time end_time days json_get_var name name json_get_var target_type target_type "device" json_get_var target target json_get_var action action "block" json_get_var start_time start_time "21:00" json_get_var end_time end_time "07:00" json_get_var days days "mon tue wed thu fri" json_cleanup if [ -z "$name" ] || [ -z "$target" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Name and target are required" json_dump return 1 fi local schedule_id="ps_$(date +%s)" uci -q batch << EOF set bandwidth.$schedule_id=parental_schedule set bandwidth.$schedule_id.name='$name' set bandwidth.$schedule_id.enabled='1' set bandwidth.$schedule_id.target_type='$target_type' set bandwidth.$schedule_id.target='$target' set bandwidth.$schedule_id.action='$action' set bandwidth.$schedule_id.start_time='$start_time' set bandwidth.$schedule_id.end_time='$end_time' set bandwidth.$schedule_id.days='$days' commit bandwidth EOF json_init json_add_boolean "success" 1 json_add_string "schedule_id" "$schedule_id" json_add_string "message" "Parental schedule created" json_dump } # Update parental schedule update_parental_schedule() { read -r input json_load "$input" local schedule_id name target_type target action start_time end_time days enabled json_get_var schedule_id schedule_id json_get_var name name json_get_var target_type target_type "device" json_get_var target target json_get_var action action "block" json_get_var start_time start_time "21:00" json_get_var end_time end_time "07:00" json_get_var days days "mon tue wed thu fri" json_get_var enabled enabled "1" json_cleanup if [ -z "$schedule_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Schedule ID is required" json_dump return 1 fi uci -q batch << EOF set bandwidth.$schedule_id.name='$name' set bandwidth.$schedule_id.enabled='$enabled' set bandwidth.$schedule_id.target_type='$target_type' set bandwidth.$schedule_id.target='$target' set bandwidth.$schedule_id.action='$action' set bandwidth.$schedule_id.start_time='$start_time' set bandwidth.$schedule_id.end_time='$end_time' set bandwidth.$schedule_id.days='$days' commit bandwidth EOF json_init json_add_boolean "success" 1 json_add_string "message" "Parental schedule updated" json_dump } # Delete parental schedule delete_parental_schedule() { read -r input json_load "$input" local schedule_id json_get_var schedule_id schedule_id json_cleanup if [ -z "$schedule_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Schedule ID is required" json_dump return 1 fi uci -q delete bandwidth.$schedule_id uci -q commit bandwidth json_init json_add_boolean "success" 1 json_add_string "message" "Parental schedule deleted" json_dump } # Toggle parental schedule toggle_parental_schedule() { read -r input json_load "$input" local schedule_id enabled json_get_var schedule_id schedule_id json_get_var enabled enabled json_cleanup if [ -z "$schedule_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Schedule ID is required" json_dump return 1 fi uci -q set bandwidth.$schedule_id.enabled="$enabled" uci -q commit bandwidth json_init json_add_boolean "success" 1 json_add_string "message" "Schedule toggled" json_dump } # List preset modes list_preset_modes() { config_load bandwidth json_init json_add_array "presets" _add_preset() { local name icon enabled action allowed_categories blocked_categories config_get name "$1" name "" config_get icon "$1" icon "clock" config_get enabled "$1" enabled "0" config_get action "$1" action "block" config_get allowed_categories "$1" allowed_categories "" config_get blocked_categories "$1" blocked_categories "" json_add_object "" json_add_string "id" "$1" json_add_string "name" "$name" json_add_string "icon" "$icon" json_add_boolean "enabled" "$enabled" json_add_string "action" "$action" json_add_string "allowed_categories" "$allowed_categories" json_add_string "blocked_categories" "$blocked_categories" json_close_object } config_foreach _add_preset preset_mode # Add built-in presets if none exist local preset_count=0 config_foreach _count_preset preset_mode _count_preset() { preset_count=$(( preset_count + 1 )); } if [ "$preset_count" -eq 0 ]; then json_add_object "" json_add_string "id" "preset_bedtime" json_add_string "name" "Bedtime" json_add_string "icon" "moon" json_add_boolean "enabled" 0 json_add_string "action" "block" json_add_string "blocked_categories" "all" json_add_boolean "builtin" 1 json_close_object json_add_object "" json_add_string "id" "preset_homework" json_add_string "name" "Homework" json_add_string "icon" "book" json_add_boolean "enabled" 0 json_add_string "action" "filter" json_add_string "allowed_categories" "education reference" json_add_string "blocked_categories" "gaming social streaming" json_add_boolean "builtin" 1 json_close_object json_add_object "" json_add_string "id" "preset_family" json_add_string "name" "Family Time" json_add_string "icon" "users" json_add_boolean "enabled" 0 json_add_string "action" "filter" json_add_string "allowed_categories" "streaming education" json_add_string "blocked_categories" "adult gambling" json_add_boolean "builtin" 1 json_close_object fi json_close_array json_dump } # Activate preset mode activate_preset_mode() { read -r input json_load "$input" local preset_id enabled json_get_var preset_id preset_id json_get_var enabled enabled "1" json_cleanup if [ -z "$preset_id" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Preset ID is required" json_dump return 1 fi # Deactivate all other presets first config_load bandwidth _deactivate_other() { [ "$1" != "$preset_id" ] && uci -q set bandwidth.$1.enabled='0' } config_foreach _deactivate_other preset_mode # Activate this one uci -q set bandwidth.$preset_id.enabled="$enabled" uci -q commit bandwidth # Apply firewall rules based on preset apply_preset_rules "$preset_id" "$enabled" json_init json_add_boolean "success" 1 json_add_string "message" "Preset mode activated" json_dump } # Apply preset firewall rules (internal) apply_preset_rules() { local preset_id="$1" local enabled="$2" # Would add iptables/nftables rules here for content filtering # For now, just log the action logger -t bandwidth-manager "Preset $preset_id set to enabled=$enabled" } # Get filter categories get_filter_categories() { json_init json_add_array "categories" # Standard categories local categories="adult gambling gaming social streaming education news shopping finance health travel technology sports entertainment reference downloads" for cat in $categories; do json_add_object "" json_add_string "id" "$cat" json_add_string "name" "$(echo "$cat" | sed 's/.*/\u&/')" json_close_object done json_close_array json_dump } # ============================================ # Bandwidth Alerts # ============================================ ALERT_HISTORY="/tmp/bandwidth_alerts.log" PENDING_ALERTS="/tmp/bandwidth_pending_alerts.json" # Get alert settings get_alert_settings() { config_load bandwidth json_init local enabled quota_threshold_80 quota_threshold_90 quota_threshold_100 new_device_alert high_bandwidth_alert high_bandwidth_threshold config_get enabled alerts enabled "0" config_get quota_threshold_80 alerts quota_threshold_80 "1" config_get quota_threshold_90 alerts quota_threshold_90 "1" config_get quota_threshold_100 alerts quota_threshold_100 "1" config_get new_device_alert alerts new_device_alert "0" config_get high_bandwidth_alert alerts high_bandwidth_alert "0" config_get high_bandwidth_threshold alerts high_bandwidth_threshold "100" json_add_boolean "enabled" "$enabled" json_add_boolean "quota_threshold_80" "$quota_threshold_80" json_add_boolean "quota_threshold_90" "$quota_threshold_90" json_add_boolean "quota_threshold_100" "$quota_threshold_100" json_add_boolean "new_device_alert" "$new_device_alert" json_add_boolean "high_bandwidth_alert" "$high_bandwidth_alert" json_add_int "high_bandwidth_threshold" "$high_bandwidth_threshold" # Email config local smtp_server smtp_port smtp_user smtp_tls recipient sender config_get smtp_server email smtp_server "" config_get smtp_port email smtp_port "587" config_get smtp_user email smtp_user "" config_get smtp_tls email smtp_tls "1" config_get recipient email recipient "" config_get sender email sender "" json_add_object "email" json_add_string "smtp_server" "$smtp_server" json_add_int "smtp_port" "$smtp_port" json_add_string "smtp_user" "$smtp_user" json_add_boolean "smtp_tls" "$smtp_tls" json_add_string "recipient" "$recipient" json_add_string "sender" "$sender" json_add_boolean "configured" "$([ -n "$smtp_server" ] && [ -n "$recipient" ] && echo 1 || echo 0)" json_close_object # SMS config local sms_provider account_sid from_number to_number config_get sms_provider sms provider "" config_get account_sid sms account_sid "" config_get from_number sms from_number "" config_get to_number sms to_number "" json_add_object "sms" json_add_string "provider" "$sms_provider" json_add_string "from_number" "$from_number" json_add_string "to_number" "$to_number" json_add_boolean "configured" "$([ -n "$sms_provider" ] && [ -n "$to_number" ] && echo 1 || echo 0)" json_close_object json_dump } # Update alert settings update_alert_settings() { read -r input json_load "$input" local enabled quota_threshold_80 quota_threshold_90 quota_threshold_100 new_device_alert high_bandwidth_alert high_bandwidth_threshold json_get_var enabled enabled "0" json_get_var quota_threshold_80 quota_threshold_80 "1" json_get_var quota_threshold_90 quota_threshold_90 "1" json_get_var quota_threshold_100 quota_threshold_100 "1" json_get_var new_device_alert new_device_alert "0" json_get_var high_bandwidth_alert high_bandwidth_alert "0" json_get_var high_bandwidth_threshold high_bandwidth_threshold "100" json_cleanup uci -q batch << EOF set bandwidth.alerts.enabled='$enabled' set bandwidth.alerts.quota_threshold_80='$quota_threshold_80' set bandwidth.alerts.quota_threshold_90='$quota_threshold_90' set bandwidth.alerts.quota_threshold_100='$quota_threshold_100' set bandwidth.alerts.new_device_alert='$new_device_alert' set bandwidth.alerts.high_bandwidth_alert='$high_bandwidth_alert' set bandwidth.alerts.high_bandwidth_threshold='$high_bandwidth_threshold' commit bandwidth EOF json_init json_add_boolean "success" 1 json_add_string "message" "Alert settings updated" json_dump } # Configure email settings configure_email() { read -r input json_load "$input" local smtp_server smtp_port smtp_user smtp_password smtp_tls recipient sender json_get_var smtp_server smtp_server json_get_var smtp_port smtp_port "587" json_get_var smtp_user smtp_user "" json_get_var smtp_password smtp_password "" json_get_var smtp_tls smtp_tls "1" json_get_var recipient recipient json_get_var sender sender "" json_cleanup if [ -z "$smtp_server" ] || [ -z "$recipient" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "SMTP server and recipient are required" json_dump return 1 fi uci -q batch << EOF set bandwidth.email.smtp_server='$smtp_server' set bandwidth.email.smtp_port='$smtp_port' set bandwidth.email.smtp_user='$smtp_user' set bandwidth.email.smtp_password='$smtp_password' set bandwidth.email.smtp_tls='$smtp_tls' set bandwidth.email.recipient='$recipient' set bandwidth.email.sender='$sender' commit bandwidth EOF json_init json_add_boolean "success" 1 json_add_string "message" "Email settings configured" json_dump } # Configure SMS settings configure_sms() { read -r input json_load "$input" local provider account_sid auth_token from_number to_number json_get_var provider provider json_get_var account_sid account_sid "" json_get_var auth_token auth_token "" json_get_var from_number from_number "" json_get_var to_number to_number json_cleanup if [ -z "$provider" ] || [ -z "$to_number" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Provider and to_number are required" json_dump return 1 fi uci -q batch << EOF set bandwidth.sms.provider='$provider' set bandwidth.sms.account_sid='$account_sid' set bandwidth.sms.auth_token='$auth_token' set bandwidth.sms.from_number='$from_number' set bandwidth.sms.to_number='$to_number' commit bandwidth EOF json_init json_add_boolean "success" 1 json_add_string "message" "SMS settings configured" json_dump } # Test notification test_notification() { read -r input json_load "$input" local type json_get_var type type "email" json_cleanup config_load bandwidth if [ "$type" = "email" ]; then local smtp_server recipient sender config_get smtp_server email smtp_server "" config_get recipient email recipient "" config_get sender email sender "secubox@router.local" if [ -z "$smtp_server" ] || [ -z "$recipient" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "Email not configured" json_dump return 1 fi # Send test email using msmtp or sendmail if command -v msmtp >/dev/null 2>&1; then echo -e "Subject: SecuBox Test Alert\n\nThis is a test notification from SecuBox Bandwidth Manager." | \ msmtp -a default "$recipient" 2>/dev/null elif command -v sendmail >/dev/null 2>&1; then echo -e "To: $recipient\nFrom: $sender\nSubject: SecuBox Test Alert\n\nThis is a test notification from SecuBox Bandwidth Manager." | \ sendmail "$recipient" 2>/dev/null else json_init json_add_boolean "success" 0 json_add_string "message" "No mail client available (msmtp or sendmail)" json_dump return 1 fi elif [ "$type" = "sms" ]; then local provider to_number config_get provider sms provider "" config_get to_number sms to_number "" if [ -z "$provider" ] || [ -z "$to_number" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "SMS not configured" json_dump return 1 fi # SMS sending would require provider-specific implementation # For now, just log logger -t bandwidth-manager "Test SMS would be sent to $to_number via $provider" fi json_init json_add_boolean "success" 1 json_add_string "message" "Test notification sent" json_dump } # Get alert history get_alert_history() { read -r input json_load "$input" local limit json_get_var limit limit "50" json_cleanup json_init json_add_array "alerts" if [ -f "$ALERT_HISTORY" ]; then tail -n "$limit" "$ALERT_HISTORY" | while IFS='|' read timestamp type severity message acknowledged; do json_add_object "" json_add_string "timestamp" "$timestamp" json_add_string "type" "$type" json_add_string "severity" "$severity" json_add_string "message" "$message" json_add_boolean "acknowledged" "${acknowledged:-0}" json_close_object done fi json_close_array json_dump } # Acknowledge alert acknowledge_alert() { read -r input json_load "$input" local timestamp json_get_var timestamp timestamp json_cleanup if [ -f "$ALERT_HISTORY" ] && [ -n "$timestamp" ]; then sed -i "s/^${timestamp}|\\(.*\\)|0\$/\1|1/" "$ALERT_HISTORY" fi json_init json_add_boolean "success" 1 json_add_string "message" "Alert acknowledged" json_dump } # Get pending (unacknowledged) alerts get_pending_alerts() { json_init json_add_array "alerts" local count=0 if [ -f "$ALERT_HISTORY" ]; then grep '|0$' "$ALERT_HISTORY" 2>/dev/null | while IFS='|' read timestamp type severity message acknowledged; do json_add_object "" json_add_string "timestamp" "$timestamp" json_add_string "type" "$type" json_add_string "severity" "$severity" json_add_string "message" "$message" json_close_object count=$(( count + 1 )) done fi json_close_array json_add_int "count" "$count" json_dump } # Check thresholds and create alerts (called by cron) check_alert_thresholds() { config_load bandwidth local enabled config_get enabled alerts enabled "0" [ "$enabled" != "1" ] && return local quota_threshold_80 quota_threshold_90 quota_threshold_100 config_get quota_threshold_80 alerts quota_threshold_80 "1" config_get quota_threshold_90 alerts quota_threshold_90 "1" config_get quota_threshold_100 alerts quota_threshold_100 "1" # Check each quota _check_quota() { local mac name limit_mb enabled config_get mac "$1" mac "" config_get name "$1" name "" config_get limit_mb "$1" limit_mb "0" config_get enabled "$1" enabled "1" [ "$enabled" != "1" ] || [ "$limit_mb" -eq 0 ] && return local used_mb=$(get_mac_usage "$mac") local percent=$(( (used_mb * 100) / limit_mb )) local alert_key="${mac}_${percent}" local sent_file="/tmp/bandwidth_alert_sent_$alert_key" if [ "$percent" -ge 100 ] && [ "$quota_threshold_100" = "1" ] && [ ! -f "$sent_file" ]; then create_alert "quota" "critical" "Device $name ($mac) has exceeded 100% of quota ($used_mb MB / $limit_mb MB)" touch "$sent_file" elif [ "$percent" -ge 90 ] && [ "$quota_threshold_90" = "1" ] && [ ! -f "/tmp/bandwidth_alert_sent_${mac}_90" ]; then create_alert "quota" "warning" "Device $name ($mac) has reached 90% of quota ($used_mb MB / $limit_mb MB)" touch "/tmp/bandwidth_alert_sent_${mac}_90" elif [ "$percent" -ge 80 ] && [ "$quota_threshold_80" = "1" ] && [ ! -f "/tmp/bandwidth_alert_sent_${mac}_80" ]; then create_alert "quota" "info" "Device $name ($mac) has reached 80% of quota ($used_mb MB / $limit_mb MB)" touch "/tmp/bandwidth_alert_sent_${mac}_80" fi } config_foreach _check_quota quota json_init json_add_boolean "success" 1 json_dump } # Create alert (internal) create_alert() { local type="$1" local severity="$2" local message="$3" local timestamp=$(date +%s) echo "${timestamp}|${type}|${severity}|${message}|0" >> "$ALERT_HISTORY" # Send notification if configured send_alert_notification "$type" "$severity" "$message" } # Send alert notification (internal) send_alert_notification() { local type="$1" local severity="$2" local message="$3" config_load bandwidth # Check if email is configured and send local smtp_server recipient config_get smtp_server email smtp_server "" config_get recipient email recipient "" if [ -n "$smtp_server" ] && [ -n "$recipient" ]; then local sender config_get sender email sender "secubox@router.local" if command -v msmtp >/dev/null 2>&1; then echo -e "Subject: [SecuBox] ${severity}: ${type} alert\n\n${message}" | \ msmtp -a default "$recipient" 2>/dev/null & fi fi # Log to syslog logger -t bandwidth-manager "ALERT [$severity] $type: $message" } # ============================================ # Traffic Graphs Data # ============================================ # Get real-time bandwidth (current Mbps) get_realtime_bandwidth() { local interface config_load bandwidth config_get interface global interface "br-lan" # Get current values local rx1=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0) local tx1=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0) # Wait 1 second sleep 1 # Get new values local rx2=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0) local tx2=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0) # Calculate Mbps local rx_bps=$(( (rx2 - rx1) * 8 )) local tx_bps=$(( (tx2 - tx1) * 8 )) local rx_mbps=$(( rx_bps / 1000000 )) local tx_mbps=$(( tx_bps / 1000000 )) json_init json_add_int "rx_mbps" "$rx_mbps" json_add_int "tx_mbps" "$tx_mbps" json_add_int "rx_bps" "$rx_bps" json_add_int "tx_bps" "$tx_bps" json_add_int "timestamp" "$(date +%s)" json_dump } # Get historical traffic data get_historical_traffic() { read -r input json_load "$input" local period granularity json_get_var period period "24h" json_get_var granularity granularity "hour" json_cleanup init_analytics_db json_init json_add_array "data" local now=$(date +%s) local threshold=0 case "$period" in "1h") threshold=$(( now - 3600 )) ;; "6h") threshold=$(( now - 21600 )) ;; "24h") threshold=$(( now - 86400 )) ;; "7d") threshold=$(( now - 604800 )) ;; "30d") threshold=$(( now - 2592000 )) ;; esac if [ -f "$ANALYTICS_DB" ]; then while IFS='|' read hour date rx tx peak_rx peak_tx clients; do [ "$hour" = "# Hour" ] && continue # Convert date to timestamp for comparison local entry_ts=$(date -d "${date:0:4}-${date:4:2}-${date:6:2} ${hour}:00" +%s 2>/dev/null || echo 0) [ "$entry_ts" -lt "$threshold" ] && continue json_add_object "" json_add_int "timestamp" "$entry_ts" json_add_int "hour" "$hour" json_add_string "date" "$date" json_add_int "rx_bytes" "$rx" json_add_int "tx_bytes" "$tx" json_add_int "clients" "$clients" json_close_object done < "$ANALYTICS_DB" fi json_close_array json_dump } # Get device-specific traffic get_device_traffic() { read -r input json_load "$input" local mac period json_get_var mac mac json_get_var period period "24h" json_cleanup if [ -z "$mac" ]; then json_init json_add_boolean "success" 0 json_add_string "message" "MAC address is required" json_dump return 1 fi json_init # Get current stats from iptables local rx_bytes=0 tx_bytes=0 if iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -qi "$mac"; then rx_bytes=$(iptables -L $IPTABLES_CHAIN -n -v -x 2>/dev/null | grep -i "$mac" | awk '{sum+=$2} END {print sum}') fi # Get hostname local hostname ip hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}') ip=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $3}') json_add_string "mac" "$mac" json_add_string "hostname" "${hostname:-unknown}" json_add_string "ip" "${ip:-}" json_add_int "rx_bytes" "${rx_bytes:-0}" json_add_int "tx_bytes" "${tx_bytes:-0}" # Historical data from usage DB json_add_array "history" if [ -f "$USAGE_DB" ]; then local now=$(date +%s) local threshold=0 case "$period" in "1h") threshold=$(( now - 3600 )) ;; "6h") threshold=$(( now - 21600 )) ;; "24h") threshold=$(( now - 86400 )) ;; "7d") threshold=$(( now - 604800 )) ;; esac grep -i "^$mac|" "$USAGE_DB" 2>/dev/null | while IFS='|' read db_mac timestamp rx tx; do [ "$timestamp" -lt "$threshold" ] && continue json_add_object "" json_add_int "timestamp" "$timestamp" json_add_int "rx_bytes" "$rx" json_add_int "tx_bytes" "$tx" json_close_object done fi json_close_array json_dump } # Get top bandwidth consumers get_top_talkers() { read -r input json_load "$input" local period limit json_get_var period period "24h" json_get_var limit limit "10" json_cleanup json_init json_add_array "talkers" if [ -f /tmp/dhcp.leases ]; then while read -r expires mac ip hostname clientid; do local used_mb=$(get_mac_usage "$mac") [ "$used_mb" -gt 0 ] && echo "$used_mb|$mac|$ip|${hostname:-unknown}" done < /tmp/dhcp.leases | sort -t'|' -k1 -nr | head -n "$limit" | while IFS='|' read used mac ip hostname; do json_add_object "" json_add_string "mac" "$mac" json_add_string "ip" "$ip" json_add_string "hostname" "$hostname" json_add_int "used_mb" "$used" json_close_object done fi json_close_array json_dump } # Get protocol breakdown get_protocol_breakdown() { read -r input json_load "$input" local period json_get_var period period "24h" json_cleanup json_init json_add_array "protocols" if [ -f "$NDPID_FLOWS" ] && command -v jq >/dev/null 2>&1; then jq -r '[.flows[]? | {protocol: (.l4_proto // "Unknown"), bytes: (.bytes_rx + .bytes_tx)}] | group_by(.protocol) | map({protocol: .[0].protocol, total_bytes: (map(.bytes) | add)}) | sort_by(.total_bytes) | reverse | .[:10][] | "\(.protocol)|\(.total_bytes)"' "$NDPID_FLOWS" 2>/dev/null | while IFS='|' read proto bytes; do json_add_object "" json_add_string "protocol" "$proto" json_add_int "bytes" "${bytes:-0}" json_close_object done else # Fallback: basic protocol info from conntrack if [ -f /proc/net/nf_conntrack ]; then for proto in tcp udp icmp; do local count=$(grep -c "$proto" /proc/net/nf_conntrack 2>/dev/null || echo 0) json_add_object "" json_add_string "protocol" "$proto" json_add_int "connections" "$count" json_close_object done fi fi json_close_array json_dump } # ============================================ # Analytics # ============================================ ANALYTICS_DB="/tmp/bandwidth_analytics.db" # Initialize analytics database init_analytics_db() { if [ ! -f "$ANALYTICS_DB" ]; then cat > "$ANALYTICS_DB" << 'EOF' # Hour|Date|Total_RX|Total_TX|Peak_RX|Peak_TX|Client_Count EOF fi } # Get analytics summary get_analytics_summary() { read -r input json_load "$input" local period json_get_var period period "24h" json_cleanup json_init # Current totals local interface config_load bandwidth config_get interface global interface "br-lan" if [ -d "/sys/class/net/$interface" ]; then local rx_bytes=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0) local tx_bytes=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0) json_add_int "total_rx_bytes" "$rx_bytes" json_add_int "total_tx_bytes" "$tx_bytes" fi # Client count local client_count=$(cat /tmp/dhcp.leases 2>/dev/null | wc -l) json_add_int "active_clients" "$client_count" # Top talkers json_add_array "top_talkers" if [ -f /tmp/dhcp.leases ]; then while read -r expires mac ip hostname clientid; do local used_mb=$(get_mac_usage "$mac") [ "$used_mb" -gt 0 ] && echo "$used_mb|$mac|$ip|${hostname:-unknown}" done < /tmp/dhcp.leases | sort -t'|' -k1 -nr | head -5 | while IFS='|' read used mac ip hostname; do json_add_object "" json_add_string "mac" "$mac" json_add_string "ip" "$ip" json_add_string "hostname" "$hostname" json_add_int "used_mb" "$used" json_close_object done fi json_close_array # Application breakdown (from nDPId) json_add_array "app_breakdown" if [ -f "$NDPID_FLOWS" ]; then jq -r '[.flows[]? | {app: (.app // "Unknown"), bytes: (.bytes_rx + .bytes_tx)}] | group_by(.app) | map({app: .[0].app, total_bytes: (map(.bytes) | add)}) | sort_by(.total_bytes) | reverse | .[:10][] | "\(.app)|\(.total_bytes)"' "$NDPID_FLOWS" 2>/dev/null | while IFS='|' read app bytes; do json_add_object "" json_add_string "app" "$app" json_add_int "bytes" "${bytes:-0}" json_close_object done fi json_close_array # Protocol breakdown (from nDPId) json_add_array "protocol_breakdown" if [ -f "$NDPID_FLOWS" ]; then jq -r '[.flows[]? | {protocol: (.l4_proto // "Unknown"), bytes: (.bytes_rx + .bytes_tx)}] | group_by(.protocol) | map({protocol: .[0].protocol, total_bytes: (map(.bytes) | add)}) | sort_by(.total_bytes) | reverse | .[:5][] | "\(.protocol)|\(.total_bytes)"' "$NDPID_FLOWS" 2>/dev/null | while IFS='|' read proto bytes; do json_add_object "" json_add_string "protocol" "$proto" json_add_int "bytes" "${bytes:-0}" json_close_object done fi json_close_array json_dump } # Get hourly usage data for charts get_hourly_data() { read -r input json_load "$input" local days json_get_var days days "7" json_cleanup init_analytics_db json_init json_add_array "hourly_data" # Read from analytics DB local threshold_date=$(date -d "$days days ago" +%Y%m%d 2>/dev/null || date +%Y%m%d) if [ -f "$ANALYTICS_DB" ]; then while IFS='|' read hour date rx tx peak_rx peak_tx clients; do [ "$hour" = "# Hour" ] && continue [ "$date" -lt "$threshold_date" ] 2>/dev/null && continue json_add_object "" json_add_int "hour" "$hour" json_add_string "date" "$date" json_add_int "rx_bytes" "$rx" json_add_int "tx_bytes" "$tx" json_add_int "peak_rx" "$peak_rx" json_add_int "peak_tx" "$peak_tx" json_add_int "clients" "$clients" json_close_object done < "$ANALYTICS_DB" fi json_close_array json_dump } # Record current stats (called by cron) record_stats() { init_analytics_db local interface config_load bandwidth config_get interface global interface "br-lan" local hour=$(date +%H) local date=$(date +%Y%m%d) local rx_bytes=0 tx_bytes=0 if [ -d "/sys/class/net/$interface" ]; then rx_bytes=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0) tx_bytes=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0) fi local client_count=$(cat /tmp/dhcp.leases 2>/dev/null | wc -l) echo "$hour|$date|$rx_bytes|$tx_bytes|$rx_bytes|$tx_bytes|$client_count" >> "$ANALYTICS_DB" # Keep only last 30 days local old_date=$(date -d "30 days ago" +%Y%m%d 2>/dev/null || echo "0") if [ -f "$ANALYTICS_DB" ]; then awk -F'|' -v old="$old_date" '$2 >= old || /^#/' "$ANALYTICS_DB" > "${ANALYTICS_DB}.tmp" mv "${ANALYTICS_DB}.tmp" "$ANALYTICS_DB" fi json_init json_add_boolean "success" 1 json_dump } # Get QoS priority classes get_classes() { json_init json_add_array "classes" # Class 1 - Highest Priority json_add_object "" json_add_int "priority" 1 json_add_string "name" "Real-time (VoIP, Gaming)" json_add_string "description" "Latency-sensitive applications requiring immediate delivery" json_add_int "rate" 30 json_add_int "ceil" 100 json_close_object # Class 2 - High Priority json_add_object "" json_add_int "priority" 2 json_add_string "name" "Interactive (SSH, RDP)" json_add_string "description" "Interactive sessions and remote administration" json_add_int "rate" 20 json_add_int "ceil" 90 json_close_object # Class 3 - Medium-High Priority json_add_object "" json_add_int "priority" 3 json_add_string "name" "Business Critical" json_add_string "description" "Email, VPN, business applications" json_add_int "rate" 15 json_add_int "ceil" 80 json_close_object # Class 4 - Normal Priority json_add_object "" json_add_int "priority" 4 json_add_string "name" "Video Streaming" json_add_string "description" "YouTube, Netflix, video conferencing" json_add_int "rate" 10 json_add_int "ceil" 70 json_close_object # Class 5 - Normal Priority json_add_object "" json_add_int "priority" 5 json_add_string "name" "Web Browsing" json_add_string "description" "HTTP/HTTPS web traffic, social media" json_add_int "rate" 10 json_add_int "ceil" 60 json_close_object # Class 6 - Low Priority json_add_object "" json_add_int "priority" 6 json_add_string "name" "Bulk Transfer" json_add_string "description" "File transfers, cloud sync" json_add_int "rate" 5 json_add_int "ceil" 50 json_close_object # Class 7 - Lower Priority json_add_object "" json_add_int "priority" 7 json_add_string "name" "Downloads (P2P)" json_add_string "description" "Torrents, large downloads" json_add_int "rate" 5 json_add_int "ceil" 40 json_close_object # Class 8 - Lowest Priority json_add_object "" json_add_int "priority" 8 json_add_string "name" "Background / Scavenger" json_add_string "description" "System updates, backups, lowest priority traffic" json_add_int "rate" 5 json_add_int "ceil" 30 json_close_object json_close_array json_dump } # Main dispatcher case "$1" in list) cat << 'EOF' { "status": {}, "list_rules": {}, "add_rule": { "name": "string", "type": "string", "target": "string", "limit_down": 0, "limit_up": 0, "priority": 5 }, "delete_rule": { "rule_id": "string" }, "list_quotas": {}, "get_quota": { "mac": "string" }, "set_quota": { "mac": "string", "name": "string", "limit_mb": 0, "action": "string", "reset_day": 1 }, "reset_quota": { "mac": "string" }, "get_usage_realtime": {}, "get_usage_history": { "timeframe": "24h", "mac": "" }, "get_media": {}, "get_classes": {}, "get_dpi_applications": {}, "get_smart_suggestions": {}, "apply_dpi_rule": { "app_name": "string", "priority": 5, "limit_down": 0, "limit_up": 0 }, "list_groups": {}, "get_group": { "group_id": "string" }, "create_group": { "name": "string", "description": "", "quota_mb": 0, "priority": 5, "members": "" }, "update_group": { "group_id": "string", "name": "string", "description": "", "quota_mb": 0, "priority": 5, "members": "" }, "delete_group": { "group_id": "string" }, "add_to_group": { "group_id": "string", "mac": "string" }, "remove_from_group": { "group_id": "string", "mac": "string" }, "get_analytics_summary": { "period": "24h" }, "get_hourly_data": { "days": 7 }, "record_stats": {}, "get_builtin_profiles": {}, "list_profiles": {}, "get_profile": { "profile_id": "string" }, "create_profile": { "name": "string", "description": "", "icon": "tag", "color": "#6366f1", "priority": 5, "limit_down": 0, "limit_up": 0, "latency_mode": "normal", "content_filter": "", "isolate": 0, "schedule": "" }, "update_profile": { "profile_id": "string", "name": "string", "description": "", "icon": "tag", "color": "#6366f1", "priority": 5, "limit_down": 0, "limit_up": 0, "latency_mode": "normal", "content_filter": "", "isolate": 0, "schedule": "", "enabled": 1 }, "delete_profile": { "profile_id": "string" }, "clone_profile": { "source_id": "string", "new_name": "string" }, "assign_profile_to_device": { "mac": "string", "profile_id": "string", "custom_limit_down": 0, "custom_limit_up": 0 }, "assign_profile_to_group": { "group_id": "string", "profile_id": "string" }, "remove_profile_assignment": { "mac": "string" }, "list_profile_assignments": {}, "list_parental_schedules": {}, "create_parental_schedule": { "name": "string", "target_type": "device", "target": "string", "action": "block", "start_time": "21:00", "end_time": "07:00", "days": "mon tue wed thu fri" }, "update_parental_schedule": { "schedule_id": "string", "name": "string", "target_type": "device", "target": "string", "action": "block", "start_time": "21:00", "end_time": "07:00", "days": "mon tue wed thu fri", "enabled": 1 }, "delete_parental_schedule": { "schedule_id": "string" }, "toggle_parental_schedule": { "schedule_id": "string", "enabled": 1 }, "list_preset_modes": {}, "activate_preset_mode": { "preset_id": "string", "enabled": 1 }, "get_filter_categories": {}, "get_alert_settings": {}, "update_alert_settings": { "enabled": 0, "quota_threshold_80": 1, "quota_threshold_90": 1, "quota_threshold_100": 1, "new_device_alert": 0, "high_bandwidth_alert": 0, "high_bandwidth_threshold": 100 }, "configure_email": { "smtp_server": "string", "smtp_port": 587, "smtp_user": "", "smtp_password": "", "smtp_tls": 1, "recipient": "string", "sender": "" }, "configure_sms": { "provider": "string", "account_sid": "", "auth_token": "", "from_number": "", "to_number": "string" }, "test_notification": { "type": "email" }, "get_alert_history": { "limit": 50 }, "acknowledge_alert": { "timestamp": "string" }, "get_pending_alerts": {}, "check_alert_thresholds": {}, "get_realtime_bandwidth": {}, "get_historical_traffic": { "period": "24h", "granularity": "hour" }, "get_device_traffic": { "mac": "string", "period": "24h" }, "get_top_talkers": { "period": "24h", "limit": 10 }, "get_protocol_breakdown": { "period": "24h" } } EOF ;; call) case "$2" in status) status ;; list_rules) list_rules ;; add_rule) add_rule ;; delete_rule) delete_rule ;; list_quotas) list_quotas ;; get_quota) get_quota ;; set_quota) set_quota ;; reset_quota) reset_quota ;; get_usage_realtime) get_usage_realtime ;; get_usage_history) get_usage_history ;; get_media) get_media ;; get_classes) get_classes ;; get_dpi_applications) get_dpi_applications ;; get_smart_suggestions) get_smart_suggestions ;; apply_dpi_rule) apply_dpi_rule ;; list_groups) list_groups ;; get_group) get_group ;; create_group) create_group ;; update_group) update_group ;; delete_group) delete_group ;; add_to_group) add_to_group ;; remove_from_group) remove_from_group ;; get_analytics_summary) get_analytics_summary ;; get_hourly_data) get_hourly_data ;; record_stats) record_stats ;; get_builtin_profiles) get_builtin_profiles ;; list_profiles) list_profiles ;; get_profile) get_profile ;; create_profile) create_profile ;; update_profile) update_profile ;; delete_profile) delete_profile ;; clone_profile) clone_profile ;; assign_profile_to_device) assign_profile_to_device ;; assign_profile_to_group) assign_profile_to_group ;; remove_profile_assignment) remove_profile_assignment ;; list_profile_assignments) list_profile_assignments ;; list_parental_schedules) list_parental_schedules ;; create_parental_schedule) create_parental_schedule ;; update_parental_schedule) update_parental_schedule ;; delete_parental_schedule) delete_parental_schedule ;; toggle_parental_schedule) toggle_parental_schedule ;; list_preset_modes) list_preset_modes ;; activate_preset_mode) activate_preset_mode ;; get_filter_categories) get_filter_categories ;; get_alert_settings) get_alert_settings ;; update_alert_settings) update_alert_settings ;; configure_email) configure_email ;; configure_sms) configure_sms ;; test_notification) test_notification ;; get_alert_history) get_alert_history ;; acknowledge_alert) acknowledge_alert ;; get_pending_alerts) get_pending_alerts ;; check_alert_thresholds) check_alert_thresholds ;; get_realtime_bandwidth) get_realtime_bandwidth ;; get_historical_traffic) get_historical_traffic ;; get_device_traffic) get_device_traffic ;; get_top_talkers) get_top_talkers ;; get_protocol_breakdown) get_protocol_breakdown ;; *) json_init json_add_boolean "success" 0 json_add_string "error" "Unknown method: $2" json_dump ;; esac ;; esac