I'm a T-Mobile 5G Home Internet user and cake-autorate has been extremely helpful in keeping my connection stable. I found though that the shaping was imperfect using ICMP, since we're just using RTT / 2 instead of OWD making it so, for example, if egress were completely saturated the ingress filter would "panic" down to its minimum shaper rate unnecessarily. I looked into using tsping but T-Mobile blocks ICMP type 13 which makes that a non-starter.
I then looked into the previous thread and found mentions of using NTP, and curiously enough irtt to get OWD data. irtt is easy enough to self-host on a VPS, it just hadn't been implemented, so I went ahead and did it.
cake-autorate.sh
@@ -494,6 +494,40 @@
case ${pinger_binary} in
+ irtt)
+ run_irtt_pinger_instance() {
+ local pinger=${1}
+ local reflector="${reflectors[pinger]}"
+
+ # This trap-less structure enables reliable termination as a service via a SIGPIPE cascade.
+ # An unbuffered pipeline (`stdbuf` for irtt, `fflush` in gawk) is essential for this mechanism.
+
+ local pipe_path="${run_path}/pinger_irtt_${pinger}_pipe"
+ rm -f "${pipe_path}"
+ mkfifo "${pipe_path}"
+
+ while true; do
+ local start_time=${EPOCHSECONDS}
+
+ stdbuf -oL irtt client -i "${reflector_ping_interval_s}s" -d 1h ${ping_extra_args} "${reflector}" 2>/dev/null | gawk -f "${SCRIPT_PREFIX}/irtt_parser.awk" > "${pipe_path}" &
+ local processor_pid=$!
+
+ while read -r dl_owd_us ul_owd_us seq; do
+ [[ -n "${seq}" ]] || continue
+ timestamp_us=${EPOCHREALTIME/.}
+ printf "%s %s %s %s %s\n" "${timestamp_us}" "${reflector}" "${seq}" "${dl_owd_us}" "${ul_owd_us}"
+ done < "${pipe_path}"
+
+ wait "${processor_pid}" 2>/dev/null
+ (( EPOCHSECONDS - start_time < 2 )) && sleep 1
+ done
+ }
+
+ sleep_until_next_pinger_time_slot "${pinger}"
+ run_irtt_pinger_instance "${pinger}" >&"${main_fd}" &
+ pinger_pids["${pinger}"]=${!}
+ proc_pids["irtt_${pinger}_pinger"]=${!}
+ ;;
tsping)
# accommodate present tsping interval/sleep handling to prevent ping flood with only one pinger
(( tsping_sleep_time = no_pingers == 1 ? ping_response_interval_ms : 0 ))
@@ -531,7 +565,7 @@
tsping|fping)
start_pinger 0
;;
- ping)
+ ping|irtt)
for ((pinger=0; pinger < no_pingers; pinger++))
do
start_pinger "${pinger}"
@@ -587,7 +621,7 @@
log_msg "DEBUG" "Killing ${pinger_binary} instance."
kill_pinger 0
;;
- ping)
+ ping|irtt)
for (( pinger=0; pinger < no_pingers; pinger++))
do
log_msg "DEBUG" "Killing pinger instance: ${pinger}"
@@ -1246,6 +1280,12 @@
*)
case "${pinger_binary}" in
+ irtt)
+ if ((${#command[@]} == 5))
+ then
+ timestamp=${command[0]} reflector=${command[1]} seq=${command[2]} dl_owd_us=${command[3]} ul_owd_us=${command[4]} reflector_response=1
+ fi
+ ;;
tsping)
if ((${#command[@]} == 10))
then
@@ -1281,6 +1321,40 @@
then
# parse pinger response according to pinger binary
case ${pinger_binary} in
+ irtt)
+ ((
+ dl_owd_delta_us=dl_owd_us - dl_owd_baselines_us[${reflector}],
+ ul_owd_delta_us=ul_owd_us - ul_owd_baselines_us[${reflector}]
+ ))
+
+ if (( (${dl_owd_delta_us#-} + ${ul_owd_delta_us#-}) < 3000000000 ))
+ then
+
+ ((
+ 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,
+
+ dl_owd_baselines_us[${reflector}]=(dl_alpha*dl_owd_us+(1000000-dl_alpha)*dl_owd_baselines_us[${reflector}])/1000000,
+ ul_owd_baselines_us[${reflector}]=(ul_alpha*ul_owd_us+(1000000-ul_alpha)*ul_owd_baselines_us[${reflector}])/1000000,
+
+ dl_owd_delta_us=dl_owd_us - dl_owd_baselines_us[${reflector}],
+ ul_owd_delta_us=ul_owd_us - ul_owd_baselines_us[${reflector}]
+ ))
+ else
+ dl_owd_baselines_us[${reflector}]=${dl_owd_us} ul_owd_baselines_us[${reflector}]=${ul_owd_us} dl_owd_delta_us=0 ul_owd_delta_us=0
+ fi
+
+ if (( load_percent[dl] < high_load_thr_percent && load_percent[ul] < high_load_thr_percent))
+ then
+ ((
+ dl_owd_delta_ewmas_us[${reflector}]=(alpha_delta_ewma*dl_owd_delta_us+(1000000-alpha_delta_ewma)*dl_owd_delta_ewmas_us[${reflector}])/1000000,
+ ul_owd_delta_ewmas_us[${reflector}]=(alpha_delta_ewma*ul_owd_delta_us+(1000000-alpha_delta_ewma)*ul_owd_delta_ewmas_us[${reflector}])/1000000
+ ))
+ fi
+
+ timestamp_us=${timestamp}
+
+ ;;
tsping)
dl_owd_us=${dl_owd_ms}000 ul_owd_us=${ul_owd_ms}000
irtt_parser.awk
# Input: seq=4 rtt=12.2ms rd=5.69ms sd=6.5ms ipdv=1.57ms
# Output: 5690 6500 4 (dl_owd_us ul_owd_us seq)
function to_us(val_str, mult) {
if (val_str ~ /^-/) return -1
if (val_str ~ /ms$/) mult = 1000
else if (val_str ~ /s$/) mult = 1000000
else if (val_str ~ /ns$/) mult = 0.001
else mult = 1
return int(val_str * mult)
}
BEGIN { FS = "[= ]+" }
/seq=/ && /rd=/ && /sd=/ {
for (i = 1; i < NF; i += 2) {
vars[$i] = $(i+1)
}
dl_owd = to_us(vars["rd"])
ul_owd = to_us(vars["sd"])
if (dl_owd >= 0 && ul_owd >= 0) {
printf("%d %d %d\n", dl_owd, ul_owd, vars["seq"])
fflush("")
}
}
Using this requires coreutils-stdbuf and gawk to be installed. I statically compiled irtt and placed its binary in /usr/bin on my router.
config.primary.sh would have overrides that look like:
pinger_binary='irtt'
no_pingers=2
reflector_ping_interval_s=0.1
reflectors=(
"x.x.x.x" "y.y.y.y"
)
or with one reflector:
pinger_binary='irtt'
no_pingers=1
reflector_ping_interval_s=0.05
reflectors=(
"x.x.x.x"
)
I want to button things up a bit more, and also get some feedback before seeing if this could or would be merged into the main project. The biggest problem with using irtt is that it's designed as a test-runner while cake-autorate needs a continuous flow of data. Working around this requires buffering line output and parsing it with awk instead of using its json output. We also need to set a reasonable test duration, like -d 1h, since irtt uses in-memory results storage meaning all results are kept, well, in-memory for the entire test duration. It's why we can't do something like -d 9999h because that would need an absurd amount of memory. And that's also why we need to re-spawn irtt if it closes "unexpectedly" (at the end of its test duration).
But in any case, the results are quite spectacular. The issue with the ingress shaper panicing to the minimum rate is completely gone, and my connection seems a bit more stable under load. I suppose the test results can speak for themselves.