1
0
Fork 0
mirror of https://github.com/Ysurac/openmptcprouter-feeds.git synced 2025-03-09 15:40:03 +00:00

Add Cake Autorate

This commit is contained in:
Ycarus (Yannick Chabanois) 2022-06-16 19:28:25 +02:00
parent c4157bfc0f
commit b8c8658720
44 changed files with 8689 additions and 7 deletions

View file

@ -0,0 +1,13 @@
{
"admin/network/sqm": {
"title": "SQM QoS",
"order": 59,
"action": {
"type": "view",
"path": "network/sqm"
},
"depends": {
"acl": [ "luci-app-sqm" ]
}
}
}

View file

@ -0,0 +1,23 @@
{
"luci-app-sqm": {
"description": "Grant UCI access for luci-app-sqm",
"read": {
"file": {
"/var/run/sqm/available_qdiscs": [ "list" ],
"/usr/lib/sqm/*.qos.help": [ "read" ],
"/etc/init.d/sqm enable" : [ "exec" ],
"/etc/init.d/sqm start" : [ "exec" ],
"/etc/init.d/sqm-autorate enable" : [ "exec" ],
"/etc/init.d/sqm-autorate start" : [ "exec" ]
},
"uci": [ "sqm" ],
"ubus": {
"file": [ "read", "list" ],
"luci": [ "setInitAction" ]
}
},
"write": {
"uci": [ "sqm" ]
}
}
}

View file

@ -0,0 +1,608 @@
#!/bin/bash
# CAKE-autorate automatically adjusts bandwidth for CAKE in dependence on detected load and OWD/RTT
# requires packages: bash; and iputils-ping
# Author: @Lynx (OpenWrt forum)
# Inspiration taken from: @moeller0 (OpenWrt forum)
# Modified by Ycarus (Yannick Chabanois) for OpenMPTCProuter:
# * Add multiples interfaces support
# Possible performance improvement
export LC_ALL=C
export TZ=UTC
trap cleanup_and_killall INT TERM EXIT
cleanup_and_killall()
{
echo "Killing all background processes and cleaning up /tmp files."
trap - INT TERM EXIT
kill $monitor_achieved_rates_pid 2> /dev/null
kill $maintain_pingers_pid 2> /dev/null
[[ -d /tmp/CAKE-autorate-${dl_if} ]] && rm -r /tmp/CAKE-autorate-${dl_if}
exit
}
install_dir="/usr/share/sqm-autorate/"
. $install_dir"config.sh" "$1"
# test if stdout is a tty (terminal)
[[ ! -t 1 ]] && exec &> /tmp/cake-autorate-${dl_if}.log
get_next_shaper_rate()
{
local min_shaper_rate_kbps=$1
local base_shaper_rate_kbps=$2
local max_shaper_rate_kbps=$3
local achieved_rate_kbps=$4
local load_condition=$5
local t_next_rate_us=$6
local -n t_last_bufferbloat_us=$7
local -n t_last_decay_us=$8
local -n shaper_rate_kbps=$9
case $load_condition in
# bufferbloat detected, so decrease the rate providing not inside bufferbloat refractory period
*delayed)
if (( $t_next_rate_us > ($t_last_bufferbloat_us+$bufferbloat_refractory_period_us) )); then
adjusted_achieved_rate_kbps=$(( ($achieved_rate_kbps*$achieved_rate_adjust_bufferbloat)/1000 ))
adjusted_shaper_rate_kbps=$(( ($shaper_rate_kbps*$shaper_rate_adjust_bufferbloat)/1000 ))
shaper_rate_kbps=$(( $adjusted_achieved_rate_kbps < $adjusted_shaper_rate_kbps ? $adjusted_achieved_rate_kbps : $adjusted_shaper_rate_kbps ))
t_last_bufferbloat_us=${EPOCHREALTIME/./}
fi
;;
# high load, so increase rate providing not inside bufferbloat refractory period
high)
if (( $t_next_rate_us > ($t_last_bufferbloat_us+$bufferbloat_refractory_period_us) )); then
shaper_rate_kbps=$(( ($shaper_rate_kbps*$shaper_rate_adjust_load_high)/1000 ))
fi
;;
# medium load, so just maintain rate as is, i.e. do nothing
medium)
:
;;
# low or idle load, so determine whether to decay down towards base rate, decay up towards base rate, or set as base rate
low|idle)
if (($t_next_rate_us > ($t_last_decay_us+$decay_refractory_period_us) )); then
if (($shaper_rate_kbps > $base_shaper_rate_kbps)); then
decayed_shaper_rate_kbps=$(( ($shaper_rate_kbps*$shaper_rate_adjust_load_low)/1000 ))
shaper_rate_kbps=$(( $decayed_shaper_rate_kbps > $base_shaper_rate_kbps ? $decayed_shaper_rate_kbps : $base_shaper_rate_kbps))
elif (($shaper_rate_kbps < $base_shaper_rate_kbps)); then
decayed_shaper_rate_kbps=$(( ((2000-$shaper_rate_adjust_load_low)*$shaper_rate_kbps)/1000 ))
shaper_rate_kbps=$(( $decayed_shaper_rate_kbps < $base_shaper_rate_kbps ? $decayed_shaper_rate_kbps : $base_shaper_rate_kbps))
fi
t_last_decay_us=${EPOCHREALTIME/./}
fi
;;
esac
# make sure to only return rates between cur_min_rate and cur_max_rate
(($shaper_rate_kbps < $min_shaper_rate_kbps)) && shaper_rate_kbps=$min_shaper_rate_kbps;
(($shaper_rate_kbps > $max_shaper_rate_kbps)) && shaper_rate_kbps=$max_shaper_rate_kbps;
}
monitor_achieved_rates()
{
# track rx and tx bytes transfered and divide by time since last update
# to determine achieved dl and ul transfer rates
local rx_bytes_path=$1
local tx_bytes_path=$2
local monitor_achieved_rates_interval_us=$3 # (microseconds)
compensated_monitor_achieved_rates_interval_us=$monitor_achieved_rates_interval_us
[[ -f $rx_bytes_path ]] && { read -r prev_rx_bytes < $rx_bytes_path; } 2> /dev/null || prev_rx_bytes=0
[[ -f $tx_bytes_path ]] && { read -r prev_tx_bytes < $tx_bytes_path; } 2> /dev/null || prev_tx_bytes=0
while true
do
t_start_us=${EPOCHREALTIME/./}
# If rx/tx bytes file exists, read it in, otherwise set to prev_bytes
# This addresses interfaces going down and back up
[[ -f $rx_bytes_path ]] && { read -r rx_bytes < $rx_bytes_path; } 2> /dev/null || rx_bytes=$prev_rx_bytes
[[ -f $tx_bytes_path ]] && { read -r tx_bytes < $tx_bytes_path; } 2> /dev/null || tx_bytes=$prev_tx_bytes
dl_achieved_rate_kbps=$(( ((8000*($rx_bytes - $prev_rx_bytes)) / $compensated_monitor_achieved_rates_interval_us ) ))
ul_achieved_rate_kbps=$(( ((8000*($tx_bytes - $prev_tx_bytes)) / $compensated_monitor_achieved_rates_interval_us ) ))
(($dl_achieved_rate_kbps<0)) && dl_achieved_rate_kbps=0
(($ul_achieved_rate_kbps<0)) && ul_achieved_rate_kbps=0
printf '%s' "$dl_achieved_rate_kbps" > /tmp/CAKE-autorate-${dl_if}/dl_achieved_rate_kbps
printf '%s' "$ul_achieved_rate_kbps" > /tmp/CAKE-autorate-${dl_if}/ul_achieved_rate_kbps
prev_rx_bytes=$rx_bytes
prev_tx_bytes=$tx_bytes
# read in the max_wire_packet_rtt_us
concurrent_read max_wire_packet_rtt_us /tmp/CAKE-autorate-${dl_if}/max_wire_packet_rtt_us
compensated_monitor_achieved_rates_interval_us=$(( (($monitor_achieved_rates_interval_us>(10*$max_wire_packet_rtt_us) )) ? $monitor_achieved_rates_interval_us : $((10*$max_wire_packet_rtt_us)) ))
sleep_remaining_tick_time $t_start_us $compensated_monitor_achieved_rates_interval_us
done
}
get_loads()
{
# read in the dl/ul achived rates and determine the loads
concurrent_read dl_achieved_rate_kbps /tmp/CAKE-autorate-${dl_if}/dl_achieved_rate_kbps
concurrent_read ul_achieved_rate_kbps /tmp/CAKE-autorate-${dl_if}/ul_achieved_rate_kbps
dl_load_percent=$(((100*10#${dl_achieved_rate_kbps})/$dl_shaper_rate_kbps))
ul_load_percent=$(((100*10#${ul_achieved_rate_kbps})/$ul_shaper_rate_kbps))
}
classify_load()
{
# classify the load according to high/low/medium/idle and add _delayed if delayed
# thus ending up with high_delayed, low_delayed, etc.
local load_percent=$1
local achieved_rate_kbps=$2
local -n load_condition=$3
if (( $load_percent > $high_load_thr_percent )); then
load_condition="high"
elif (( $load_percent > $medium_load_thr_percent )); then
load_condition="medium"
elif (( $achieved_rate_kbps > $connection_active_thr_kbps )); then
load_condition="low"
else
load_condition="idle"
fi
(($bufferbloat_detected)) && load_condition=$load_condition"_delayed"
}
monitor_reflector_responses()
{
# ping reflector, maintain baseline and output deltas to a common fifo
local pinger=$1
local rtt_baseline_us=$2
while read -r timestamp _ _ _ reflector seq_rtt
do
# If no match then skip onto the next one
[[ $seq_rtt =~ icmp_[s|r]eq=([0-9]+).*time=([0-9]+)\.?([0-9]+)?[[:space:]]ms ]] || continue
seq=${BASH_REMATCH[1]}
rtt_us=${BASH_REMATCH[3]}000
rtt_us=$((${BASH_REMATCH[2]}000+10#${rtt_us:0:3}))
reflector=${reflector//:/}
rtt_delta_us=$(( $rtt_us-$rtt_baseline_us ))
alpha=$(( (( $rtt_delta_us >=0 )) ? $alpha_baseline_increase : $alpha_baseline_decrease ))
rtt_baseline_us=$(( ( (1000-$alpha)*$rtt_baseline_us+$alpha*$rtt_us )/1000 ))
printf '%s %s %s %s %s %s\n' "$timestamp" "$reflector" "$seq" "$rtt_baseline_us" "$rtt_us" "$rtt_delta_us" > /tmp/CAKE-autorate-${dl_if}/ping_fifo
printf '%s' "${timestamp//[[\[\].]}" > /tmp/CAKE-autorate-${dl_if}/reflector_${pinger}_last_timestamp_us
done</tmp/CAKE-autorate-${dl_if}/pinger_${pinger}_fifo
}
kill_pingers()
{
for (( pinger=0; pinger<$no_pingers; pinger++))
do
kill ${pinger_pids[$pinger]} 2> /dev/null
[[ -p /tmp/CAKE-autorate-${dl_if}/pinger_${pinger}_fifo ]] && rm /tmp/CAKE-autorate-${dl_if}/pinger_${pinger}_fifo
done
exit
}
maintain_pingers()
{
# this initiates the pingers and monitors reflector health, rotating reflectors as necessary
trap kill_pingers TERM
declare -A pinger_pids
declare -A rtt_baselines_us
reflector_offences_idx=0
# For each pinger: create fifos, get baselines and initialize record of offences
for ((pinger=0; pinger<$no_pingers; pinger++))
do
mkfifo /tmp/CAKE-autorate-${dl_if}/pinger_${pinger}_fifo
[[ $(ping -B -I ${dl_if} -q -c 5 -i 0.1 ${reflectors[$pinger]} | tail -1) =~ ([0-9.]+)/ ]] && printf -v rtt_baselines_us[$pinger] %.0f\\n "${BASH_REMATCH[1]}e3" || rtt_baselines_us[$pinger]=0
declare -n reflector_offences="reflector_${pinger}_offences"
for ((i=0; i<$reflector_misbehaving_detection_window; i++)) do reflector_offences[i]=0; done
sum_reflector_offences[$pinger]=0
done
pingers_t_start_us=${EPOCHREALTIME/./}
# Initiate pingers
for ((pinger=0; pinger<$no_pingers; pinger++))
do
printf '%s' "$pingers_t_start_us" > /tmp/CAKE-autorate-${dl_if}/reflector_${pinger}_last_timestamp_us
start_pinger_next_pinger_time_slot $pinger pid
pinger_pids[$pinger]=$pid
done
# Reflector health check loop - verifies reflectors have not gone stale and rotates reflectors as necessary
while true
do
sleep_s $reflector_health_check_interval_s
for ((pinger=0; pinger<$no_pingers; pinger++))
do
reflector_check_time_us=${EPOCHREALTIME/./}
concurrent_read reflector_last_timestamp_us /tmp/CAKE-autorate-${dl_if}/reflector_${pinger}_last_timestamp_us
declare -n reflector_offences="reflector_${pinger}_offences"
(( ${reflector_offences[$reflector_offences_idx]} )) && ((sum_reflector_offences[$pinger]--))
reflector_offences[$reflector_offences_idx]=$(( (((${EPOCHREALTIME/./}-$reflector_last_timestamp_us) > $reflector_response_deadline_us)) ? 1 : 0 ))
((reflector_offences[$reflector_offences_idx])) && ((sum_reflector_offences[$pinger]++))
if ((sum_reflector_offences[$pinger]>=$reflector_misbehaving_detection_thr)); then
(($debug)) && echo "DEBUG: Warning: reflector: "${reflectors[$pinger]}" seems to be misbehaving."
if(($no_reflectors>$no_pingers)); then
# pingers always use reflectors[0]..[$no_pingers-1] as the initial set
# and the additional reflectors are spare reflectors should any from initial set go stale
# a bad reflector in the initial set is replaced with $reflectors[$no_pingers]
# $reflectors[$no_pingers] is then unset
# and the the bad reflector moved to the back of the queue (last element in $reflectors[])
# and finally the indices for $reflectors are updated to reflect the new order
(($debug)) && echo "DEBUG: Replacing reflector: "${reflectors[$pinger]}" with "${reflectors[$no_pingers]}"."
kill ${pinger_pids[$pinger]} 2> /dev/null
bad_reflector=${reflectors[$pinger]}
# overwrite the bad reflector with the reflector that is next in the queue (the one after 0..$no_pingers-1)
reflectors[$pinger]=${reflectors[$no_pingers]}
# remove the new reflector from the list of additional reflectors beginning from $reflectors[$no_pingers]
unset reflectors[$no_pingers]
# bad reflector goes to the back of the queue
reflectors+=($bad_reflector)
# reset array indices
reflectors=(${reflectors[*]})
# set up the new pinger with the new reflector and retain pid
start_pinger_next_pinger_time_slot $pinger pid
pinger_pids[$pinger]=$pid
else
(($debug)) && echo "DEBUG: No additional reflectors specified so just retaining: "${reflectors[$pinger]}"."
reflector_offences[$pinger]=0
fi
for ((i=0; i<$reflector_misbehaving_detection_window; i++)) do reflector_offences[i]=0; done
sum_reflector_offences[$pinger]=0
fi
done
((reflector_offences_idx=(reflector_offences_idx+1)%$reflector_misbehaving_detection_window))
done
}
start_pinger_next_pinger_time_slot()
{
# wait until next pinger time slot and start pinger in its slot
# this allows pingers to be stopped and started (e.g. during sleep or reflector rotation)
# whilst ensuring pings will remain spaced out appropriately to maintain granularity
local pinger=$1
local -n pinger_pid=$2
t_start_us=${EPOCHREALTIME/./}
time_to_next_time_slot_us=$(( ($reflector_ping_interval_us-($t_start_us-$pingers_t_start_us)%$reflector_ping_interval_us) + $pinger*$ping_response_interval_us ))
sleep_remaining_tick_time $t_start_us $time_to_next_time_slot_us
if (($debug)); then
ping -B -I ${dl_if} -D -i $reflector_ping_interval_s ${reflectors[$pinger]} > /tmp/CAKE-autorate-${dl_if}/pinger_${pinger}_fifo &
pinger_pid=$!
else
ping -B -I ${dl_if} -D -i $reflector_ping_interval_s ${reflectors[$pinger]} > /tmp/CAKE-autorate-${dl_if}/pinger_${pinger}_fifo 2> /dev/null &
pinger_pid=$!
fi
monitor_reflector_responses $pinger ${rtt_baselines_us[$pinger]} &
}
set_cake_rate()
{
local interface=$1
local shaper_rate_kbps=$2
local -n time_rate_set_us=$3
(($output_cake_changes)) && echo "tc qdisc change root dev ${interface} cake bandwidth ${shaper_rate_kbps}Kbit"
if (($debug)); then
tc qdisc change root dev $interface cake bandwidth ${shaper_rate_kbps}Kbit
else
tc qdisc change root dev $interface cake bandwidth ${shaper_rate_kbps}Kbit 2> /dev/null
fi
time_rate_set_us=${EPOCHREALTIME/./}
}
set_shaper_rates()
{
if (( $dl_shaper_rate_kbps != $last_dl_shaper_rate_kbps || $ul_shaper_rate_kbps != $last_ul_shaper_rate_kbps )); then
# fire up tc in each direction if there are rates to change, and if rates change in either direction then update max wire calcs
(( $dl_shaper_rate_kbps != $last_dl_shaper_rate_kbps )) && { set_cake_rate $dl_if $dl_shaper_rate_kbps t_prev_dl_rate_set_us; last_dl_shaper_rate_kbps=$dl_shaper_rate_kbps; }
(( $ul_shaper_rate_kbps != $last_ul_shaper_rate_kbps )) && { set_cake_rate $ul_if $ul_shaper_rate_kbps t_prev_ul_rate_set_us; last_ul_shaper_rate_kbps=$ul_shaper_rate_kbps; }
update_max_wire_packet_compensation
fi
}
get_max_wire_packet_size_bits()
{
local interface=$1
local -n max_wire_packet_size_bits=$2
read -r max_wire_packet_size_bits < "/sys/class/net/${interface}/mtu"
[[ $(tc qdisc show dev $interface) =~ (atm|noatm)[[:space:]]overhead[[:space:]]([0-9]+) ]]
[[ ! -z "${BASH_REMATCH[2]}" ]] && max_wire_packet_size_bits=$((8*($max_wire_packet_size_bits+${BASH_REMATCH[2]})))
# atm compensation = 53*ceil(X/48) bytes = 8*53*((X+8*(48-1)/(8*48)) bits = 424*((X+376)/384) bits
[[ "${BASH_REMATCH[1]}" == "atm" ]] && max_wire_packet_size_bits=$(( 424*(($max_wire_packet_size_bits+376)/384) ))
}
update_max_wire_packet_compensation()
{
# Compensate for delays imposed by active traffic shaper
# This will serve to increase the delay thr at rates below around 12Mbit/s
max_wire_packet_rtt_us=$(( (1000*$dl_max_wire_packet_size_bits)/$dl_shaper_rate_kbps + (1000*$ul_max_wire_packet_size_bits)/$ul_shaper_rate_kbps ))
compensated_delay_thr_us=$(( $delay_thr_us + $max_wire_packet_rtt_us ))
# write out max_wire_packet_rtt_us
printf '%s' "$max_wire_packet_rtt_us" > /tmp/CAKE-autorate-${dl_if}/max_wire_packet_rtt_us
}
concurrent_read()
{
# in the context of separate processes writing using > and reading form file
# it seems costly calls to the external flock binary can be avoided
# read either succeeds as expected or occassionally reads in bank value
# so just test for blank value and re-read until not blank
local -n value=$1
local path=$2
read -r value < $path
while [[ -z $value ]]; do
sleep_us $concurrent_read_interval_us
read -r value < $path;
done
}
verify_ifs_up()
{
# Check the rx/tx paths exist and give extra time for ifb's to come up if needed
# This will block if ifs never come up
while [[ ! -f $rx_bytes_path || ! -f $tx_bytes_path ]]
do
(($debug)) && [[ ! -f $rx_bytes_path ]] && echo "DEBUG Warning: $rx_bytes_path does not exist. Waiting "$if_up_check_interval_s" seconds for interface to come up."
(($debug)) && [[ ! -f $tx_bytes_path ]] && echo "DEBUG Warning: $tx_bytes_path does not exist. Waiting "$if_up_check_interval_s" seconds for interface to come up."
sleep_s $if_up_check_interval_s
done
}
sleep_s()
{
# calling external sleep binary is slow
# bash does have a loadable sleep
# but read's timeout can more portably be exploited and this is apparently even faster anyway
local sleep_duration_s=$1 # (seconds, e.g. 0.5, 1 or 1.5)
read -t $sleep_duration_s < /tmp/CAKE-autorate-${dl_if}/sleep_fifo
}
sleep_us()
{
# calling external sleep binary is slow
# bash does have a loadable sleep
# but read's timeout can more portably be exploited and this is apparently even fastera anyway
local sleep_duration_us=$1 # (microseconds)
sleep_duration_s=000000$sleep_duration_us
sleep_duration_s=${sleep_duration_s::-6}.${sleep_duration_s: -6}
read -t $sleep_duration_s < /tmp/CAKE-autorate-${dl_if}/sleep_fifo
}
sleep_remaining_tick_time()
{
# sleeps until the end of the tick duration
local t_start_us=$1 # (microseconds)
local tick_duration_us=$2 # (microseconds)
sleep_duration_us=$(( $t_start_us + $tick_duration_us - ${EPOCHREALTIME/./} ))
if (( $sleep_duration_us > 0 )); then
sleep_us $sleep_duration_us
fi
}
# Set up tmp directory, sleep fifo and perform various sanity checks
# /tmp/CAKE-autorate-${dl_if}/ is used to store temporary files
# it should not exist on startup so if it does exit, else create the directory
if [[ -d /tmp/CAKE-autorate-${dl_if} ]]; then
echo "Error: /tmp/CAKE-autorate-${dl_if} already exists. Is another instance running? Exiting script."
trap - INT TERM EXIT
exit
else
mkdir /tmp/CAKE-autorate-${dl_if}
fi
mkfifo /tmp/CAKE-autorate-${dl_if}/sleep_fifo
exec 3<> /tmp/CAKE-autorate-${dl_if}/sleep_fifo
no_reflectors=${#reflectors[@]}
# Check no_pingers <= no_reflectors
(( $no_pingers > $no_reflectors)) && { echo "Error: number of pingers cannot be greater than number of reflectors. Exiting script."; exit; }
# Check dl/if interface not the same
[[ $dl_if == $ul_if ]] && { echo "Error: download interface and upload interface are both set to: '"$dl_if"', but cannot be the same. Exiting script."; exit; }
# Check bufferbloat detection threshold not greater than window length
(( $bufferbloat_detection_thr > $bufferbloat_detection_window )) && { echo "Error: bufferbloat_detection_thr cannot be greater than bufferbloat_detection_window. Exiting script."; exit; }
# Wait if $startup_wait_s > 0
if (($startup_wait_s>0)); then
(($debug)) && echo "DEBUG Waiting "$startup_wait_s" seconds before startup."
sleep_s $startup_wait_s
fi
# Check interfaces are up and wait if necessary for them to come up
verify_ifs_up
# Initialize variables
# Convert human readable parameters to values that work with integer arithmetic
printf -v alpha_baseline_increase %.0f\\n "${alpha_baseline_increase}e3"
printf -v alpha_baseline_decrease %.0f\\n "${alpha_baseline_decrease}e3"
printf -v achieved_rate_adjust_bufferbloat %.0f\\n "${achieved_rate_adjust_bufferbloat}e3"
printf -v shaper_rate_adjust_bufferbloat %.0f\\n "${shaper_rate_adjust_bufferbloat}e3"
printf -v shaper_rate_adjust_load_high %.0f\\n "${shaper_rate_adjust_load_high}e3"
printf -v shaper_rate_adjust_load_low %.0f\\n "${shaper_rate_adjust_load_low}e3"
printf -v high_load_thr_percent %.0f\\n "${high_load_thr}e2"
printf -v medium_load_thr_percent %.0f\\n "${medium_load_thr}e2"
printf -v reflector_ping_interval_us %.0f\\n "${reflector_ping_interval_s}e6"
printf -v monitor_achieved_rates_interval_us %.0f\\n "${monitor_achieved_rates_interval_ms}e3"
printf -v sustained_idle_sleep_thr_us %.0f\\n "${sustained_idle_sleep_thr_s}e6"
printf -v reflector_response_deadline_us %.0f\\n "${reflector_response_deadline_s}e6"
bufferbloat_refractory_period_us=$(( 1000*$bufferbloat_refractory_period_ms ))
decay_refractory_period_us=$(( 1000*$decay_refractory_period_ms ))
delay_thr_us=$(( 1000*$delay_thr_ms ))
ping_response_interval_us=$(($reflector_ping_interval_us/$no_pingers))
concurrent_read_interval_us=$(($ping_response_interval_us/4))
dl_shaper_rate_kbps=$base_dl_shaper_rate_kbps
ul_shaper_rate_kbps=$base_ul_shaper_rate_kbps
last_dl_shaper_rate_kbps=$dl_shaper_rate_kbps
last_ul_shaper_rate_kbps=$ul_shaper_rate_kbps
get_max_wire_packet_size_bits $dl_if dl_max_wire_packet_size_bits
get_max_wire_packet_size_bits $ul_if ul_max_wire_packet_size_bits
set_cake_rate $dl_if $dl_shaper_rate_kbps t_prev_dl_rate_set_us
set_cake_rate $ul_if $ul_shaper_rate_kbps t_prev_ul_rate_set_us
update_max_wire_packet_compensation
t_start_us=${EPOCHREALTIME/./}
t_end_us=${EPOCHREALTIME/./}
t_prev_ul_rate_set_us=$t_start_us
t_prev_dl_rate_set_us=$t_start_us
t_ul_last_bufferbloat_us=$t_start_us
t_ul_last_decay_us=$t_start_us
t_dl_last_bufferbloat_us=$t_start_us
t_dl_last_decay_us=$t_start_us
t_sustained_connection_idle_us=0
declare -a delays=( $(for i in {1..$bufferbloat_detection_window}; do echo 0; done) )
delays_idx=0
sum_delays=0
mkfifo /tmp/CAKE-autorate-${dl_if}/ping_fifo
exec 4<> /tmp/CAKE-autorate-${dl_if}/ping_fifo
maintain_pingers &
maintain_pingers_pid=$!
# Initiate achived rate monitor
monitor_achieved_rates $rx_bytes_path $tx_bytes_path $monitor_achieved_rates_interval_us&
monitor_achieved_rates_pid=$!
prev_timestamp=0
while true
do
while read -t $global_ping_response_timeout_s -r timestamp reflector seq rtt_baseline_us rtt_us rtt_delta_us
do
t_start_us=${EPOCHREALTIME/./}
if ((($t_start_us - "${timestamp//[[\[\].]}")>500000)); then
(($debug)) && echo "DEBUG processed response from [" $reflector "] that is > 500ms old. Skipping."
continue
fi
# Keep track of number of delays across detection window
(( ${delays[$delays_idx]} )) && ((sum_delays--))
delays[$delays_idx]=$(( $rtt_delta_us > $compensated_delay_thr_us ? 1 : 0 ))
((delays[$delays_idx])) && ((sum_delays++))
(( delays_idx=(delays_idx+1)%$bufferbloat_detection_window ))
bufferbloat_detected=$(( (($sum_delays>=$bufferbloat_detection_thr)) ? 1 : 0 ))
get_loads
classify_load $dl_load_percent $dl_achieved_rate_kbps dl_load_condition
classify_load $ul_load_percent $ul_achieved_rate_kbps ul_load_condition
get_next_shaper_rate $min_dl_shaper_rate_kbps $base_dl_shaper_rate_kbps $max_dl_shaper_rate_kbps $dl_achieved_rate_kbps $dl_load_condition $t_start_us t_dl_last_bufferbloat_us t_dl_last_decay_us dl_shaper_rate_kbps
get_next_shaper_rate $min_ul_shaper_rate_kbps $base_ul_shaper_rate_kbps $max_ul_shaper_rate_kbps $ul_achieved_rate_kbps $ul_load_condition $t_start_us t_ul_last_bufferbloat_us t_ul_last_decay_us ul_shaper_rate_kbps
(($output_processing_stats)) && printf '%s %-6s %-6s %-3s %-3s %s %-15s %-6s %-6s %-6s %-6s %-6s %s %-14s %-14s %-6s %-6s\n' $EPOCHREALTIME $dl_achieved_rate_kbps $ul_achieved_rate_kbps $dl_load_percent $ul_load_percent $timestamp $reflector $seq $rtt_baseline_us $rtt_us $rtt_delta_us $compensated_delay_thr_us $sum_delays $dl_load_condition $ul_load_condition $dl_shaper_rate_kbps $ul_shaper_rate_kbps
set_shaper_rates
# If base rate is sustained, increment sustained base rate timer (and break out of processing loop if enough time passes)
if (($enable_sleep_function)); then
if [[ $dl_load_condition == idle* && $ul_load_condition == idle* ]]; then
((t_sustained_connection_idle_us+=$((${EPOCHREALTIME/./}-$t_end_us))))
(($t_sustained_connection_idle_us>$sustained_idle_sleep_thr_us)) && break
else
# reset timer
t_sustained_connection_idle_us=0
fi
fi
t_end_us=${EPOCHREALTIME/./}
done</tmp/CAKE-autorate-${dl_if}/ping_fifo
(($debug)) && {(( ${PIPESTATUS[0]} == 142 )) && echo "DEBUG Warning: global ping response timeout. Enforcing minimum shaper rates." || echo "DEBUG Connection idle. Enforcing minimum shaper rates.";}
# in any case, we broke out of processing loop, so conservatively set hard minimums and wait until there is a load increase again
dl_shaper_rate_kbps=$min_dl_shaper_rate_kbps
ul_shaper_rate_kbps=$min_ul_shaper_rate_kbps
set_shaper_rates
# Kill off ping processes
kill $maintain_pingers_pid 2> /dev/null
# reset idle timer
t_sustained_connection_idle_us=0
# verify interfaces are up (e.g. following ping response timeout from interfaces going down)
verify_ifs_up
# wait until load increases again
while true
do
t_start_us=${EPOCHREALTIME/./}
get_loads
(($dl_load_percent>$medium_load_thr_percent || $ul_load_percent>$medium_load_thr_percent)) && break
sleep_remaining_tick_time $t_start_us $reflector_ping_interval_us
done
# Start up ping processes
maintain_pingers &
maintain_pingers_pid=$!
done

View file

@ -0,0 +1,158 @@
#!/bin/bash
# config.sh sets up defaults for CAKE-autorate
# config.sh is a part of CAKE-autorate
# CAKE-autorate automatically adjusts bandwidth for CAKE in dependence on detected load and RTT
# Author: @Lynx (OpenWrt forum)
# Inspiration taken from: @moeller0 (OpenWrt forum)
# Modified by Ycarus (Yannick Chabanois) for OpenMPTCProuter:
# * Get configuration via uci
INTERFACE=$1
# *** OUTPUT OPTIONS ***
#output_processing_stats=1 # enable (1) or disable (0) output monitoring lines showing processing stats
output_processing_stats=$(uci -q get sqm.${INTERFACE}.output_processing_stats || echo "0")
#output_cake_changes=0 # enable (1) or disable (0) output monitoring lines showing cake bandwidth changes
output_cake_changes=$(uci -q get sqm.${INTERFACE}.output_cake_changes || echo "0")
#debug=0 # enable (1) or disable (0) out of debug lines
debug=$(uci -q get sqm.common.debug || echo "0")
# *** STANDARD CONFIGURATION OPTIONS ***
#dl_if=ifb4eth1 # download interface
#dl_if=$(uci -q get sqm.${INTERFACE}.download_interface)
dl_if=ifb4$(uci -q get sqm.${INTERFACE}.interface)
#ul_if=eth1 # upload interface
#ul_if=$(uci -q get sqm.${INTERFACE}.upload_interface)
ul_if=$(uci -q get sqm.${INTERFACE}.interface)
#reflector_ping_interval_s=0.2 # (seconds, e.g. 0.2s or 2s)
reflector_ping_interval_s=$(uci -q get sqm.${INTERFACE}.reflector_ping_interval_s || echo "0.2")
# list of reflectors to use and number of pingers to initiate
# pingers will be initiated with reflectors in the order specified in the list
# additional reflectors will be used to replace any reflectors that go stale
# so e.g. if 6 reflectors are specified and the number of pingers is set to 4, the first 4 reflectors will be used initially
# and the remaining 2 reflectors in the list will be used in the event any of the first 4 go bad
# a bad reflector will go to the back of the queue on reflector rotation
reflectors=("1.1.1.1" "1.0.0.1" "8.8.8.8" "8.8.4.4" "9.9.9.9" "9.9.9.10")
no_pingers=$(uci -q get sqm.${INTERFACE}.no_pingers || echo "4")
# delay threshold in ms is the extent of RTT increase to classify as a delay
# this is automatically adjusted based on maximum on the wire packet size
# (adjustment significant at sub 12Mbit/s rates, else negligible)
#delay_thr_ms=25 # (milliseconds)
delay_thr_ms=$(uci -q get sqm.${INTERFACE}.delay_thr_ms || echo "25")
#min_dl_shaper_rate_kbps=10000 # minimum bandwidth for download (Kbit/s)
min_dl_shaper_rate_kbps=$(uci -q get sqm.${INTERFACE}.min_download)
#base_dl_shaper_rate_kbps=100000 # steady state bandwidth for download (Kbit/s)
base_dl_shaper_rate_kbps=$(uci -q get sqm.${INTERFACE}.download)
#max_dl_shaper_rate_kbps=200000 # maximum bandwidth for download (Kbit/s)
max_dl_shaper_rate_kbps=$(uci -q get sqm.${INTERFACE}.max_download)
#min_ul_shaper_rate_kbps=10000 # minimum bandwidth for upload (Kbit/s)
min_ul_shaper_rate_kbps=$(uci -q get sqm.${INTERFACE}.min_upload)
#base_ul_shaper_rate_kbps=100000 # steady state bandwidth for upload (KBit/s)
base_ul_shaper_rate_kbps=$(uci -q get sqm.${INTERFACE}.upload)
#max_ul_shaper_rate_kbps=200000 # maximum bandwidth for upload (Kbit/s)
max_ul_shaper_rate_kbps=$(uci -q get sqm.${INTERFACE}.max_upload)
# sleep functionality saves unecessary pings and CPU cycles by
# pausing all active pingers when connection is not in active use
#enable_sleep_function=1 # enable (1) or disable (0) sleep functonality
enable_sleep_function=$(uci -q get sqm.${INTERFACE}.enable_sleep_functions || echo "1")
#connection_active_thr_kbps=500 # threshold in Kbit/s below which dl/ul is considered idle
connection_active_thr_kbps=$(uci -q get sqm.${INTERFACE}.connection_active_thr_kbps || echo "500")
#sustained_idle_sleep_thr_s=60 # time threshold to put pingers to sleep on sustained dl/ul achieved rate < idle_thr (seconds)
sustained_idle_sleep_thr_s=$(uci -q get sqm.${INTERFACE}.substained_idle_sleep_thr_s || echo "60")
#startup_wait_s=0 # number of seconds to wait on startup (e.g. to wait for things to settle on router reboot)
startup_wait_s=$(uci -q get sqm.${INTERFACE}.startup_wait_s || echo "60")
# *** ADVANCED CONFIGURATION OPTIONS ***
# interval in ms for monitoring achieved rx/tx rates
# this is automatically adjusted based on maximum on the wire packet size
# (adjustment significant at sub 12Mbit/s rates, else negligible)
monitor_achieved_rates_interval_ms=100 # (milliseconds)
# bufferbloat is detected when (bufferbloat_detection_thr) samples
# out of the last (bufferbloat detection window) samples are delayed
bufferbloat_detection_window=4 # number of samples to retain in detection window
bufferbloat_detection_thr=2 # number of delayed samples for bufferbloat detection
# RTT baseline against which to measure delays
# the idea is that the baseline is allowed to increase slowly to allow for path changes
# and slowly enough such that bufferbloat will be corrected well before the baseline increases,
# but it will decrease very rapidly to ensure delays are measured against the shortest path
alpha_baseline_increase=0.001 # how rapidly baseline RTT is allowed to increase
alpha_baseline_decrease=0.9 # how rapidly baseline RTT is allowed to decrease
# rate adjustment parameters
# bufferbloat adjustment works with the lower of the adjusted achieved rate and adjusted shaper rate
# to exploit that transfer rates during bufferbloat provide an indication of line capacity
# otherwise shaper rate is adjusted up on load high, and down on load idle or low
# and held the same on load medium
achieved_rate_adjust_bufferbloat=0.9 # how rapidly to reduce achieved rate upon detection of bufferbloat
shaper_rate_adjust_bufferbloat=0.9 # how rapidly to reduce shaper rate upon detection of bufferbloat
shaper_rate_adjust_load_high=1.01 # how rapidly to increase shaper rate upon high load detected
shaper_rate_adjust_load_low=0.98 # how rapidly to return to base shaper rate upon idle or low load detected
# the load is categoried as low if < medium_load_thr, medium if > medium_load_thr and high if > high_load_thr relative to the current shaper rate
medium_load_thr=0.25 # % of currently set bandwidth for detecting medium load
high_load_thr=0.75 # % of currently set bandwidth for detecting high load
# refractory periods between successive bufferbloat/decay rate changes
bufferbloat_refractory_period_ms=300 # (milliseconds)
decay_refractory_period_ms=1000 # (milliseconds)
# interval for checking reflector health
reflector_health_check_interval_s=1 # (seconds)
# deadline for reflector response not to be classified as an offence against reflector
reflector_response_deadline_s=1 # (seconds)
# reflector misbehaving is detected when $reflector_misbehaving_detection_thr samples
# out of the last (reflector misbehaving detection window) samples are offences
# thus with a 1s interval, window 60 and detection_thr 3, this is tantamount to
# 3 offences within the last 60s
reflector_misbehaving_detection_window=60
reflector_misbehaving_detection_thr=3
global_ping_response_timeout_s=10 # timeout to set shaper rates to min on no ping response whatsoever (seconds)
if_up_check_interval_s=10 # time to wait before re-checking if rx/tx bytes files exist (e.g. from boot state)
# verify these are correct using 'cat /sys/class/...'
case "${dl_if}" in
\veth*)
rx_bytes_path="/sys/class/net/${dl_if}/statistics/tx_bytes"
;;
\ifb*)
rx_bytes_path="/sys/class/net/${dl_if}/statistics/tx_bytes"
;;
*)
rx_bytes_path="/sys/class/net/${dl_if}/statistics/rx_bytes"
;;
esac
case "${ul_if}" in
\veth*)
tx_bytes_path="/sys/class/net/${ul_if}/statistics/rx_bytes"
;;
\ifb*)
tx_bytes_path="/sys/class/net/${ul_if}/statistics/rx_bytes"
;;
*)
tx_bytes_path="/sys/class/net/${ul_if}/statistics/tx_bytes"
;;
esac
if (( $debug )) ; then
echo "DEBUG: rx_bytes_path: $rx_bytes_path"
echo "DEBUG: tx_bytes_path: $tx_bytes_path"
fi