Alan Jenkins noted a bug in the smq luci GUI that effectively erased several configuration paramters if two checkboxes were deselected. This behaviour seems consistent in luci but certainly has the potential to confuse users. While confusion can not really be avoided generally it seems wise to change the default interpretation for empty or non-existent itarget and etarget variables from the qdisc's default (5ms in the case of one of the codels) to automatic determination of tghis variable dependent on the configured bandwidth, as codels target variable should be large enough to contain at least one full packet. With this change sqm-scripts will do the right thing by default, but will yet allow the user to specify over-ridding values (as long as the user does not un-check the entry-field exposing check boxes). Survives light testing... This change set also changes the sqm-scripts luci gui to note the user of the change. For compatibility with existing setups sqm-scripts will still honor "auto" as an alternative explicit way of requesting automatic target selection. This might turn into a warning in the future and might be phased out... Signed-off-by: Sebastian Moeller <moeller0@gmx.de>
486 lines
14 KiB
Bash
486 lines
14 KiB
Bash
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License version 2 as
|
|
# published by the Free Software Foundation.
|
|
#
|
|
# Copyright (C) 2012-4 Michael D. Taht, Toke Høiland-Jørgensen, Sebastian Moeller
|
|
|
|
#improve the logread output
|
|
sqm_logger() {
|
|
logger -t SQM -s ${1}
|
|
}
|
|
|
|
insmod() {
|
|
lsmod | grep -q ^$1 || $INSMOD $1
|
|
}
|
|
|
|
ipt() {
|
|
d=`echo $* | sed s/-A/-D/g`
|
|
[ "$d" != "$*" ] && {
|
|
iptables $d > /dev/null 2>&1
|
|
ip6tables $d > /dev/null 2>&1
|
|
}
|
|
d=`echo $* | sed s/-I/-D/g`
|
|
[ "$d" != "$*" ] && {
|
|
iptables $d > /dev/null 2>&1
|
|
ip6tables $d > /dev/null 2>&1
|
|
}
|
|
iptables $* > /dev/null 2>&1
|
|
ip6tables $* > /dev/null 2>&1
|
|
}
|
|
|
|
do_modules() {
|
|
#sm TODO: check first whether the modules exist and only load then
|
|
insmod act_ipt
|
|
insmod sch_$QDISC
|
|
insmod sch_ingress
|
|
insmod act_mirred
|
|
insmod cls_fw
|
|
insmod sch_htb
|
|
}
|
|
|
|
|
|
# You need to jiggle these parameters. Note limits are tuned towards a <10Mbit uplink <60Mbup down
|
|
|
|
[ -z "$UPLINK" ] && UPLINK=2302
|
|
[ -z "$DOWNLINK" ] && DOWNLINK=14698
|
|
[ -z "$IFACE" ] && IFACE=ge00
|
|
[ -z "$QDISC" ] && QDISC=fq_codel
|
|
[ -z "$LLAM" ] && LLAM="tc_stab"
|
|
[ -z "$LINKLAYER" ] && LINKLAYER="none"
|
|
[ -z "$OVERHEAD" ] && OVERHEAD=0
|
|
[ -z "$STAB_MTU" ] && STAB_MTU=2047
|
|
[ -z "$STAB_MPU" ] && STAB_MPU=0
|
|
[ -z "$STAB_TSIZE" ] && STAB_TSIZE=512
|
|
[ -z "$AUTOFLOW" ] && AUTOFLOW=0
|
|
[ -z "$LIMIT" ] && LIMIT=1001 # sane global default for *LIMIT for fq_codel on a small memory device
|
|
[ -z "$ILIMIT" ] && ILIMIT=
|
|
[ -z "$ELIMIT" ] && ELIMIT=
|
|
[ -z "$ITARGET" ] && ITARGET=
|
|
[ -z "$ETARGET" ] && ETARGET=
|
|
[ -z "$IECN" ] && IECN="ECN"
|
|
[ -z "$EECN" ] && EECN="NOECN"
|
|
[ -z "$SQUASH_DSCP" ] && SQUASH_DSCP="1"
|
|
[ -z "$SQUASH_INGRESS" ] && SQUASH_INGRESS="1"
|
|
[ -z "$IQDISC_OPTS" ] && IQDISC_OPTS=""
|
|
[ -z "$EQDISC_OPTS" ] && EQDISC_OPTS=""
|
|
[ -z "$TC" ] && TC=`which tc`
|
|
#[ -z "$TC" ] && TC="sqm_logger tc"# this redirects all tc calls into the log
|
|
[ -z "$IP" ] && IP=$( which ip )
|
|
[ -z "$INSMOD" ] && INSMOD=`which insmod`
|
|
[ -z "$TARGET" ] && TARGET="5ms"
|
|
[ -z "$IPT_MASK" ] && IPT_MASK="0xff"
|
|
[ -z "$IPT_MASK_STRING" ] && IPT_MASK_STRING="/${IPT_MASK}" # for set-mark
|
|
|
|
#sqm_logger "${0} IPT_MASK: ${IPT_MASK_STRING}"
|
|
|
|
|
|
|
|
# find the ifb device associated with a specific interface, return nothing of no ifb is associated with IF
|
|
get_ifb_associated_with_if() {
|
|
CUR_IF=$1
|
|
# CUR_IFB=$( tc -p filter show parent ffff: dev ${CUR_IF} | grep -o -e ifb'[[:digit:]]\+' )
|
|
CUR_IFB=$( tc -p filter show parent ffff: dev ${CUR_IF} | grep -o -e ifb'[^)]\+' ) # my editor's syntax coloration is limitied so I need a single quote in this line (between eiditor and s)
|
|
sqm_logger "ifb associated with interface ${CUR_IF}: ${CUR_IFB}"
|
|
echo ${CUR_IFB}
|
|
}
|
|
|
|
# ATTENTION, IFB names can only be 15 chararcters, so we chop of excessive characters at the start of the interface name
|
|
# if required
|
|
create_new_ifb_for_if() {
|
|
CUR_IF=$1
|
|
MAX_IF_NAME_LENGTH=15
|
|
IFB_PREFIX="ifb4"
|
|
NEW_IFB="${IFB_PREFIX}${CUR_IF}"
|
|
IFB_NAME_LENGTH=${#NEW_IFB}
|
|
if [ ${IFB_NAME_LENGTH} -gt ${MAX_IF_NAME_LENGTH} ];
|
|
then
|
|
sqm_logger "The requsted IFB name ${NEW_IFB} is longer than the allowed 15 characters, trying to make it shorter"
|
|
OVERLIMIT=$(( ${#NEW_IFB} - ${MAX_IF_NAME_LENGTH} ))
|
|
NEW_IFB=${IFB_PREFIX}${CUR_IF:${OVERLIMIT}:$(( ${MAX_IF_NAME_LENGTH} - ${#IFB_PREFIX} ))}
|
|
fi
|
|
sqm_logger "trying to create new IFB: ${NEW_IFB}"
|
|
$IP link add name ${NEW_IFB} type ifb #>/dev/null 2>&1 # better be verbose
|
|
echo ${NEW_IFB}
|
|
}
|
|
|
|
# the best match is either the IFB already associated with the current interface or a new named IFB
|
|
get_ifb_for_if() {
|
|
CUR_IF=$1
|
|
# if an ifb is already associated return that
|
|
CUR_IFB=$( get_ifb_associated_with_if ${CUR_IF} )
|
|
[ -z "$CUR_IFB" ] && CUR_IFB=$( create_new_ifb_for_if ${CUR_IF} )
|
|
[ -z "$CUR_IFB" ] && sqm_logger "Could not find existing IFB for ${CUR_IF}, nor create a new IFB instead..."
|
|
echo ${CUR_IFB}
|
|
}
|
|
|
|
#sm: we need the functions above before trying to set the ingress IFB device
|
|
[ -z "$DEV" ] && DEV=$( get_ifb_for_if ${IFACE} ) # automagically get the right IFB device for the IFACE"
|
|
|
|
|
|
get_htb_adsll_string() {
|
|
ADSLL=""
|
|
if [ "$LLAM" = "htb_private" -a "$LINKLAYER" != "none" ];
|
|
then
|
|
# HTB defaults to MTU 1600 and an implicit fixed TSIZE of 256, but HTB as of around 3.10.0
|
|
# does not actually use a table in the kernel
|
|
ADSLL="mpu ${STAB_MPU} linklayer ${LINKLAYER} overhead ${OVERHEAD} mtu ${STAB_MTU}"
|
|
sqm_logger "ADSLL: ${ADSLL}"
|
|
fi
|
|
echo ${ADSLL}
|
|
}
|
|
|
|
get_stab_string() {
|
|
STABSTRING=""
|
|
if [ "${LLAM}" = "tc_stab" -a "$LINKLAYER" != "none" ];
|
|
then
|
|
STABSTRING="stab mtu ${STAB_MTU} tsize ${STAB_TSIZE} mpu ${STAB_MPU} overhead ${OVERHEAD} linklayer ${LINKLAYER}"
|
|
sqm_logger "STAB: ${STABSTRING}"
|
|
fi
|
|
echo ${STABSTRING}
|
|
}
|
|
|
|
sqm_stop() {
|
|
$TC qdisc del dev $IFACE ingress
|
|
$TC qdisc del dev $IFACE root
|
|
$TC qdisc del dev $DEV root
|
|
}
|
|
|
|
# Note this has side effects on the prio variable
|
|
# and depends on the interface global too
|
|
|
|
fc() {
|
|
$TC filter add dev $interface protocol ip parent $1 prio $prio u32 match ip tos $2 0xfc classid $3
|
|
prio=$(($prio + 1))
|
|
$TC filter add dev $interface protocol ipv6 parent $1 prio $prio u32 match ip6 priority $2 0xfc classid $3
|
|
prio=$(($prio + 1))
|
|
}
|
|
|
|
fc_pppoe() {
|
|
PPPOE_SESSION_ETHERTYPE="0x8864"
|
|
PPPOE_DISCOVERY_ETHERTYPE="0x8863"
|
|
PPP_PROTO_IP4="0x0021"
|
|
PPP_PROTO_IP6="0x0057"
|
|
ARP_PROTO_IP4="0x0806"
|
|
$TC filter add dev $interface protocol ip parent $1 prio $prio u32 match ip tos $2 0xfc classid $3
|
|
$TC filter add dev $interface parent $1 protocol ${PPPOE_SESSION_ETHERTYPE} prio $(( 400 + ${prio} )) u32 \
|
|
match u16 ${PPP_PROTO_IP4} 0xffff at 6 \
|
|
match u8 $2 0xfc at 9 \
|
|
flowid $3
|
|
|
|
prio=$(($prio + 1))
|
|
$TC filter add dev $interface protocol ipv6 parent $1 prio $prio u32 match ip6 priority $2 0xfc classid $3
|
|
$TC filter add dev $interface parent $1 protocol ${PPPOE_SESSION_ETHERTYPE} prio $(( 600 + ${prio} )) u32 \
|
|
match u16 ${PPP_PROTO_IP6} 0xffff at 6 \
|
|
match u16 0x0${2:2:2}0 0x0fc0 at 8 \
|
|
flowid $3
|
|
|
|
|
|
|
|
|
|
prio=$(($prio + 1))
|
|
|
|
|
|
}
|
|
# FIXME: actually you need to get the underlying MTU on PPOE thing
|
|
|
|
get_mtu() {
|
|
BW=$2
|
|
F=`cat /sys/class/net/$1/mtu`
|
|
if [ -z "$F" ]
|
|
then
|
|
F=1500
|
|
fi
|
|
if [ $BW -gt 20000 ]
|
|
then
|
|
F=$(($F * 2))
|
|
fi
|
|
if [ $BW -gt 30000 ]
|
|
then
|
|
F=$(($F * 2))
|
|
fi
|
|
if [ $BW -gt 40000 ]
|
|
then
|
|
F=$(($F * 2))
|
|
fi
|
|
if [ $BW -gt 50000 ]
|
|
then
|
|
F=$(($F * 2))
|
|
fi
|
|
if [ $BW -gt 60000 ]
|
|
then
|
|
F=$(($F * 2))
|
|
fi
|
|
if [ $BW -gt 80000 ]
|
|
then
|
|
F=$(($F * 2))
|
|
fi
|
|
echo $F
|
|
}
|
|
|
|
# FIXME should also calculate the limit
|
|
# Frankly I think Xfq_codel can pretty much always run with high numbers of flows
|
|
# now that it does fate sharing
|
|
# But right now I'm trying to match the ns2 model behavior better
|
|
# So SET the autoflow variable to 1 if you want the cablelabs behavior
|
|
|
|
get_flows() {
|
|
if [ "$AUTOFLOW" -eq "1" ]
|
|
then
|
|
FLOWS=8
|
|
[ $1 -gt 999 ] && FLOWS=16
|
|
[ $1 -gt 2999 ] && FLOWS=32
|
|
[ $1 -gt 7999 ] && FLOWS=48
|
|
[ $1 -gt 9999 ] && FLOWS=64
|
|
[ $1 -gt 19999 ] && FLOWS=128
|
|
[ $1 -gt 39999 ] && FLOWS=256
|
|
[ $1 -gt 69999 ] && FLOWS=512
|
|
[ $1 -gt 99999 ] && FLOWS=1024
|
|
case $QDISC in
|
|
codel|ns2_codel|pie|*fifo|pfifo_fast) ;;
|
|
fq_codel|*fq_codel|sfq) echo flows $FLOWS ;;
|
|
esac
|
|
fi
|
|
}
|
|
|
|
# set the target parameter, also try to only take well formed inputs
|
|
# Note, the link bandwidth in the current direction (ingress or egress)
|
|
# is required to adjust the target for slow links
|
|
get_target() {
|
|
local CUR_TARGET=${1}
|
|
local CUR_LINK_KBPS=${2}
|
|
[ ! -z "$CUR_TARGET" ] && sqm_logger "cur_target: ${CUR_TARGET} cur_bandwidth: ${CUR_LINK_KBPS}"
|
|
CUR_TARGET_STRING=
|
|
# either e.g. 100ms or auto
|
|
CUR_TARGET_VALUE=$( echo ${CUR_TARGET} | grep -o -e \^'[[:digit:]]\+' )
|
|
CUR_TARGET_UNIT=$( echo ${CUR_TARGET} | grep -o -e '[[:alpha:]]\+'\$ )
|
|
#[ ! -z "$CUR_TARGET" ] && sqm_logger "CUR_TARGET_VALUE: $CUR_TARGET_VALUE"
|
|
#[ ! -z "$CUR_TARGET" ] && sqm_logger "CUR_TARGET_UNIT: $CUR_TARGET_UNIT"
|
|
|
|
AUTO_TARGET=
|
|
UNIT_VALID=
|
|
|
|
case $QDISC in
|
|
*codel|*pie)
|
|
if [ ! -z "${CUR_TARGET_VALUE}" -a ! -z "${CUR_TARGET_UNIT}" ];
|
|
then
|
|
case ${CUR_TARGET_UNIT} in
|
|
# permissible units taken from: tc_util.c get_time()
|
|
s|sec|secs|ms|msec|msecs|us|usec|usecs)
|
|
CUR_TARGET_STRING="target ${CUR_TARGET_VALUE}${CUR_TARGET_UNIT}"
|
|
UNIT_VALID="1"
|
|
;;
|
|
esac
|
|
fi
|
|
# empty field in GUI or undefined GUI variable now defaults to auto
|
|
if [ -z "${CUR_TARGET_VALUE}" -a -z "${CUR_TARGET_UNIT}" ];
|
|
then
|
|
if [ ! -z "${CUR_LINK_KBPS}" ];
|
|
then
|
|
TMP_TARGET_US=$( adapt_target_to_slow_link $CUR_LINK_KBPS )
|
|
TMP_INTERVAL_STRING=$( adapt_interval_to_slow_link $TMP_TARGET_US )
|
|
CUR_TARGET_STRING="target ${TMP_TARGET_US}us ${TMP_INTERVAL_STRING}"
|
|
AUTO_TARGET="1"
|
|
sqm_logger "get_target defaulting to auto."
|
|
else
|
|
sqm_logger "required link bandwidth in kbps not passed to get_target()."
|
|
fi
|
|
fi
|
|
# but still allow explicit use of the keyword auto for backward compatibility
|
|
case ${CUR_TARGET_UNIT} in
|
|
auto|Auto|AUTO)
|
|
if [ ! -z "${CUR_LINK_KBPS}" ];
|
|
then
|
|
TMP_TARGET_US=$( adapt_target_to_slow_link $CUR_LINK_KBPS )
|
|
TMP_INTERVAL_STRING=$( adapt_interval_to_slow_link $TMP_TARGET_US )
|
|
CUR_TARGET_STRING="target ${TMP_TARGET_US}us ${TMP_INTERVAL_STRING}"
|
|
AUTO_TARGET="1"
|
|
else
|
|
sqm_logger "required link bandwidth in kbps not passed to get_target()."
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
case ${CUR_TARGET_UNIT} in
|
|
default|Default|DEFAULT)
|
|
if [ ! -z "${CUR_LINK_KBPS}" ];
|
|
then
|
|
CUR_TARGET_STRING="" # return nothing so the default target is not over-ridden...
|
|
AUTO_TARGET="1"
|
|
#sqm_logger "get_target using qdisc default, no explicit target string passed."
|
|
else
|
|
sqm_logger "required link bandwidth in kbps not passed to get_target()."
|
|
fi
|
|
;;
|
|
esac
|
|
if [ ! -z "${CUR_TARGET}" ];
|
|
then
|
|
if [ -z "${CUR_TARGET_VALUE}" -o -z "${UNIT_VALID}" ];
|
|
then
|
|
[ -z "$AUTO_TARGET" ] && sqm_logger "${CUR_TARGET} is not a well formed tc target specifier; e.g.: 5ms (or s, us), or one of the strings auto or default."
|
|
fi
|
|
fi
|
|
;;
|
|
esac
|
|
# sqm_logger "target: ${CUR_TARGET_STRING}"
|
|
echo $CUR_TARGET_STRING
|
|
}
|
|
|
|
# for low bandwidth links fq_codels default target of 5ms does not work too well
|
|
# so increase target for slow links (note below roughly 2500kbps a single packet will \
|
|
# take more than 5 ms to be tansfered over the wire)
|
|
adapt_target_to_slow_link() {
|
|
CUR_LINK_KBPS=$1
|
|
CUR_EXTENDED_TARGET_US=
|
|
MAX_PAKET_DELAY_IN_US_AT_1KBPS=$(( 1000 * 1000 *1540 * 8 / 1000 ))
|
|
CUR_EXTENDED_TARGET_US=$(( ${MAX_PAKET_DELAY_IN_US_AT_1KBPS} / ${CUR_LINK_KBPS} )) # note this truncates the decimals
|
|
# do not change anything for fast links
|
|
[ "$CUR_EXTENDED_TARGET_US" -lt 5000 ] && CUR_EXTENDED_TARGET_US=5000
|
|
case ${QDISC} in
|
|
*codel|pie)
|
|
echo "${CUR_EXTENDED_TARGET_US}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# codel looks at a whole interval to figure out wether observed latency stayed below target
|
|
# if target >= interval that will not work well, so increase interval by the same amonut that target got increased
|
|
adapt_interval_to_slow_link() {
|
|
CUR_TARGET_US=$1
|
|
case ${QDISC} in
|
|
*codel)
|
|
CUR_EXTENDED_INTERVAL_US=$(( (100 - 5) * 1000 + ${CUR_TARGET_US} ))
|
|
echo "interval ${CUR_EXTENDED_INTERVAL_US}us"
|
|
;;
|
|
pie)
|
|
## not sure if pie needs this, probably not
|
|
#CUR_EXTENDED_TUPDATE_US=$(( (30 - 20) * 1000 + ${CUR_TARGET_US} ))
|
|
#echo "tupdate ${CUR_EXTENDED_TUPDATE_US}us"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
|
|
# set quantum parameter if available for this qdisc
|
|
get_quantum() {
|
|
case $QDISC in
|
|
*fq_codel|fq_pie|drr) echo quantum $1 ;;
|
|
*) ;;
|
|
esac
|
|
}
|
|
|
|
# only show limits to qdiscs that can handle them...
|
|
# Note that $LIMIT contains the default limit
|
|
get_limit() {
|
|
CURLIMIT=$1
|
|
case $QDISC in
|
|
*codel|*pie|pfifo_fast|sfq|pfifo) [ -z ${CURLIMIT} ] && CURLIMIT=${LIMIT} # use the global default limit
|
|
;;
|
|
bfifo) [ -z "$CURLIMIT" ] && [ ! -z "$LIMIT" ] && CURLIMIT=$(( ${LIMIT} * $( cat /sys/class/net/${IFACE}/mtu ) )) # bfifo defaults to txquelength * MTU,
|
|
;;
|
|
*) sqm_logger "${QDISC} does not support a limit"
|
|
;;
|
|
esac
|
|
sqm_logger "get_limit: $1 CURLIMIT: ${CURLIMIT}"
|
|
|
|
if [ ! -z "$CURLIMIT" ]
|
|
then
|
|
echo "limit ${CURLIMIT}"
|
|
fi
|
|
}
|
|
|
|
get_ecn() {
|
|
CURECN=$1
|
|
#sqm_logger CURECN: $CURECN
|
|
case ${CURECN} in
|
|
ECN)
|
|
case $QDISC in
|
|
*codel|*pie|*red)
|
|
CURECN=ecn
|
|
;;
|
|
*)
|
|
CURECN=""
|
|
;;
|
|
esac
|
|
;;
|
|
NOECN)
|
|
case $QDISC in
|
|
*codel|*pie|*red)
|
|
CURECN=noecn
|
|
;;
|
|
*)
|
|
CURECN=""
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
sqm_logger "ecn value $1 not handled"
|
|
;;
|
|
esac
|
|
#sqm_logger "get_ECN: $1 CURECN: ${CURECN} IECN: ${IECN} EECN: ${EECN}"
|
|
echo ${CURECN}
|
|
|
|
}
|
|
|
|
# This could be a complete diffserv implementation
|
|
|
|
diffserv() {
|
|
|
|
interface=$1
|
|
prio=1
|
|
|
|
# Catchall
|
|
|
|
$TC filter add dev $interface parent 1:0 protocol all prio 999 u32 \
|
|
match ip protocol 0 0x00 flowid 1:12
|
|
|
|
# Find the most common matches fast
|
|
#fc_pppoe() instead of fc() with effectice ingress classification for pppoe is very expensive and destroys LUL
|
|
|
|
fc 1:0 0x00 1:12 # BE
|
|
fc 1:0 0x20 1:13 # CS1
|
|
fc 1:0 0x10 1:11 # IMM
|
|
fc 1:0 0xb8 1:11 # EF
|
|
fc 1:0 0xc0 1:11 # CS3
|
|
fc 1:0 0xe0 1:11 # CS6
|
|
fc 1:0 0x90 1:11 # AF42 (mosh)
|
|
|
|
# Arp traffic
|
|
$TC filter add dev $interface protocol arp parent 1:0 prio $prio handle 500 fw flowid 1:11
|
|
|
|
|
|
prio=$(($prio + 1))
|
|
|
|
|
|
}
|
|
|
|
diffserv_pppoe() {
|
|
|
|
interface=$1
|
|
prio=1
|
|
|
|
# Catchall
|
|
|
|
$TC filter add dev $interface parent 1:0 protocol all prio 999 u32 \
|
|
match ip protocol 0 0x00 flowid 1:12
|
|
|
|
# Find the most common matches fast
|
|
#fc_pppoe() instead of fc() with effectice ingress classification for pppoe is very expensive and destroys LUL
|
|
|
|
fc_pppoe 1:0 0x00 1:12 # BE
|
|
fc_pppoe 1:0 0x20 1:13 # CS1
|
|
fc_pppoe 1:0 0x10 1:11 # IMM
|
|
fc_pppoe 1:0 0xb8 1:11 # EF
|
|
fc_pppoe 1:0 0xc0 1:11 # CS3
|
|
fc_pppoe 1:0 0xe0 1:11 # CS6
|
|
fc_pppoe 1:0 0x90 1:11 # AF42 (mosh)
|
|
|
|
# Arp traffic
|
|
$TC filter add dev $interface protocol arp parent 1:0 prio $prio handle 500 fw flowid 1:11
|
|
|
|
|
|
prio=$(($prio + 1))
|
|
|
|
|
|
}
|
|
|
|
|