From 063a468465a39b5f5d6692d9c76fe6a82b3ebc65 Mon Sep 17 00:00:00 2001 From: "Ycarus (Yannick Chabanois)" Date: Tue, 28 Mar 2023 20:26:02 +0200 Subject: [PATCH] Update SQM autorate --- .../luci-static/resources/view/network/sqm.js | 4 + .../root/etc/init.d/sqm-autorate | 28 +- luci-app-sqm-autorate/root/root/cake-autorate | 1 + .../usr/share/sqm-autorate/CAKE-autorate.sh | 658 ------ .../usr/share/sqm-autorate/cake-autorate.sh | 1790 +++++++++++++++++ .../sqm-autorate/cake-autorate_defaults.sh | 209 ++ .../sqm-autorate/cake-autorate_launcher.sh | 26 + .../share/sqm-autorate/cake-autorate_lib.sh | 257 +++ .../sqm-autorate/cake-autorate_template.sh | 213 ++ .../root/usr/share/sqm-autorate/config.sh | 32 +- .../share/omr/post-tracking.d/post-tracking | 3 +- 11 files changed, 2526 insertions(+), 695 deletions(-) create mode 120000 luci-app-sqm-autorate/root/root/cake-autorate delete mode 100755 luci-app-sqm-autorate/root/usr/share/sqm-autorate/CAKE-autorate.sh create mode 100755 luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate.sh create mode 100755 luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_defaults.sh create mode 100755 luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_launcher.sh create mode 100755 luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_lib.sh create mode 100755 luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_template.sh mode change 100644 => 100755 luci-app-sqm-autorate/root/usr/share/sqm-autorate/config.sh diff --git a/luci-app-sqm-autorate/htdocs/luci-static/resources/view/network/sqm.js b/luci-app-sqm-autorate/htdocs/luci-static/resources/view/network/sqm.js index c7f33a8b8..c367e660c 100644 --- a/luci-app-sqm-autorate/htdocs/luci-static/resources/view/network/sqm.js +++ b/luci-app-sqm-autorate/htdocs/luci-static/resources/view/network/sqm.js @@ -260,6 +260,10 @@ return view.extend({ o.default = false; o.depends("autorate","1"); + o = s.taboption("tab_autorate", form.Flag, "sss_compensation", _("Starlink support")); + o.default = false; + o.depends("autorate","1"); + o = s.taboption("tab_autorate", form.Value, "reflector_ping_interval_s", _("Reflector ping interval in seconds:")); o.default = "0.2"; o.depends("autorate","1"); diff --git a/luci-app-sqm-autorate/root/etc/init.d/sqm-autorate b/luci-app-sqm-autorate/root/etc/init.d/sqm-autorate index 1646d2e2a..7ab556492 100755 --- a/luci-app-sqm-autorate/root/etc/init.d/sqm-autorate +++ b/luci-app-sqm-autorate/root/etc/init.d/sqm-autorate @@ -14,8 +14,8 @@ . /usr/lib/unbound/iptools.sh . /lib/functions/network.sh -_launch_autorate() { - logger -t "SQM-autorate" "Launch on $1" +_config_autorate() { + logger -t "SQM-autorate" "Set config for $1" config_get enabled "$1" enabled [ "${enabled}" != "1" ] && return config_get autorate "$1" autorate @@ -29,20 +29,36 @@ _launch_autorate() { config_get upload "$1" upload config_get max_upload "$1" max_upload [ "${min_upload}" == "0" ] || [ "${max_upload}" == "0" ] || [ "${upload}" == "0" ] && return + config_get interface "$1" interface + cp /usr/share/sqm-autorate/cake-autorate_template.sh /usr/share/sqm-autorate/cake-autorate_config.${interface}.sh +} +_launch_autorate() { + logger -t "SQM-autorate" "Launch..." procd_open_instance # shellcheck disable=SC2086 - procd_set_param command /usr/share/sqm-autorate/CAKE-autorate.sh "$1" -# procd_append_param env "OMR_TRACKER_SERVER_HTTP_TEST=$server_http_test" + procd_set_param command /usr/share/sqm-autorate/cake-autorate_launcher.sh procd_set_param limits nofile="51200 51200" procd_set_param respawn 0 10 0 procd_set_param stderr 1 procd_close_instance - sleep 2 } start_service() { config_load sqm - config_foreach _launch_autorate queue + config_foreach _config_autorate queue + _launch_autorate } +stop_service() { + rm -f /usr/share/sqm-autorate/cake-autorate_config.*.sh +} + +reload_service() { + stop + start +} + +service_triggers() { + procd_add_reload_trigger sqm +} \ No newline at end of file diff --git a/luci-app-sqm-autorate/root/root/cake-autorate b/luci-app-sqm-autorate/root/root/cake-autorate new file mode 120000 index 000000000..66f5d6886 --- /dev/null +++ b/luci-app-sqm-autorate/root/root/cake-autorate @@ -0,0 +1 @@ +../usr/share/sqm-autorate \ No newline at end of file diff --git a/luci-app-sqm-autorate/root/usr/share/sqm-autorate/CAKE-autorate.sh b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/CAKE-autorate.sh deleted file mode 100755 index 560039b22..000000000 --- a/luci-app-sqm-autorate/root/usr/share/sqm-autorate/CAKE-autorate.sh +++ /dev/null @@ -1,658 +0,0 @@ -#!/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 - # Initiate termination of ping processes and wait until complete - kill $maintain_pingers_pid 2> /dev/null - wait $maintain_pingers_pid - [[ -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 - - # 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 - ;; - # bufferbloat detected, so decrease the rate providing not inside bufferbloat refractory period - *bb*) - if (( $t_next_rate_us > ($t_last_bufferbloat_us+$bufferbloat_refractory_period_us) )); then - 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 )) - 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_up_load_high)/1000 )) - fi - ;; - # medium load, so just maintain rate as is, i.e. do nothing - *med*) - : - ;; - # 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_down_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=$(( ($shaper_rate_kbps*$shaper_rate_adjust_up_load_low)/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_positive_integer 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_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 - - 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="med" - elif (( $achieved_rate_kbps > $connection_active_thr_kbps )); then - load_condition="low" - else - load_condition="idle" - fi - - (($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 -} - -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 /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_positive_integer 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_positive_integer() -{ - # 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 - while true - do - read -r value < $path; - 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 - 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_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" -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 )) - -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" - -ping_response_interval_us=$(($reflector_ping_interval_us/$no_pingers)) - -concurrent_read_positive_integer_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 - -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 - -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 - 10#"${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 - - dl_load_condition="dl_"$dl_load_condition - ul_load_condition="ul_"$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 /dev/null - wait $maintain_pingers_pid - - # 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 diff --git a/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate.sh b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate.sh new file mode 100755 index 000000000..426c08579 --- /dev/null +++ b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate.sh @@ -0,0 +1,1790 @@ +#!/bin/bash + +# CAKE-autorate automatically adjusts CAKE bandwidth(s) +# in dependence on: a) receive and transmit transfer rates; and b) latency +# (or can just be used to monitor and log transfer rates and latency) + +# requires packages: bash; and one of the supported ping binaries + +# each cake-autorate instance must be configured using a corresponding config file + +# Project homepage: https://github.com/lynxthecat/cake-autorate +# Licence details: https://github.com/lynxthecat/cake-autorate/blob/master/LICENCE.md + +# Author: @Lynx (OpenWrt forum) +# Inspiration taken from: @moeller0 (OpenWrt forum) + +# Initialize file descriptors +## -1 signifies that the log file fd will not be used and +## that the log file will be written to directly +log_fd=-1 +## ping_fd should be open for the duration of the script +## as it's integral to the operation of the script +exec {ping_fd}<> <(:) || true + +# Bash correctness options +## Disable globbing (expansion of *). +set -f +## Forbid using unset variables. +set -u +## The exit status of a pipeline is the status of the last +## command to exit with a non-zero status, or zero if no +## command exited with a non-zero status. +set -o pipefail + +# Possible performance improvement +export LC_ALL=C + +# Set PREFIX +PREFIX=/root/cake-autorate + +# shellcheck source=cake-autorate_lib.sh +. "${PREFIX}/cake-autorate_lib.sh" +# shellcheck source=cake-autorate_defaults.sh +. "${PREFIX}/cake-autorate_defaults.sh" + +trap cleanup_and_killall INT TERM EXIT + +cleanup_and_killall() +{ + trap true INT TERM EXIT + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + log_msg "INFO" "Stopping cake-autorate with PID: ${BASHPID} and config: ${config_path}" + + log_msg "INFO" "Killing all background processes and cleaning up temporary files." + + proc_man_stop maintain_pingers + proc_man_stop monitor_achieved_rates + proc_man_stop maintain_log_file + + [[ -d "${run_path}" ]] && rm -r "${run_path}" + rmdir /var/run/cake-autorate 2>/dev/null + + log_msg "SYSLOG" "Stopped cake-autorate with PID: ${BASHPID} and config: ${config_path}" + + trap - INT TERM EXIT + exit +} + +log_msg() +{ + # send logging message to terminal, log file fifo, log file and/or system logger + + local type=${1} + local msg=${2} + local instance_id=${instance_id:-"unknown"} + + case ${type} in + + DEBUG) + [[ "${debug}" == "0" ]] && return # skip over DEBUG messages where debug disabled + log_timestamp=${EPOCHREALTIME} + ((log_DEBUG_messages_to_syslog)) && ((use_logger)) && logger -t "cake-autorate.${instance_id}" "${type}: ${log_timestamp} ${msg}" + ;; + + ERROR) + log_timestamp=${EPOCHREALTIME} + ((use_logger)) && logger -t "cake-autorate.${instance_id}" "${type}: ${log_timestamp} ${msg}" + ;; + + SYSLOG) + log_timestamp=${EPOCHREALTIME} + ((use_logger)) && logger -t "cake-autorate.${instance_id}" "INFO: ${log_timestamp} ${msg}" + ;; + + *) + log_timestamp=${EPOCHREALTIME} + ;; + esac + + # Output to the log file fifo if available (for rotation handling) + # else output directly to the log file + if (( log_fd >= 0 )); then + ((log_to_file)) && printf '%s; %(%F-%H:%M:%S)T; %s; %s\n' "${type}" -1 "${log_timestamp}" "${msg}" >&"${log_fd}" + else + ((log_to_file)) && printf '%s; %(%F-%H:%M:%S)T; %s; %s\n' "${type}" -1 "${log_timestamp}" "${msg}" >> "${log_file_path}" + fi + + ((terminal)) && printf '%s; %(%F-%H:%M:%S)T; %s; %s\n' "${type}" -1 "${log_timestamp}" "${msg}" +} + +print_headers() +{ + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + header="DATA_HEADER; LOG_DATETIME; LOG_TIMESTAMP; PROC_TIME_US; DL_ACHIEVED_RATE_KBPS; UL_ACHIEVED_RATE_KBPS; DL_LOAD_PERCENT; UL_LOAD_PERCENT; RTT_TIMESTAMP; REFLECTOR; SEQUENCE; DL_OWD_BASELINE; DL_OWD_US; DL_OWD_DELTA_EWMA_US; DL_OWD_DELTA_US; DL_ADJ_DELAY_THR; UL_OWD_BASELINE; UL_OWD_US; UL_OWD_DELTA_EWMA_US; UL_OWD_DELTA_US; UL_ADJ_DELAY_THR; SUM_DL_DELAYS; SUM_UL_DELAYS; DL_LOAD_CONDITION; UL_LOAD_CONDITION; CAKE_DL_RATE_KBPS; CAKE_UL_RATE_KBPS" + ((log_to_file)) && printf '%s\n' "${header}" >> "${log_file_path}" + ((terminal)) && printf '%s\n' "${header}" + + header="LOAD_HEADER; LOG_DATETIME; LOG_TIMESTAMP; PROC_TIME_US; DL_ACHIEVED_RATE_KBPS; UL_ACHIEVED_RATE_KBPS; CAKE_DL_RATE_KBPS; CAKE_UL_RATE_KBPS" + ((log_to_file)) && printf '%s\n' "${header}" >> "${log_file_path}" + ((terminal)) && printf '%s\n' "${header}" + + header="REFLECTOR_HEADER; LOG_DATETIME; LOG_TIMESTAMP; PROC_TIME_US; REFLECTOR; DL_MIN_BASELINE_US; DL_BASELINE_US; DL_BASELINE_DELTA_US; DL_BASELINE_DELTA_THR_US; DL_MIN_DELTA_EWMA_US; DL_DELTA_EWMA_US; DL_DELTA_EWMA_DELTA_US; DL_DELTA_EWMA_DELTA_THR; UL_MIN_BASELINE_US; UL_BASELINE_US; UL_BASELINE_DELTA_US; UL_BASELINE_DELTA_THR_US; UL_MIN_DELTA_EWMA_US; UL_DELTA_EWMA_US; UL_DELTA_EWMA_DELTA_US; UL_DELTA_EWMA_DELTA_THR" + ((log_to_file)) && printf '%s\n' "${header}" >> "${log_file_path}" + ((terminal)) && printf '%s\n' "${header}" +} + +# MAINTAIN_LOG_FILE + HELPER FUNCTIONS + +rotate_log_file() +{ + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + if [[ -f ${log_file_path} ]] + then + cat "${log_file_path}" > "${log_file_path}.old" + true > "${log_file_path}" + fi + ((output_processing_stats)) && print_headers +} + +generate_log_file_exporter() +{ + cat > "${run_path}/export_log_file" <<- EOT + #!/bin/bash + + . "${PREFIX}/cake-autorate_lib.sh" + PROC_STATE_FILE="${run_path}/proc_state" + PROC_STATE_FILE_LOCK="${run_path}/proc_state.lock" + + timeout_s=\${1:-20} + + if ! proc_man_signal maintain_log_file "USR1" + then + printf "ERROR: Failed to signal maintain_log_file process.\n" >&2 + exit 1 + fi + rm -f "${run_path}/last_log_file_export" + + read_try=0 + + while [[ ! -f "${run_path}/last_log_file_export" ]] + do + sleep 1 + if (( ++read_try >= \${timeout_s} )); then + printf "ERROR: Timeout (\${timeout_s}s) reached before new log file export identified.\n" >&2 + exit 1 + fi + done + + read -r log_file_export_path < "${run_path}/last_log_file_export" + + printf "Log file export complete.\n" + + printf "Log file available at location: " + printf "\${log_file_export_path}\n" + EOT + + chmod +x "${run_path}/export_log_file" +} + +export_log_file() +{ + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + printf -v log_file_export_datetime '%(%Y_%m_%d_%H_%M_%S)T' + log_file_export_path="${log_file_path/.log/_${log_file_export_datetime}.log}" + log_msg "DEBUG" "Exporting log file with path: ${log_file_path/.log/_${log_file_export_datetime}.log}" + + # Now export with or without compression to the appropriate export path + if ((log_file_export_compress)); then + log_file_export_path="${log_file_export_path}.gz" + if [[ -f "${log_file_path}.old" ]]; then + gzip -c "${log_file_path}.old" > "${log_file_export_path}" + gzip -c "${log_file_path}" >> "${log_file_export_path}" + else + gzip -c "${log_file_path}" > "${log_file_export_path}" + fi + else + if [[ -f "${log_file_path}.old" ]]; then + cp "${log_file_path}.old" "${log_file_export_path}" + cat "${log_file_path}" >> "${log_file_export_path}" + else + cp "${log_file_path}" "${log_file_export_path}" + fi + fi + + printf '%s' "${log_file_export_path}" > "${run_path}/last_log_file_export" +} + +flush_log_fd() +{ + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + while read -r -t 0.01 -u "${log_fd}" log_line + do + printf '%s\n' "${log_line}" >> "${log_file_path}" + done +} + +get_log_file_size_bytes() +{ + log_file_size_bytes=$(wc -c "${log_file_path}" 2>/dev/null | awk '{print $1}') + log_file_size_bytes=${log_file_size_bytes:-0} +} + +kill_maintain_log_file() +{ + trap - TERM EXIT + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + flush_log_fd + exit +} + +maintain_log_file() +{ + trap '' INT + trap 'kill_maintain_log_file' TERM EXIT + trap 'export_log_file' USR1 + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + t_log_file_start_us=${EPOCHREALTIME/./} + + get_log_file_size_bytes + + while true + do + while read -r -u "${log_fd}" log_line + do + + printf '%s\n' "${log_line}" >> "${log_file_path}" + + # Verify log file size < configured maximum + # The following two lines with costly call to 'du': + # read log_file_size_bytes< <(du -b ${log_file_path}/cake-autorate.log) + # log_file_size_bytes=${log_file_size_bytes//[!0-9]/} + # can be more efficiently handled with this line: + ((log_file_size_bytes=log_file_size_bytes+${#log_line}+1)) + + # Verify log file time < configured maximum + if (( (${EPOCHREALTIME/./}-t_log_file_start_us) > log_file_max_time_us )); then + + log_msg "DEBUG" "log file maximum time: ${log_file_max_time_mins} minutes has elapsed so rotating log file" + break + fi + + if (( log_file_size_bytes > log_file_max_size_bytes )); then + + log_file_size_KB=$((log_file_size_bytes/1024)) + log_msg "DEBUG" "log file size: ${log_file_size_KB} KB has exceeded configured maximum: ${log_file_max_size_KB} KB so rotating log file" + break + fi + + done + + flush_log_fd + rotate_log_file + t_log_file_start_us=${EPOCHREALTIME/./} + get_log_file_size_bytes + + done +} + +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 + + # 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=$(( shaper_rate_kbps > base_shaper_rate_kbps ? base_shaper_rate_kbps : shaper_rate_kbps )) + ;; + # bufferbloat detected, so decrease the rate providing not inside bufferbloat refractory period + *bb*) + if (( t_next_rate_us > (t_last_bufferbloat_us+bufferbloat_refractory_period_us) )); then + 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 )) + shaper_rate_kbps=$(( adjusted_achieved_rate_kbps > min_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_up_load_high)/1000 )) + fi + ;; + # 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_down_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=$(( (shaper_rate_kbps*shaper_rate_adjust_up_load_low)/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 + ;; + *) + log_msg "ERROR" "unknown load condition: ${load_condition} in get_next_shaper_rate" + exit 1 + ;; + 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() +{ + trap '' INT + + # 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) + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + 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}" > "${run_path}/dl_achieved_rate_kbps" + printf '%s' "${ul_achieved_rate_kbps}" > "${run_path}/ul_achieved_rate_kbps" + + if ((output_load_stats)); then + + concurrent_read_integer dl_shaper_rate_kbps "${run_path}/dl_shaper_rate_kbps" + concurrent_read_integer ul_shaper_rate_kbps "${run_path}/ul_shaper_rate_kbps" + printf -v load_stats '%s; %s; %s; %s; %s' "${EPOCHREALTIME}" "${dl_achieved_rate_kbps}" "${ul_achieved_rate_kbps}" "${dl_shaper_rate_kbps}" "${ul_shaper_rate_kbps}" + log_msg "LOAD" "${load_stats}" + fi + + prev_rx_bytes=${rx_bytes} + prev_tx_bytes=${tx_bytes} + + # read in the max_wire_packet_rtt_us + concurrent_read_integer max_wire_packet_rtt_us "${run_path}/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 achieved rates and determine the loads + + concurrent_read_integer dl_achieved_rate_kbps "${run_path}/dl_achieved_rate_kbps" + concurrent_read_integer ul_achieved_rate_kbps "${run_path}/ul_achieved_rate_kbps" + + dl_load_percent=$(( (100*dl_achieved_rate_kbps)/dl_shaper_rate_kbps )) + ul_load_percent=$(( (100*ul_achieved_rate_kbps)/ul_shaper_rate_kbps )) + + printf '%s' "${dl_load_percent}" > "${run_path}/dl_load_percent" + printf '%s' "${ul_load_percent}" > "${run_path}/ul_load_percent" +} + +classify_load() +{ + # classify the load according to high/low/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 bufferbloat_detected=${3} + local -n load_condition=${4} + + if (( load_percent > high_load_thr_percent )); then + load_condition="high" + elif (( achieved_rate_kbps > connection_active_thr_kbps )); then + load_condition="low" + else + load_condition="idle" + fi + + ((bufferbloat_detected)) && load_condition=${load_condition}"_bb" + + if ((sss_compensation)); then + # shellcheck disable=SC2154 + 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 +} + +# MAINTAIN PINGERS + ASSOCIATED HELPER FUNCTIONS + +# TSPING FUNCTIONS # + +kill_monitor_reflector_responses_tsping() +{ + trap - TERM EXIT + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + # Store baselines and ewmas to files ready for next instance (e.g. after sleep) + for (( reflector=0; reflector "${run_path}/reflector_${reflectors[reflector]//./-}_dl_baseline_us" + [[ -n "${ul_owd_baselines_us[${reflectors[reflector]}]}" ]] && printf '%s' "${ul_owd_baselines_us[${reflectors[reflector]}]}" > "${run_path}/reflector_${reflectors[reflector]//./-}_ul_baseline_us" + [[ -n "${dl_owd_delta_ewmas_us[${reflectors[reflector]}]}" ]] && printf '%s' "${dl_owd_delta_ewmas_us[${reflectors[reflector]}]}" > "${run_path}/reflector_${reflectors[reflector]//./-}_dl_delta_ewma_us" + [[ -n "${ul_owd_delta_ewmas_us[${reflectors[reflector]}]}" ]] && printf '%s' "${ul_owd_delta_ewmas_us[${reflectors[reflector]}]}" > "${run_path}/reflector_${reflectors[reflector]//./-}_ul_delta_ewma_us" + done + + exit +} + +monitor_reflector_responses_tsping() +{ + trap '' INT + trap kill_monitor_reflector_responses_tsping TERM EXIT + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + declare -A dl_owd_baselines_us + declare -A ul_owd_baselines_us + declare -A dl_owd_delta_ewmas_us + declare -A ul_owd_delta_ewmas_us + + t_start_us=${EPOCHREALTIME/./} + + # Read in baselines if they exist, else just set them to 1s (rapidly converges downwards on new OWDs) + for (( reflector=0; reflector < no_reflectors; reflector++ )) + do + if [[ -f "${run_path}/reflector_${reflectors[reflector]//./-}_dl_baseline_us" ]]; then + read -r "dl_owd_baselines_us[${reflectors[reflector]}]" < "${run_path}/reflector_${reflectors[reflector]//./-}_dl_baseline_us" + else + dl_owd_baselines_us[${reflectors[reflector]}]=100000 + fi + if [[ -f "${run_path}/reflector_${reflectors[reflector]//./-}_ul_baseline_us" ]]; then + read -r "ul_owd_baselines_us[${reflectors[reflector]}]" < "${run_path}/reflector_${reflectors[reflector]//./-}_ul_baseline_us" + else + ul_owd_baselines_us[${reflectors[reflector]}]=100000 + fi + if [[ -f "${run_path}/reflector_${reflectors[reflector]//./-}_dl_delta_ewma_us" ]]; then + read -r "dl_owd_delta_ewmas_us[${reflectors[reflector]}]" < "${run_path}/reflector_${reflectors[reflector]//./-}_dl_delta_ewma_us" + else + dl_owd_delta_ewmas_us[${reflectors[reflector]}]=0 + fi + if [[ -f "${run_path}/reflector_${reflectors[reflector]//./-}_ul_delta_ewma_us" ]]; then + read -r "ul_owd_delta_ewmas_us[${reflectors[reflector]}]" < "${run_path}/reflector_${reflectors[reflector]//./-}_ul_delta_ewma_us" + else + ul_owd_delta_ewmas_us[${reflectors[reflector]}]=0 + fi + done + + # shellcheck disable=SC2154 + while read -r -u "${pinger_fds[pinger]}" timestamp reflector seq _ _ _ _ _ dl_owd_ms ul_owd_ms + do + t_start_us=${EPOCHREALTIME/./} + + dl_owd_us=${dl_owd_ms}000 + ul_owd_us=${ul_owd_ms}000 + + dl_alpha=$(( (( dl_owd_us >= dl_owd_baselines_us[${reflector}] )) ? alpha_baseline_increase : alpha_baseline_decrease )) + ul_alpha=$(( (( ul_owd_us >= ul_owd_baselines_us[${reflector}] )) ? alpha_baseline_increase : alpha_baseline_decrease )) + + ewma_iteration "${dl_owd_us}" "${dl_alpha}" "dl_owd_baselines_us[${reflector}]" + ewma_iteration "${ul_owd_us}" "${ul_alpha}" "ul_owd_baselines_us[${reflector}]" + + dl_owd_delta_us=$(( dl_owd_us - dl_owd_baselines_us[${reflector}] )) + ul_owd_delta_us=$(( ul_owd_us - ul_owd_baselines_us[${reflector}] )) + + concurrent_read_integer dl_load_percent "${run_path}/dl_load_percent" + concurrent_read_integer ul_load_percent "${run_path}/ul_load_percent" + + if (( dl_load_percent < high_load_thr_percent && ul_load_percent < high_load_thr_percent)); then + ewma_iteration "${dl_owd_delta_us}" "${alpha_delta_ewma}" "dl_owd_delta_ewmas_us[${reflector}]" + ewma_iteration "${ul_owd_delta_us}" "${alpha_delta_ewma}" "ul_owd_delta_ewmas_us[${reflector}]" + fi + + timestamp=${timestamp//[\[\]]}0 + + printf '%s %s %s %s %s %s %s %s %s %s %s\n' "${timestamp}" "${reflector}" "${seq}" "${dl_owd_baselines_us[${reflector}]}" "${dl_owd_us}" "${dl_owd_delta_ewmas_us[${reflector}]}" "${dl_owd_delta_us}" "${ul_owd_baselines_us[${reflector}]}" "${ul_owd_us}" "${ul_owd_delta_ewmas_us[${reflector}]}" "${ul_owd_delta_us}" >&"${ping_fd}" + + timestamp_us=${timestamp//[.]} + + printf '%s' "${timestamp_us}" > "${run_path}/reflector_${reflector//./-}_last_timestamp_us" + + printf '%s' "${dl_owd_baselines_us[${reflector}]}" > "${run_path}/reflector_${reflector//./-}_dl_owd_baseline_us" + printf '%s' "${ul_owd_baselines_us[${reflector}]}" > "${run_path}/reflector_${reflector//./-}_ul_owd_baseline_us" + + printf '%s' "${dl_owd_delta_ewmas_us[${reflector}]}" > "${run_path}/reflector_${reflector//./-}_dl_owd_delta_ewma_us" + printf '%s' "${ul_owd_delta_ewmas_us[${reflector}]}" > "${run_path}/reflector_${reflector//./-}_ul_owd_delta_ewma_us" + + printf '%s' "${timestamp_us}" > "${run_path}/reflectors_last_timestamp_us" + + done +} + +# FPING FUNCTIONS # + +kill_monitor_reflector_responses_fping() +{ + trap - TERM EXIT + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + # Store baselines and ewmas to files ready for next instance (e.g. after sleep) + for (( reflector=0; reflector "${run_path}/reflector_${reflectors[reflector]//./-}_baseline_us" + [[ -n "${rtt_delta_ewmas_us[${reflectors[reflector]}]}" ]] && printf '%s' "${rtt_delta_ewmas_us[${reflectors[reflector]}]}" > "${run_path}/reflector_${reflectors[reflector]//./-}_delta_ewma_us" + done + + exit +} + +monitor_reflector_responses_fping() +{ + trap '' INT + trap kill_monitor_reflector_responses_fping TERM EXIT + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + declare -A rtt_baselines_us + declare -A rtt_delta_ewmas_us + + t_start_us=${EPOCHREALTIME/./} + + # Read in baselines if they exist, else just set them to 1s (rapidly converges downwards on new RTTs) + for (( reflector=0; reflector < no_reflectors; reflector++ )) + do + if [[ -f "${run_path}/reflector_${reflectors[reflector]//./-}_baseline_us" ]]; then + read -r "rtt_baselines_us[${reflectors[reflector]}]" < "${run_path}/reflector_${reflectors[reflector]//./-}_baseline_us" + else + rtt_baselines_us[${reflectors[reflector]}]=100000 + fi + if [[ -f "${run_path}/reflector_${reflectors[reflector]//./-}_delta_ewma_us" ]]; then + read -r "rtt_delta_ewmas_us[${reflectors[reflector]}]" < "${run_path}/reflector_${reflectors[reflector]//./-}_delta_ewma_us" + else + rtt_delta_ewmas_us[${reflectors[reflector]}]=0 + fi + done + + # shellcheck disable=SC2154 + while read -r -u "${pinger_fds[pinger]}" timestamp reflector _ seq_rtt 2>/dev/null + do + t_start_us=${EPOCHREALTIME/./} + + [[ ${seq_rtt} =~ \[([0-9]+)\].*[[:space:]]([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})) + + alpha=$(( (( rtt_us >= rtt_baselines_us[${reflector}] )) ? alpha_baseline_increase : alpha_baseline_decrease )) + + ewma_iteration "${rtt_us}" "${alpha}" "rtt_baselines_us[${reflector}]" + + rtt_delta_us=$(( rtt_us-rtt_baselines_us[${reflector}] )) + + concurrent_read_integer dl_load_percent "${run_path}/dl_load_percent" + concurrent_read_integer ul_load_percent "${run_path}/ul_load_percent" + + if (( dl_load_percent < high_load_thr_percent && ul_load_percent < high_load_thr_percent)); then + ewma_iteration "${rtt_delta_us}" "${alpha_delta_ewma}" "rtt_delta_ewmas_us[${reflector}]" + fi + + dl_owd_baseline_us=$((rtt_baselines_us[${reflector}]/2)) + ul_owd_baseline_us=${dl_owd_baseline_us} + + dl_owd_delta_ewma_us=$((rtt_delta_ewmas_us[${reflector}]/2)) + ul_owd_delta_ewma_us=${dl_owd_delta_ewma_us} + + dl_owd_us=$((rtt_us/2)) + ul_owd_us=${dl_owd_us} + + dl_owd_delta_us=$((rtt_delta_us/2)) + ul_owd_delta_us=${dl_owd_delta_us} + + timestamp=${timestamp//[\[\]]}0 + + printf '%s %s %s %s %s %s %s %s %s %s %s\n' "${timestamp}" "${reflector}" "${seq}" "${dl_owd_baseline_us}" "${dl_owd_us}" "${dl_owd_delta_ewma_us}" "${dl_owd_delta_us}" "${ul_owd_baseline_us}" "${ul_owd_us}" "${ul_owd_delta_ewma_us}" "${ul_owd_delta_us}" >&"${ping_fd}" + + timestamp_us=${timestamp//[.]} + + printf '%s' "${timestamp_us}" > "${run_path}/reflector_${reflector//./-}_last_timestamp_us" + + printf '%s' "${dl_owd_baseline_us}" > "${run_path}/reflector_${reflector//./-}_dl_owd_baseline_us" + printf '%s' "${ul_owd_baseline_us}" > "${run_path}/reflector_${reflector//./-}_ul_owd_baseline_us" + + printf '%s' "${dl_owd_delta_ewma_us}" > "${run_path}/reflector_${reflector//./-}_dl_owd_delta_ewma_us" + printf '%s' "${ul_owd_delta_ewma_us}" > "${run_path}/reflector_${reflector//./-}_ul_owd_delta_ewma_us" + + printf '%s' "${timestamp_us}" > "${run_path}/reflectors_last_timestamp_us" + + done 2>/dev/null +} + +# IPUTILS-PING FUNCTIONS + +kill_monitor_reflector_responses_ping() +{ + trap - TERM EXIT + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + [[ -n "${rtt_baseline_us:-}" ]] && printf '%s' "${rtt_baseline_us}" > "${run_path}/reflector_${reflectors[pinger]//./-}_baseline_us" + [[ -n "${rtt_delta_ewma_us:-}" ]] && printf '%s' "${rtt_delta_ewma_us}" > "${run_path}/reflector_${reflectors[pinger]//./-}_delta_ewma_us" + exit +} + +monitor_reflector_responses_ping() +{ + trap '' INT + trap kill_monitor_reflector_responses_ping TERM EXIT + + # ping reflector, maintain baseline and output deltas to a common fifo + + local pinger=${1} + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + if [[ -f "${run_path}/reflector_${reflectors[pinger]//./-}_baseline_us" ]]; then + read -r rtt_baseline_us < "${run_path}/reflector_${reflectors[pinger]//./-}_baseline_us" + else + rtt_baseline_us=100000 + fi + + if [[ -f "${run_path}/reflector_${reflectors[pinger]//./-}_delta_ewma_us" ]]; then + read -r rtt_delta_ewma_us < "${run_path}/reflector_${reflectors[pinger]//./-}_delta_ewma_us" + else + rtt_delta_ewma_us=0 + fi + + while read -r -u "${pinger_fds[pinger]}" timestamp _ _ _ reflector seq_rtt 2>/dev/null + 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//:/} + + alpha=$(( (( rtt_us >= rtt_baseline_us )) ? alpha_baseline_increase : alpha_baseline_decrease )) + + ewma_iteration "${rtt_us}" "${alpha}" rtt_baseline_us + + rtt_delta_us=$(( rtt_us-rtt_baseline_us )) + + concurrent_read_integer dl_load_percent "${run_path}/dl_load_percent" + concurrent_read_integer ul_load_percent "${run_path}/ul_load_percent" + + if (( dl_load_percent < high_load_thr_percent && ul_load_percent < high_load_thr_percent )); then + ewma_iteration "${rtt_delta_us}" "${alpha_delta_ewma}" rtt_delta_ewma_us + fi + + dl_owd_baseline_us=$((rtt_baseline_us/2)) + ul_owd_baseline_us=${dl_owd_baseline_us} + + dl_owd_delta_ewma_us=$((rtt_delta_ewma_us/2)) + ul_owd_delta_ewma_us=${dl_owd_delta_ewma_us} + + dl_owd_us=$((rtt_us/2)) + ul_owd_us=${dl_owd_us} + + dl_owd_delta_us=$((rtt_delta_us/2)) + ul_owd_delta_us=${dl_owd_delta_us} + + timestamp=${timestamp//[\[\]]} + + printf '%s %s %s %s %s %s %s %s %s %s %s\n' "${timestamp}" "${reflector}" "${seq}" "${dl_owd_baseline_us}" "${dl_owd_us}" "${dl_owd_delta_ewma_us}" "${dl_owd_delta_us}" "${ul_owd_baseline_us}" "${ul_owd_us}" "${ul_owd_delta_ewma_us}" "${ul_owd_delta_us}" >&"${ping_fd}" + + timestamp_us=${timestamp//[.]} + + printf '%s' "${timestamp_us}" > "${run_path}/reflector_${reflector//./-}_last_timestamp_us" + + printf '%s' "${dl_owd_baseline_us}" > "${run_path}/reflector_${reflector//./-}_dl_owd_baseline_us" + printf '%s' "${ul_owd_baseline_us}" > "${run_path}/reflector_${reflector//./-}_ul_owd_baseline_us" + + printf '%s' "${dl_owd_delta_ewma_us}" > "${run_path}/reflector_${reflector//./-}_dl_owd_delta_ewma_us" + printf '%s' "${ul_owd_delta_ewma_us}" > "${run_path}/reflector_${reflector//./-}_ul_owd_delta_ewma_us" + + printf '%s' "${timestamp_us}" > "${run_path}/reflectors_last_timestamp_us" + + done 2>/dev/null +} + +# END OF IPUTILS-PING FUNCTIONS + +# GENERIC PINGER START AND STOP FUNCTIONS + +start_pinger() +{ + local pinger=${1} + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + # shellcheck disable=SC1083,SC2086,SC2261 + case ${pinger_binary} in + + tsping) + pinger=0 + exec {pinger_fds[pinger]}<> <(:) || true + proc_man_start "pinger_${pinger}" ${ping_prefix_string} tsping ${ping_extra_args} --print-timestamps --machine-readable=' ' --sleep-time "0" --target-spacing "${ping_response_interval_ms}" "${reflectors[@]:0:${no_pingers}}" 2> /dev/null >&"${pinger_fds[pinger]}" + ;; + fping) + pinger=0 + exec {pinger_fds[pinger]}<> <(:) || true + proc_man_start "pinger_${pinger}" ${ping_prefix_string} fping ${ping_extra_args} --timestamp --loop --period "${reflector_ping_interval_ms}" --interval "${ping_response_interval_ms}" --timeout 10000 "${reflectors[@]:0:${no_pingers}}" 2> /dev/null >&"${pinger_fds[pinger]}" + ;; + ping) + exec {pinger_fds[pinger]}<> <(:) || true + sleep_until_next_pinger_time_slot "${pinger}" + proc_man_start "pinger_${pinger}" ${ping_prefix_string} ping ${ping_extra_args} -D -i "${reflector_ping_interval_s}" "${reflectors[pinger]}" 2> /dev/null >&"${pinger_fds[pinger]}" + ;; + *) + log_msg "ERROR" "Unknown pinger binary: ${pinger_binary}" + exit 1 + ;; + esac + + proc_man_start "monitor_${pinger}" "monitor_reflector_responses_${pinger_binary}" "${pinger}" +} + +start_pingers() +{ + # Initiate pingers + log_msg "DEBUG" "Starting pingers." + case ${pinger_binary} in + + tsping|fping) + start_pinger 0 + ;; + ping) + for ((pinger=0; pinger < no_pingers; pinger++)) + do + start_pinger "${pinger}" + done + ;; + *) + log_msg "ERROR" "Unknown pinger binary: ${pinger_binary}" + exit 1 + ;; + esac +} + +sleep_until_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} + + 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}" +} + +kill_pinger() +{ + local pinger=${1} + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + case ${pinger_binary} in + tsping|fping) + pinger=0 + ;; + + *) + : + ;; + esac + + proc_man_stop "pinger_${pinger}" + proc_man_stop "monitor_${pinger}" + + # shellcheck disable=SC1083 + exec {pinger_fds[pinger]}<&- +} + +kill_pingers() +{ + case ${pinger_binary} in + + tsping|fping) + log_msg "DEBUG" "Killing ${pinger_binary} instance." + kill_pinger 0 + ;; + ping) + for (( pinger=0; pinger < no_pingers; pinger++)) + do + log_msg "DEBUG" "Killing pinger instance: ${pinger}" + kill_pinger "${pinger}" + done + ;; + *) + log_msg "ERROR" "Unknown pinger binary: ${pinger_binary}" + exit 1 + ;; + esac +} + +replace_pinger_reflector() +{ + # 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 + + local pinger=${1} + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + lock "${run_path}/replace_pinger_reflector_lock" + + if ((no_reflectors > no_pingers)); then + log_msg "DEBUG" "replacing reflector: ${reflectors[pinger]} with ${reflectors[no_pingers]}." + kill_pinger "${pinger}" + 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 + mapfile -t reflectors < <(for i in "${reflectors[@]}"; do printf '%s\n' "${i}"; done) + # set up the new pinger with the new reflector and retain pid + start_pinger "${pinger}" + else + log_msg "DEBUG" "No additional reflectors specified so just retaining: ${reflectors[pinger]}." + reflector_offences[pinger]=0 + fi + + unlock "${run_path}/replace_pinger_reflector_lock" +} + +# END OF GENERIC PINGER START AND STOP FUNCTIONS + +kill_maintain_pingers() +{ + trap - TERM EXIT + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + lock "${run_path}/replace_pinger_reflector_lock" + + log_msg "DEBUG" "Terminating maintain_pingers." + + kill_pingers + + unlock "${run_path}/replace_pinger_reflector_lock" + + exit +} + +change_state_maintain_pingers() +{ + local maintain_pingers_next_state=${1:-unset} + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + if [[ "${maintain_pingers_next_state}" == "unset" ]]; then + if [[ -f "${run_path}/maintain_pingers_next_state" ]]; then + for ((read_try=1; read_try<11; read_try++)) + do + read -r maintain_pingers_next_state < "${run_path}/maintain_pingers_next_state" + maintain_pingers_next_state=${maintain_pingers_next_state:-unset} + [[ "${maintain_pingers_next_state}" != "unset" ]] && break + done + else + log_msg "ERROR" "Received change signal but ${run_path}/maintain_pingers_next_state does not exist. Exiting now." + kill -INT $$ + fi + fi + + case ${maintain_pingers_next_state} in + + START|STOP|PAUSED|RUNNING) + + if [[ "${maintain_pingers_state}" != "${maintain_pingers_next_state}" ]] + then + log_msg "DEBUG" "Changing maintain_pingers state from: ${maintain_pingers_state} to: ${maintain_pingers_next_state}" + maintain_pingers_state=${maintain_pingers_next_state} + printf "%s" ${maintain_pingers_state} > ${run_path}/maintain_pingers_state + else + log_msg "ERROR" "Received request to change maintain_pingers state to existing state." + fi + ;; + + *) + + log_msg "ERROR" "Received unrecognized state change request: ${maintain_pingers_next_state}. Exiting now." + kill -INT $$ + ;; + esac +} + +maintain_pingers() +{ + # this initiates the pingers and monitors reflector health, rotating reflectors as necessary + + trap '' INT + trap 'kill_maintain_pingers' TERM EXIT + + trap 'change_state_maintain_pingers' USR1 + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + declare -A dl_owd_baselines_us + declare -A ul_owd_baselines_us + declare -A dl_owd_delta_ewmas_us + declare -A ul_owd_delta_ewmas_us + + err_silence=0 + reflector_offences_idx=0 + pingers_active=0 + + pingers_t_start_us=${EPOCHREALTIME/./} + t_last_reflector_replacement_us=${EPOCHREALTIME/./} + t_last_reflector_comparison_us=${EPOCHREALTIME/./} + + for ((reflector=0; reflector < no_reflectors; reflector++)) + do + printf '%s' "${pingers_t_start_us}" > "${run_path}/reflector_${reflectors[reflector]//./-}_last_timestamp_us" + done + + printf '%s' "${pingers_t_start_us}" > "${run_path}/reflectors_last_timestamp_us" + + # For each pinger initialize record of offences + for ((pinger=0; pinger < no_pingers; pinger++)) + do + # shellcheck disable=SC2178 + declare -n reflector_offences="reflector_${pinger}_offences" + for ((i=0; i(t_last_reflector_replacement_us+reflector_replacement_interval_mins*60*1000000))); then + + log_msg "DEBUG" "reflector: ${reflectors[pinger]} randomly selected for replacement." + replace_pinger_reflector $((RANDOM%no_pingers)) + t_last_reflector_replacement_us=${EPOCHREALTIME/./} + continue + fi + + if (( ${EPOCHREALTIME/./}>(t_last_reflector_comparison_us+reflector_comparison_interval_mins*60*1000000) )); then + + t_last_reflector_comparison_us=${EPOCHREALTIME/./} + + concurrent_read_integer dl_min_owd_baseline_us "${run_path}/reflector_${reflectors[0]//./-}_dl_owd_baseline_us" 0 || continue + concurrent_read_integer dl_min_owd_delta_ewma_us "${run_path}/reflector_${reflectors[0]//./-}_dl_owd_delta_ewma_us" 0 || continue + concurrent_read_integer ul_min_owd_baseline_us "${run_path}/reflector_${reflectors[0]//./-}_ul_owd_baseline_us" 0 || continue + concurrent_read_integer ul_min_owd_delta_ewma_us "${run_path}/reflector_${reflectors[0]//./-}_ul_owd_delta_ewma_us" 0 || continue + + concurrent_read_integer compensated_dl_delay_thr_us "${run_path}/compensated_dl_delay_thr_us" + concurrent_read_integer compensated_ul_delay_thr_us "${run_path}/compensated_ul_delay_thr_us" + + for ((pinger=0; pinger < no_pingers; pinger++)) + do + concurrent_read_integer "dl_owd_baselines_us[${reflectors[pinger]}]" "${run_path}/reflector_${reflectors[pinger]//./-}_dl_owd_baseline_us" 0 || continue 2 + concurrent_read_integer "dl_owd_delta_ewmas_us[${reflectors[pinger]}]" "${run_path}/reflector_${reflectors[pinger]//./-}_dl_owd_delta_ewma_us" 0 || continue 2 + concurrent_read_integer "ul_owd_baselines_us[${reflectors[pinger]}]" "${run_path}/reflector_${reflectors[pinger]//./-}_ul_owd_baseline_us" 0 || continue 2 + concurrent_read_integer "ul_owd_delta_ewmas_us[${reflectors[pinger]}]" "${run_path}/reflector_${reflectors[pinger]//./-}_ul_owd_delta_ewma_us" 0 || continue 2 + + (( dl_owd_baselines_us[${reflectors[pinger]}] < dl_min_owd_baseline_us )) && dl_min_owd_baseline_us="${dl_owd_baselines_us[${reflectors[pinger]}]}" + (( dl_owd_delta_ewmas_us[${reflectors[pinger]}] < dl_min_owd_delta_ewma_us )) && dl_min_owd_delta_ewma_us="${dl_owd_delta_ewmas_us[${reflectors[pinger]}]}" + (( ul_owd_baselines_us[${reflectors[pinger]}] < ul_min_owd_baseline_us )) && ul_min_owd_baseline_us="${ul_owd_baselines_us[${reflectors[pinger]}]}" + (( ul_owd_delta_ewmas_us[${reflectors[pinger]}] < ul_min_owd_delta_ewma_us )) && ul_min_owd_delta_ewma_us="${ul_owd_delta_ewmas_us[${reflectors[pinger]}]}" + done + + for ((pinger=0; pinger < no_pingers; pinger++)) + do + + dl_owd_baseline_delta_us=$(( dl_owd_baselines_us[${reflectors[pinger]}] - dl_min_owd_baseline_us )) + dl_owd_delta_ewma_delta_us=$(( dl_owd_delta_ewmas_us[${reflectors[pinger]}] - dl_min_owd_delta_ewma_us )) + ul_owd_baseline_delta_us=$(( ul_owd_baselines_us[${reflectors[pinger]}] - ul_min_owd_baseline_us )) + ul_owd_delta_ewma_delta_us=$(( ul_owd_delta_ewmas_us[${reflectors[pinger]}] - ul_min_owd_delta_ewma_us )) + + if ((output_reflector_stats)); then + printf -v reflector_stats '%s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s' "${EPOCHREALTIME}" "${reflectors[pinger]}" "${dl_min_owd_baseline_us}" "${dl_owd_baselines_us[${reflectors[pinger]}]}" "${dl_owd_baseline_delta_us}" "${reflector_owd_baseline_delta_thr_us}" "${dl_min_owd_delta_ewma_us}" "${dl_owd_delta_ewmas_us[${reflectors[pinger]}]}" "${dl_owd_delta_ewma_delta_us}" "${reflector_owd_delta_ewma_delta_thr_us}" "${ul_min_owd_baseline_us}" "${ul_owd_baselines_us[${reflectors[pinger]}]}" "${ul_owd_baseline_delta_us}" "${reflector_owd_baseline_delta_thr_us}" "${ul_min_owd_delta_ewma_us}" "${ul_owd_delta_ewmas_us[${reflectors[pinger]}]}" "${ul_owd_delta_ewma_delta_us}" "${reflector_owd_delta_ewma_delta_thr_us}" + log_msg "REFLECTOR" "${reflector_stats}" + fi + + if (( dl_owd_baseline_delta_us > reflector_owd_baseline_delta_thr_us )); then + log_msg "DEBUG" "Warning: reflector: ${reflectors[pinger]} dl_owd_baseline_us exceeds the minimum by set threshold." + replace_pinger_reflector "${pinger}" + continue 2 + fi + + if (( dl_owd_delta_ewma_delta_us > reflector_owd_delta_ewma_delta_thr_us )); then + log_msg "DEBUG" "Warning: reflector: ${reflectors[pinger]} dl_owd_delta_ewma_us exceeds the minimum by set threshold." + replace_pinger_reflector "${pinger}" + continue 2 + fi + + if (( ul_owd_baseline_delta_us > reflector_owd_baseline_delta_thr_us )); then + log_msg "DEBUG" "Warning: reflector: ${reflectors[pinger]} ul_owd_baseline_us exceeds the minimum by set threshold." + replace_pinger_reflector "${pinger}" + continue 2 + fi + + if (( ul_owd_delta_ewma_delta_us > reflector_owd_delta_ewma_delta_thr_us )); then + log_msg "DEBUG" "Warning: reflector: ${reflectors[pinger]} ul_owd_delta_ewma_us exceeds the minimum by set threshold." + replace_pinger_reflector "${pinger}" + continue 2 + fi + done + + fi + + enable_replace_pinger_reflector=1 + + for ((pinger=0; pinger < no_pingers; pinger++)) + do + reflector_check_time_us=${EPOCHREALTIME/./} + concurrent_read_integer reflector_last_timestamp_us "${run_path}/reflector_${reflectors[pinger]//./-}_last_timestamp_us" + # shellcheck disable=SC2178 + declare -n reflector_offences="reflector_${pinger}_offences" + + (( reflector_offences[reflector_offences_idx] )) && ((sum_reflector_offences[pinger]--)) + # shellcheck disable=SC2154 + reflector_offences[reflector_offences_idx]=$(( (((reflector_check_time_us-reflector_last_timestamp_us) > reflector_response_deadline_us)) ? 1 : 0 )) + + if (( reflector_offences[reflector_offences_idx] )); then + ((sum_reflector_offences[pinger]++)) + log_msg "DEBUG" "no ping response from reflector: ${reflectors[pinger]} within reflector_response_deadline: ${reflector_response_deadline_s}s" + log_msg "DEBUG" "reflector=${reflectors[pinger]}, sum_reflector_offences=${sum_reflector_offences[pinger]} and reflector_misbehaving_detection_thr=${reflector_misbehaving_detection_thr}" + fi + + if (( sum_reflector_offences[pinger] >= reflector_misbehaving_detection_thr )); then + + log_msg "DEBUG" "Warning: reflector: ${reflectors[pinger]} seems to be misbehaving." + if ((enable_replace_pinger_reflector)); then + replace_pinger_reflector "${pinger}" + for ((i=0; i /dev/null + fi + + else + ((output_cake_changes)) && log_msg "DEBUG" "adjust_shaper_rate set to 0 in config, so skipping the tc qdisc change call" + fi +} + +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 + if (( dl_shaper_rate_kbps != last_dl_shaper_rate_kbps )); then + set_cake_rate "${dl_if}" "${dl_shaper_rate_kbps}" adjust_dl_shaper_rate + printf '%s' "${dl_shaper_rate_kbps}" > "${run_path}/dl_shaper_rate_kbps" + last_dl_shaper_rate_kbps=${dl_shaper_rate_kbps} + fi + if (( ul_shaper_rate_kbps != last_ul_shaper_rate_kbps )); then + set_cake_rate "${ul_if}" "${ul_shaper_rate_kbps}" adjust_ul_shaper_rate + printf '%s' "${ul_shaper_rate_kbps}" > "${run_path}/ul_shaper_rate_kbps" + last_ul_shaper_rate_kbps=${ul_shaper_rate_kbps} + fi + + update_max_wire_packet_compensation + fi +} + +set_min_shaper_rates() +{ + log_msg "DEBUG" "Enforcing minimum shaper rates." + dl_shaper_rate_kbps=${min_dl_shaper_rate_kbps} + ul_shaper_rate_kbps=${min_ul_shaper_rate_kbps} + set_shaper_rates +} + +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}" || true) =~ (atm|noatm)[[:space:]]overhead[[:space:]]([0-9]+) ]] + [[ -n "${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 + + # compensated OWD delay thresholds in microseconds + compensated_dl_delay_thr_us=$(( dl_delay_thr_us + (1000*dl_max_wire_packet_size_bits)/dl_shaper_rate_kbps )) + compensated_ul_delay_thr_us=$(( ul_delay_thr_us + (1000*ul_max_wire_packet_size_bits)/ul_shaper_rate_kbps )) + + printf '%s' "${compensated_dl_delay_thr_us}" > "${run_path}/compensated_dl_delay_thr_us" + printf '%s' "${compensated_ul_delay_thr_us}" > "${run_path}/compensated_ul_delay_thr_us" + + # determine and write out ${max_wire_packet_rtt_us} + 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 )) + printf '%s' "${max_wire_packet_rtt_us}" > "${run_path}/max_wire_packet_rtt_us" +} + +concurrent_read_integer() +{ + # in the context of a single process that writes to a file and + # a separate process that reads from the file, costly calls to + # the external flock binary can be avoided for the reason that + # the read either reads in a blank value or the last true value + # and so it is possible to just read, test and reread if necessary + + local -n value=${1} + local path=${2} + local exit_on_no_file=${3:-1} + + if ! [[ -f ${path} ]] + then + if ((exit_on_no_file)) + then + log_msg "ERROR" "Non-existent file at: ${path} and exit_on_no_file enabled. Exiting now." + kill -$$ INT + else + log_msg "DEBUG" "Non-existent file at: ${path} but exit_on_no_file disabled. Returning 1." + value=0 + return 1 + fi + fi + + for ((read_try=1; read_try<11; read_try++)) + do + read -r value < "${path}" + value="${value:-unset}" + + # printf '%.0f' is used here to sanitize unsigned integers: + # - it removes any leading zeros whilst preserving the sign; and + # - it returns false if ${value} is not a number + if printf -v sanitized_value '%.0f' "${value}" 2>/dev/null; then + + value=${sanitized_value} + return 0 + + else + if ((debug)); then + read -r caller_output< <(caller) || true + log_msg "DEBUG" "concurrent_read_integer() misfire: ${read_try} of 10, with the following particulars:" + log_msg "DEBUG" "caller=${caller_output}, value=${value} and path=${path}" + fi + sleep_us "${concurrent_read_integer_interval_us}" + continue + fi + done + + if ((debug)); then + read -r caller_output< <(caller) || true + log_msg "ERROR" "If you see this, then please report these messages (ideally with log file)" + log_msg "ERROR" "at the cake-autorate forum of OpenWrt and/or at github.com/lynxthecat/cake-autorate" + log_msg "ERROR" "concurrent_read_integer() 10x misfires, with the following particulars:" + log_msg "ERROR" "caller=${caller_output}, value=${value} and path=${path}" + fi + value=0 + return 1 +} + +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 + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + while [[ ! -f ${rx_bytes_path} || ! -f ${tx_bytes_path} ]] + do + [[ ! -f ${rx_bytes_path} ]] && log_msg "DEBUG" "Warning: The configured download interface: '${dl_if}' does not appear to be present. Waiting ${if_up_check_interval_s} seconds for the interface to come up." + [[ ! -f ${tx_bytes_path} ]] && log_msg "DEBUG" "Warning: The configured upload interface: '${ul_if}' does not appear to be present. Waiting ${if_up_check_interval_s} seconds for the interface to come up." + sleep_s "${if_up_check_interval_s}" + done +} + +ewma_iteration() +{ + local value=${1} + local alpha=${2} # alpha must be scaled by factor of 1000000 + local -n ewma=${3} + + prev_ewma=${ewma} + ewma=$(( (alpha*value+(1000000-alpha)*prev_ewma)/1000000 )) +} + +# redirect stderr to log_msg and exit cake-autorate +intercept_stderr() +{ + exec 2> >( + while read -r error + do + log_msg "ERROR" "${error}" + kill -INT $$ + done + ) +} + +# Debug command wrapper +# Inspired by cmd_wrapper of sqm-script +debug_cmd() +{ + # Usage: debug_cmd debug_msg err_silence cmd arg1 arg2, etc. + + # Error messages are output as log_msg ERROR messages + # Or set error_silence=1 to output errors as log_msg DEBUG messages + + local debug_msg=${1} + local err_silence=${2} + local cmd=${3} + + log_msg "DEBUG" "Starting: ${FUNCNAME[0]} with PID: ${BASHPID}" + + shift 3 + + local args=("${@}") + + local caller_id + local err_type + + local ret + local stderr + + err_type="ERROR" + + if ((err_silence)); then + err_type="DEBUG" + fi + + stderr=$(${cmd} "${args[@]}" 2>&1) + ret=${?} + + caller_id=$(caller) + + if ((ret==0)); then + log_msg "DEBUG" "debug_cmd: err_silence=${err_silence}; debug_msg=${debug_msg}; caller_id=${caller_id}; command=${cmd} ${args[*]}; result=SUCCESS" + else + [[ "${err_type}" == "DEBUG" && "${debug}" == "0" ]] && return # if debug disabled, then skip on DEBUG but not on ERROR + + log_msg "${err_type}" "debug_cmd: err_silence=${err_silence}; debug_msg=${debug_msg}; caller_id=${caller_id}; command=${cmd} ${args[*]}; result=FAILURE (${ret})" + log_msg "${err_type}" "debug_cmd: LAST ERROR (${stderr})" + + frame=1 + while caller_output=$(caller "${frame}") + do + log_msg "${err_type}" "debug_cmd: CALL CHAIN: ${caller_output}" + ((++frame)) + done + fi +} + +# ======= Start of the Main Routine ======== + +[[ -t 1 ]] && terminal=1 || terminal=0 + +type logger &> /dev/null && use_logger=1 || use_logger=0 # only perform the test once. + +log_file_path=/var/log/cake-autorate.log + +# *** WARNING: take great care if attempting to alter the run_path! *** +# *** cake-autorate issues mkdir -p ${run_path} and rm -r ${run_path} on exit. *** +run_path=/var/run/cake-autorate/ + +# cake-autorate first argument is config file path +if [[ -n ${1:-} ]]; then + config_path=${1} +else + config_path="$PREFIX/cake-autorate_config.primary.sh" +fi + +if [[ ! -f "${config_path}" ]]; then + log_msg "ERROR" "No config file found. Exiting now." + exit +fi + +# shellcheck source=cake-autorate_config.primary.sh +. "${config_path}" + +if [[ ${config_file_check} != "cake-autorate" ]]; then + log_msg "ERROR" "Config file error. Please check config file entries." + exit +fi + +if [[ ${config_path} =~ cake-autorate_config\.(.*)\.sh ]]; then + instance_id=${BASH_REMATCH[1]} + run_path=/var/run/cake-autorate/${instance_id} +else + log_msg "ERROR" "Instance identifier 'X' set by cake-autorate_config.X.sh cannot be empty. Exiting now." + exit +fi + +PROC_STATE_FILE="${run_path}/proc_state" +PROC_STATE_FILE_LOCK="${run_path}/proc_state.lock" + +if [[ -n "${log_file_path_override:-}" ]]; then + if [[ ! -d ${log_file_path_override} ]]; then + broken_log_file_path_override=${log_file_path_override} + log_file_path=/var/log/cake-autorate${instance_id:+.${instance_id}}.log + log_msg "ERROR" "Log file path override: '${broken_log_file_path_override}' does not exist. Exiting now." + exit + fi + log_file_path=${log_file_path_override}/cake-autorate${instance_id:+.${instance_id}}.log +else + log_file_path=/var/log/cake-autorate${instance_id:+.${instance_id}}.log +fi + +rotate_log_file # rotate here to force header prints at top of log file + +# Intercept stderr, redirect it to log_msg and exit cake-autorate +intercept_stderr + +log_msg "SYSLOG" "Starting cake-autorate with PID: ${BASHPID} and config: ${config_path}" + +# ${run_path}/ is used to store temporary files +# it should not exist on startup so if it does exit, else create the directory +if [[ -d "${run_path}" ]]; then + if [[ -f "${run_path}/pid" ]] && [[ -d "/proc/$(<"${run_path}/pid")" ]]; then + log_msg "ERROR" "${run_path} already exists and an instance may be running. Exiting script." + trap - INT TERM EXIT + exit + else + log_msg "DEBUG" "${run_path} already exists but no instance is running. Removing and recreating." + rm -r "${run_path}" + mkdir -p "${run_path}" + fi +else + mkdir -p "${run_path}" +fi + +# Initialize proc_man +proc_man_initialize + +printf "%s" "${BASHPID}" > "${run_path}/pid" + +no_reflectors=${#reflectors[@]} + +# Check ping binary exists +command -v "${pinger_binary}" &> /dev/null || { log_msg "ERROR" "ping binary ${pinger_binary} does not exist. Exiting script."; exit; } + +# Check no_pingers <= no_reflectors +(( no_pingers > no_reflectors )) && { log_msg "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}" ]] && { log_msg "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 )) && { log_msg "ERROR" "bufferbloat_detection_thr cannot be greater than bufferbloat_detection_window. Exiting script."; exit; } + +# Passed error checks + +if ((log_to_file)); then + log_file_max_time_us=$((log_file_max_time_mins*60000000)) + log_file_max_size_bytes=$((log_file_max_size_KB*1024)) + exec {log_fd}<> <(:) || true + proc_man_start "maintain_log_file" maintain_log_file +fi + +# test if stdout is a tty (terminal) +if ! ((terminal)); then + echo "stdout not a terminal so redirecting output to: ${log_file_path}" + ((log_to_file)) && exec 1>&"${log_fd}" +fi + +# Initialize rx_bytes_path and tx_bytes_path if not set +if [[ -z "${rx_bytes_path:-}" ]]; then + 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/tx_bytes" + ;; + esac +fi +if [[ -z "${tx_bytes_path:-}" ]]; then + 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 +fi + +if ((debug)) ; then + log_msg "DEBUG" "CAKE-autorate version: ${cake_autorate_version}" + log_msg "DEBUG" "config_path: ${config_path}" + log_msg "DEBUG" "run_path: ${run_path}" + log_msg "DEBUG" "log_file_path: ${log_file_path}" + log_msg "DEBUG" "pinger_binary:${pinger_binary}" + log_msg "DEBUG" "download interface: ${dl_if} (${min_dl_shaper_rate_kbps} / ${base_dl_shaper_rate_kbps} / ${max_dl_shaper_rate_kbps})" + log_msg "DEBUG" "upload interface: ${ul_if} (${min_ul_shaper_rate_kbps} / ${base_ul_shaper_rate_kbps} / ${max_ul_shaper_rate_kbps})" + log_msg "DEBUG" "rx_bytes_path: ${rx_bytes_path}" + log_msg "DEBUG" "tx_bytes_path: ${tx_bytes_path}" +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 dl_delay_thr_us %.0f "${dl_delay_thr_ms}e3" +printf -v ul_delay_thr_us %.0f "${ul_delay_thr_ms}e3" +printf -v alpha_baseline_increase %.0f "${alpha_baseline_increase}e6" +printf -v alpha_baseline_decrease %.0f "${alpha_baseline_decrease}e6" +printf -v alpha_delta_ewma %.0f "${alpha_delta_ewma}e6" +printf -v achieved_rate_adjust_down_bufferbloat %.0f "${achieved_rate_adjust_down_bufferbloat}e3" +printf -v shaper_rate_adjust_down_bufferbloat %.0f "${shaper_rate_adjust_down_bufferbloat}e3" +printf -v shaper_rate_adjust_up_load_high %.0f "${shaper_rate_adjust_up_load_high}e3" +printf -v shaper_rate_adjust_down_load_low %.0f "${shaper_rate_adjust_down_load_low}e3" +printf -v shaper_rate_adjust_up_load_low %.0f "${shaper_rate_adjust_up_load_low}e3" +printf -v high_load_thr_percent %.0f "${high_load_thr}e2" +printf -v reflector_ping_interval_ms %.0f "${reflector_ping_interval_s}e3" +printf -v reflector_ping_interval_us %.0f "${reflector_ping_interval_s}e6" +printf -v monitor_achieved_rates_interval_us %.0f "${monitor_achieved_rates_interval_ms}e3" +printf -v sustained_idle_sleep_thr_us %.0f "${sustained_idle_sleep_thr_s}e6" +printf -v reflector_response_deadline_us %.0f "${reflector_response_deadline_s}e6" +printf -v reflector_owd_baseline_delta_thr_us %.0f "${reflector_owd_baseline_delta_thr_ms}e3" +printf -v reflector_owd_delta_ewma_delta_thr_us %.0f "${reflector_owd_delta_ewma_delta_thr_ms}e3" +printf -v startup_wait_us %.0f "${startup_wait_s}e6" +printf -v global_ping_response_timeout_us %.0f "${global_ping_response_timeout_s}e6" +printf -v bufferbloat_refractory_period_us %.0f "${bufferbloat_refractory_period_ms}e3" +printf -v decay_refractory_period_us %.0f "${decay_refractory_period_ms}e3" + +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 "${sss_compensation_pre_duration_ms}e3" +printf -v sss_compensation_post_duration_us %.0f "${sss_compensation_post_duration_ms}e3" + +ping_response_interval_us=$(( reflector_ping_interval_us/no_pingers )) +ping_response_interval_ms=$(( ping_response_interval_us/1000 )) + +stall_detection_timeout_us=$(( stall_detection_thr*ping_response_interval_us )) +stall_detection_timeout_s=000000${stall_detection_timeout_us} +stall_detection_timeout_s=$(( 10#${stall_detection_timeout_s::-6})).${stall_detection_timeout_s: -6} + +concurrent_read_integer_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=0 +last_ul_shaper_rate_kbps=0 + +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_shaper_rates + +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 + +mapfile -t dl_delays < <(for ((i=1; i <= bufferbloat_detection_window; i++)); do echo 0; done) +mapfile -t ul_delays < <(for ((i=1; i <= bufferbloat_detection_window; i++)); do echo 0; done) + +delays_idx=0 +sum_dl_delays=0 +sum_ul_delays=0 + +if ((debug)); then + if (( bufferbloat_refractory_period_us < (bufferbloat_detection_window*ping_response_interval_us) )); then + log_msg "DEBUG" "Warning: bufferbloat refractory period: ${bufferbloat_refractory_period_us} us." + log_msg "DEBUG" "Warning: but expected time to overwrite samples in bufferbloat detection window is: $((bufferbloat_detection_window*ping_response_interval_us)) us." + log_msg "DEBUG" "Warning: Consider increasing bufferbloat refractory period or decreasing bufferbloat detection window." + fi + if (( reflector_response_deadline_us < 2*reflector_ping_interval_us )); then + log_msg "DEBUG" "Warning: reflector_response_deadline_s < 2*reflector_ping_interval_s" + log_msg "DEBUG" "Warning: consider setting an increased reflector_response_deadline." + fi +fi + +# Randomize reflectors array providing randomize_reflectors set to 1 +((randomize_reflectors)) && randomize_array reflectors + +# Wait if ${startup_wait_s} > 0 +if ((startup_wait_us>0)); then + log_msg "DEBUG" "Waiting ${startup_wait_s} seconds before startup." + sleep_us "${startup_wait_us}" +fi + +# Initiate achieved rate monitor +proc_man_start monitor_achieved_rates monitor_achieved_rates "${rx_bytes_path}" "${tx_bytes_path}" "${monitor_achieved_rates_interval_us}" + +printf '%s' "0" > "${run_path}/dl_load_percent" +printf '%s' "0" > "${run_path}/ul_load_percent" + +proc_man_start maintain_pingers maintain_pingers + +generate_log_file_exporter + +log_msg "INFO" "Started cake-autorate with PID: ${BASHPID} and config: ${config_path}" + +while true +do + while read -r -t "${stall_detection_timeout_s}" -u "${ping_fd}" timestamp reflector seq dl_owd_baseline_us dl_owd_us dl_owd_delta_ewma_us dl_owd_delta_us ul_owd_baseline_us ul_owd_us ul_owd_delta_ewma_us ul_owd_delta_us + do + t_start_us=${EPOCHREALTIME/./} + if (( (t_start_us - 10#"${timestamp//[.]}")>500000 )); then + log_msg "DEBUG" "processed response from [${reflector}] that is > 500ms old. Skipping." + continue + fi + + # Keep track of number of dl delays across detection window + # .. for download: + (( dl_delays[delays_idx] )) && ((sum_dl_delays--)) + dl_delays[delays_idx]=$(( dl_owd_delta_us > compensated_dl_delay_thr_us ? 1 : 0 )) + ((dl_delays[delays_idx])) && ((sum_dl_delays++)) + # .. for upload + (( ul_delays[delays_idx] )) && ((sum_ul_delays--)) + ul_delays[delays_idx]=$(( ul_owd_delta_us > compensated_ul_delay_thr_us ? 1 : 0 )) + ((ul_delays[delays_idx])) && ((sum_ul_delays++)) + # .. and move index on + (( delays_idx=(delays_idx+1)%bufferbloat_detection_window )) + + dl_bufferbloat_detected=$(( ((sum_dl_delays >= bufferbloat_detection_thr)) ? 1 : 0 )) + ul_bufferbloat_detected=$(( ((sum_ul_delays >= bufferbloat_detection_thr)) ? 1 : 0 )) + + get_loads + + classify_load "${dl_load_percent}" "${dl_achieved_rate_kbps}" "${dl_bufferbloat_detected}" dl_load_condition + classify_load "${ul_load_percent}" "${ul_achieved_rate_kbps}" "${ul_bufferbloat_detected}" ul_load_condition + + dl_load_condition="dl_"${dl_load_condition} + ul_load_condition="ul_"${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 + + set_shaper_rates + + if (( output_processing_stats )); then + printf -v processing_stats '%s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s; %s' "${EPOCHREALTIME}" "${dl_achieved_rate_kbps}" "${ul_achieved_rate_kbps}" "${dl_load_percent}" "${ul_load_percent}" "${timestamp}" "${reflector}" "${seq}" "${dl_owd_baseline_us}" "${dl_owd_us}" "${dl_owd_delta_ewma_us}" "${dl_owd_delta_us}" "${compensated_dl_delay_thr_us}" "${ul_owd_baseline_us}" "${ul_owd_us}" "${ul_owd_delta_ewma_us}" "${ul_owd_delta_us}" "${compensated_ul_delay_thr_us}" "${sum_dl_delays}" "${sum_ul_delays}" "${dl_load_condition}" "${ul_load_condition}" "${dl_shaper_rate_kbps}" "${ul_shaper_rate_kbps}" + log_msg "DATA" "${processing_stats}" + fi + + # 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 + + # stall handling procedure + # PIPESTATUS[0] == 142 corresponds with while loop timeout + # i.e. no reflector responses within ${stall_detection_thr} * ${ping_response_interval_us} + if (( PIPESTATUS[0] == 142 )); then + + log_msg "DEBUG" "Warning: no reflector response within: ${stall_detection_timeout_s} seconds. Checking for loads." + + get_loads + + log_msg "DEBUG" "load check is: ((${dl_achieved_rate_kbps} kbps > ${connection_stall_thr_kbps} kbps && ${ul_achieved_rate_kbps} kbps > ${connection_stall_thr_kbps} kbps))" + + # non-zero load so despite no reflector response within stall interval, the connection not considered to have stalled + # and therefore resume normal operation + if (( dl_achieved_rate_kbps > connection_stall_thr_kbps && ul_achieved_rate_kbps > connection_stall_thr_kbps )); then + + log_msg "DEBUG" "load above connection stall threshold so resuming normal operation." + continue + + fi + + log_msg "DEBUG" "Warning: connection stall detection. Waiting for new ping or increased load" + + # save intial global reflector timestamp to check against for any new reflector response + concurrent_read_integer initial_reflectors_last_timestamp_us "${run_path}/reflectors_last_timestamp_us" + + # update maintain_pingers state + printf "PAUSED" > ${run_path}/maintain_pingers_next_state + proc_man_signal maintain_pingers "USR1" + + t_connection_stall_time_us=${EPOCHREALTIME/./} + + global_ping_response_timeout=0 + + # wait until load resumes or ping response received (or global reflector response timeout) + while true + do + t_start_us=${EPOCHREALTIME/./} + + concurrent_read_integer new_reflectors_last_timestamp_us "${run_path}/reflectors_last_timestamp_us" + get_loads + + # shellcheck disable=SC2154 + if (( new_reflectors_last_timestamp_us != initial_reflectors_last_timestamp_us || ( dl_achieved_rate_kbps > connection_stall_thr_kbps && ul_achieved_rate_kbps > connection_stall_thr_kbps) )); then + + log_msg "DEBUG" "Connection stall ended. Resuming normal operation." + + # update maintain_pingers state + printf "RUNNING" > ${run_path}/maintain_pingers_next_state + proc_man_signal maintain_pingers "USR1" + + # continue main loop (i.e. skip idle/global timeout handling below) + continue 2 + fi + + sleep_remaining_tick_time "${t_start_us}" "${reflector_ping_interval_us}" + + if (( global_ping_response_timeout==0 && t_start_us > (t_connection_stall_time_us + global_ping_response_timeout_us - stall_detection_timeout_us) )); then + log_msg "SYSLOG" "Warning: Configured global ping response timeout: ${global_ping_response_timeout_s} seconds exceeded." + ((min_shaper_rates_enforcement)) && set_min_shaper_rates + global_ping_response_timeout=1 + fi + done + + else + log_msg "DEBUG" "Connection idle. Waiting for minimum load." + ((min_shaper_rates_enforcement)) && set_min_shaper_rates + fi + + # update maintain_pingers state + printf "STOP" > ${run_path}/maintain_pingers_next_state + proc_man_signal maintain_pingers "USR1" + + # reset idle timer + t_sustained_connection_idle_us=0 + + # wait until load increases again + while true + do + t_start_us=${EPOCHREALTIME/./} + get_loads + + if (( dl_achieved_rate_kbps > connection_active_thr_kbps || ul_achieved_rate_kbps > connection_active_thr_kbps )); then + log_msg "DEBUG" "dl achieved rate: ${dl_achieved_rate_kbps} kbps or ul achieved rate: ${ul_achieved_rate_kbps} kbps exceeded connection active threshold: ${connection_active_thr_kbps} kbps. Resuming normal operation." + break + fi + sleep_remaining_tick_time "${t_start_us}" "${reflector_ping_interval_us}" + done + + # update maintain_pingers state + printf "START" > ${run_path}/maintain_pingers_next_state + proc_man_signal maintain_pingers "USR1" + + t_end_us=${EPOCHREALTIME/./} +done diff --git a/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_defaults.sh b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_defaults.sh new file mode 100755 index 000000000..e6f290d82 --- /dev/null +++ b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_defaults.sh @@ -0,0 +1,209 @@ +#!/bin/bash + +# cake-autorate automatically adjusts bandwidth for CAKE in dependence on detected load and RTT + +# cake-autorate_config.sh is a script that sets up defaults for cake-autorate + +# Author: @Lynx (OpenWrt forum) +# Inspiration taken from: @moeller0 (OpenWrt forum) + +cake_autorate_version="2.0.0" + +# *** OUTPUT AND LOGGING OPTIONS *** + +output_processing_stats=1 # enable (1) or disable (0) output monitoring lines showing processing stats +output_load_stats=1 # enable (1) or disable (0) output monitoring lines showing achieved loads +output_reflector_stats=1 # enable (1) or disable (0) output monitoring lines showing reflector stats +output_cake_changes=0 # enable (1) or disable (0) output monitoring lines showing cake bandwidth changes +debug=1 # enable (1) or disable (0) out of debug lines + +# This can generate a LOT of records so be careful: +log_DEBUG_messages_to_syslog=0 # enable (1) or disable (0) logging of all DEBUG records into the system log. + +# ** Take care with these settings to ensure you won't run into OOM issues on your router *** +# every write the cumulative write time and bytes associated with each log line are checked +# and if either exceeds the configured values below, the log log file is rotated +log_to_file=1 # enable (1) or disable (0) output logging to file (/tmp/cake-autorate.log) +log_file_max_time_mins=10 # maximum time between log file rotations +log_file_max_size_KB=2000 # maximum KB (i.e. bytes/1024) worth of log lines between log file rotations + +# log file path defaults to /var/log/ +# or, if set below, then ${log_file_path_override} +log_file_path_override="" + +# *** STANDARD CONFIGURATION OPTIONS *** + +### For multihomed setups, it is the responsibility of the user to ensure that the probes +### sent by this instance of cake-autorate actually travel through these interfaces. +### See ping_extra_args and ping_prefix_string + +dl_if=ifb-wan # download interface +ul_if=wan # upload interface + +# pinger selection can be any of: +# fping - round robin pinging (rtts) +# ping - (iputils-ping) individual pinging (rtts) +# hping3 - individidual pinging (owds) +pinger_binary=ping + +# 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" # Cloudflare +"8.8.8.8" "8.8.4.4" # Google +"9.9.9.9" "9.9.9.10" "9.9.9.11" # Quad9 +"94.140.14.15" "94.140.14.140" "94.140.14.141" "94.140.15.15" "94.140.15.16" # AdGuard +"64.6.65.6" "156.154.70.1" "156.154.70.2" "156.154.70.3" "156.154.70.4" "156.154.70.5" "156.154.71.1" "156.154.71.2" "156.154.71.3" "156.154.71.4" "156.154.71.5" # Neustar +"208.67.220.2" "208.67.220.123" "208.67.220.220" "208.67.222.2" "208.67.222.123" # OpenDNS +"185.228.168.9" "185.228.168.10" # CleanBrowsing +) + +randomize_reflectors=1 # enable (1) or disable (0) randomization of reflectors on startup + +# Think carefully about the following settings +# to avoid excessive CPU use (proportional with ping interval / number of pingers) +# and to avoid abusive network activity (excessive ICMP frequency to one reflector) +# The author has found an ICMP rate of 1/(0.2/4) = 20 Hz to give satisfactory performance on 4G +no_pingers=6 # number of pingers to maintain +reflector_ping_interval_s=0.3 # (seconds, e.g. 0.2s or 2s) + +# delay threshold in ms is the extent of OWD increase to classify as a delay +# these are automatically adjusted based on maximum on the wire packet size +# (adjustment significant at sub 12Mbit/s rates, else negligible) +dl_delay_thr_ms=30 # (milliseconds) +ul_delay_thr_ms=30 # (milliseconds) + +# Set either of the below to 0 to adjust one direction only +# or alternatively set both to 0 to simply use cake-autorate to monitor a connection +adjust_dl_shaper_rate=1 # enable (1) or disable (0) actually changing the dl shaper rate +adjust_ul_shaper_rate=1 # enable (1) or disable (0) actually changing the ul shaper rate + +min_dl_shaper_rate_kbps=5000 # minimum bandwidth for download (Kbit/s) +base_dl_shaper_rate_kbps=20000 # steady state bandwidth for download (Kbit/s) +max_dl_shaper_rate_kbps=80000 # maximum bandwidth for download (Kbit/s) + +min_ul_shaper_rate_kbps=5000 # minimum bandwidth for upload (Kbit/s) +base_ul_shaper_rate_kbps=20000 # steady state bandwidth for upload (KBit/s) +max_ul_shaper_rate_kbps=35000 # maximum bandwidth for upload (Kbit/s) + +# 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 +connection_active_thr_kbps=1000 # threshold in Kbit/s below which dl/ul is considered idle +sustained_idle_sleep_thr_s=60.0 # time threshold to put pingers to sleep on sustained dl/ul achieved rate < idle_thr (seconds) + +min_shaper_rates_enforcement=0 # enable (1) or disable (0) dropping down to minimum shaper rates on connection idle or stall + +startup_wait_s=0.0 # number of seconds to wait on startup (e.g. to wait for things to settle on router reboot) + +# *** ADVANCED CONFIGURATION OPTIONS *** + +log_file_export_compress=1 # compress log file exports using gzip and append .gz to export filename + +### In multi-homed setups, it is mandatory to use either ping_extra_args +### or ping_prefix_string to direct the pings through $dl_if and $ul_if. +### No universal recommendation exists, because there are multiple +### policy-routing packages available (e.g. vpn-policy-routing and mwan3). +### Typically they either react to a firewall mark set on the pings, or +### provide a convenient wrapper. +### +### In a traditional single-homed setup, there is usually no need for these parameters. +### +### These arguments can also be used for any other purpose - e.g. for setting a +### particular QoS mark. + +# extra arguments for ping or fping +# e.g., here is how you can set the correct outgoing interface and +# the firewall mark for ping: +# ping_extra_args="-I wwan0 -m $((0x300))" +# Unfortunately, fping does not offer a command line switch to set +# the firewall mark. +# WARNING: no error checking so use at own risk! +ping_extra_args="" + +# a wrapper for ping binary - used as a prefix for the real command +# e.g., when using mwan3, it is recommended to set it like this: +# ping_prefix_string="mwan3 use gpon exec" +# WARNING: the wrapper must exec ping as the final step, not run it as a subprocess. +# Running ping or fping as a subprocess will lead to problems stopping it. +# WARNING: no error checking - so use at own risk! +ping_prefix_string="" + +# 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=200 # (milliseconds) + +# bufferbloat is detected when (bufferbloat_detection_thr) samples +# out of the last (bufferbloat detection window) samples are delayed +bufferbloat_detection_window=6 # number of samples to retain in detection window +bufferbloat_detection_thr=3 # number of delayed samples for bufferbloat detection + +# OWD 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 + +# OWD delta from baseline is tracked using ewma with alpha set below +alpha_delta_ewma=0.095 + +# 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 +achieved_rate_adjust_down_bufferbloat=0.9 # how rapidly to reduce achieved rate upon detection of bufferbloat +shaper_rate_adjust_down_bufferbloat=0.9 # how rapidly to reduce shaper rate upon detection of bufferbloat +shaper_rate_adjust_up_load_high=1.01 # how rapidly to increase shaper rate upon high load detected +shaper_rate_adjust_down_load_low=0.99 # how rapidly to return down to base shaper rate upon idle or low load detected +shaper_rate_adjust_up_load_low=1.01 # how rapidly to return up to base shaper rate upon idle or low load detected + +# the load is categoried as low if < high_load_thr and high if > high_load_thr relative to the current shaper rate +high_load_thr=0.75 # % of currently set bandwidth for detecting high load + +# refractory periods between successive bufferbloat/decay rate changes +# the bufferbloat refractory period should be greater than the +# average time it would take to replace the bufferbloat +# detection window with new samples upon a bufferbloat event +bufferbloat_refractory_period_ms=300 # (milliseconds) +decay_refractory_period_ms=1000 # (milliseconds) + +# interval for checking reflector health +reflector_health_check_interval_s=1.0 # (seconds) +# deadline for reflector response not to be classified as an offence against reflector +reflector_response_deadline_s=1.0 # (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 + +reflector_replacement_interval_mins=60 # how often to replace a random reflector from the present list + +reflector_comparison_interval_mins=1 # how often to compare reflectors +reflector_owd_baseline_delta_thr_ms=10 # max increase from min baseline before reflector rotated +reflector_owd_delta_ewma_delta_thr_ms=10 # mac increase from min delta ewma before reflector rotated + +# stall is detected when the following two conditions are met: +# 1) no reflector responses within $stall_detection_thr*$ping_response_interval_us; and +# 2) either $rx_achieved_rate or $tx_achieved_rate < $connection_stall_thr +stall_detection_thr=5 +connection_stall_thr_kbps=10 + +global_ping_response_timeout_s=10.0 # timeout to set shaper rates to min on no ping response whatsoever (seconds) + +if_up_check_interval_s=10.0 # time to wait before re-checking if rx/tx bytes files exist (e.g. from boot state or sleep recovery) + +# Starlink satellite switch (sss) compensation options +sss_compensation=0 # enable (1) or disable (0) Starlink handling +# satellite switch compensation start times in seconds of each minute +sss_times_s=("12.0" "27.0" "42.0" "57.0") +sss_compensation_pre_duration_ms=300 +sss_compensation_post_duration_ms=200 diff --git a/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_launcher.sh b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_launcher.sh new file mode 100755 index 000000000..f708f328a --- /dev/null +++ b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_launcher.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +cake_instances=(/usr/share/sqm-autorate/cake-autorate_config*sh) +cake_instance_pids=() + +trap kill_cake_instances INT TERM EXIT + +kill_cake_instances() +{ + trap - INT TERM EXIT + + echo "Killing all instances of cake one-by-one now." + + for ((cake_instance=0; cake_instance<${#cake_instances[@]}; cake_instance++)) + do + kill "${cake_instance_pids[${cake_instance}]}" 2>/dev/null || true + done + wait +} + +for cake_instance in "${cake_instances[@]}" +do + /usr/share/sqm-autorate/cake-autorate.sh "${cake_instance}" & + cake_instance_pids+=(${!}) +done +wait diff --git a/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_lib.sh b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_lib.sh new file mode 100755 index 000000000..413d191c0 --- /dev/null +++ b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_lib.sh @@ -0,0 +1,257 @@ +#!/bin/bash +# cake-autorate_lib.sh -- common functions for use by cake-autorate.sh +# This file is part of cake-autorate. + +__set_e=0 +if [[ ! ${-} =~ e ]]; then + set -e + __set_e=1 +fi + +exec {__sleep_fd}<> <(:) || true + +sleep_s() +{ + # Calling the external sleep binary could be rather slow, + # especially as it is called very frequently and typically on mediocre hardware. + # + # bash's loadable sleep module is not typically available + # in OpenWRT and most embedded systems, and use of the bash + # read command with a timeout offers performance that is + # at least on a par with bash's sleep module. + # + # For benchmarks, check the following links: + # - https://github.com/lynxthecat/cake-autorate/issues/174#issuecomment-1460057382 + # - https://github.com/lynxthecat/cake-autorate/issues/174#issuecomment-1460074498 + + local sleep_duration_s=${1} # (seconds, e.g. 0.5, 1 or 1.5) + read -r -t "${sleep_duration_s}" -u "${__sleep_fd}" || : & + wait "${!}" +} + +sleep_us() +{ + local sleep_duration_us=${1} # (microseconds) + + sleep_duration_s=000000${sleep_duration_us} + sleep_duration_s=$((10#${sleep_duration_s::-6})).${sleep_duration_s: -6} + sleep_s "${sleep_duration_s}" +} + +sleep_remaining_tick_time() +{ + # sleeps until the end of the tick duration + + local t_start_us=${1} # (microseconds) + local tick_duration_us=${2} # (microseconds) + + # shellcheck disable=SC2154 + sleep_duration_us=$(( t_start_us + tick_duration_us - ${EPOCHREALTIME/./} )) + + if (( sleep_duration_us > 0 )); then + sleep_us "${sleep_duration_us}" + fi +} + +randomize_array() +{ + local -n array=${1} + + subset=("${array[@]}") + array=() + for ((set=${#subset[@]}; set>0; set--)) + do + idx=$((RANDOM%set)) + array+=("${subset[idx]}") + unset "subset[idx]" + subset=("${subset[@]}") + done +} + +lock() +{ + local path=${1} + + while true; do + ( set -o noclobber; echo "$$" > "${path:?}" ) 2> /dev/null && return 0 + sleep_us 100000 + done +} + +unlock() +{ + local path=${1} + + rm -f "${path:?}" +} + +_proc_man_set_key() +{ + local key=${1} + local value=${2} + + lock "${PROC_STATE_FILE_LOCK:?}" + trap 'unlock "${PROC_STATE_FILE_LOCK:?}"' RETURN + + local entered=0 + while read -r line; do + if [[ ${line} =~ ^${key}= ]]; then + printf '%s\n' "${key}=${value}" + entered=1 + else + printf '%s\n' "${line}" + fi + done < "${PROC_STATE_FILE:?}" > "${PROC_STATE_FILE:?}.tmp" + if (( entered == 0 )); then + printf '%s\n' "${key}=${value}" >> "${PROC_STATE_FILE:?}.tmp" + fi + mv "${PROC_STATE_FILE:?}.tmp" "${PROC_STATE_FILE:?}" + return 0 +} + +_proc_man_get_key_value() +{ + local key=${1} + + lock "${PROC_STATE_FILE_LOCK:?}" + trap 'unlock "${PROC_STATE_FILE_LOCK:?}"' RETURN + + while read -r line; do + if [[ ${line} =~ ^${key}= ]]; then + printf '%s\n' "${line#*=}" + return 0 + fi + done < "${PROC_STATE_FILE:?}" + return 1 +} + +proc_man() +{ + local action=${1} + local name=${2} + shift 2 + + if [[ ! -f "${PROC_STATE_FILE:?}" ]]; then + return 1 + fi + + # shellcheck disable=SC2311 + case "${action}" in + "start") + pid=$(_proc_man_get_key_value "${name}") + if (( pid && pid > 0 )) && kill -0 "${pid}" 2> /dev/null; then + return 1; + fi + + "${@}" & + local pid=${!} + _proc_man_set_key "${name}" "${pid}" + ;; + "stop") + local pid + pid=$(_proc_man_get_key_value "${name}") + if ! (( pid && pid > 0 )); then + return 0; + fi + + kill "${pid}" 2> /dev/null || true + + # wait for process to die + killed=0 + for ((i=0; i<10; i++)); + do + if kill -0 "${pid}" 2> /dev/null; then + sleep_us 100000 + else + killed=1 + break + fi + done + + # if process still alive, kill it with fire + if (( killed == 0 )); then + kill -9 "${pid}" 2> /dev/null || true + fi + + _proc_man_set_key "${name}" "-1" "${PROC_STATE_FILE:?}" + ;; + "status") + local pid + pid=$(_proc_man_get_key_value "${name}") + if (( pid && pid > 0 )); then + if kill -0 "${pid}" 2> /dev/null; then + printf '%s\n' "running" + else + printf '%s\n' "dead" + fi + else + printf '%s\n' "stopped" + fi + ;; + "wait") + local pid + pid=$(_proc_man_get_key_value "${name}") + if (( pid && pid > 0 )); then + wait "${pid}" && return 0 + fi + + return 1 + ;; + "signal") + shift 3 + + local pid + pid=$(_proc_man_get_key_value "${name}") + if (( pid && pid > 0 )); then + kill -s "${1}" "${pid}" 2>/dev/null && return 0 + fi + + return 1 + ;; + "initialize") + proc_man_initialize + return $? + ;; + *) + printf '%s\n' "unknown action: ${action}" >&2 + return 1 + ;; + esac + + return 0 +} + +proc_man_initialize() +{ + true > "${PROC_STATE_FILE:?}" +} + +proc_man_start() +{ + proc_man start "${@}" +} + +proc_man_stop() +{ + proc_man stop "${@}" +} + +proc_man_status() +{ + proc_man status "${@}" +} + +proc_man_wait() +{ + proc_man wait "${@}" +} + +proc_man_signal() +{ + proc_man signal "${@}" +} + +if (( __set_e == 1 )); then + set +e +fi +unset __set_e diff --git a/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_template.sh b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_template.sh new file mode 100755 index 000000000..5d3195dff --- /dev/null +++ b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/cake-autorate_template.sh @@ -0,0 +1,213 @@ +#!/bin/bash + +# cake-autorate automatically adjusts bandwidth for CAKE in dependence on detected load and RTT + +# cake-autorate_config.sh is a script that sets up defaults for cake-autorate + +# Author: @Lynx (OpenWrt forum) +# Inspiration taken from: @moeller0 (OpenWrt forum) + +INTERFACE=$(basename "$0" | cut -d. -f2) + +cake_autorate_version="2.0.0" + +# *** OUTPUT AND LOGGING OPTIONS *** + +output_processing_stats=$(uci -q get sqm.${INTERFACE}.output_processing_stats || echo '0') # enable (1) or disable (0) output monitoring lines showing processing stats +output_load_stats=$(uci -q get sqm.${INTERFACE}.output_load_stats || echo '0') # enable (1) or disable (0) output monitoring lines showing achieved loads +output_reflector_stats=$(uci -q get sqm.${INTERFACE}.output_reflector_stats || echo '0') # enable (1) or disable (0) output monitoring lines showing reflector stats +output_cake_changes=$(uci -q get sqm.${INTERFACE}.output_cake_changes || echo '0') # enable (1) or disable (0) output monitoring lines showing cake bandwidth changes +debug=$(uci -q get sqm.${INTERFACE}.debug || echo '0') # enable (1) or disable (0) out of debug lines + +# This can generate a LOT of records so be careful: +log_DEBUG_messages_to_syslog=0 # enable (1) or disable (0) logging of all DEBUG records into the system log. + +# ** Take care with these settings to ensure you won't run into OOM issues on your router *** +# every write the cumulative write time and bytes associated with each log line are checked +# and if either exceeds the configured values below, the log log file is rotated +log_to_file=1 # enable (1) or disable (0) output logging to file (/tmp/cake-autorate.log) +log_file_max_time_mins=10 # maximum time between log file rotations +log_file_max_size_KB=2000 # maximum KB (i.e. bytes/1024) worth of log lines between log file rotations + +# log file path defaults to /var/log/ +# or, if set below, then ${log_file_path_override} +log_file_path_override="" + +# *** STANDARD CONFIGURATION OPTIONS *** + +### For multihomed setups, it is the responsibility of the user to ensure that the probes +### sent by this instance of cake-autorate actually travel through these interfaces. +### See ping_extra_args and ping_prefix_string + +dl_if=ifb4$(uci -q get sqm.${INTERFACE}.interface) # download interface +ul_if=$(uci -q get sqm.${INTERFACE}.interface) # upload interface + +# pinger selection can be any of: +# fping - round robin pinging (rtts) +# ping - (iputils-ping) individual pinging (rtts) +# hping3 - individidual pinging (owds) +pinger_binary=ping + +# 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=($(uci -q get omr-tracker.defaults.hosts)) +#reflectors=( +#"1.1.1.1" "1.0.0.1" # Cloudflare +#"8.8.8.8" "8.8.4.4" # Google +#"9.9.9.9" "9.9.9.10" "9.9.9.11" # Quad9 +#"94.140.14.15" "94.140.14.140" "94.140.14.141" "94.140.15.15" "94.140.15.16" # AdGuard +#"64.6.65.6" "156.154.70.1" "156.154.70.2" "156.154.70.3" "156.154.70.4" "156.154.70.5" "156.154.71.1" "156.154.71.2" "156.154.71.3" "156.154.71.4" "156.154.71.5" # Neustar +#"208.67.220.2" "208.67.220.123" "208.67.220.220" "208.67.222.2" "208.67.222.123" # OpenDNS +#"185.228.168.9" "185.228.168.10" # CleanBrowsing +#) + +randomize_reflectors=1 # enable (1) or disable (0) randomization of reflectors on startup + +# Think carefully about the following settings +# to avoid excessive CPU use (proportional with ping interval / number of pingers) +# and to avoid abusive network activity (excessive ICMP frequency to one reflector) +# The author has found an ICMP rate of 1/(0.2/4) = 20 Hz to give satisfactory performance on 4G +no_pingers=$(uci -q get sqm.${INTERFACE}.no_pingers || echo "6") # number of pingers to maintain +reflector_ping_interval_s=$(uci -q get sqm.${INTERFACE}.reflector_ping_interval_s || echo "0.3") # (seconds, e.g. 0.2s or 2s) + +# delay threshold in ms is the extent of OWD increase to classify as a delay +# these are automatically adjusted based on maximum on the wire packet size +# (adjustment significant at sub 12Mbit/s rates, else negligible) +dl_delay_thr_ms=$(uci -q get sqm.${INTERFACE}.delay_thr_ms || echo "30") # (milliseconds) +ul_delay_thr_ms=$(uci -q get sqm.${INTERFACE}.delay_thr_ms || echo "30") # (milliseconds) + +# Set either of the below to 0 to adjust one direction only +# or alternatively set both to 0 to simply use cake-autorate to monitor a connection +adjust_dl_shaper_rate=1 # enable (1) or disable (0) actually changing the dl shaper rate +adjust_ul_shaper_rate=1 # enable (1) or disable (0) actually changing the ul shaper rate + +min_dl_shaper_rate_kbps=$(uci -q get sqm.${INTERFACE}.min_download || echo "5000") # minimum bandwidth for download (Kbit/s) +base_dl_shaper_rate_kbps=$(uci -q get sqm.${INTERFACE}.download || echo "40000") # steady state bandwidth for download (Kbit/s) +max_dl_shaper_rate_kbps=$(uci -q get sqm.${INTERFACE}.max_download || echo "80000") # maximum bandwidth for download (Kbit/s) + +min_ul_shaper_rate_kbps=$(uci -q get sqm.${INTERFACE}.min_upload || echo "5000") # minimum bandwidth for upload (Kbit/s) +base_ul_shaper_rate_kbps=$(uci -q get sqm.${INTERFACE}.upload || echo "20000") # steady state bandwidth for upload (KBit/s) +max_ul_shaper_rate_kbps=$(uci -q get sqm.${INTERFACE}.max_upload || echo "35000") # maximum bandwidth for upload (Kbit/s) + +# sleep functionality saves unecessary pings and CPU cycles by +# pausing all active pingers when connection is not in active use +enable_sleep_function=$(uci -q get sqm.${INTERFACE}.enable_sleep_functions || echo "1") # enable (1) or disable (0) sleep functonality +connection_active_thr_kbps=$(uci -q get sqm.${INTERFACE}.connection_active_thr_kpbs || echo "1000") # threshold in Kbit/s below which dl/ul is considered idle +sustained_idle_sleep_thr_s=$(uci -q get sqm.${INTERFACE}.sustained_idle_sleep_thr || echo "60.0") # time threshold to put pingers to sleep on sustained dl/ul achieved rate < idle_thr (seconds) + +min_shaper_rates_enforcement=$(uci -q get sqm.${INTERFACE}.min_shaper_rates_enforcement || echo "0") # enable (1) or disable (0) dropping down to minimum shaper rates on connection idle or stall + +startup_wait_s=$(uci -q get sqm.${INTERFACE}.startup_wait_s || echo "60.0") # number of seconds to wait on startup (e.g. to wait for things to settle on router reboot) + +# *** ADVANCED CONFIGURATION OPTIONS *** + +log_file_export_compress=1 # compress log file exports using gzip and append .gz to export filename + +### In multi-homed setups, it is mandatory to use either ping_extra_args +### or ping_prefix_string to direct the pings through $dl_if and $ul_if. +### No universal recommendation exists, because there are multiple +### policy-routing packages available (e.g. vpn-policy-routing and mwan3). +### Typically they either react to a firewall mark set on the pings, or +### provide a convenient wrapper. +### +### In a traditional single-homed setup, there is usually no need for these parameters. +### +### These arguments can also be used for any other purpose - e.g. for setting a +### particular QoS mark. + +# extra arguments for ping or fping +# e.g., here is how you can set the correct outgoing interface and +# the firewall mark for ping: +# ping_extra_args="-I wwan0 -m $((0x300))" +# Unfortunately, fping does not offer a command line switch to set +# the firewall mark. +# WARNING: no error checking so use at own risk! +ping_extra_args="- B -I ${INTERFACE}" + +# a wrapper for ping binary - used as a prefix for the real command +# e.g., when using mwan3, it is recommended to set it like this: +# ping_prefix_string="mwan3 use gpon exec" +# WARNING: the wrapper must exec ping as the final step, not run it as a subprocess. +# Running ping or fping as a subprocess will lead to problems stopping it. +# WARNING: no error checking - so use at own risk! +ping_prefix_string="" +# 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=200 # (milliseconds) + +# bufferbloat is detected when (bufferbloat_detection_thr) samples +# out of the last (bufferbloat detection window) samples are delayed +bufferbloat_detection_window=6 # number of samples to retain in detection window +bufferbloat_detection_thr=3 # number of delayed samples for bufferbloat detection + +# OWD 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 + +# OWD delta from baseline is tracked using ewma with alpha set below +alpha_delta_ewma=0.095 + +# 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 +achieved_rate_adjust_down_bufferbloat=0.9 # how rapidly to reduce achieved rate upon detection of bufferbloat +shaper_rate_adjust_down_bufferbloat=0.9 # how rapidly to reduce shaper rate upon detection of bufferbloat +shaper_rate_adjust_up_load_high=1.01 # how rapidly to increase shaper rate upon high load detected +shaper_rate_adjust_down_load_low=0.99 # how rapidly to return down to base shaper rate upon idle or low load detected +shaper_rate_adjust_up_load_low=1.01 # how rapidly to return up to base shaper rate upon idle or low load detected + +# the load is categoried as low if < high_load_thr and high if > high_load_thr relative to the current shaper rate +high_load_thr=0.75 # % of currently set bandwidth for detecting high load + +# refractory periods between successive bufferbloat/decay rate changes +# the bufferbloat refractory period should be greater than the +# average time it would take to replace the bufferbloat +# detection window with new samples upon a bufferbloat event +bufferbloat_refractory_period_ms=300 # (milliseconds) +decay_refractory_period_ms=1000 # (milliseconds) + +# interval for checking reflector health +reflector_health_check_interval_s=1.0 # (seconds) +# deadline for reflector response not to be classified as an offence against reflector +reflector_response_deadline_s=1.0 # (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 + +reflector_replacement_interval_mins=60 # how often to replace a random reflector from the present list + +reflector_comparison_interval_mins=1 # how often to compare reflectors +reflector_owd_baseline_delta_thr_ms=10 # max increase from min baseline before reflector rotated +reflector_owd_delta_ewma_delta_thr_ms=10 # mac increase from min delta ewma before reflector rotated + +# stall is detected when the following two conditions are met: +# 1) no reflector responses within $stall_detection_thr*$ping_response_interval_us; and +# 2) either $rx_achieved_rate or $tx_achieved_rate < $connection_stall_thr +stall_detection_thr=5 +connection_stall_thr_kbps=10 + +global_ping_response_timeout_s=10.0 # timeout to set shaper rates to min on no ping response whatsoever (seconds) + +if_up_check_interval_s=10.0 # time to wait before re-checking if rx/tx bytes files exist (e.g. from boot state or sleep recovery) + +# Starlink satellite switch (sss) compensation options +sss_compensation=$(uci -q get sqm.${INTERFACE}.sss_compensation || echo "0") # enable (1) or disable (0) Starlink handling +# satellite switch compensation start times in seconds of each minute +sss_times_s=("12.0" "27.0" "42.0" "57.0") +sss_compensation_pre_duration_ms=300 +sss_compensation_post_duration_ms=200 + +config_file_check="cake-autorate" \ No newline at end of file diff --git a/luci-app-sqm-autorate/root/usr/share/sqm-autorate/config.sh b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/config.sh old mode 100644 new mode 100755 index 7d0948f2a..509dee635 --- a/luci-app-sqm-autorate/root/usr/share/sqm-autorate/config.sh +++ b/luci-app-sqm-autorate/root/usr/share/sqm-autorate/config.sh @@ -19,7 +19,8 @@ output_processing_stats=$(uci -q get sqm.${INTERFACE}.output_processing_stats || #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") +#debug=$(uci -q get sqm.common.debug || echo "0") +debug=1 # *** STANDARD CONFIGURATION OPTIONS *** @@ -128,32 +129,3 @@ global_ping_response_timeout_s=10 # timeout to set shaper rates to min on no pin 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 diff --git a/mptcp/files/usr/share/omr/post-tracking.d/post-tracking b/mptcp/files/usr/share/omr/post-tracking.d/post-tracking index 79ea2fcb9..506828a73 100755 --- a/mptcp/files/usr/share/omr/post-tracking.d/post-tracking +++ b/mptcp/files/usr/share/omr/post-tracking.d/post-tracking @@ -1010,6 +1010,7 @@ if [ "$OMR_TRACKER_INTERFACE" = "glorytun" ] || [ "$OMR_TRACKER_INTERFACE" = "om uci -q set openmptcprouter.$OMR_TRACKER_INTERFACE.latency="$OMR_TRACKER_LATENCY" #if [ "$(uci -q get glorytun.vpn.enable)" != "1" ] || [ "$(uci -q get glorytun-udp.vpn.enable)" != "1" ]; then OMR_NETWORK_DEVICE=$(find_network_device ${OMR_TRACKER_INTERFACE}) + #_log "OMR_NETWORK_DEVICE: $OMR_NETWORK_DEVICE for $OMR_TRACKER_DEVICE - $OMR_TRACKER_INTERFACE" if [ -n "$OMR_NETWORK_DEVICE" ] && [ -n "$(uci -q get network.$OMR_NETWORK_DEVICE.mtu)" ] && [ -n "$OMR_TRACKER_DEVICE" ]; then mtu=$(uci -q get network.$OMR_NETWORK_DEVICE.mtu) uci -q set openmptcprouter.${OMR_TRACKER_INTERFACE}.mtu=$mtu @@ -1751,7 +1752,7 @@ if [ "$(pgrep -f openmptcprouter-vps)" = "" ] && ([ "$(uci -q show openmptcprout sleep 5 fi -if [ "$(uci -q get sqm.${OMR_TRACKER_INTERFACE}.enabled)" = "1" ] && [ "$(uci -q get sqm.${OMR_TRACKER_INTERFACE}.autorate)" = "1" ] && [ -z $(pgrep -f "autorate.sh ${OMR_TRACKER_INTERFACE}") ]; then +if [ "$(uci -q get sqm.${OMR_TRACKER_INTERFACE}.enabled)" = "1" ] && [ "$(uci -q get sqm.${OMR_TRACKER_INTERFACE}.autorate)" = "1" ] && [ -n "$OMR_TRACKER_DEVICE" ] && [ -z $(pgrep -f "autorate.*${OMR_TRACKER_DEVICE}") ]; then /etc/init.d/sqm-autorate restart >/dev/null 2>&1 sleep 5 fi