Updated: to v0.5 (11/01/18)
I've implemented Active Congestion Control, based on the info from https://www.gargoyle-router.com/wiki/doku.php?id=qos. I'm using it for my wan connections.
Usage:
* meant to control buffering at ISP
* needs QoS on upload to be implemented for reliable detection
* will measure latency for ping (to selected target, over selected interface "intfP"), and depending on outcome adjust download rate
* outcome can be (timings can be changed in script):
** response < 25ms -> increase rate by 1/10 of range (max rate - min rate)
** 25 <= response < xxx -> decrease rate by "floor(response / 50) * 1/10 of range"
** loss of response => response time will default to 100ms
* expects a queue tree on egress route with a max-limit set, interface "intfQ"
* should be scheduled with interval execution for continuous monitoring
* logging is reduced to minimal, but can be enabled at will (#:log ... -> :log ...)
I thought it might be useful to others.
First time actions:
* import functions, can be done as part of script run on startup
* enable some logs to verify correct functioning (and reimport)
* execute command from terminal
* validate log entries
* disable unwanted logs, reimport & define scheduled task
Note: any changes to functions (in a script) must be followed by execution of that script to reimport updated code
To invoke:
$applyACC target=8.8.8.8 intfP=ether1 minRt=120 maxRt=190 intfQ=ether5
Example scheduler entry:
:global applyACC do={
# v0.4
# variables set:
# target ping target
# intfP ping interface
# minRt minimal rate, in Megabits
# maxRt maximal rate, in Megabits
# intfQ queue interface
#:log info "ACC: target: $target, intfP: $intfP, minRt: $minRt, maxRt: $maxRt, intfQ: $intfQ"
:local adj 0
:local pcount 1
# safe timeout for ping, if exceeded will be interpreted as packet loss
:local ptimeout .2
:local mil 1000000
:local rateStep (($maxRt-$minRt)/10)
:if ($rateStep = 0) do={
:log warn "Error: rate increment < 1, defaulting to 1"
:set rateStep 1;
}
#:log info "ACC: rate increment=$rateInc"
# Delay limits
# <25ms increment rate ceiling
:local incMs 25
# <50ms keep rate ceiling
:local keepMs 50
:if ($keepMs <= $incMs) do={
:log error "Error: timings incorrect, ensure $incMs < $keepMs"
:error "Error: timings incorrect"
}
:global getRTT;
:global setRate;
:local rtt [$getRTT target=$target intf=$intfP count=$pcount timeout=$ptimeout]
:if ([:typeof $rtt]="nil") do={
# :log info "Packet loss, defaulting to 100"
:set rtt 100
}
# FOR TESTING :set rtt 49
#:log info "ACC: rtt: $rtt"
:if ($rtt < $incMs) do={
# 1-step increment
:set adj 1
} else={
# n-step decrement
:set adj (-$rtt / $keepMs)
}
# :log info "ACC: rate adjustment $adj"
$setRate minRt=($minRt*$mil) maxRt=($maxRt*$mil) intf=$intfQ adjRt=($adj*$rateStep*$mil)
}
:global setRate do={
# variables set:
# minRt minimal rate
# adjRt adjustment to rate
# maxRt maximum
# intf interface
#:log info "setRate: minRt: $minRt, adjRt: $adjRt, maxRt: $maxRt, intf=$intf"
:local currRt [/queue tree get [find parent=$intf] max-limit ];
#:log info "setRate: current rate: $currRt"
:local nextRt ($currRt + $adjRt)
:if ($minRt > $nextRt) do={ :set nextRt $minRt}
:if ($maxRt < $nextRt) do={ :set nextRt $maxRt}
:if ($currRt != $nextRt) do={
:log info "setRate: adjusting $intf max-limit to $nextRt"
/queue tree set [find parent=$intf] max-limit=$nextRt;
}
}
:global getRTT do={
# variables set:
# target ping target
# intf ping interface
# count ping count
# timeout ping timeout
#:log info "getRTT: target: $target, intf: $intf, count: $count, timeout: $timeout"
:local tm [/system clock get time]
:set tm ([:pick $tm 0 2] . [:pick $tm 3 5] . [:pick $tm 6 8])
:local pgfl "ping-$target-$tm.txt"
:execute script="{ping count=$count interface=$intf interval=$timeout $target}" file=$pgfl
:delay ([:totime $timeout] * $count)
:do {
:delay .2
# :put "...ping wait"
} while=([:len [:file find name=$pgfl ]] = 0 || ([:find [:file get $pgfl contents] "sent="] < 0 ))
:local resp [:file get $pgfl contents]
:local rttpos [:find $resp "avg-rtt"]
:set resp [:pick $resp ($rttpos + 8) ($rttpos + 15)]
:set resp [:pick $resp 0 [:find $resp "ms"]]
/file remove $pgfl
:return [:tonum $resp]
}