├── platform ├── linux │ ├── sqm-tmpfiles.conf │ ├── sqm.conf │ ├── sqm-ifup │ ├── sqm@.service │ ├── default.conf │ └── sqm-bin └── openwrt │ ├── sqm.conf │ ├── sqm-init │ ├── sqm-uci │ └── sqm-hotplug ├── qdisc-diagram.dia ├── qdisc-diagram.png ├── src ├── simplest.qos.help ├── simple.qos.help ├── simplest_tbf.qos.help ├── layer_cake.qos.help ├── piece_of_cake.qos.help ├── update-available-qdiscs ├── layer_cake.qos ├── piece_of_cake.qos ├── stop-sqm ├── start-sqm ├── simplest_tbf.qos ├── simplest.qos ├── defaults.sh ├── run-openwrt.sh ├── simple.qos └── functions.sh ├── Makefile └── README.md /platform/linux/sqm-tmpfiles.conf: -------------------------------------------------------------------------------- 1 | d /run/sqm 700 root root 2 | -------------------------------------------------------------------------------- /qdisc-diagram.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tohojo/sqm-scripts/HEAD/qdisc-diagram.dia -------------------------------------------------------------------------------- /qdisc-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tohojo/sqm-scripts/HEAD/qdisc-diagram.png -------------------------------------------------------------------------------- /src/simplest.qos.help: -------------------------------------------------------------------------------- 1 | Simplest possible configuration: HTB rate limiter with your qdisc attached. 2 | -------------------------------------------------------------------------------- /src/simple.qos.help: -------------------------------------------------------------------------------- 1 | BW-limited three-tier prioritisation scheme with your qdisc on each queue. (default) 2 | -------------------------------------------------------------------------------- /platform/linux/sqm.conf: -------------------------------------------------------------------------------- 1 | SQM_LIB_DIR=/usr/lib/sqm 2 | SQM_STATE_DIR=/run/sqm 3 | SQM_QDISC_STATE_DIR=${SQM_STATE_DIR}/available_qdiscs 4 | SQM_CHECK_QDISCS="fq_codel codel pie sfq cake" 5 | SQM_SYSLOG=0 6 | 7 | -------------------------------------------------------------------------------- /src/simplest_tbf.qos.help: -------------------------------------------------------------------------------- 1 | Simplest possible configuration (TBF): TBF rate limiter with your qdisc attached. 2 | TBF may give better performance than HTB on some architectures. This script forces fq_codel usage if cake is selected as qdisc. 3 | -------------------------------------------------------------------------------- /platform/openwrt/sqm.conf: -------------------------------------------------------------------------------- 1 | SQM_LIB_DIR=/usr/lib/sqm 2 | SQM_STATE_DIR=/var/run/sqm 3 | SQM_QDISC_STATE_DIR=${SQM_STATE_DIR}/available_qdiscs 4 | SQM_CHECK_QDISCS="fq_codel codel pie sfq cake" 5 | SQM_SYSLOG=1 6 | IP6TABLES_BINARY=$(command -v ip6tables-nft) 7 | IPTABLES_BINARY=$(command -v iptables-nft) 8 | -------------------------------------------------------------------------------- /src/layer_cake.qos.help: -------------------------------------------------------------------------------- 1 | This uses the cake qdisc as a replacement for both htb as shaper and fq_codel as leaf qdisc. 2 | This exercises cake's diffserv profile(s) as different "layers" of priority. 3 | This script requires that cake is selected as qdisc, and forces its usage. 4 | See: http://www.bufferbloat.net/projects/codel/wiki/Cake for more information 5 | -------------------------------------------------------------------------------- /src/piece_of_cake.qos.help: -------------------------------------------------------------------------------- 1 | This just uses the cake qdisc as a replacement for both htb as shaper and fq_codel as leaf qdisc. 2 | It just does not come any simpler than this, in other words it truely is a "piece of cake". 3 | This script requires that cake is selected as qdisc, and forces its usage. 4 | See: http://www.bufferbloat.net/projects/codel/wiki/Cake for more information 5 | -------------------------------------------------------------------------------- /platform/linux/sqm-ifup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | [ -n "$IFACE" ] || exit 1 4 | 5 | . /etc/sqm/sqm.conf 6 | 7 | if [ -f /etc/sqm/${IFACE}.iface.conf ]; then 8 | [ -f /etc/sqm/default.conf ] && . /etc/sqm/default.conf 9 | . /etc/sqm/${IFACE}.iface.conf 10 | 11 | [ -f ${SQM_STATE_DIR}/$IFACE.state ] && ( . ${SQM_LIB_DIR}/stop-sqm ) 12 | ( . ${SQM_LIB_DIR}/start-sqm ) 13 | fi 14 | -------------------------------------------------------------------------------- /src/update-available-qdiscs: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . /etc/sqm/sqm.conf 4 | . ${SQM_LIB_DIR}/functions.sh 5 | . ${SQM_LIB_DIR}/defaults.sh 6 | 7 | [ -d "${SQM_QDISC_STATE_DIR}" ] || mkdir -p "${SQM_QDISC_STATE_DIR}" 8 | 9 | SQM_VERBOSITY_MIN=5 # Silence errors while checking 10 | 11 | for qdisc in $SQM_CHECK_QDISCS; do 12 | [ -f ${SQM_QDISC_STATE_DIR}/$qdisc ] && continue 13 | verify_qdisc $qdisc && touch ${SQM_QDISC_STATE_DIR}/$qdisc 14 | done 15 | -------------------------------------------------------------------------------- /platform/openwrt/sqm-init: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | 3 | START=50 4 | USE_PROCD=1 5 | 6 | service_triggers() 7 | { 8 | procd_add_reload_trigger "sqm" 9 | } 10 | 11 | reload_service() 12 | { 13 | stop "$@" 14 | start "$@" 15 | } 16 | 17 | start_service() 18 | { 19 | /usr/lib/sqm/run.sh start "$@" 20 | } 21 | 22 | stop_service() 23 | { 24 | /usr/lib/sqm/run.sh stop "$@" 25 | } 26 | 27 | boot() 28 | { 29 | export SQM_VERBOSITY_MIN=5 # Silence errors 30 | start "$@" 31 | } 32 | -------------------------------------------------------------------------------- /platform/openwrt/sqm-uci: -------------------------------------------------------------------------------- 1 | 2 | config queue 'eth1' 3 | option enabled '0' 4 | option interface 'eth1' 5 | option download '85000' 6 | option upload '10000' 7 | option qdisc 'cake' 8 | option script 'piece_of_cake.qos' 9 | option qdisc_advanced '0' 10 | option ingress_ecn 'ECN' 11 | option egress_ecn 'ECN' 12 | option qdisc_really_really_advanced '0' 13 | option itarget 'auto' 14 | option etarget 'auto' 15 | option linklayer 'none' 16 | 17 | -------------------------------------------------------------------------------- /platform/linux/sqm@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=SQM scripts for iface %i 3 | Wants=network.target 4 | Before=network.target 5 | BindsTo=sys-subsystem-net-devices-%i.device 6 | After=sys-subsystem-net-devices-%i.device 7 | 8 | [Service] 9 | Type=oneshot 10 | EnvironmentFile=/etc/sqm/default.conf 11 | EnvironmentFile=-/etc/sqm/%I.iface.conf 12 | Environment=IFACE=%I ENABLED=1 13 | ExecStart=/usr/lib/sqm/start-sqm 14 | ExecStop=/usr/lib/sqm/stop-sqm 15 | RemainAfterExit=1 16 | 17 | [Install] 18 | WantedBy=sys-subsystem-net-devices-%i.device 19 | -------------------------------------------------------------------------------- /platform/openwrt/sqm-hotplug: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . /lib/functions.sh 4 | 5 | [ -n "$DEVICE" ] || exit 0 6 | 7 | list_ports() { 8 | local section 9 | local find_name 10 | local devname 11 | section="$1" 12 | find_name="$2" 13 | devname=$(config_get "$section" "name") 14 | [ "$devname" = "$find_name" ] || return 15 | config_get "$section" "ports" 16 | } 17 | 18 | ports_for_device() { 19 | local devname 20 | devname=$1 21 | [ -z "$devname" ] && return 22 | config_load network 23 | config_foreach list_ports device "$devname" 24 | } 25 | 26 | ALL_DEVICES=$(echo $DEVICE $(uci -q get network.$INTERFACE.ifname) $(ports_for_device $(uci -q get network.$INTERFACE.device)) | tr ' ' '\n' | sort -u) 27 | 28 | restart_sqm() { 29 | for dev in $ALL_DEVICES; do 30 | /usr/lib/sqm/run.sh stop $dev 31 | /usr/lib/sqm/run.sh start $dev 32 | done 33 | } 34 | 35 | [ "$ACTION" = ifup ] && /etc/init.d/sqm enabled && restart_sqm 36 | 37 | if [ "$ACTION" = ifdown ]; then 38 | for dev in $ALL_DEVICES; do 39 | /usr/lib/sqm/run.sh stop $dev 40 | done 41 | fi 42 | -------------------------------------------------------------------------------- /platform/linux/default.conf: -------------------------------------------------------------------------------- 1 | # Default SQM config; the variables defined here will be applied to all 2 | # interfaces. To override values for a particular interface, copy this file to 3 | # .iface.conf (e.g., "eth0.iface.conf" for eth0). 4 | # 5 | # When using ifupdown, the interface config file needs to exist for sqm-scripts 6 | # to be activated on that interface. However, these defaults are still applied, 7 | # so the interface config can be empty. 8 | 9 | # Uplink and Downlink values are in kbps 10 | UPLINK=1000 11 | DOWNLINK=85000 12 | 13 | # SQM recipe to use. For more information, see /usr/lib/sqm/*.help 14 | SCRIPT=piece_of_cake.qos 15 | 16 | # Optional/advanced config 17 | 18 | #ENABLED=1 19 | #QDISC=cake 20 | 21 | #LLAM=tc_stab 22 | #LINKLAYER=none 23 | #OVERHEAD=0 24 | #STAB_MTU=2047 25 | #STAB_TSIZE=512 26 | #STAB_MPU=0 27 | 28 | #ILIMIT= 29 | #ELIMIT= 30 | #ITARGET= 31 | #ETARGET= 32 | 33 | # ECN ingress resp. egress. Values are ECN or NOECN. 34 | #IECN=ECN 35 | #EECN=ECN 36 | 37 | # Extra qdisc options ingress resp. egress 38 | #IQDISC_OPTS="" 39 | #EQDISC_OPTS="" 40 | 41 | # CoDel target 42 | #TARGET=5ms 43 | 44 | #ZERO_DSCP_INGRESS=1 45 | #IGNORE_DSCP_INGRESS=1 46 | 47 | -------------------------------------------------------------------------------- /src/layer_cake.qos: -------------------------------------------------------------------------------- 1 | # Cero3 Shaper 2 | # A cake shaper and AQM solution that allows several diffserv marking schemes 3 | # for ethernet gateways 4 | 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # Copyright (C) 2012-5 Michael D. Taht, Toke Høiland-Jørgensen, Sebastian Moeller 10 | 11 | 12 | #sm: TODO pass in the cake diffserv keyword 13 | 14 | . ${SQM_LIB_DIR}/defaults.sh 15 | QDISC=cake 16 | 17 | # Default traffic classication is passed in INGRESS_CAKE_OPTS and EGRESS_CAKE_OPTS, defined in defaults.sh now 18 | 19 | 20 | egress() { 21 | SILENT=1 $TC qdisc del dev $IFACE root 22 | $TC qdisc add dev $IFACE root $( get_stab_string ) cake \ 23 | bandwidth ${UPLINK}kbit $( get_cake_lla_string ) ${EGRESS_CAKE_OPTS} ${EQDISC_OPTS} 24 | 25 | } 26 | 27 | 28 | ingress() { 29 | 30 | SILENT=1 $TC qdisc del dev $IFACE handle ffff: ingress 31 | $TC qdisc add dev $IFACE handle ffff: ingress 32 | 33 | SILENT=1 $TC qdisc del dev $DEV root 34 | 35 | [ "$IGNORE_DSCP_INGRESS" -eq "1" ] && INGRESS_CAKE_OPTS="$INGRESS_CAKE_OPTS besteffort" 36 | [ "$ZERO_DSCP_INGRESS" -eq "1" ] && INGRESS_CAKE_OPTS="$INGRESS_CAKE_OPTS wash" 37 | 38 | $TC qdisc add dev $DEV root $( get_stab_string ) cake \ 39 | bandwidth ${DOWNLINK}kbit $( get_cake_lla_string ) ${INGRESS_CAKE_OPTS} ${IQDISC_OPTS} 40 | 41 | $IP link set dev $DEV up 42 | 43 | # redirect all IP packets arriving in $IFACE to ifb0 44 | 45 | $TC filter add dev $IFACE parent ffff: protocol all prio 10 u32 \ 46 | match u32 0 0 flowid 1:1 action mirred egress redirect dev $DEV 47 | } 48 | 49 | sqm_prepare_script() { 50 | do_modules 51 | verify_qdisc $QDISC "cake" || return 1 52 | } 53 | -------------------------------------------------------------------------------- /src/piece_of_cake.qos: -------------------------------------------------------------------------------- 1 | # Cero3 Simple Shaper 2 | # A 1 tin cake shaper for 3 | # ethernet gateways. This is nearly the simplest possible 4 | 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # Copyright (C) 2012-5 Michael D. Taht, Toke Høiland-Jørgensen, Sebastian Moeller 10 | 11 | 12 | . ${SQM_LIB_DIR}/defaults.sh 13 | QDISC=cake 14 | 15 | 16 | # to keep this as simple as possible we append the *_CAKE_OPTS from defaults.sh 17 | # since cake will always just keep the last instance of competing keywords this 18 | # will effectively override defaults.sh's diffservX 19 | INGRESS_CAKE_OPTS="${INGRESS_CAKE_OPTS} besteffort" 20 | EGRESS_CAKE_OPTS="${EGRESS_CAKE_OPTS} besteffort" 21 | 22 | 23 | egress() { 24 | sqm_debug "egress" 25 | SILENT=1 $TC qdisc del dev $IFACE root 26 | $TC qdisc add dev $IFACE root $( get_stab_string ) cake \ 27 | bandwidth ${UPLINK}kbit $( get_cake_lla_string ) ${EGRESS_CAKE_OPTS} ${EQDISC_OPTS} 28 | } 29 | 30 | 31 | ingress() { 32 | sqm_debug "ingress" 33 | 34 | SILENT=1 $TC qdisc del dev $IFACE handle ffff: ingress 35 | $TC qdisc add dev $IFACE handle ffff: ingress 36 | SILENT=1 $TC qdisc del dev $DEV root 37 | 38 | [ "$ZERO_DSCP_INGRESS" -eq "1" ] && INGRESS_CAKE_OPTS="$INGRESS_CAKE_OPTS wash" 39 | 40 | $TC qdisc add dev $DEV root $( get_stab_string ) cake \ 41 | bandwidth ${DOWNLINK}kbit $( get_cake_lla_string ) ${INGRESS_CAKE_OPTS} ${IQDISC_OPTS} 42 | 43 | $IP link set dev $DEV up 44 | 45 | # redirect all IP packets arriving in $IFACE to ifb0 46 | 47 | $TC filter add dev $IFACE parent ffff: protocol all prio 10 u32 \ 48 | match u32 0 0 flowid 1:1 action mirred egress redirect dev $DEV 49 | } 50 | 51 | sqm_prepare_script() { 52 | do_modules 53 | verify_qdisc $QDISC "cake" || return 1 54 | } 55 | -------------------------------------------------------------------------------- /src/stop-sqm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License version 2 as 5 | # published by the Free Software Foundation. 6 | # 7 | # Copyright (C) 2012-4 Michael D. Taht, Toke Høiland-Jørgensen, Sebastian Moeller 8 | 9 | # allow passing in the IFACE as first command line argument 10 | 11 | [ -n "$IFACE" ] || exit 1 12 | 13 | . /etc/sqm/sqm.conf 14 | . ${SQM_LIB_DIR}/functions.sh 15 | . ${SQM_LIB_DIR}/defaults.sh 16 | 17 | check_state_dir 18 | # log file for the most recent sqm instance stop 19 | if [ "$SQM_DEBUG" -eq "1" ] ; then 20 | SQM_DEBUG_LOG="${SQM_STOP_LOG}" 21 | OUTPUT_TARGET="${SQM_DEBUG_LOG}" 22 | echo "stop-sqm: Log for interface ${IFACE}: $(date)" > "${OUTPUT_TARGET}" 23 | fi 24 | 25 | if [ ! -f "${SQM_STATE_DIR}/${IFACE}.state" ] ; then 26 | sqm_error "State file does not exist; SQM was not running on interface ${IFACE}" 27 | exit 1 28 | fi 29 | STATE_FILE="${SQM_STATE_DIR}/${IFACE}.state" 30 | 31 | if [ -z "${SCRIPT}" ] ; then 32 | sqm_error "SCRIPT value is not defined in /etc/sqm/${IFACE}.iface.conf" 33 | sqm_error "Please check your configuration and try again." 34 | exit 1 35 | fi 36 | 37 | sqm_trace; sqm_trace "$(date): Stopping." # Add some space and a date stamp to verbose log output and log files to separate runs 38 | 39 | # make sure to only delete the ifb associated with the current interface 40 | CUR_IFB=$( get_ifb_associated_with_if ${IFACE} ) 41 | [ -z "$CUR_IFB" ] && CUR_IFB=$( ifb_name ${IFACE} ) 42 | 43 | if [ ! -f "${SQM_LIB_DIR}/$SCRIPT" ]; then 44 | sqm_error "SQM script ${SCRIPT} not found!" 45 | exit 1 46 | fi 47 | 48 | . "${SQM_LIB_DIR}/$SCRIPT" 49 | 50 | if [ "$CLEANUP" -eq "1" ]; then 51 | sqm_log "Cleaning up SQM state on ${IFACE}" 52 | sqm_cleanup 1 53 | else 54 | sqm_log "Stopping SQM on ${IFACE}" 55 | sqm_stop 56 | fi 57 | rm -f "${STATE_FILE}" 58 | 59 | exit 0 60 | -------------------------------------------------------------------------------- /platform/linux/sqm-bin: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . /etc/sqm/sqm.conf 4 | ACTION="$1" 5 | RUN_IFACE="$2" 6 | CLEANUP=0 7 | 8 | if [ "$ACTION" = "cleanup" ]; then 9 | CLEANUP=1 10 | ACTION=stop 11 | fi 12 | 13 | if [ "$(id -u)" -ne "0" ]; then 14 | echo "This script must be run as root." >&2 15 | exit 1 16 | fi 17 | 18 | if [ "$ACTION" != "start" -a "$ACTION" != "stop" -a "$ACTION" != "reload" ]; then 19 | echo "Usage: $0 [iface]." >&2 20 | exit 1 21 | fi 22 | 23 | # Stopping all active interfaces 24 | if [ "$ACTION" = "stop" -a -z "$RUN_IFACE" ]; then 25 | echo "Stopping SQM on all active interfaces." >&2 26 | for f in ${SQM_STATE_DIR}/*.state; do 27 | # Source the state file prior to stopping; we need the $IFACE and 28 | # $SCRIPT variables saved in there. 29 | [ -f "$f" ] && ( . $f; IFACE=$IFACE SCRIPT=$SCRIPT CLEANUP=$CLEANUP SQM_DEBUG=$SQM_DEBUG SQM_DEBUG_LOG=$SQM_DEBUG_LOG OUTPUT_TARGET=$OUTPUT_TARGET ${SQM_LIB_DIR}/stop-sqm ) 30 | done 31 | exit 0 32 | fi 33 | 34 | if [ -n "$RUN_IFACE" ]; then 35 | if [ ! -f /etc/sqm/${RUN_IFACE}.iface.conf ]; then 36 | echo "No config file found for iface $RUN_IFACE." >&2 37 | exit 1 38 | fi 39 | IFACE=$RUN_IFACE 40 | . /etc/sqm/${RUN_IFACE}.iface.conf 41 | [ -f ${SQM_STATE_DIR}/$IFACE.state ] && ( . ${SQM_LIB_DIR}/stop-sqm ) 42 | [ "$ACTION" = "stop" ] || ( . ${SQM_LIB_DIR}/start-sqm ) 43 | else 44 | echo "Starting SQM on all configured interfaces." >&2 45 | for f in /etc/sqm/*.iface.conf; do 46 | if [ -f "$f" ]; then 47 | IFACE=$(basename $f .iface.conf) 48 | [ -f ${SQM_STATE_DIR}/$IFACE.state ] && ( . $f; . ${SQM_LIB_DIR}/stop-sqm ) 49 | [ "$ACTION" = "stop" ] || ( . $f; . ${SQM_LIB_DIR}/start-sqm ) 50 | else 51 | echo >&2 52 | echo "Error: No valid interface configuration found." >&2 53 | echo "A interface must be defined in /etc/sqm/.iface.conf" >&2 54 | echo "Please define a interface configuration in /etc/sqm." >&2 55 | echo "For more information, see the template file in" >&2 56 | echo "/etc/sqm/eth0.iface.conf.example" >&2 57 | exit 1 58 | fi 59 | done 60 | fi 61 | -------------------------------------------------------------------------------- /src/start-sqm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License version 2 as 5 | # published by the Free Software Foundation. 6 | # 7 | # Copyright (C) 2012-4 Michael D. Taht, Toke Høiland-Jørgensen, Sebastian Moeller 8 | 9 | 10 | [ -n "$IFACE" ] || exit 1 11 | 12 | . /etc/sqm/sqm.conf 13 | . ${SQM_LIB_DIR}/functions.sh 14 | . ${SQM_LIB_DIR}/defaults.sh 15 | STATE_FILE="${SQM_STATE_DIR}/${IFACE}.state" 16 | 17 | check_state_dir 18 | 19 | # log file for the most recent sqm instance start 20 | if [ "$SQM_DEBUG" -eq "1" ] ; then 21 | SQM_DEBUG_LOG="${SQM_START_LOG}" 22 | OUTPUT_TARGET="${SQM_DEBUG_LOG}" 23 | echo "start-sqm: Log for interface ${IFACE}: $(date)" > "${OUTPUT_TARGET}" 24 | fi 25 | 26 | if [ -z "${SCRIPT}" ] ; then 27 | sqm_error "SCRIPT value is not defined in /etc/sqm/${IFACE}.iface.conf" 28 | sqm_error "Please check your configuration and try again." 29 | exit 1 30 | fi 31 | 32 | if [ -f "${STATE_FILE}" ]; then 33 | sqm_error "SQM already activated on ${IFACE}." 34 | exit 1 35 | fi 36 | 37 | # in case of spurious hotplug events, try double check whether the interface is really up 38 | if [ ! -d /sys/class/net/${IFACE} ] ; then 39 | sqm_error "${IFACE} does currently not exist, not even trying to start SQM on nothing." 40 | exit 1 41 | fi 42 | 43 | if [ "${ENABLED:-1}" -ne "1" ]; then 44 | sqm_log "SQM config disabled on ${IFACE}." 45 | exit 0 46 | fi 47 | 48 | if [ ! -f "${SQM_LIB_DIR}/$SCRIPT" ]; then 49 | sqm_error "SQM script ${SCRIPT} not found!" 50 | exit 1 51 | fi 52 | 53 | . "${SQM_LIB_DIR}/$SCRIPT" 54 | 55 | sqm_trace; sqm_trace "$(date): Starting." # Add some space and a date stamp to verbose log output and log files to separate runs 56 | sqm_log "Starting SQM script: ${SCRIPT} on ${IFACE}, in: ${DOWNLINK} Kbps, out: ${UPLINK} Kbps" 57 | 58 | if fn_exists sqm_start ; then 59 | sqm_debug "Using script specific sqm_start function overriding the generic sqm_start_default." 60 | sqm_start && write_state_file ${STATE_FILE} && sqm_log "${SCRIPT} was started on ${IFACE} successfully" 61 | else 62 | sqm_debug "Using generic sqm_start_default function." 63 | sqm_start_default && write_state_file ${STATE_FILE} && sqm_log "${SCRIPT} was started on ${IFACE} successfully" 64 | fi 65 | 66 | exit 0 67 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX:=/usr 2 | DESTDIR:= 3 | PLATFORM:=linux 4 | PKG_CONFIG:=$(shell which pkg-config 2>/dev/null) 5 | UNIT_DIR:=$(if $(PKG_CONFIG),\ 6 | $(DESTDIR)$(shell $(PKG_CONFIG) --variable systemdsystemunitdir systemd),\ 7 | $(DESTDIR)$(PREFIX)/lib/systemd/system) 8 | 9 | all: 10 | @echo "Run 'make install' to install." 11 | 12 | .PHONY: install 13 | install: install-$(PLATFORM) 14 | 15 | .PHONY: install-openwrt 16 | install-openwrt: install-lib 17 | install -m 0755 -d $(DESTDIR)/etc/hotplug.d/iface $(DESTDIR)/etc/config \ 18 | $(DESTDIR)/etc/init.d 19 | install -m 0600 platform/openwrt/sqm-hotplug $(DESTDIR)/etc/hotplug.d/iface/11-sqm 20 | install -m 0755 platform/openwrt/sqm-init $(DESTDIR)/etc/init.d/sqm 21 | install -m 0644 platform/openwrt/sqm-uci $(DESTDIR)/etc/config/sqm 22 | install -m 0744 src/run-openwrt.sh $(DESTDIR)$(PREFIX)/lib/sqm/run.sh 23 | 24 | .PHONY: install-linux 25 | install-linux: install-lib 26 | install -m 0755 -d $(UNIT_DIR) $(DESTDIR)$(PREFIX)/lib/tmpfiles.d \ 27 | $(DESTDIR)$(PREFIX)/bin 28 | install -m 0644 -C -b platform/linux/default.conf $(DESTDIR)/etc/sqm 29 | install -m 0644 platform/linux/sqm@.service $(UNIT_DIR) 30 | install -m 0644 platform/linux/sqm-tmpfiles.conf \ 31 | $(DESTDIR)$(PREFIX)/lib/tmpfiles.d/sqm.conf 32 | install -m 0755 platform/linux/sqm-bin $(DESTDIR)$(PREFIX)/bin/sqm 33 | test -d $(DESTDIR)/etc/network/if-up.d && install -m 0755 platform/linux/sqm-ifup \ 34 | $(DESTDIR)/etc/network/if-up.d/sqm || exit 0 35 | 36 | .PHONY: install-lib 37 | install-lib: 38 | install -m 0755 -d $(DESTDIR)/etc/sqm $(DESTDIR)$(PREFIX)/lib/sqm 39 | install -m 0644 -C -b platform/$(PLATFORM)/sqm.conf $(DESTDIR)/etc/sqm/sqm.conf 40 | install -m 0644 src/functions.sh src/defaults.sh \ 41 | src/*.qos src/*.help $(DESTDIR)$(PREFIX)/lib/sqm 42 | install -m 0744 src/start-sqm src/stop-sqm src/update-available-qdiscs \ 43 | $(DESTDIR)$(PREFIX)/lib/sqm 44 | 45 | .PHONY: uninstall 46 | uninstall: uninstall-$(PLATFORM) 47 | 48 | .PHONY: uninstall-openwrt 49 | uninstall-openwrt: uninstall-lib 50 | @for f in $(DESTDIR)/etc/hotplug.d/iface/11-sqm $(DESTDIR)/etc/init.d/sqm; do \ 51 | if [ -f "$$f" ]; then rm -vf "$$f"; fi; done 52 | @echo "Not removing config in $(DESTDIR)/etc/sqm and $(DESTDIR)/etc/config/sqm - remove manually if needed" 53 | 54 | .PHONY: uninstall-linux 55 | uninstall-linux: uninstall-lib 56 | @for f in $(UNIT_DIR)/sqm@.service $(DESTDIR)$(PREFIX)/lib/tmpfiles.d/sqm.conf \ 57 | $(DESTDIR)$(PREFIX)/bin/sqm $(DESTDIR)/etc/network/if-up.d/sqm; do \ 58 | if [ -f "$$f" ]; then rm -vf "$$f"; fi; done 59 | @echo "Not removing config in $(DESTDIR)/etc/sqm - remove manually if needed" 60 | 61 | .PHONY: uninstall-lib 62 | uninstall-lib: 63 | @for f in $(DESTDIR)$(PREFIX)/lib/sqm/*; do \ 64 | if [ -f "$$f" ]; then rm -vf "$$f"; fi; done 65 | @rmdir -v $(DESTDIR)$(PREFIX)/lib/sqm 66 | -------------------------------------------------------------------------------- /src/simplest_tbf.qos: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # simplest_tbf.qos (Simple TBF shaper) 3 | # 4 | # Abstract: 5 | # This is a single band fq_codel and ipv6 enabled shaping script for Ethernet 6 | # gateways. This is nearly the simplest possible. With FQ_CODEL, the sparseness 7 | # priority will work pretty well for a casual network. Flow-hashes should not 8 | # overlap much with only a few users. 9 | # 10 | # Uses TBF instead of HTB as that may give better performance on some 11 | # architectures. 12 | # 13 | # References: 14 | # This alternate shaper attempts to go for 1/u performance in a clever way 15 | # http://git.coverfire.com/?p=linux-qos-scripts.git;a=blob;f=src-3tos.sh;hb=HEAD 16 | # 17 | ################################################################################ 18 | # 19 | # This program is free software; you can redistribute it and/or modify 20 | # it under the terms of the GNU General Public License version 2 as 21 | # published by the Free Software Foundation. 22 | # 23 | # Copyright (C) 2012-2017 24 | # Michael D. Taht, Toke Høiland-Jørgensen, Sebastian Moeller 25 | # 26 | ################################################################################ 27 | 28 | . ${SQM_LIB_DIR}/defaults.sh 29 | 30 | ################################################################################ 31 | 32 | egress() { 33 | 34 | MTU=$(get_mtu $IFACE) 35 | BURST="$(get_burst ${MTU:-1514} ${UPLINK} ${ESHAPER_BURST_DUR_US})" 36 | BURST=${BURST:-1514} 37 | 38 | SILENT=1 $TC qdisc del dev $IFACE root 39 | 40 | $TC qdisc add dev $IFACE root handle 1: $(get_stab_string) tbf \ 41 | rate ${UPLINK}kbit burst $BURST latency 300ms $(get_htb_adsll_string) 42 | $TC qdisc add dev $IFACE parent 1: handle 110: $QDISC \ 43 | $(get_limit ${ELIMIT}) $(get_target "${ETARGET}" ${UPLINK}) \ 44 | $(get_ecn ${EECN}) $(get_flows ${UPLINK}) ${EQDISC_OPTS} 45 | 46 | } 47 | 48 | ingress() { 49 | sqm_debug "ingress" 50 | SILENT=1 $TC qdisc del dev $IFACE handle ffff: ingress 51 | $TC qdisc add dev $IFACE handle ffff: ingress 52 | 53 | MTU=$(get_mtu $IFACE) 54 | BURST="$(get_burst ${MTU:-1514} ${DOWNLINK} ${ISHAPER_BURST_DUR_US})" 55 | BURST=${BURST:-1514} 56 | 57 | SILENT=1 $TC qdisc del dev $DEV root 58 | 59 | $TC qdisc add dev $DEV root handle 1: $(get_stab_string) tbf \ 60 | rate ${DOWNLINK}kbit burst $BURST latency 300ms $(get_htb_adsll_string) 61 | $TC qdisc add dev $DEV parent 1: handle 110: $QDISC \ 62 | $(get_limit ${ILIMIT}) $(get_target "${ITARGET}" ${DOWNLINK}) \ 63 | $(get_ecn ${IECN}) $(get_flows ${DOWNLINK}) ${IQDISC_OPTS} 64 | 65 | $IP link set dev $DEV up 66 | 67 | # redirect all IP packets arriving in $IFACE to ifb0 68 | 69 | $TC filter add dev $IFACE parent ffff: protocol all prio 10 u32 \ 70 | match u32 0 0 flowid 1:1 action mirred egress redirect dev $DEV 71 | 72 | } 73 | 74 | sqm_prepare_script() { 75 | do_modules 76 | verify_qdisc "tbf" || return 1 77 | 78 | case $QDISC in 79 | cake*) 80 | sqm_warn "Cake is not supported with this script; falling back to FQ-CoDel" 81 | QDISC=fq_codel ;; 82 | esac 83 | } 84 | 85 | ################################################################################ 86 | -------------------------------------------------------------------------------- /src/simplest.qos: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # simplest.qos (Cero3 Simple Shaper) 3 | # 4 | # Abstract: 5 | # This is a single band fq_codel and ipv6 enabled shaping script for Ethernet 6 | # gateways. This is nearly the simplest possible. With FQ_CODEL, the sparseness 7 | # priority will work pretty well for a casual network. Flow-hashes should not 8 | # overlap much with only a few users. 9 | # 10 | # References: 11 | # This alternate shaper attempts to go for 1/u performance in a clever way 12 | # http://git.coverfire.com/?p=linux-qos-scripts.git;a=blob;f=src-3tos.sh;hb=HEAD 13 | # 14 | ################################################################################ 15 | # 16 | # This program is free software; you can redistribute it and/or modify 17 | # it under the terms of the GNU General Public License version 2 as 18 | # published by the Free Software Foundation. 19 | # 20 | # Copyright (C) 2012-2016 21 | # Michael D. Taht, Toke Høiland-Jørgensen, Sebastian Moeller 22 | # 23 | ################################################################################ 24 | 25 | . ${SQM_LIB_DIR}/defaults.sh 26 | 27 | ################################################################################ 28 | 29 | cake_egress() 30 | { 31 | $TC qdisc add dev $IFACE root `get_stab_string` cake bandwidth ${UPLINK}kbit besteffort `get_cake_lla_string` ${EQDISC_OPTS} 32 | } 33 | 34 | egress() { 35 | 36 | LQ="quantum `get_htb_quantum $IFACE ${UPLINK} ${ESHAPER_QUANTUM_DUR_US}`" 37 | BURST="`get_htb_burst $IFACE ${UPLINK} ${ESHAPER_BURST_DUR_US}`" 38 | 39 | SILENT=1 $TC qdisc del dev $IFACE root 40 | 41 | case $QDISC in 42 | cake*) cake_egress; return ;; 43 | esac 44 | 45 | $TC qdisc add dev $IFACE root handle 1: `get_stab_string` htb default 10 46 | $TC class add dev $IFACE parent 1: classid 1:1 htb $LQ rate ${UPLINK}kbit ceil ${UPLINK}kbit $BURST `get_htb_adsll_string` 47 | $TC class add dev $IFACE parent 1:1 classid 1:10 htb $LQ rate ${UPLINK}kbit ceil ${UPLINK}kbit $BURST prio 0 `get_htb_adsll_string` 48 | $TC qdisc add dev $IFACE parent 1:10 handle 110: $QDISC \ 49 | `get_limit ${ELIMIT}` `get_target "${ETARGET}" ${UPLINK}` `get_ecn ${EECN}` `get_flows ${UPLINK}` ${EQDISC_OPTS} 50 | 51 | } 52 | 53 | cake_ingress() 54 | { 55 | $TC qdisc add dev $DEV root `get_stab_string` cake bandwidth ${DOWNLINK}kbit besteffort `get_cake_lla_string` ${IQDISC_OPTS} 56 | $IP link set dev $DEV up 57 | 58 | # redirect all IP packets arriving in $IFACE to $DEV 59 | 60 | $TC filter add dev $IFACE parent ffff: protocol all prio 10 u32 \ 61 | match u32 0 0 flowid 1:1 action mirred egress redirect dev $DEV 62 | } 63 | 64 | ingress() { 65 | sqm_debug "ingress" 66 | SILENT=1 $TC qdisc del dev $IFACE handle ffff: ingress 67 | $TC qdisc add dev $IFACE handle ffff: ingress 68 | 69 | LQ="quantum `get_htb_quantum $IFACE ${DOWNLINK} ${ISHAPER_QUANTUM_DUR_US}`" 70 | BURST="`get_htb_burst $IFACE ${DOWNLINK} ${ISHAPER_BURST_DUR_US}`" 71 | 72 | SILENT=1 $TC qdisc del dev $DEV root 73 | 74 | case $QDISC in 75 | cake*) cake_ingress; return ;; 76 | esac 77 | 78 | $TC qdisc add dev $DEV root handle 1: `get_stab_string` htb default 10 79 | $TC class add dev $DEV parent 1: classid 1:1 htb $LQ rate ${DOWNLINK}kbit ceil ${DOWNLINK}kbit $BURST `get_htb_adsll_string` 80 | $TC class add dev $DEV parent 1:1 classid 1:10 htb $LQ rate ${DOWNLINK}kbit ceil ${DOWNLINK}kbit $BURST prio 0 `get_htb_adsll_string` 81 | 82 | # FIXME: I'd prefer to use a pre-nat filter but we need to detect if nat is on this interface 83 | # AND we need to permute by a random number which we can't do from userspace filters 84 | 85 | # Most high rate flows are REALLY close. This stomps on those harder, but hurts on high rate long distance 86 | #$TC qdisc add dev $DEV parent 1:10 handle 110: $QDISC limit $LIMIT $ECN interval 20ms target 3ms `get_flows ${DOWNLINK}` 87 | $TC qdisc add dev $DEV parent 1:10 handle 110: $QDISC \ 88 | `get_limit ${ILIMIT}` `get_target "${ITARGET}" ${DOWNLINK}` `get_ecn ${IECN}` `get_flows ${DOWNLINK}` ${IQDISC_OPTS} 89 | 90 | $IP link set dev $DEV up 91 | 92 | # redirect all IP packets arriving in $IFACE to ifb0 93 | 94 | $TC filter add dev $IFACE parent ffff: protocol all prio 10 u32 \ 95 | match u32 0 0 flowid 1:1 action mirred egress redirect dev $DEV 96 | 97 | } 98 | 99 | sqm_prepare_script() { 100 | do_modules 101 | verify_qdisc "htb" || return 1 102 | } 103 | 104 | ################################################################################ 105 | -------------------------------------------------------------------------------- /src/defaults.sh: -------------------------------------------------------------------------------- 1 | # You need to jiggle these parameters. Note limits are tuned towards a <10Mbit uplink <60Mbup down 2 | 3 | [ -z "$SCRIPT" ] && SCRIPT= 4 | [ -z "$UPLINK" ] && UPLINK=2302 5 | [ -z "$DOWNLINK" ] && DOWNLINK=14698 6 | [ -z "$IFACE" ] && IFACE=eth0 7 | [ -z "$QDISC" ] && QDISC=fq_codel 8 | [ -z "$LLAM" ] && LLAM="default" 9 | [ -z "$LINKLAYER" ] && LINKLAYER="none" 10 | [ -z "$OVERHEAD" ] && OVERHEAD=0 11 | [ -z "$STAB_MTU" ] && STAB_MTU=2047 12 | [ -z "$STAB_MPU" ] && STAB_MPU=0 13 | [ -z "$STAB_TSIZE" ] && STAB_TSIZE=512 14 | [ -z "$AUTOFLOW" ] && AUTOFLOW=0 15 | [ -z "$LIMIT" ] && LIMIT=1001 # sane global default for *LIMIT for fq_codel on a small memory device 16 | [ -z "$ILIMIT" ] && ILIMIT= 17 | [ -z "$ELIMIT" ] && ELIMIT= 18 | [ -z "$ITARGET" ] && ITARGET= 19 | [ -z "$ETARGET" ] && ETARGET= 20 | [ -z "$IECN" ] && IECN="ECN" 21 | [ -z "$EECN" ] && EECN="ECN" 22 | # These two used to be called something else; preserve backwards compatibility 23 | [ -z "$ZERO_DSCP_INGRESS" ] && ZERO_DSCP_INGRESS="${ZERO_DSCP:-${SQUASH_DSCP:-1}}" 24 | [ -z "$IGNORE_DSCP_INGRESS" ] && IGNORE_DSCP_INGRESS="${IGNORE_DSCP:-${SQUASH_INGRESS:-1}}" 25 | 26 | [ -z "$IQDISC_OPTS" ] && IQDISC_OPTS="" 27 | [ -z "$EQDISC_OPTS" ] && EQDISC_OPTS="" 28 | 29 | # handling of specific important binaries 30 | [ -z "$TC" ] && TC=tc_wrapper 31 | [ -z "$TC_BINARY" ] && TC_BINARY=$(command -v tc) 32 | [ -z "$IP" ] && IP=ip_wrapper 33 | [ -z "$IP_BINARY" ] && IP_BINARY=$(command -v ip) 34 | [ -z "$IPTABLES" ] && IPTABLES=iptables_wrapper 35 | [ -z "$IPTABLES_BINARY" ] && IPTABLES_BINARY=$(command -v iptables) 36 | [ -z "$IPTABLES_BINARY" ] && IPTABLES_BINARY=$(command -v iptables-nft) 37 | [ -z "$IP6TABLES" ] && IP6TABLES=ip6tables_wrapper 38 | [ -z "$IP6TABLES_BINARY" ] && IP6TABLES_BINARY=$(command -v ip6tables) 39 | [ -z "$IP6TABLES_BINARY" ] && IP6TABLES_BINARY=$(command -v ip6tables-nft) 40 | [ -z "$IPTABLES_ARGS" ] && IPTABLES_ARGS="-w 1" 41 | 42 | 43 | # Try modprobe first, fall back to insmod 44 | if [ -z "$INSMOD" ]; then 45 | INSMOD=$(command -v modprobe) 46 | if [ -n "$INSMOD" ]; then 47 | INSMOD="${INSMOD} -q" 48 | else 49 | INSMOD=$(command -v insmod) 50 | fi 51 | fi 52 | 53 | [ -z "$TARGET" ] && TARGET="5ms" 54 | [ -z "$IPT_MASK" ] && IPT_MASK="0xff" # to disable: set mask to 0xffffffff 55 | #sm: we need the functions above before trying to set the ingress IFB device 56 | #sm: *_CAKE_OPTS should contain the diffserv keyword for cake 57 | [ -z "$INGRESS_CAKE_OPTS" ] && INGRESS_CAKE_OPTS="diffserv3 nat" 58 | [ -z "$EGRESS_CAKE_OPTS" ] && EGRESS_CAKE_OPTS="diffserv3 nat" 59 | 60 | [ -z "$CUR_DIRECTION" ] && CUR_DIRECTION="NONE" 61 | 62 | 63 | # HTB without a sufficiently large burst/cburst value is a bit CPU hungry 64 | # so allow to specify the permitted burst in the time domain (microseconds) 65 | # so the user has a feeling for the associated worst case latency cost 66 | # set to zero to use htb default butst of one MTU 67 | [ -z "$SHAPER_BURST_DUR_US" ] && SHAPER_BURST_DUR_US=1000 68 | [ -z "$ISHAPER_BURST_DUR_US" ] && ISHAPER_BURST_DUR_US=$SHAPER_BURST_DUR_US 69 | [ -z "$ESHAPER_BURST_DUR_US" ] && ESHAPER_BURST_DUR_US=$SHAPER_BURST_DUR_US 70 | 71 | # use the same logic for the calculation of htb's quantum 72 | # quantum controlls how many bytes htb tries to deque from the current tier 73 | # before switching tiers. 74 | [ -z "$SHAPER_QUANTUM_DUR_US" ] && SHAPER_QUANTUM_DUR_US=$SHAPER_BURST_DUR_US 75 | [ -z "$ISHAPER_QUANTUM_DUR_US" ] && ISHAPER_QUANTUM_DUR_US=$SHAPER_QUANTUM_DUR_US 76 | [ -z "$ESHAPER_QUANTUM_DUR_US" ] && ESHAPER_QUANTUM_DUR_US=$SHAPER_QUANTUM_DUR_US 77 | 78 | 79 | # Logging verbosity 80 | VERBOSITY_SILENT=0 81 | VERBOSITY_ERROR=1 82 | VERBOSITY_WARNING=2 83 | VERBOSITY_INFO=5 84 | VERBOSITY_DEBUG=8 85 | VERBOSITY_TRACE=10 86 | [ -z "$SQM_VERBOSITY_MAX" ] && SQM_VERBOSITY_MAX=$VERBOSITY_INFO 87 | # For silencing only errors 88 | [ -z "$SQM_VERBOSITY_MIN" ] && SQM_VERBOSITY_MIN=$VERBOSITY_SILENT 89 | 90 | [ -z "$SQM_DEBUG" ] && SQM_DEBUG=0 91 | if [ "$SQM_DEBUG" -eq "1" ] 92 | then 93 | SQM_DEBUG_STEM="${SQM_STATE_DIR}/${IFACE}" 94 | SQM_START_LOG="${SQM_DEBUG_STEM}.start-sqm.log" 95 | SQM_STOP_LOG="${SQM_DEBUG_STEM}.stop-sqm.log" 96 | [ -z "SQM_DEBUG_LOG" ] && SQM_DEBUG_LOG="${SQM_DEBUG_STEM}.debug.log" 97 | OUTPUT_TARGET="${SQM_DEBUG_LOG}" 98 | else 99 | OUTPUT_TARGET="/dev/null" 100 | fi 101 | 102 | 103 | # Can be overridden by callers that want to silence error output for a 104 | # particular command 105 | SILENT=0 106 | 107 | # If set to 1, stop-sqm will run the (silent) cleanup function instead of a full 108 | # stop operation 109 | [ -z "$CLEANUP" ] && CLEANUP=0 110 | 111 | # Transaction log for unwinding ipt rules 112 | IPT_TRANS_LOG="${SQM_STATE_DIR}/${IFACE}.iptables.log" 113 | 114 | # These are the modules that do_modules() will attempt to load 115 | ALL_MODULES="sch_$QDISC sch_ingress act_mirred cls_fw cls_flow cls_u32 sch_htb" 116 | -------------------------------------------------------------------------------- /src/run-openwrt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License version 2 as 5 | # published by the Free Software Foundation. 6 | # 7 | # Copyright (C) 2012-4 Michael D. Taht, Toke Høiland-Jørgensen, Sebastian Moeller 8 | 9 | 10 | . /lib/functions.sh 11 | 12 | . /etc/sqm/sqm.conf 13 | . ${SQM_LIB_DIR}/functions.sh 14 | 15 | ACTION="${1:-start}" 16 | RUN_IFACE="$2" 17 | LOCKDIR="${SQM_STATE_DIR}/sqm-run.lock" 18 | 19 | CLEANUP=0 20 | if [ "$ACTION" = "cleanup" ]; then 21 | CLEANUP=1 22 | ACTION=stop 23 | fi 24 | 25 | check_state_dir 26 | [ -d "${SQM_QDISC_STATE_DIR}" ] || ${SQM_LIB_DIR}/update-available-qdiscs 27 | 28 | stop_statefile() { 29 | local f 30 | f="$1" 31 | # Source the state file prior to stopping; we need the variables saved in 32 | # there. 33 | [ -f "$f" ] && ( . "$f"; 34 | IFACE=$IFACE SCRIPT=$SCRIPT SQM_DEBUG=$SQM_DEBUG \ 35 | SQM_DEBUG_LOG=$SQM_DEBUG_LOG CLEANUP=$CLEANUP \ 36 | SQM_VERBOSITY_MAX=$SQM_VERBOSITY_MAX \ 37 | SQM_VERBOSITY_MIN=$SQM_VERBOSITY_MIN \ 38 | OUTPUT_TARGET=$OUTPUT_TARGET ${SQM_LIB_DIR}/stop-sqm ) 39 | } 40 | 41 | start_sqm_section() { 42 | local section 43 | section="$1" 44 | export IFACE=$(config_get "$section" interface) 45 | 46 | [ -z "$RUN_IFACE" -o "$RUN_IFACE" = "$IFACE" ] || return 47 | [ "$(config_get "$section" enabled)" -eq 1 ] || return 0 48 | [ -f "${SQM_STATE_DIR}/${IFACE}.state" ] && return 49 | 50 | export UPLINK=$(config_get "$section" upload) 51 | export DOWNLINK=$(config_get "$section" download) 52 | export LLAM=$(config_get "$section" linklayer_adaptation_mechanism) 53 | export LINKLAYER=$(config_get "$section" linklayer) 54 | export OVERHEAD=$(config_get "$section" overhead) 55 | export STAB_MTU=$(config_get "$section" tcMTU) 56 | export STAB_TSIZE=$(config_get "$section" tcTSIZE) 57 | export STAB_MPU=$(config_get "$section" tcMPU) 58 | export ILIMIT=$(config_get "$section" ilimit) 59 | export ELIMIT=$(config_get "$section" elimit) 60 | export ITARGET=$(config_get "$section" itarget) 61 | export ETARGET=$(config_get "$section" etarget) 62 | export IECN=$(config_get "$section" ingress_ecn) 63 | export EECN=$(config_get "$section" egress_ecn) 64 | export IQDISC_OPTS=$(config_get "$section" iqdisc_opts) 65 | export EQDISC_OPTS=$(config_get "$section" eqdisc_opts) 66 | export TARGET=$(config_get "$section" target) 67 | export QDISC=$(config_get "$section" qdisc) 68 | export SCRIPT=$(config_get "$section" script) 69 | 70 | # The UCI names for these two variables are confusing and should have been 71 | # changed ages ago. For now, keep the bad UCI names but use meaningful 72 | # variable names in the scripts to not break user configs. 73 | export ZERO_DSCP_INGRESS=$(config_get "$section" squash_dscp) 74 | export IGNORE_DSCP_INGRESS=$(config_get "$section" squash_ingress) 75 | 76 | # If SQM_DEBUG or SQM_VERBOSITY_* were passed in via the command line make 77 | # them available to the other scripts this allows to override sqm's log 78 | # level as set in the GUI for quick debugging without GUI accesss. 79 | export SQM_DEBUG=${SQM_DEBUG:-$(config_get "$section" debug_logging)} 80 | export SQM_VERBOSITY_MAX=${SQM_VERBOSITY_MAX:-$(config_get "$section" verbosity)} 81 | export SQM_VERBOSITY_MIN 82 | 83 | "${SQM_LIB_DIR}/start-sqm" 84 | } 85 | 86 | release_lock() { 87 | PID=$(cat "$LOCKDIR/pid") 88 | if [ "$PID" -ne "$$" ]; then 89 | sqm_error "Trying to release lock with wrong PID $PID != $$" 90 | return 1 91 | fi 92 | 93 | rm -rf "$LOCKDIR" 94 | return 0 95 | } 96 | 97 | take_lock() { 98 | 99 | if mkdir "$LOCKDIR" 2>/dev/null; then 100 | sqm_trace "Acquired run lock" 101 | echo $$ > "$LOCKDIR/pid" 102 | 103 | trap release_lock 0 104 | return 0 105 | fi 106 | PID=$(cat "$LOCKDIR/pid") 107 | sqm_warn "Unable to get run lock - already held by $PID" 108 | return 1 109 | } 110 | 111 | MAX_TRIES=10 112 | tries=$MAX_TRIES 113 | while ! take_lock; do 114 | sleep 1 115 | tries=$((tries - 1)) 116 | if [ "$tries" -eq 0 ]; then 117 | sqm_error "Giving up on getting lock after $MAX_TRIES attempts" 118 | sqm_error "This is a bug; please report it at https://github.com/tohojo/sqm-scripts/issues" 119 | sqm_error "Then, to re-enable sqm-scripts, manually remove $LOCKDIR" 120 | exit 1 121 | fi 122 | done 123 | 124 | if [ "$ACTION" = "stop" ]; then 125 | if [ -z "$RUN_IFACE" ]; then 126 | # Stopping all active interfaces 127 | for f in ${SQM_STATE_DIR}/*.state; do 128 | stop_statefile "$f" 129 | done 130 | else 131 | stop_statefile "${SQM_STATE_DIR}/${RUN_IFACE}.state" 132 | fi 133 | else 134 | config_load sqm 135 | config_foreach start_sqm_section 136 | fi 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The sqm-scripts traffic shaper 2 | 3 | [![DOI](https://zenodo.org/badge/36661217.svg)](https://zenodo.org/badge/latestdoi/36661217) 4 | 5 | This repository contains the sqm-scripts traffic shaper from the CeroWrt 6 | project. See: 7 | http://www.bufferbloat.net/projects/cerowrt/wiki/Smart_Queue_Management 8 | 9 | ## How does sqm-scripts set up traffic shaping? 10 | 11 | sqm-scripts uses the Linux qdisc mechanism to configure traffic shaping and 12 | scheduling. Either by a combination of the HTB qdisc (for shaping) and fq_codel 13 | (for packet scheduling), or using the CAKE integrated scheduler to do both at 14 | once. 15 | 16 | Because qdiscs only see traffic as it is *leaving* an interface, for ingress 17 | shaping sqm-scripts sets up an IFB device. Incoming packets are redirected to 18 | this device where a regular qdisc can handle them. The IFB device is named for 19 | the egress interface it is paired with, so the IFB for 'eth0' will be called 20 | 'ifb4eth0'. 21 | 22 | This is all illustrated on the following diagram: 23 | 24 | ![qdisc diagram](qdisc-diagram.png) 25 | Diagram contributed by Matt Taggart (@taggart). Source in [qdisc-diagram.dia](qdisc-diagram.dia). 26 | 27 | ## Requirements 28 | 29 | To run sqm-scripts you just need a Linux machine with a kernel from the last 30 | decade or so (any of the longterm releases on kernel.org should work - please 31 | don't run a kernel that is not a currently released version, longterm or 32 | current). The only exception is that if you want to use the `cake` qdisc, you 33 | either need kernel 4.19 or newer (with matching `iproute2` version), or you need 34 | to build CAKE yourself from the out-of-tree repository at 35 | https://github.com/dtaht/sch_cake. 36 | 37 | ## Installing 38 | `sudo make install` should install things on a regular Linux box. For 39 | OpenWrt, there are packages available in the distribution, so just install the 40 | sqm-scripts package, and optionally luci-app-sqm for GUI support. 41 | 42 | ## Running on regular Linux distributions 43 | After installing using `make install`, do the following to enable sqm-scripts: 44 | 45 | 1. Copy `/etc/sqm/default.conf` to `/etc/sqm/.iface.conf` where `` is 46 | the name of the interface you wish to run sqm-scripts on. Then adjust the 47 | values in the file to your environment, setting at least UPLINK, DOWNLINK, 48 | and possibly SCRIPT. 49 | 50 | 2. If you're on a Debian-derived distribution that uses old-style network config 51 | in `/etc/network/`, the Makefile should detect this and drop in appropriate 52 | hotplug scripts, so sqm-script should automatically run on the next 'ifup'. 53 | 54 | 3. If you're on a systemd-enabled distro, just enable the `sqm@` service 55 | corresponding to your interface name. E.g., for `eth0`, issue `systemctl 56 | enable sqm@eth0` (and run `systemctl start sqm@eth0` to start sqm-scripts 57 | immediately). 58 | 59 | ## "Installing" the current development version from git on OpenWrt 60 | 61 | Run the steps below on your own computer (not on the router) to retrieve the newest script version from this repository, create the scripts, then copy those new scripts to your router. 62 | 63 | 1. Make a local clone of the git repository (if you have not already): 64 | 65 | `git clone https://github.com/tohojo/sqm-scripts` 66 | 67 | 2. Change into the new directory: 68 | 69 | `cd ./sqm-scripts` 70 | 71 | 3. Make sure the source is updated: 72 | 73 | `git pull` 74 | 75 | 4. Create the scripts for your platform (PLATFORM is either linux or openwrt) and output them to a local `current_sqm_base` directory: 76 | 77 | `make install PLATFORM=openwrt DESTDIR=./current_sqm_base` 78 | 79 | 5. Change to `./current_sqm_base`: 80 | 81 | `cd ./current_sqm_base` 82 | 83 | 6. Optional for OpenWrt: The final step will overwrite your router's current sqm configuration file (at `/etc/config/sqm`). If you want to preserve the current configuration, delete the newly created config file from the local `etc/config`: 84 | 85 | `rm -r etc/config` 86 | 87 | 7. Now, use scp to copy the new scripts to the router. Change `$YOUR.SQM.HOSTNAME` to the address/DNS name for your computer - probably `192.168.1.1` or on cerowrt `gw.hom.lan`. If your account on the router is not "root", change "root" to your account: 88 | 89 | 90 | `scp -r ./* root@$YOUR.SQM.HOSTNAME:/` 91 | 92 | Note this method relies on the presence of the required qdiscs on the router/destination host. On openwrt, you should first install the "normal" sqm-scripts package to take care of all the dependencies, then use this procedure to update to the newest sqm-scripts. 93 | 94 | ## Run-time debugging 95 | 96 | SQM_VERBOSITY_MAX controls the verbosity of sqm's output to the shell and syslog (0: no logging; 8: full debug output). 97 | SQM_DEBUG controls whether sqm will log the output of the last invocation of start-sqm into `var/run/sqm/${interface_name}.start-sqm.log` and the ouput of the last invocation of stop-sqm into `var/run/sqm/${interface_name}.stop-sqm.log` e.g. for pppoe-wan `/var/run/sqm/pppoe-wan.start-sqm.log` and `/var/run/sqm/pppoe-wan.stop-sqm.log`. 98 | 99 | #### Examples 100 | 101 | - Log only the binary invocations and their output: 102 | 103 | `/etc/init.d/sqm stop ; SQM_DEBUG=1 SQM_VERBOSITY_MAX=0 /etc/init.d/sqm start` 104 | 105 | - Log verbose debug output and all the binary invocations and their output: 106 | 107 | `/etc/init.d/sqm stop ; SQM_DEBUG=1 SQM_VERBOSITY_MAX=8 /etc/init.d/sqm start` 108 | 109 | - Log both start and stop: 110 | 111 | `SQM_DEBUG=1 SQM_VERBOSITY_MAX=8 /etc/init.d/sqm stop ; SQM_DEBUG=1 SQM_VERBOSITY_MAX=8 /etc/init.d/sqm start` 112 | 113 | Note: Both the start and stop log are re-written on every sqm instance start and stop and are logging all output independent of the value of `SQM_VERBOSITY_MAX`. They will not grow indefintely, but they are written repeatedly. On reliably rewritable media like hard disk, ssd, flash with wear-leveling, or ram-disk, `SQM_DEBUG` can be safely set to 1 in `defaults.sh`, but on media like NOR flash that do only allow few write-cycles, keeping the default at 0 and using the above invocations to run a single instance with `SQM_DEBUG=1` is recommended. 114 | -------------------------------------------------------------------------------- /src/simple.qos: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # simple.qos (Cero3 Shaper) 3 | # 4 | # Abstract: 5 | # This is a three band fq_codel and ipv6 enabled shaping script for Ethernet 6 | # gateways. Compared to the complexity that debloat had become this cleanly 7 | # shows a means of going from diffserv marking to prioritization using the 8 | # current tools ip(6)tables and tc. We should note that the complexity of 9 | # debloat exists for a reason, and it is expected that script is run first to 10 | # setup various other parameters such as BQL and ethtool. 11 | # 12 | # (Assume the debloat script has setup the other interfaces.) 13 | # 14 | # Notes: 15 | # This does the right thing with ipv6 traffic. It also tries to leverage 16 | # diffserv to some sane extent. In particular, the 'priority' queue is limited 17 | # to 33% of the total, so EF, and IMM traffic cannot starve other types. The 18 | # rfc suggested 30%. 30% is probably a lot in today's world. 19 | # 20 | # References: 21 | # This alternate shaper attempts to go for 1/u performance in a clever way 22 | # http://git.coverfire.com/?p=linux-qos-scripts.git;a=blob;f=src-3tos.sh;hb=HEAD 23 | # 24 | ################################################################################ 25 | # 26 | # This program is free software; you can redistribute it and/or modify 27 | # it under the terms of the GNU General Public License version 2 as 28 | # published by the Free Software Foundation. 29 | # 30 | # Copyright (C) 2012-2016 31 | # Michael D. Taht, Toke Høiland-Jørgensen, Sebastian Moeller 32 | # 33 | ################################################################################ 34 | 35 | . ${SQM_LIB_DIR}/defaults.sh 36 | 37 | ################################################################################ 38 | 39 | ipt_setup() { 40 | 41 | ipt -t mangle -N QOS_MARK_${IFACE} 42 | 43 | case $QDISC in 44 | cake*) 45 | sqm_debug "cake does all the diffserv work - no need for iptables rules" 46 | ;; 47 | *) 48 | ipt -t mangle -A QOS_MARK_${IFACE} -j MARK --set-mark 0x2/${IPT_MASK} 49 | # You can go further with classification but... 50 | ipt -t mangle -A QOS_MARK_${IFACE} -m dscp --dscp-class CS1 -j MARK --set-mark 0x3/${IPT_MASK} 51 | ipt -t mangle -A QOS_MARK_${IFACE} -m dscp --dscp-class CS6 -j MARK --set-mark 0x1/${IPT_MASK} 52 | ipt -t mangle -A QOS_MARK_${IFACE} -m dscp --dscp-class EF -j MARK --set-mark 0x1/${IPT_MASK} 53 | ipt -t mangle -A QOS_MARK_${IFACE} -m dscp --dscp-class AF42 -j MARK --set-mark 0x1/${IPT_MASK} 54 | ipt -t mangle -A QOS_MARK_${IFACE} -m tos --tos Minimize-Delay -j MARK --set-mark 0x1/${IPT_MASK} 55 | ;; 56 | esac 57 | 58 | # Turn it on. Preserve classification if already performed 59 | # 60 | #sm: is it correct to do this in $IFACE? Should ingress not be on $DEV? since HTB acts on $DEV? 61 | # 62 | # ZERO also does not work on $DEV (that is the IFB will still see the 63 | # incoming ToS bits whether we squash or not) 64 | # 65 | # ZERO is still useful to protect internal machines... 66 | if [ "$ZERO_DSCP_INGRESS" = "1" ]; then 67 | sqm_debug "Squashing differentiated services code points (DSCP) from ingress." 68 | ipt -t mangle -I PREROUTING -i $IFACE -m dscp ! --dscp 0 -j DSCP --set-dscp-class be 69 | else 70 | sqm_debug "Keeping differentiated services code points (DSCP) from ingress." 71 | ipt -t mangle -A PREROUTING -i $IFACE -m mark --mark 0x00/${IPT_MASK} -g QOS_MARK_${IFACE} 72 | fi 73 | 74 | ipt -t mangle -A POSTROUTING -o $IFACE -m mark --mark 0x00/${IPT_MASK} -g QOS_MARK_${IFACE} 75 | 76 | # The Syn optimization was nice but fq_codel does it for us 77 | # ipt -t mangle -A PREROUTING -i s+ -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -j MARK --set-mark 0x01 78 | # Not sure if this will work. Encapsulation is a problem period 79 | 80 | ipt -t mangle -I PREROUTING -i vtun+ -p tcp -j MARK --set-mark 0x2/${IPT_MASK} # tcp tunnels need ordering 81 | 82 | # Emanating from router, do a little more optimization 83 | # but don't bother with it too much. 84 | 85 | ipt -t mangle -A OUTPUT -p udp -m multiport --ports 123,53 -j DSCP --set-dscp-class AF42 86 | 87 | #Not clear if the second line is needed 88 | #ipt -t mangle -A OUTPUT -o $IFACE -g QOS_MARK_${IFACE} 89 | 90 | } 91 | 92 | 93 | 94 | cake_egress() 95 | { 96 | $TC qdisc add dev $IFACE root `get_stab_string` $QDISC bandwidth ${CEIL}kbit `get_cake_lla_string` ${EQDISC_OPTS} 97 | } 98 | 99 | egress() { 100 | 101 | CEIL=${UPLINK} 102 | PRIO_RATE=`expr $CEIL / 3` # Ceiling for prioirty 103 | BE_RATE=`expr $CEIL / 6` # Min for best effort 104 | BK_RATE=`expr $CEIL / 6` # Min for background 105 | BE_CEIL=`expr $CEIL - 16` # A little slop at the top 106 | 107 | LQ="quantum `get_htb_quantum $IFACE $CEIL ${ESHAPER_QUANTUM_DUR_US}`" 108 | BURST="`get_htb_burst $IFACE $CEIL ${ESHAPER_BURST_DUR_US}`" 109 | 110 | SILENT=1 $TC qdisc del dev $IFACE root 111 | 112 | case $QDISC in 113 | cake*) cake_egress; return;; 114 | esac 115 | 116 | $TC qdisc add dev $IFACE root handle 1: `get_stab_string` htb default 12 117 | $TC class add dev $IFACE parent 1: classid 1:1 htb $LQ rate ${CEIL}kbit ceil ${CEIL}kbit $BURST `get_htb_adsll_string` 118 | $TC class add dev $IFACE parent 1:1 classid 1:11 htb $LQ rate 128kbit ceil ${PRIO_RATE}kbit prio 1 `get_htb_adsll_string` 119 | $TC class add dev $IFACE parent 1:1 classid 1:12 htb $LQ rate ${BE_RATE}kbit ceil ${BE_CEIL}kbit $BURST prio 2 `get_htb_adsll_string` 120 | $TC class add dev $IFACE parent 1:1 classid 1:13 htb $LQ rate ${BK_RATE}kbit ceil ${BE_CEIL}kbit $BURST prio 3 `get_htb_adsll_string` 121 | 122 | $TC qdisc add dev $IFACE parent 1:11 handle 110: $QDISC \ 123 | `get_limit ${ELIMIT}` `get_target "${ETARGET}" ${UPLINK}` `get_ecn ${EECN}` `get_quantum 300` `get_flows ${PRIO_RATE}` ${EQDISC_OPTS} 124 | $TC qdisc add dev $IFACE parent 1:12 handle 120: $QDISC \ 125 | `get_limit ${ELIMIT}` `get_target "${ETARGET}" ${UPLINK}` `get_ecn ${EECN}` `get_quantum 300` `get_flows ${BE_RATE}` ${EQDISC_OPTS} 126 | $TC qdisc add dev $IFACE parent 1:13 handle 130: $QDISC \ 127 | `get_limit ${ELIMIT}` `get_target "${ETARGET}" ${UPLINK}` `get_ecn ${EECN}` `get_quantum 300` `get_flows ${BK_RATE}` ${EQDISC_OPTS} 128 | 129 | # Need a catchall rule 130 | 131 | $TC filter add dev $IFACE parent 1:0 protocol all prio 999 u32 \ 132 | match ip protocol 0 0x00 flowid 1:12 133 | 134 | # FIXME should probably change the filter here to do pre-nat 135 | 136 | $TC filter add dev $IFACE parent 1:0 protocol ip prio 1 handle 1/${IPT_MASK} fw classid 1:11 137 | $TC filter add dev $IFACE parent 1:0 protocol ip prio 2 handle 2/${IPT_MASK} fw classid 1:12 138 | $TC filter add dev $IFACE parent 1:0 protocol ip prio 3 handle 3/${IPT_MASK} fw classid 1:13 139 | 140 | # ipv6 support. Note that the handle indicates the fw mark bucket that is looked for 141 | 142 | $TC filter add dev $IFACE parent 1:0 protocol ipv6 prio 4 handle 1/${IPT_MASK} fw classid 1:11 143 | $TC filter add dev $IFACE parent 1:0 protocol ipv6 prio 5 handle 2/${IPT_MASK} fw classid 1:12 144 | $TC filter add dev $IFACE parent 1:0 protocol ipv6 prio 6 handle 3/${IPT_MASK} fw classid 1:13 145 | 146 | # Arp traffic 147 | 148 | $TC filter add dev $IFACE parent 1:0 protocol arp prio 7 handle 1/${IPT_MASK} fw classid 1:11 149 | 150 | # ICMP traffic - Don't impress your friends. Deoptimize to manage ping floods 151 | # better instead 152 | 153 | $TC filter add dev $IFACE parent 1:0 protocol ip prio 8 \ 154 | u32 match ip protocol 1 0xff flowid 1:13 155 | 156 | $TC filter add dev $IFACE parent 1:0 protocol ipv6 prio 9 \ 157 | u32 match ip protocol 1 0xff flowid 1:13 158 | } 159 | 160 | 161 | cake_ingress() 162 | { 163 | CAKEARGS= 164 | [ "$IGNORE_DSCP_INGRESS" = "1" ] && CAKEARGS="$CAKEARGS besteffort" 165 | $TC qdisc add dev $DEV root `get_stab_string` $QDISC bandwidth ${DOWNLINK}kbit \ 166 | $CAKEARGS `get_cake_lla_string` ${IQDISC_OPTS} 167 | 168 | $IP link set dev $DEV up 169 | 170 | # redirect all IP packets arriving in $IFACE to $DEV 171 | 172 | $TC filter add dev $IFACE parent ffff: protocol all prio 10 u32 \ 173 | match u32 0 0 flowid 1:1 action mirred egress redirect dev $DEV 174 | } 175 | 176 | ingress() { 177 | 178 | CEIL=$DOWNLINK 179 | PRIO_RATE=`expr $CEIL / 3` # Ceiling for prioirty 180 | BE_RATE=`expr $CEIL / 6` # Min for best effort 181 | BK_RATE=`expr $CEIL / 6` # Min for background 182 | BE_CEIL=`expr $CEIL - 16` # A little slop at the top 183 | 184 | LQ="quantum `get_htb_quantum $IFACE $CEIL ${ISHAPER_QUANTUM_DUR_US}`" 185 | BURST="`get_htb_burst $IFACE $CEIL ${ISHAPER_BURST_DUR_US}`" 186 | 187 | SILENT=1 $TC qdisc del dev $IFACE handle ffff: ingress 188 | $TC qdisc add dev $IFACE handle ffff: ingress 189 | 190 | SILENT=1 $TC qdisc del dev $DEV root 191 | 192 | case $QDISC in 193 | cake*) cake_ingress; return ;; 194 | esac 195 | 196 | if [ "$IGNORE_DSCP_INGRESS" = "1" ]; then 197 | sqm_debug "Do not perform DSCP based filtering on ingress. (1-tier classification)" 198 | $TC qdisc add dev $DEV root handle 1: `get_stab_string` htb default 10 199 | $TC class add dev $DEV parent 1: classid 1:1 htb $LQ rate ${DOWNLINK}kbit ceil ${DOWNLINK}kbit $BURST `get_htb_adsll_string` 200 | $TC class add dev $DEV parent 1:1 classid 1:10 htb $LQ rate ${DOWNLINK}kbit ceil ${DOWNLINK}kbit $BURST prio 0 `get_htb_adsll_string` 201 | $TC qdisc add dev $DEV parent 1:10 handle 110: $QDISC \ 202 | `get_limit ${ILIMIT}` `get_target "${ITARGET}" ${DOWNLINK}` `get_ecn ${IECN}` `get_flows ${DOWNLINK}` ${IQDISC_OPTS} 203 | else 204 | sqm_debug "Perform DSCP based filtering on ingress. (3-tier classification)" 205 | $TC qdisc add dev $DEV root handle 1: `get_stab_string` htb default 12 206 | $TC class add dev $DEV parent 1: classid 1:1 htb $LQ rate ${CEIL}kbit ceil ${CEIL}kbit $BURST `get_htb_adsll_string` 207 | $TC class add dev $DEV parent 1:1 classid 1:11 htb $LQ rate 32kbit ceil ${PRIO_RATE}kbit prio 1 `get_htb_adsll_string` 208 | $TC class add dev $DEV parent 1:1 classid 1:12 htb $LQ rate ${BE_RATE}kbit ceil ${BE_CEIL}kbit $BURST prio 2 `get_htb_adsll_string` 209 | $TC class add dev $DEV parent 1:1 classid 1:13 htb $LQ rate ${BK_RATE}kbit ceil ${BE_CEIL}kbit $BURST prio 3 `get_htb_adsll_string` 210 | 211 | $TC qdisc add dev $DEV parent 1:11 handle 110: $QDISC \ 212 | `get_limit ${ILIMIT}` `get_target "${ITARGET}" ${DOWNLINK}` `get_ecn ${IECN}` `get_quantum 500` `get_flows ${PRIO_RATE}` ${IQDISC_OPTS} 213 | $TC qdisc add dev $DEV parent 1:12 handle 120: $QDISC \ 214 | `get_limit ${ILIMIT}` `get_target "${ITARGET}" ${DOWNLINK}` `get_ecn ${IECN}` `get_quantum 1500` `get_flows ${BE_RATE}` ${IQDISC_OPTS} 215 | $TC qdisc add dev $DEV parent 1:13 handle 130: $QDISC \ 216 | `get_limit ${ILIMIT}` `get_target "${ITARGET}" ${DOWNLINK}` `get_ecn ${IECN}` `get_quantum 300` `get_flows ${BK_RATE}` ${IQDISC_OPTS} 217 | 218 | diffserv $DEV 219 | fi 220 | 221 | $IP link set dev $DEV up 222 | 223 | # redirect all IP packets arriving in $IFACE to $DEV 224 | 225 | $TC filter add dev $IFACE parent ffff: protocol all prio 10 u32 \ 226 | match u32 0 0 flowid 1:1 action mirred egress redirect dev $DEV 227 | 228 | } 229 | 230 | sqm_prepare_script() { 231 | do_modules 232 | verify_qdisc "htb" || return 1 233 | verify_iptables || return 1 234 | ipt_setup 235 | } 236 | -------------------------------------------------------------------------------- /src/functions.sh: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # (sqm) functions.sh 3 | # 4 | # These are all helper functions for various parts of SQM scripts. If you want 5 | # to play around with your own shaper-qdisc-filter configuration look here for 6 | # ready made tools, or examples start of on your own. 7 | # 8 | # Please note the SQM logger function is broken down into levels of logging. 9 | # Use only levels appropriate to touch points in your script and realize the 10 | # potential to overflow SYSLOG. 11 | # 12 | ################################################################################ 13 | # 14 | # This program is free software; you can redistribute it and/or modify 15 | # it under the terms of the GNU General Public License version 2 as 16 | # published by the Free Software Foundation. 17 | # 18 | # Copyright (C) 2012-2019 19 | # Michael D. Taht, Toke Høiland-Jørgensen, Sebastian Moeller 20 | # Eric Luehrsen 21 | # 22 | ################################################################################ 23 | 24 | # Logging verbosity 25 | VERBOSITY_SILENT=0 26 | VERBOSITY_ERROR=1 27 | VERBOSITY_WARNING=2 28 | VERBOSITY_INFO=5 29 | VERBOSITY_DEBUG=8 30 | VERBOSITY_TRACE=10 31 | 32 | sqm_logger() { 33 | local level_min 34 | local level_max 35 | local debug 36 | 37 | case $1 in 38 | ''|*[!0-9]*) LEVEL=$VERBOSITY_INFO ;; # empty or non-numbers 39 | *) LEVEL=$1; shift ;; 40 | esac 41 | 42 | level_min=${SQM_VERBOSITY_MIN:-$VERBOSITY_SILENT} 43 | level_max=${SQM_VERBOSITY_MAX:-$VERBOSITY_INFO} 44 | debug=${SQM_DEBUG:-0} 45 | 46 | if [ "$level_max" -ge "$LEVEL" ] && [ "$level_min" -le "$LEVEL" ] ; then 47 | if [ "$SQM_SYSLOG" -eq "1" ]; then 48 | logger -t SQM -s "$*" 49 | else 50 | echo "$@" >&2 51 | fi 52 | fi 53 | 54 | # this writes into SQM_START_LOG or SQM_STOP_LOG, log files are trucated in 55 | # start-sqm/stop-sqm respectively and should only take little space 56 | if [ "$debug" -eq "1" ]; then 57 | echo "$@" >> "${SQM_DEBUG_LOG}" 58 | fi 59 | } 60 | 61 | sqm_error() { sqm_logger $VERBOSITY_ERROR ERROR: "$@"; } 62 | sqm_warn() { sqm_logger $VERBOSITY_WARNING WARNING: "$@"; } 63 | sqm_log() { sqm_logger $VERBOSITY_INFO "$@"; } 64 | sqm_debug() { sqm_logger $VERBOSITY_DEBUG "$@"; } 65 | sqm_trace() { sqm_logger $VERBOSITY_TRACE "$@"; } 66 | 67 | 68 | # Inspired from https://stackoverflow.com/questions/85880/determine-if-a-function-exists-in-bash 69 | #fn_exists() { LC_ALL=C type $1 | grep -q 'is a function'; } 70 | fn_exists() { 71 | local FN_CANDIDATE 72 | local CUR_LC_ALL 73 | local TYPE_OUTPUT 74 | local RET 75 | FN_CANDIDATE=$1 76 | # check that a candidate nme was given 77 | if [ -z "${FN_CANDIDATE}" ]; then 78 | sqm_error "fn_exists: no function name specified as first argument." 79 | return 1 80 | fi 81 | sqm_debug "fn_exists: function candidate name: ${FN_CANDIDATE}" 82 | 83 | # extract the textual type description 84 | TYPE_OUTPUT=$( LC_ALL=C type $1 2>&1 ) 85 | sqm_debug "fn_exists: TYPE_OUTPUT: $TYPE_OUTPUT" 86 | 87 | # OpenWrt (2019) returns 'is a function' 88 | # Debian Buster/raspbian returns 'is a shell function' 89 | # let's just hope no Linux system reurn 'is a shell builtin function' 90 | echo ${TYPE_OUTPUT} | grep -q 'is .*function' 91 | RET=$? 92 | 93 | sqm_debug "fn_exists: return value: ${RET}" 94 | return ${RET} 95 | } 96 | 97 | 98 | # Transaction logging for ipt rules to allow for gracefull final teardown 99 | ipt_log_restart() { 100 | [ -f "$IPT_TRANS_LOG" ] && rm -f "$IPT_TRANS_LOG" 101 | } 102 | 103 | 104 | # Function to negate iptables commands. Turns addition and insertion into 105 | # deletion, and creation of new chains into deletion 106 | # Its output has quotes around all parameters so we can preserve arguments 107 | # containing whitespace across log file write & re-read 108 | ipt_negate() 109 | { 110 | for var in "$@"; do 111 | case "$var" in 112 | "-A"|"-I") echo -n '"-D" ' ;; 113 | "-N") echo -n '"-X" ' ;; 114 | *) echo -n "\"$var\" " ;; 115 | esac 116 | done 117 | echo "" 118 | } 119 | 120 | ipt_log() 121 | { 122 | echo "$@" >> $IPT_TRANS_LOG 123 | } 124 | 125 | # Split a string containing an iptables command line parameter invocation, then 126 | # run it through ipt(). This is used to turn lines read from the log file, or 127 | # output from ipt_negate() back into proper parameters contained in $@ 128 | ipt_run_split() 129 | { 130 | eval "set -- $1" 131 | ipt "$@" 132 | } 133 | 134 | # Read the transaction log in reverse and execute using ipt to undo changes. 135 | # Since we logged only ipt '-D' commands, ipt won't add them again to the 136 | # transaction log, but will include them in the syslog/debug log. 137 | ipt_log_rewind() { 138 | [ -f "$IPT_TRANS_LOG" ] || return 0 139 | sed -n '1!G;h;$p' "$IPT_TRANS_LOG" | 140 | while read line; do 141 | [ -n "$line" ] || continue 142 | ipt_run_split "$line" 143 | done 144 | 145 | # We just rewound the log, make sure to restart it 146 | ipt_log_restart 147 | } 148 | 149 | ipt() { 150 | local neg 151 | 152 | for var in "$@"; do 153 | case "$var" in 154 | "-A"|"-I"|"-N") 155 | # If the rule is an addition rule, we first run its negation, 156 | # then log that negation to be used by ipt_log_rewind() on 157 | # shutdown 158 | neg="$(ipt_negate "$@")" 159 | ipt_run_split "$neg" 160 | ipt_log "$neg" 161 | ;; 162 | esac 163 | done 164 | 165 | SILENT=1 ${IPTABLES} $IPTABLES_ARGS "$@" 166 | SILENT=1 ${IP6TABLES} $IPTABLES_ARGS "$@" 167 | } 168 | 169 | 170 | # wrapper to call iptables to allow debug logging 171 | iptables_wrapper(){ 172 | cmd_wrapper iptables ${IPTABLES_BINARY} "$@" 173 | } 174 | 175 | # wrapper to call ip6tables to allow debug logging 176 | ip6tables_wrapper(){ 177 | cmd_wrapper ip6tables ${IP6TABLES_BINARY} "$@" 178 | } 179 | 180 | verify_iptables() 181 | { 182 | local ret 183 | ret=0 184 | 185 | if [ -z "$IPTABLES_BINARY" ]; then 186 | sqm_error "No iptables binary found, please install 'iptables' or 'iptables-nft' to use this script" 187 | ret=1 188 | fi 189 | if [ -z "$IP6TABLES_BINARY" ]; then 190 | sqm_error "No ip6tables binary found, please install 'ip6tables' or 'ip6tables-nft' to use this script" 191 | ret=1 192 | fi 193 | return $ret 194 | } 195 | 196 | # wrapper to call tc to allow debug logging 197 | tc_wrapper(){ 198 | cmd_wrapper tc ${TC_BINARY} "$@" 199 | } 200 | 201 | # wrapper to call ip to allow debug logging 202 | ip_wrapper(){ 203 | cmd_wrapper ip ${IP_BINARY} "$@" 204 | } 205 | 206 | # the actual command execution wrapper 207 | cmd_wrapper(){ 208 | # $1: the symbolic name of the command for informative output 209 | # $2: the name of the binary to call (potentially including the full path) 210 | # $3-$end: the actual arguments for $2 211 | local CALLERID 212 | local CMD_BINARY 213 | local LAST_ERROR 214 | local RET 215 | local ERRLOG 216 | 217 | CALLERID=$1 ; shift 1 # extract and remove the id string 218 | CMD_BINARY=$1 ; shift 1 # extract and remove the binary 219 | 220 | # Handle silencing of errors from callers 221 | ERRLOG="sqm_error" 222 | if [ "$SILENT" -eq "1" ]; then 223 | ERRLOG="sqm_debug" 224 | sqm_debug "cmd_wrapper: ${CALLERID}: invocation silenced by request, FAILURE either expected or acceptable." 225 | # The busybox shell doesn't understand the concept of an inline variable 226 | # only applying to a single command, so we need to reset SILENT 227 | # afterwards. Ugly, but it works... 228 | SILENT=0 229 | fi 230 | 231 | sqm_trace "cmd_wrapper: COMMAND: ${CMD_BINARY} $@" 232 | LAST_ERROR=$( ${CMD_BINARY} "$@" 2>&1 ) 233 | RET=$? 234 | 235 | if [ "$RET" -eq "0" ] ; then 236 | sqm_debug "cmd_wrapper: ${CALLERID}: SUCCESS: ${CMD_BINARY} $@" 237 | else 238 | # this went south, try to capture & report more detail 239 | $ERRLOG "cmd_wrapper: ${CALLERID}: FAILURE (${RET}): ${CMD_BINARY} $@" 240 | $ERRLOG "cmd_wrapper: ${CALLERID}: LAST ERROR: ${LAST_ERROR}" 241 | fi 242 | 243 | return $RET 244 | } 245 | 246 | 247 | do_modules() { 248 | for m in $ALL_MODULES; do 249 | [ -d /sys/module/${m} ] || ${INSMOD} $m 2>>${OUTPUT_TARGET} 250 | done 251 | } 252 | 253 | # Write a state file to the filename given as $1. This version will extract all 254 | # variable names defined in defaults.sh and since defaults.sh should contain all 255 | # used variables this should be the complete set. 256 | write_state_file() { 257 | local filename 258 | local awkscript 259 | awkscript='match($0, /[A-Z0-9_]+=/) {print substr($0, RSTART, RLENGTH-1)}' 260 | filename=$1 261 | shift 262 | awk "$awkscript" ${SQM_LIB_DIR}/defaults.sh | sort -u | while read var; do 263 | val=$(eval echo '$'$var) 264 | echo "$var=\"$val\"" 265 | done > $filename 266 | } 267 | 268 | check_state_dir() { 269 | local PERM 270 | local OWNER 271 | 272 | if [ -z "${SQM_STATE_DIR}" ]; then 273 | SQM_DEBUG=0 sqm_error '$SQM_STATE_DIR is unset - check your config!' 274 | exit 1 275 | fi 276 | [ -d "${SQM_STATE_DIR}" ] || ( umask 077; mkdir -p "$SQM_STATE_DIR" ) || exit 1 277 | 278 | if [ ! -w "${SQM_STATE_DIR}" ] || [ ! -x "${SQM_STATE_DIR}" ]; then 279 | SQM_DEBUG=0 sqm_error "Cannot write to state dir '$SQM_STATE_DIR'" 280 | exit 1 281 | fi 282 | 283 | # OpenWrt doesn't have stat by default, and if it does have it, stat is 284 | # usually built without support for the -c parameter. Check for these and 285 | # skip the remaining tests if we don't have a usable 'stat' binary. 286 | (command -v stat && stat -c '%a' /) >/dev/null 2>&1 || return 0 287 | 288 | PERM="0$(stat -L -c '%a' "${SQM_STATE_DIR}")" 289 | if [ "$((PERM & 0002))" -ne 0 ]; then 290 | SQM_DEBUG=0 sqm_error "State dir '$SQM_STATE_DIR' is world writable; this is unsafe, please fix" 291 | exit 1 292 | fi 293 | OWNER="$(stat -L -c '%u' "${SQM_STATE_DIR}")" 294 | if [ "$OWNER" -ne "$(id -u)" ]; then 295 | SQM_DEBUG=0 sqm_error "State dir '$SQM_STATE_DIR' is owned by a different user; this is unsafe, please fix" 296 | exit 1 297 | fi 298 | } 299 | 300 | 301 | # find the ifb device associated with a specific interface, return nothing of no 302 | # ifb is associated with IF 303 | get_ifb_associated_with_if() { 304 | local CUR_IF 305 | local CUR_IFB 306 | local TMP 307 | CUR_IF=$1 308 | # Stray ' in the comment is a fix for broken editor syntax highlighting 309 | CUR_IFB=$( $TC_BINARY -p filter show parent ffff: dev ${CUR_IF} | grep -o -E ifb'[^)\ ]+' ) # ' 310 | sqm_debug "ifb associated with interface ${CUR_IF}: ${CUR_IFB}" 311 | 312 | # we could not detect an associated IFB for CUR_IF 313 | if [ -z "${CUR_IFB}" ]; then 314 | TMP=$( $TC_BINARY -p filter show parent ffff: dev ${CUR_IF} ) 315 | if [ ! -z "${TMP}" ]; then 316 | # oops, there is output but we failed to properly parse it? Ask for a user report 317 | sqm_error "#---- CUT HERE ----#" 318 | sqm_error "get_ifb_associated_with_if failed to extrect the ifb name from:" 319 | sqm_error $( $TC_BINARY -p filter show parent ffff: dev ${CUR_IF} ) 320 | sqm_error "Please report this as an issue at https://github.com/tohojo/sqm-scripts" 321 | sqm_error "Please copy and paste everything below the cut-here line into your issue report, thanks." 322 | else 323 | sqm_debug "Currently no ifb is associated with ${CUR_IF}, this is normal during starting of the sqm system." 324 | fi 325 | fi 326 | echo ${CUR_IFB} 327 | } 328 | 329 | ifb_name() { 330 | local CUR_IF 331 | local MAX_IF_NAME_LENGTH 332 | local IFB_PREFIX 333 | local NEW_IFB 334 | CUR_IF=$1 335 | MAX_IF_NAME_LENGTH=15 336 | IFB_PREFIX="ifb4" 337 | NEW_IFB=$( echo -n "${IFB_PREFIX}${CUR_IF}" | head -c $MAX_IF_NAME_LENGTH ) 338 | 339 | echo ${NEW_IFB} 340 | } 341 | 342 | # if required 343 | create_new_ifb_for_if() { 344 | local NEW_IFB 345 | NEW_IFB=$(ifb_name $1) 346 | create_ifb ${NEW_IFB} 347 | RET=$? 348 | echo $NEW_IFB 349 | return $RET 350 | } 351 | 352 | 353 | # TODO: report failures 354 | create_ifb() { 355 | local CUR_IFB 356 | CUR_IFB=${1} 357 | $IP link add name ${CUR_IFB} type ifb 358 | } 359 | 360 | delete_ifb() { 361 | local CUR_IFB 362 | CUR_IFB=${1} 363 | $IP link set dev ${CUR_IFB} down 364 | $IP link delete ${CUR_IFB} type ifb 365 | } 366 | 367 | 368 | # the best match is either the IFB already associated with the current interface 369 | # or a new named IFB 370 | get_ifb_for_if() { 371 | local CUR_IF 372 | local CUR_IFB 373 | CUR_IF=$1 374 | # if an ifb is already associated return that 375 | CUR_IFB=$( get_ifb_associated_with_if ${CUR_IF} ) 376 | [ -z "$CUR_IFB" ] && CUR_IFB=$( create_new_ifb_for_if ${CUR_IF} ) 377 | [ -z "$CUR_IFB" ] && sqm_warn "Could not find existing IFB for ${CUR_IF}, nor create a new IFB instead..." 378 | echo ${CUR_IFB} 379 | } 380 | 381 | 382 | # Verify that a qdisc works, and optionally that it is part of a set of 383 | # supported qdiscs. If passed a $2, this function will first check if $1 is in 384 | # that (space-separated) list and return an error if it's not. 385 | # 386 | # note the ingress qdisc is different in that it requires tc qdisc replace dev 387 | # tmp_ifb ingress instead of "root ingress" 388 | verify_qdisc() { 389 | local qdisc 390 | local supported 391 | local ifb 392 | local root_string 393 | local args 394 | local IFB_MTU 395 | local found 396 | local randnum 397 | qdisc=$1 398 | supported="$2" 399 | randnum=$(tr -cd 0-9a-f < /dev/urandom 2>/dev/null | head -c 5) 400 | ifb=SQM_IFB_$randnum 401 | root_string="root" # this works for most qdiscs 402 | args="" 403 | IFB_MTU=1514 404 | 405 | if [ -n "$supported" ]; then 406 | found=0 407 | for q in $supported; do 408 | [ "$qdisc" = "$q" ] && found=1 409 | done 410 | [ "$found" -eq "1" ] || return 1 411 | fi 412 | create_ifb $ifb || return 1 413 | 414 | 415 | case $qdisc in 416 | #ingress is special 417 | ingress) root_string="" ;; 418 | #cannot instantiate tbf without args 419 | tbf) 420 | IFB_MTU=$( get_mtu $ifb ) 421 | IFB_MTU=$(( ${IFB_MTU} + 14 )) # TBF's warning is confused, it says MTU but it checks MTU + 14 422 | args="limit 1 burst ${IFB_MTU} rate 1kbps" 423 | ;; 424 | esac 425 | 426 | $TC qdisc replace dev $ifb $root_string $qdisc $args 427 | res=$? 428 | if [ "$res" = "0" ] ; then 429 | sqm_debug "QDISC $qdisc is useable." 430 | else 431 | sqm_error "QDISC $qdisc is NOT useable." 432 | fi 433 | delete_ifb $ifb 434 | return $res 435 | } 436 | 437 | 438 | get_htb_adsll_string() { 439 | ADSLL="" 440 | if [ "$LLAM" = "htb_private" -a "$LINKLAYER" != "none" ]; then 441 | # HTB defaults to MTU 1600 and an implicit fixed TSIZE of 256, but HTB 442 | # as of around 3.10.0 does not actually use a table in the kernel 443 | ADSLL="mpu ${STAB_MPU} linklayer ${LINKLAYER} overhead ${OVERHEAD} mtu ${STAB_MTU}" 444 | sqm_debug "ADSLL: ${ADSLL}" 445 | fi 446 | echo ${ADSLL} 447 | } 448 | 449 | get_stab_string() { 450 | local STABSTRING 451 | local TMP_LLAM 452 | STABSTRING="" 453 | TMP_LLAM=${LLAM} 454 | if [ "${LLAM}" = "default" -a "$QDISC" != "cake" ]; then 455 | sqm_debug "LLA: default link layer adjustment method for !cake is tc_stab" 456 | TMP_LLAM="tc_stab" 457 | fi 458 | 459 | if [ "${TMP_LLAM}" = "tc_stab" -a "$LINKLAYER" != "none" ]; then 460 | STABSTRING="stab mtu ${STAB_MTU} tsize ${STAB_TSIZE} mpu ${STAB_MPU} overhead ${OVERHEAD} linklayer ${LINKLAYER}" 461 | sqm_debug "STAB: ${STABSTRING}" 462 | fi 463 | echo ${STABSTRING} 464 | } 465 | 466 | # cake knows how to handle ATM and per packet overhead, so expose and use this... 467 | get_cake_lla_string() { 468 | local STABSTRING 469 | local TMP_LLAM 470 | STABSTRING="" 471 | TMP_LLAM=${LLAM} 472 | if [ "${LLAM}" = "default" -a "$QDISC" = "cake" ]; then 473 | sqm_debug "LLA: default link layer adjustment method for cake is cake" 474 | TMP_LLAM="cake" 475 | fi 476 | 477 | if [ "${TMP_LLAM}" = "cake" -a "${LINKLAYER}" != "none" ]; then 478 | if [ "${LINKLAYER}" = "atm" ]; then 479 | STABSTRING="atm" 480 | fi 481 | 482 | STABSTRING="${STABSTRING} overhead ${OVERHEAD} mpu ${STAB_MPU}" 483 | 484 | sqm_debug "cake link layer adjustments: ${STABSTRING}" 485 | fi 486 | echo ${STABSTRING} 487 | } 488 | 489 | 490 | # centralize the implementation for the default sqm_start sqeuence 491 | # the individual sqm_start function only need to do the individually 492 | # necessary checking. 493 | # This expects the calling script to supply both an egress() and ingress() function 494 | # and will warn if they are missing 495 | sqm_start_default() { 496 | #sqm_error "sqm_start_default" 497 | [ -n "$IFACE" ] || return 1 498 | 499 | # reset the iptables trace log 500 | ipt_log_restart 501 | 502 | if fn_exists sqm_prepare_script ; then 503 | sqm_debug "sqm_start_default: starting sqm_prepare_script" 504 | sqm_prepare_script 505 | else 506 | sqm_debug "sqm_start_default: no sqm_prepare_script function found, proceeding without." 507 | fi 508 | 509 | do_modules 510 | verify_qdisc $QDISC || return 1 511 | sqm_debug "sqm_start_default: Starting ${SCRIPT}" 512 | 513 | [ -z "$DEV" ] && DEV=$( get_ifb_for_if ${IFACE} ) 514 | 515 | if [ "${UPLINK}" -ne 0 ]; 516 | then 517 | CUR_DIRECTION="egress" 518 | fn_exists egress && egress || sqm_warn "sqm_start_default: ${SCRIPT} lacks an egress() function" 519 | #egress 520 | sqm_debug "sqm_start_default: egress shaping activated" 521 | else 522 | sqm_debug "sqm_start_default: egress shaping deactivated" 523 | SILENT=1 $TC qdisc del dev ${IFACE} root 524 | fi 525 | if [ "${DOWNLINK}" -ne 0 ]; 526 | then 527 | CUR_DIRECTION="ingress" 528 | verify_qdisc ingress "ingress" || return 1 529 | fn_exists ingress && ingress || sqm_warn "sqm_start_default: ${SCRIPT} lacks an ingress() function" 530 | #ingress 531 | sqm_debug "sqm_start_default: ingress shaping activated" 532 | else 533 | sqm_debug "sqm_start_default: ingress shaping deactivated" 534 | SILENT=1 $TC qdisc del dev ${DEV} root 535 | SILENT=1 $TC qdisc del dev ${IFACE} ingress 536 | fi 537 | 538 | return 0 539 | } 540 | 541 | 542 | sqm_cleanup() 543 | { 544 | local silent 545 | silent=${1:-0} 546 | 547 | # undo accumulated ipt commands during shutdown 548 | ipt_log_rewind 549 | 550 | [ -n "$CUR_IFB" ] || return 0 551 | 552 | SILENT=$silent $IP link delete dev ${CUR_IFB} type ifb 553 | sqm_debug "${0}: ${CUR_IFB} interface deleted" 554 | } 555 | 556 | 557 | sqm_stop() { 558 | [ "${DOWNLINK}" -ne 0 ] && $TC qdisc del dev $IFACE ingress 559 | $TC qdisc del dev $IFACE root 560 | 561 | sqm_cleanup 562 | } 563 | 564 | # Note this has side effects on the prio variable 565 | # and depends on the interface global too 566 | fc() { 567 | $TC filter add dev $interface protocol ip parent $1 prio $prio u32 match ip tos $2 0xfc classid $3 568 | prio=$(($prio + 1)) 569 | $TC filter add dev $interface protocol ipv6 parent $1 prio $prio u32 match ip6 priority $2 0xfc classid $3 570 | prio=$(($prio + 1)) 571 | } 572 | 573 | 574 | # allow better control over HTB's quantum variable 575 | # this controlls how many bytes htb ties to deque from the current tier before 576 | # switching to the next, if this is large mixing between pririty tiers will be 577 | # lumpy, but at a lower CPU cost. In first approximation quantum should not be 578 | # larger than burst. 579 | get_htb_quantum() { 580 | local HTB_MTU 581 | local BANDWIDTH 582 | local DURATION_US 583 | local MIN_QUANTUM 584 | local QUANTUM 585 | HTB_MTU=$( get_mtu $1 ) 586 | BANDWIDTH=$2 587 | DURATION_US=$3 588 | 589 | sqm_debug "get_htb_quantum: 1: ${1}, 2: ${2}, 3: ${3}" 590 | 591 | if [ -z "${DURATION_US}" ] ; then 592 | DURATION_US=${SHAPER_QUANTUM_DUR_US} # the duration of the burst in microseconds 593 | sqm_warn "get_htb_quantum (by duration): Defaulting to ${DURATION_US} microseconds." 594 | fi 595 | 596 | if [ -n "${HTB_MTU}" -a "${DURATION_US}" -gt "0" ] ; then 597 | QUANTUM=$( get_burst ${HTB_MTU} ${BANDWIDTH} ${DURATION_US} ) 598 | fi 599 | 600 | if [ -z "$QUANTUM" ]; then 601 | MIN_QUANTUM=$(( ${HTB_MTU} + 48 )) # add 48 bytes to MTU for the ovehead 602 | MIN_QUANTUM=$(( ${MIN_QUANTUM} + 47 )) # now do ceil(Min_BURST / 48) * 53 in shell integer arithmic 603 | MIN_QUANTUM=$(( ${MIN_QUANTUM} / 48 )) 604 | MIN_QUANTUM=$(( ${MIN_QUANTUM} * 53 )) # for MTU 1489 to 1536 this will result in MIN_BURST = 1749 Bytes 605 | sqm_warn "get_htb_quantum: 0 bytes quantum will not work, defaulting to one ATM/AAL5 expanded MTU packet with overhead: ${MIN_QUANTUM}" 606 | echo ${MIN_QUANTUM} 607 | else 608 | echo ${QUANTUM} 609 | fi 610 | } 611 | 612 | 613 | 614 | 615 | # try to define the burst parameter in the duration required to transmit a burst 616 | # at the configured bandwidth conceptuallly the matching quantum for this burst 617 | # should be BURST/number_of_tiers to give each htb tier a chance to dequeue into 618 | # each burst, but that most likely will end up with a somewhat too small quantum 619 | # note: to get htb to report the configured burst/cburt one needs to issue the 620 | # following command (for ifbpppoe-wan): 621 | # tc -d class show dev ifb4pppoe-wan 622 | get_burst() { 623 | local MTU 624 | local BANDWIDTH 625 | local SHAPER_BURST_US 626 | local MIN_BURST 627 | local BURST 628 | MTU=$1 629 | BANDWIDTH=$2 # note bandwidth is always given in kbps 630 | SHAPER_BURST_US=$3 631 | 632 | sqm_debug "get_burst: 1: ${1}, 2: ${2}, 3: ${3}" 633 | 634 | if [ -z "${SHAPER_BURST_US}" ] ; then 635 | SHAPER_BURST_US=1000 # the duration of the burst in microseconds 636 | sqm_warn "get_burst (by duration): Defaulting to ${SHAPER_BURST_US} microseconds bursts." 637 | fi 638 | 639 | # let's assume ATM/AAL5 to be the worst case encapsulation 640 | # and 48 Bytes a reasonable worst case per packet overhead 641 | MIN_BURST=$(( ${MTU} + 48 )) # add 48 bytes to MTU for the ovehead 642 | MIN_BURST=$(( ${MIN_BURST} + 47 )) # now do ceil(Min_BURST / 48) * 53 in shell integer arithmic 643 | MIN_BURST=$(( ${MIN_BURST} / 48 )) 644 | MIN_BURST=$(( ${MIN_BURST} * 53 )) # for MTU 1489 to 1536 this will result in MIN_BURST = 1749 Bytes 645 | 646 | # htb/tbf expect burst to be specified in bytes, while bandwidth is in kbps 647 | BURST=$(( ((${SHAPER_BURST_US} * ${BANDWIDTH}) / 8000) )) 648 | 649 | if [ ${BURST} -lt ${MIN_BURST} ] ; then 650 | sqm_log "get_burst (by duration): the calculated burst/quantum size of ${BURST} bytes was below the minimum of ${MIN_BURST} bytes." 651 | BURST=${MIN_BURST} 652 | fi 653 | 654 | sqm_debug "get_burst (by duration): BURST [Byte]: ${BURST}, BANDWIDTH [Kbps]: ${BANDWIDTH}, DURATION [us]: ${SHAPER_BURST_US}" 655 | 656 | echo ${BURST} 657 | } 658 | 659 | 660 | # Create optional burst parameters to leap over CPU interupts when the CPU is 661 | # severly loaded. We need to be conservative though. 662 | get_htb_burst() { 663 | local HTB_MTU 664 | local BANDWIDTH 665 | local DURATION_US 666 | local BURST 667 | HTB_MTU=$( get_mtu $1 ) 668 | BANDWIDTH=$2 669 | DURATION_US=$3 670 | 671 | sqm_debug "get_htb_burst: 1: ${1}, 2: ${2}, 3: ${3}" 672 | 673 | if [ -z "${DURATION_US}" ] ; then 674 | DURATION_US=${SHAPER_BURST_DUR_US} # the duration of the burst in microseconds 675 | sqm_warn "get_htb_burst (by duration): Defaulting to ${SHAPER_BURST_DUR_US} microseconds." 676 | fi 677 | 678 | if [ -n "${HTB_MTU}" -a "${DURATION_US}" -gt "0" ] ; then 679 | BURST=$( get_burst ${HTB_MTU} ${BANDWIDTH} ${DURATION_US} ) 680 | fi 681 | 682 | if [ -z "$BURST" ]; then 683 | sqm_debug "get_htb_burst: Default Burst, HTB will use MTU plus shipping and handling" 684 | else 685 | echo burst $BURST cburst $BURST 686 | fi 687 | } 688 | 689 | # For a default PPPoE link this returns 1492 just as expected but I fear we 690 | # actually need the wire size of the whole thing not so much the MTU 691 | get_mtu() { 692 | CUR_MTU=$(cat /sys/class/net/$1/mtu) 693 | sqm_debug "IFACE: ${1} MTU: ${CUR_MTU}" 694 | echo ${CUR_MTU} 695 | } 696 | 697 | # Set the autoflow variable to 1 if you want to limit the number of flows 698 | # otherwise the default of 1024 will be used for all Xfq_codel qdiscs. 699 | 700 | get_flows() { 701 | case $QDISC in 702 | codel|ns2_codel|pie|*fifo|pfifo_fast) ;; 703 | fq_codel|*fq_codel|sfq) echo flows $( get_flows_count ${1} ) ;; 704 | esac 705 | } 706 | 707 | get_flows_count() { 708 | if [ "${AUTOFLOW}" -eq "1" ]; then 709 | FLOWS=8 710 | [ $1 -gt 999 ] && FLOWS=16 711 | [ $1 -gt 2999 ] && FLOWS=32 712 | [ $1 -gt 7999 ] && FLOWS=48 713 | [ $1 -gt 9999 ] && FLOWS=64 714 | [ $1 -gt 19999 ] && FLOWS=128 715 | [ $1 -gt 39999 ] && FLOWS=256 716 | [ $1 -gt 69999 ] && FLOWS=512 717 | [ $1 -gt 99999 ] && FLOWS=1024 718 | case $QDISC in 719 | codel|ns2_codel|pie|*fifo|pfifo_fast) ;; 720 | fq_codel|*fq_codel|sfq) echo $FLOWS ;; 721 | esac 722 | else 723 | case $QDISC in 724 | codel|ns2_codel|pie|*fifo|pfifo_fast) ;; 725 | fq_codel|*fq_codel|sfq) echo 1024 ;; 726 | esac 727 | fi 728 | } 729 | 730 | # set the target parameter, also try to only take well formed inputs 731 | # Note, the link bandwidth in the current direction (ingress or egress) 732 | # is required to adjust the target for slow links 733 | get_target() { 734 | local CUR_TARGET 735 | local CUR_LINK_KBPS 736 | CUR_TARGET=${1} 737 | CUR_LINK_KBPS=${2} 738 | [ ! -z "$CUR_TARGET" ] && sqm_debug "cur_target: ${CUR_TARGET} cur_bandwidth: ${CUR_LINK_KBPS}" 739 | CUR_TARGET_STRING= 740 | # either e.g. 100ms or auto 741 | CUR_TARGET_VALUE=$( echo ${CUR_TARGET} | grep -o -e \^'[[:digit:]]\+' ) 742 | CUR_TARGET_UNIT=$( echo ${CUR_TARGET} | grep -o -e '[[:alpha:]]\+'\$ ) 743 | 744 | AUTO_TARGET= 745 | UNIT_VALID= 746 | 747 | case $QDISC in 748 | *codel|*pie) 749 | if [ ! -z "${CUR_TARGET_VALUE}" -a ! -z "${CUR_TARGET_UNIT}" ]; 750 | then 751 | case ${CUR_TARGET_UNIT} in 752 | # permissible units taken from: tc_util.c get_time() 753 | s|sec|secs|ms|msec|msecs|us|usec|usecs) 754 | CUR_TARGET_STRING="target ${CUR_TARGET_VALUE}${CUR_TARGET_UNIT}" 755 | UNIT_VALID="1" 756 | ;; 757 | esac 758 | fi 759 | # empty field in GUI or undefined GUI variable now defaults to auto 760 | if [ -z "${CUR_TARGET_VALUE}" -a -z "${CUR_TARGET_UNIT}" ]; 761 | then 762 | if [ ! -z "${CUR_LINK_KBPS}" ]; then 763 | TMP_TARGET_US=$( adapt_target_to_slow_link $CUR_LINK_KBPS ) 764 | TMP_INTERVAL_STRING=$( adapt_interval_to_slow_link $TMP_TARGET_US ) 765 | CUR_TARGET_STRING="target ${TMP_TARGET_US}us ${TMP_INTERVAL_STRING}" 766 | AUTO_TARGET="1" 767 | sqm_debug "get_target defaulting to auto." 768 | else 769 | sqm_warn "required link bandwidth in kbps not passed to get_target()." 770 | fi 771 | fi 772 | # but still allow explicit use of the keyword auto for backward compatibility 773 | case ${CUR_TARGET_UNIT} in 774 | auto|Auto|AUTO) 775 | if [ ! -z "${CUR_LINK_KBPS}" ]; then 776 | TMP_TARGET_US=$( adapt_target_to_slow_link $CUR_LINK_KBPS ) 777 | TMP_INTERVAL_STRING=$( adapt_interval_to_slow_link $TMP_TARGET_US ) 778 | CUR_TARGET_STRING="target ${TMP_TARGET_US}us ${TMP_INTERVAL_STRING}" 779 | AUTO_TARGET="1" 780 | else 781 | sqm_warn "required link bandwidth in kbps not passed to get_target()." 782 | fi 783 | ;; 784 | esac 785 | 786 | case ${CUR_TARGET_UNIT} in 787 | default|Default|DEFAULT) 788 | if [ ! -z "${CUR_LINK_KBPS}" ]; then 789 | CUR_TARGET_STRING="" # return nothing so the default target is not over-ridden... 790 | AUTO_TARGET="1" 791 | sqm_debug "get_target using qdisc default, no explicit target string passed." 792 | else 793 | sqm_warn "required link bandwidth in kbps not passed to get_target()." 794 | fi 795 | ;; 796 | esac 797 | if [ ! -z "${CUR_TARGET}" ]; then 798 | if [ -z "${CUR_TARGET_VALUE}" -o -z "${UNIT_VALID}" ]; then 799 | [ -z "$AUTO_TARGET" ] && sqm_warn "${CUR_TARGET} is not a well formed tc target specifier; e.g.: 5ms (or s, us), or one of the strings auto or default." 800 | fi 801 | fi 802 | ;; 803 | esac 804 | echo $CUR_TARGET_STRING 805 | } 806 | 807 | # for low bandwidth links fq_codels default target of 5ms does not work too well 808 | # so increase target for slow links (note below roughly 2500kbps a single packet 809 | # will take more than 5 ms to be tansfered over the wire) 810 | adapt_target_to_slow_link() { 811 | LINK_BW=$1 812 | # for ATM the worst case expansion including overhead seems to be 33 clls of 813 | # 53 bytes each 814 | MAX_DELAY=$(( 1000 * 1000 * 33 * 53 * 8 / 1000 )) # Max delay in us at 1kbps 815 | TARGET=$(( ${MAX_DELAY} / ${LINK_BW} )) # note this truncates the decimals 816 | 817 | # do not change anything for fast links 818 | [ "$TARGET" -lt 5000 ] && TARGET=5000 819 | case ${QDISC} in 820 | *codel|pie) 821 | echo "${TARGET}" 822 | ;; 823 | esac 824 | } 825 | 826 | # codel looks at a whole interval to figure out wether observed latency stayed 827 | # below target if target >= interval that will not work well, so increase 828 | # interval by the same amonut that target got increased 829 | adapt_interval_to_slow_link() { 830 | TARGET=$1 831 | case ${QDISC} in 832 | *codel) 833 | # Note this is not following codel theory to well as target should 834 | # be 5-10% of interval and the simple addition does not conserve 835 | # that relationship 836 | INTERVAL=$(( (100 - 5) * 1000 + ${TARGET} )) 837 | echo "interval ${INTERVAL}us" 838 | ;; 839 | pie) 840 | ## not sure if pie needs this, probably not 841 | #TUPDATE=$(( (30 - 20) * 1000 + ${TARGET} )) 842 | #echo "tupdate ${TUPDATE}us" 843 | ;; 844 | esac 845 | } 846 | 847 | 848 | # set quantum parameter if available for this qdisc 849 | get_quantum() { 850 | case $QDISC in 851 | *fq_codel|fq_pie|drr) echo quantum $1 ;; 852 | *) ;; 853 | esac 854 | } 855 | 856 | # only show limits to qdiscs that can handle them... 857 | # Note that $LIMIT contains the default limit 858 | get_limit() { 859 | CURLIMIT=$1 860 | case $QDISC in 861 | *codel|*pie|pfifo_fast|sfq|pfifo) [ -z ${CURLIMIT} ] && CURLIMIT=${LIMIT} # global default limit 862 | ;; 863 | bfifo) [ -z "$CURLIMIT" ] && [ ! -z "$LIMIT" ] && CURLIMIT=$(( ${LIMIT} * $( cat /sys/class/net/${IFACE}/mtu ) )) # bfifo defaults to txquelength * MTU, 864 | ;; 865 | *) sqm_warn "qdisc ${QDISC} does not support a limit" 866 | ;; 867 | esac 868 | sqm_debug "get_limit: $1 CURLIMIT: ${CURLIMIT}" 869 | 870 | if [ ! -z "$CURLIMIT" ]; then 871 | echo "limit ${CURLIMIT}" 872 | fi 873 | } 874 | 875 | get_ecn() { 876 | CURECN=$1 877 | case ${CURECN} in 878 | ECN) 879 | case $QDISC in 880 | *codel|*pie|*red) 881 | CURECN=ecn 882 | ;; 883 | *) 884 | CURECN="" 885 | ;; 886 | esac 887 | ;; 888 | NOECN) 889 | case $QDISC in 890 | *codel|*pie|*red) 891 | CURECN=noecn 892 | ;; 893 | *) 894 | CURECN="" 895 | ;; 896 | esac 897 | ;; 898 | *) 899 | sqm_warn "ecn value $1 not handled" 900 | ;; 901 | esac 902 | sqm_debug "get_ECN: $1 CURECN: ${CURECN} IECN: ${IECN} EECN: ${EECN}" 903 | echo ${CURECN} 904 | 905 | } 906 | 907 | # This could be a complete diffserv implementation 908 | 909 | diffserv() { 910 | 911 | interface=$1 912 | prio=1 913 | 914 | # Catchall 915 | 916 | $TC filter add dev $interface parent 1:0 protocol all prio 999 u32 \ 917 | match ip protocol 0 0x00 flowid 1:12 918 | 919 | # Find the most common matches fast 920 | 921 | fc 1:0 0x00 1:12 # BE 922 | fc 1:0 0x20 1:13 # CS1 923 | fc 1:0 0x10 1:11 # IMM 924 | fc 1:0 0xb8 1:11 # EF 925 | fc 1:0 0xc0 1:11 # CS3 926 | fc 1:0 0xe0 1:11 # CS6 927 | fc 1:0 0x90 1:11 # AF42 (mosh) 928 | 929 | # Arp traffic 930 | $TC filter add dev $interface protocol arp parent 1:0 prio $prio handle 500 fw flowid 1:11 931 | 932 | prio=$(($prio + 1)) 933 | } 934 | 935 | eth_setup() { 936 | ethtool -K $IFACE gso off 937 | ethtool -K $IFACE tso off 938 | ethtool -K $IFACE ufo off 939 | ethtool -K $IFACE gro off 940 | 941 | if [ -e /sys/class/net/$IFACE/queues/tx-0/byte_queue_limits ]; then 942 | for i in /sys/class/net/$IFACE/queues/tx-*/byte_queue_limits 943 | do 944 | echo $(( 4 * $( get_mtu ${IFACE} ) )) > $i/limit_max 945 | done 946 | fi 947 | } 948 | --------------------------------------------------------------------------------