I am trying to set bandwidth limits for a device using its MAC ID and the limits received via an API request. The script I wrote is below:
#!/bin/bash
# Read the JSON payload from stdin
POST_DATA=$(cat)
# Debugging: Log POST data
echo "Received POST Data: $POST_DATA" > /tmp/api_debug.log
# Parse JSON input using awk
MAC_ADDRESS=$(echo "$POST_DATA" | awk -F'"mac_address":"' '{print $2}' | awk -F'"' '{print $1}')
DOWNLOAD=$(echo "$POST_DATA" | awk -F'"download":"' '{print $2}' | awk -F'"' '{print $1}')
UPLOAD=$(echo "$POST_DATA" | awk -F'"upload":"' '{print $2}' | awk -F'"' '{print $1}')
# Debugging: Log parsed values
echo "MAC_ADDRESS: $MAC_ADDRESS, DOWNLOAD: $DOWNLOAD, UPLOAD: $UPLOAD" >> /tmp/api_debug.log
# Validate input
if [ -z "$MAC_ADDRESS" ] || [ -z "$DOWNLOAD" ] || [ -z "$UPLOAD" ]; then
echo "Content-Type: application/json"
echo ""
echo '{"status":"error","message":"Missing parameters"}'
exit 1
fi
# Define network interfaces
INTERFACE_DOWNLOAD="phy1-ap0" # Interface for download traffic
INTERFACE_UPLOAD="wan" # Interface for upload traffic
# Clear existing traffic control rules
tc qdisc del dev $INTERFACE_DOWNLOAD root 2>/dev/null
tc qdisc del dev $INTERFACE_UPLOAD root 2>/dev/null
# Download limits on the download interface
tc qdisc add dev $INTERFACE_DOWNLOAD root handle 1: htb default 2
tc class add dev $INTERFACE_DOWNLOAD parent 1: classid 1:1 htb rate 100mbit ceil 100mbit
tc class add dev $INTERFACE_DOWNLOAD parent 1:1 classid 1:2 htb rate ${DOWNLOAD}kbit ceil ${DOWNLOAD}kbit burst 15k
tc filter add dev $INTERFACE_DOWNLOAD protocol all parent 1: prio 1 u32 match ether src $MAC_ADDRESS flowid 1:2
# Upload limits on the upload interface
tc qdisc add dev $INTERFACE_UPLOAD root handle 1: htb default 3
tc class add dev $INTERFACE_UPLOAD parent 1: classid 1:1 htb rate 100mbit ceil 100mbit
tc class add dev $INTERFACE_UPLOAD parent 1:1 classid 1:3 htb rate ${UPLOAD}kbit ceil ${UPLOAD}kbit burst 15k
tc filter add dev $INTERFACE_UPLOAD protocol all parent 1: prio 1 u32 match ether dst $MAC_ADDRESS flowid 1:3
# Return success response
echo "Content-Type: application/json"
echo ""
echo '{"status":"success","message":"Bandwidth limits applied"}'
But this script doesn't work for multiple devices being connected with their own bandwidth limits, so I modified the script (given below) with a unique class ID, but this script is not working at all, in the sense the script runs and sets the bandwidth limits as per logs, but the same is not getting updated on device speedtest.
#!/bin/bash
# Read the JSON payload from stdin
POST_DATA=$(cat)
# Debugging: Log POST data
echo "Received POST Data: $POST_DATA" > /tmp/api_debug.log
# Parse JSON input using awk
MAC_ADDRESS=$(echo "$POST_DATA" | awk -F'"mac_address":"' '{print $2}' | awk -F'"' '{print $1}')
DOWNLOAD=$(echo "$POST_DATA" | awk -F'"download":"' '{print $2}' | awk -F'"' '{print $1}')
UPLOAD=$(echo "$POST_DATA" | awk -F'"upload":"' '{print $2}' | awk -F'"' '{print $1}')
# Debugging: Log parsed values
echo "MAC_ADDRESS: $MAC_ADDRESS, DOWNLOAD: $DOWNLOAD, UPLOAD: $UPLOAD" >> /tmp/api_debug.log
# Validate input
if [ -z "$MAC_ADDRESS" ] || [ -z "$DOWNLOAD" ] || [ -z "$UPLOAD" ]; then
echo "Content-Type: application/json"
echo ""
echo '{"status":"error","message":"Missing parameters"}'
exit 1
fi
# Define network interfaces
INTERFACE_DOWNLOAD="phy1-ap0" # Interface for download traffic
INTERFACE_UPLOAD="wan" # Interface for upload traffic
# Generate a unique class ID for the device based on MAC address
CLASS_ID_DOWNLOAD=$(echo $MAC_ADDRESS | md5sum | awk '{print substr($1, 1, 4)}')
CLASS_ID_UPLOAD=$(echo $MAC_ADDRESS | md5sum | awk '{print substr($1, 5, 4)}')
# Debugging: Log generated class IDs
echo "Generated CLASS_ID_DOWNLOAD: $CLASS_ID_DOWNLOAD, CLASS_ID_UPLOAD: $CLASS_ID_UPLOAD" >> /tmp/api_debug.log
# Remove existing classes and filters associated with the MAC address (without deleting the root qdisc)
tc filter del dev $INTERFACE_DOWNLOAD protocol all parent 1: prio 1 u32 match ether src $MAC_ADDRESS flowid $CLASS_ID_DOWNLOAD 2>/dev/null
tc filter del dev $INTERFACE_UPLOAD protocol all parent 1: prio 1 u32 match ether dst $MAC_ADDRESS flowid $CLASS_ID_UPLOAD 2>/dev/null
tc class del dev $INTERFACE_DOWNLOAD classid $CLASS_ID_DOWNLOAD 2>/dev/null
tc class del dev $INTERFACE_UPLOAD classid $CLASS_ID_UPLOAD 2>/dev/null
# Download limits on the download interface
tc qdisc add dev $INTERFACE_DOWNLOAD root handle 1: htb default 2
tc class add dev $INTERFACE_UPLOAD parent 1: classid 1:1 htb rate 100mbit ceil 100mbit
tc class add dev $INTERFACE_DOWNLOAD parent 1: classid $CLASS_ID_DOWNLOAD htb rate ${DOWNLOAD}kbit ceil ${DOWNLOAD}kbit burst 15k
tc filter add dev $INTERFACE_DOWNLOAD protocol all parent 1: prio 1 u32 match ether src $MAC_ADDRESS flowid $CLASS_ID_DOWNLOAD
# Upload limits on the upload interface
tc qdisc add dev $INTERFACE_UPLOAD root handle 1: htb default 3
tc class add dev $INTERFACE_UPLOAD parent 1: classid 1:1 htb rate 100mbit ceil 100mbit
tc class add dev $INTERFACE_UPLOAD parent 1: classid $CLASS_ID_UPLOAD htb rate ${UPLOAD}kbit ceil ${UPLOAD}kbit burst 15k
tc filter add dev $INTERFACE_UPLOAD protocol all parent 1: prio 1 u32 match ether dst $MAC_ADDRESS flowid $CLASS_ID_UPLOAD
# Return success response
echo "Content-Type: application/json"
echo ""
echo '{"status":"success","message":"Bandwidth limits applied"}'
I really appreciate any help with the code. Thanks.
Yes but I don't want anything else being used for this functionality, I need the same code to work for the mentioned logic with any updations or modifications. Thanks for the suggestion anyway.
Your flowids should be “1:uniqueid”, they currently just look like “uniqueid” if I understand correctly?
In addition they should be base10 not base16 I believe.
I would just use a simple number that increments in your script rather than the md5sum convolution.
Thanks to the flowid correction with that logic changed and implementing simple increment logic the script is working fine with multiple MAC IDs, but now the limits are not being updated properly. Is there any other way to generate unique classids apart from md5sum or increments?
#!/bin/bash
# Ensure proper CGI response headers are sent immediately
echo "Content-Type: application/json"
echo ""
# Function to handle errors and exit gracefully
handle_error() {
echo "{\"status\":\"error\",\"message\":\"$1\"}"
exit 1
}
# Function to apply bandwidth limits using tc
apply_bandwidth_limits() {
local MAC_ADDRESS="$1"
local DOWNLOAD="$2"
local UPLOAD="$3"
local INTERFACE_DOWNLOAD="phy1-ap0"
local INTERFACE_UPLOAD="wan"
# Initialize counters for class IDs
if [ ! -f /tmp/class_id_counter_download ]; then
echo 10 > /tmp/class_id_counter_download
fi
if [ ! -f /tmp/class_id_counter_upload ]; then
echo 10 > /tmp/class_id_counter_upload
fi
# Read and increment the counters
local CLASS_ID_DOWNLOAD=$(cat /tmp/class_id_counter_download)
echo $((CLASS_ID_DOWNLOAD + 1)) > /tmp/class_id_counter_download
local CLASS_ID_UPLOAD=$(cat /tmp/class_id_counter_upload)
echo $((CLASS_ID_UPLOAD + 1)) > /tmp/class_id_counter_upload
# Set up root qdisc if not already set
tc qdisc show dev $INTERFACE_DOWNLOAD | grep -q "htb 1:" || tc qdisc add dev $INTERFACE_DOWNLOAD root handle 1: htb default 10 || return 1
tc qdisc show dev $INTERFACE_UPLOAD | grep -q "htb 1:" || tc qdisc add dev $INTERFACE_UPLOAD root handle 1: htb default 10 || return 1
# Add root classes if not already set
tc class show dev $INTERFACE_DOWNLOAD | grep -q "class htb 1:1" || tc class add dev $INTERFACE_DOWNLOAD parent 1: classid 1:1 htb rate 100mbit || return 1
tc class show dev $INTERFACE_UPLOAD | grep -q "class htb 1:1" || tc class add dev $INTERFACE_UPLOAD parent 1: classid 1:1 htb rate 100mbit || return 1
# Remove existing classes and filters associated with the MAC address (without deleting the root qdisc)
tc filter del dev $INTERFACE_DOWNLOAD parent 1: protocol ip prio 1 u32 match ether dst $MAC_ADDRESS flowid 1: 2>/dev/null
tc filter del dev $INTERFACE_UPLOAD parent 1: protocol ip prio 1 u32 match ether src $MAC_ADDRESS flowid 1: 2>/dev/null
tc class del dev $INTERFACE_DOWNLOAD classid 1:$CLASS_ID_DOWNLOAD 2>/dev/null
tc class del dev $INTERFACE_UPLOAD classid 1:$CLASS_ID_UPLOAD 2>/dev/null
# Add bandwidth limit classes
tc class add dev $INTERFACE_DOWNLOAD parent 1:1 classid 1:$CLASS_ID_DOWNLOAD htb rate ${DOWNLOAD}kbit ceil ${DOWNLOAD}kbit burst 15k || return 1
tc class add dev $INTERFACE_UPLOAD parent 1:1 classid 1:$CLASS_ID_UPLOAD htb rate ${UPLOAD}kbit ceil ${UPLOAD}kbit burst 15k || return 1
# Add packet scheduler
tc qdisc add dev $INTERFACE_DOWNLOAD parent 1:$CLASS_ID_DOWNLOAD handle ${CLASS_ID_DOWNLOAD}0: fq_codel || return 1
tc qdisc add dev $INTERFACE_UPLOAD parent 1:$CLASS_ID_UPLOAD handle ${CLASS_ID_UPLOAD}0: fq_codel || return 1
# Add filters for MAC address matching
tc filter add dev $INTERFACE_DOWNLOAD parent 1: protocol ip prio 1 u32 match ether dst $MAC_ADDRESS flowid 1:$CLASS_ID_DOWNLOAD || return 1
tc filter add dev $INTERFACE_UPLOAD parent 1: protocol ip prio 1 u32 match ether src $MAC_ADDRESS flowid 1:$CLASS_ID_UPLOAD || return 1
return 0
}
# Read and validate POST data
read -n $CONTENT_LENGTH POST_DATA
MAC_ADDRESS=$(echo "$POST_DATA" | awk -F'"mac_address":"' '{print $2}' | awk -F'"' '{print $1}')
DOWNLOAD=$(echo "$POST_DATA" | awk -F'"download":"' '{print $2}' | awk -F'"' '{print $1}')
UPLOAD=$(echo "$POST_DATA" | awk -F'"upload":"' '{print $2}' | awk -F'"' '{print $1}')
# Validate parsed data
[ -z "$MAC_ADDRESS" ] && handle_error "MAC address is required"
[ -z "$DOWNLOAD" ] && handle_error "Download speed is required"
[ -z "$UPLOAD" ] && handle_error "Upload speed is required"
# Validate MAC address format
echo "$MAC_ADDRESS" | grep -E '^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$' > /dev/null || \
handle_error "Invalid MAC address format"
# Validate speed values are numbers
echo "$DOWNLOAD" | grep -E '^[0-9]+$' > /dev/null || handle_error "Invalid download speed"
echo "$UPLOAD" | grep -E '^[0-9]+$' > /dev/null || handle_error "Invalid upload speed"
# Apply bandwidth limits
if apply_bandwidth_limits "$MAC_ADDRESS" "$DOWNLOAD" "$UPLOAD"; then
echo "{\"status\":\"success\",\"message\":\"Bandwidth limits applied for MAC: $MAC_ADDRESS\"}"
else
handle_error "Failed to apply bandwidth limits"
fi
# Log configuration for debugging (won't affect API response)
{
echo "Configuration applied at: $(date)"
echo "MAC: $MAC_ADDRESS"
echo "Download: ${DOWNLOAD}kbit"
echo "Upload: ${UPLOAD}kbit"
echo "TC Configuration:"
tc -s class show dev phy1-ap0
tc -s class show dev wan
} >> /tmp/bandwidth_control.log 2>&1
I would create a run file with the format
UniqueID MACADDR
1 AA:BB:CC:DD:EE:FF
3 11:22:33:44:55:66
When an api request is made, look at the file and search for MAC. If it exists, grab the flowid. Otherwise grab the highest entry and add 2 to get your next uniqueid.