2022-06-16 17:28:25 +00:00
#!/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
2022-08-17 18:52:21 +00:00
# Initiate termination of ping processes and wait until complete
2022-06-16 17:28:25 +00:00
kill $maintain_pingers_pid 2> /dev/null
2022-08-17 18:52:21 +00:00
wait $maintain_pingers_pid
2022-06-16 17:28:25 +00:00
[ [ -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
2022-08-17 18:52:21 +00:00
# upload Starlink satelite switching compensation, so drop down to minimum rate for upload through switching period
ul*sss)
shaper_rate_kbps = $min_shaper_rate_kbps
; ;
# download Starlink satelite switching compensation, so drop down to base rate for download through switching period
dl*sss)
shaper_rate_kbps = $base_shaper_rate_kbps
; ;
2022-06-16 17:28:25 +00:00
# bufferbloat detected, so decrease the rate providing not inside bufferbloat refractory period
2022-08-17 18:52:21 +00:00
*bb*)
2022-06-16 17:28:25 +00:00
if ( ( $t_next_rate_us > ( $t_last_bufferbloat_us +$bufferbloat_refractory_period_us ) ) ) ; then
2022-08-17 18:52:21 +00:00
adjusted_achieved_rate_kbps = $(( ( $achieved_rate_kbps * $achieved_rate_adjust_down_bufferbloat ) / 1000 ))
adjusted_shaper_rate_kbps = $(( ( $shaper_rate_kbps * $shaper_rate_adjust_down_bufferbloat ) / 1000 ))
2022-06-16 17:28:25 +00:00
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
2022-08-17 18:52:21 +00:00
*high*)
2022-06-16 17:28:25 +00:00
if ( ( $t_next_rate_us > ( $t_last_bufferbloat_us +$bufferbloat_refractory_period_us ) ) ) ; then
2022-08-17 18:52:21 +00:00
shaper_rate_kbps = $(( ( $shaper_rate_kbps * $shaper_rate_adjust_up_load_high ) / 1000 ))
2022-06-16 17:28:25 +00:00
fi
; ;
# medium load, so just maintain rate as is, i.e. do nothing
2022-08-17 18:52:21 +00:00
*med*)
2022-06-16 17:28:25 +00:00
:
; ;
# low or idle load, so determine whether to decay down towards base rate, decay up towards base rate, or set as base rate
2022-08-17 18:52:21 +00:00
*low*| *idle*)
2022-06-16 17:28:25 +00:00
if ( ( $t_next_rate_us > ( $t_last_decay_us +$decay_refractory_period_us ) ) ) ; then
if ( ( $shaper_rate_kbps > $base_shaper_rate_kbps ) ) ; then
2022-08-17 18:52:21 +00:00
decayed_shaper_rate_kbps = $(( ( $shaper_rate_kbps * $shaper_rate_adjust_down_load_low ) / 1000 ))
2022-06-16 17:28:25 +00:00
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
2022-08-17 18:52:21 +00:00
decayed_shaper_rate_kbps = $(( ( $shaper_rate_kbps * $shaper_rate_adjust_up_load_low ) / 1000 ))
2022-06-16 17:28:25 +00:00
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
2022-08-17 18:52:21 +00:00
prev_rx_bytes = $rx_bytes
prev_tx_bytes = $tx_bytes
2022-06-16 17:28:25 +00:00
# read in the max_wire_packet_rtt_us
2022-08-17 18:52:21 +00:00
concurrent_read_positive_integer max_wire_packet_rtt_us /tmp/CAKE-autorate-${ dl_if } /max_wire_packet_rtt_us
2022-06-16 17:28:25 +00:00
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
2022-08-17 18:52:21 +00:00
concurrent_read_positive_integer dl_achieved_rate_kbps /tmp/CAKE-autorate-${ dl_if } /dl_achieved_rate_kbps
concurrent_read_positive_integer ul_achieved_rate_kbps /tmp/CAKE-autorate-${ dl_if } /ul_achieved_rate_kbps
2022-06-16 17:28:25 +00:00
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
2022-08-17 18:52:21 +00:00
load_condition = "med"
2022-06-16 17:28:25 +00:00
elif ( ( $achieved_rate_kbps > $connection_active_thr_kbps ) ) ; then
load_condition = "low"
else
load_condition = "idle"
fi
2022-08-17 18:52:21 +00:00
( ( $bufferbloat_detected ) ) && load_condition = $load_condition "_bb"
if ( ( sss_compensation) ) ; then
for sss_time_us in " ${ sss_times_us [@] } "
do
( ( timestamp_usecs_past_minute = ${ EPOCHREALTIME /./ } %60000000) )
if ( ( ( $timestamp_usecs_past_minute > ( $sss_time_us -$sss_compensation_pre_duration_us ) ) && ( $timestamp_usecs_past_minute < ( $sss_time_us +$sss_compensation_post_duration_us ) ) ) ) ; then
load_condition = $load_condition "_sss"
break
fi
done
fi
2022-06-16 17:28:25 +00:00
}
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 /./ }
2022-08-17 18:52:21 +00:00
concurrent_read_positive_integer reflector_last_timestamp_us /tmp/CAKE-autorate-${ dl_if } /reflector_${ pinger } _last_timestamp_us
2022-06-16 17:28:25 +00:00
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
}
2022-08-17 18:52:21 +00:00
concurrent_read_positive_integer( )
2022-06-16 17:28:25 +00:00
{
# 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
2022-08-17 18:52:21 +00:00
while true
do
2022-06-16 17:28:25 +00:00
read -r value < $path ;
2022-08-17 18:52:21 +00:00
if [ [ -z " ${ value ##*[!0-9]* } " ] ] ; then
if ( ( $debug ) ) ; then
read -r caller_output< <( caller )
echo "DEBUG concurrent_read_positive_integer() misfire with the following particulars:"
echo "DEBUG caller=" $caller_output "; value=" $value "; and path=" $path
fi
sleep_us $concurrent_read_positive_integer_interval_us
continue
else
break
fi
2022-06-16 17:28:25 +00:00
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 "
2022-08-17 18:52:21 +00:00
printf -v achieved_rate_adjust_down_bufferbloat %.0f\\ n " ${ achieved_rate_adjust_down_bufferbloat } e3 "
printf -v shaper_rate_adjust_down_bufferbloat %.0f\\ n " ${ shaper_rate_adjust_down_bufferbloat } e3 "
printf -v shaper_rate_adjust_up_load_high %.0f\\ n " ${ shaper_rate_adjust_up_load_high } e3 "
printf -v shaper_rate_adjust_down_load_low %.0f\\ n " ${ shaper_rate_adjust_down_load_low } e3 "
printf -v shaper_rate_adjust_up_load_low %.0f\\ n " ${ shaper_rate_adjust_up_load_low } e3 "
2022-06-16 17:28:25 +00:00
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 ))
2022-08-17 18:52:21 +00:00
for ( ( i = 0; i<${# sss_times_s [@] } ; i++ ) ) ;
do
printf -v sss_times_us[ i] %.0f\\ n " ${ sss_times_s [i] } e6 "
done
printf -v sss_compensation_pre_duration_us %.0f\\ n " ${ sss_compensation_pre_duration_ms } e3 "
printf -v sss_compensation_post_duration_us %.0f\\ n " ${ sss_compensation_post_duration_ms } e3 "
2022-06-16 17:28:25 +00:00
ping_response_interval_us = $(( $reflector_ping_interval_us / $no_pingers ))
2022-08-17 18:52:21 +00:00
concurrent_read_positive_integer_interval_us = $(( $ping_response_interval_us / 4 ))
2022-06-16 17:28:25 +00:00
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
2022-08-17 18:52:21 +00:00
if ( ( $debug ) ) ; then
if ( ( $bufferbloat_refractory_period_us <= ( $bufferbloat_detection_window *$ping_response_interval_us ) ) ) ; then
echo "DEBUG Warning: bufferbloat refractory period: " $bufferbloat_refractory_period_us " us."
echo "DEBUG Warning: but expected time to overwrite samples in bufferbloat detection window is: " $(( $bufferbloat_detection_window * $ping_response_interval_us )) " us."
echo "DEBUG Warning: Consider increasing bufferbloat refractory period or decreasing bufferbloat detection window."
fi
fi
2022-06-16 17:28:25 +00:00
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 /./ }
2022-08-17 18:52:21 +00:00
if ( ( ( $t_start_us - 10#" ${ timestamp //[[ \[ \] .] } " ) >500000) ) ; then
2022-06-16 17:28:25 +00:00
( ( $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
2022-08-17 18:52:21 +00:00
dl_load_condition = "dl_" $dl_load_condition
ul_load_condition = "ul_" $ul_load_condition
2022-06-16 17:28:25 +00:00
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
2022-08-17 18:52:21 +00:00
# Initiate termination of ping processes and wait until complete
2022-06-16 17:28:25 +00:00
kill $maintain_pingers_pid 2> /dev/null
2022-08-17 18:52:21 +00:00
wait $maintain_pingers_pid
2022-06-16 17:28:25 +00:00
# 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