Skip to content

Instantly share code, notes, and snippets.

@alexforsale
Last active October 31, 2022 00:13
Show Gist options
  • Save alexforsale/f02615e4c4ac94670c41834d5ea8abf6 to your computer and use it in GitHub Desktop.
Save alexforsale/f02615e4c4ac94670c41834d5ea8abf6 to your computer and use it in GitHub Desktop.
My first attempt at creating hfsc shaper for lan & wan
#!/usr/bin/env bash
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# <[email protected]>
# TODO: there has to be a better way of doing this, I'm still new to tc and
# nftables.
# references:
# https://github.com/automatthias/hfsc/blob/master/hfsc
#set -x
DEV=${2:-intern0}
IFB=ifb_${DEV}
DOWNLINK=${3:-50000}
UPLINK=${4:-20000}
# in kbit
DOWNRATE=${5:-2000}
UPRATE=${6:-1000}
ACTION=${1}
function set_tc(){
ip link set ${IFB} up
# root, set default to 99
tc qdisc add dev ${DEV} root handle 1: hfsc default 99
tc qdisc add dev ${IFB} root handle 2: hfsc default 99
# main rate limit class
tc class add dev ${DEV} parent 1: classid 1:1 hfsc sc rate $(( UPLINK*98/100 ))kbit ul rate $(( UPLINK*98/100 ))kbit
tc class add dev ${IFB} parent 2: classid 2:1 hfsc sc rate $(( DOWNLINK*98/100 ))kbit ul rate $(( DOWNLINK*98/100 ))kbit
# default: don't guarantee anything for the first two seconds, then guarantee 1/20
tc class add dev ${DEV} parent 1:1 classid 1:99 hfsc \
sc m1 0 d 2s m2 $(( 1*$DOWNLINK/20 ))kbit \
ul rate ${DOWNLINK}kbit
tc class add dev ${IFB} parent 2:1 classid 2:99 hfsc \
sc m1 0 d 2s m2 $(( 1*$UPLINK/20 ))kbit \
ul rate ${UPLINK}kbit
}
function set_nft(){
nft add table inet limiter
nft add chain inet limiter shaper
nft add chain inet limiter ifb_shaper
nft add chain inet limiter post { type filter hook postrouting priority 0 \; policy accept \; }
nft add chain inet limiter forward { type filter hook forward priority 0 \; policy accept \; }
nft add chain inet limiter output { type filter hook output priority 0 \; policy accept \; }
nft insert rule inet limiter post oifname "${DEV}" counter jump shaper
nft insert rule inet limiter forward oifname "${DEV}" counter jump ifb_shaper
nft insert rule inet limiter output oifname "${DEV}" counter jump ifb_shaper
nft "add map inet limiter client_prio { type ipv4_addr : classid ; flags interval; }"
nft "add map inet limiter client_mark { type ipv4_addr : mark ; flags interval; }"
for i in $(seq 100 200); do
nft "add element inet limiter client_prio { 10.0.10.${i} : 1:${i} }"
nft "add element inet limiter client_mark { 10.0.10.${i} : 0x$(printf %x ${i}) }"
done
nft "add rule inet limiter shaper meta priority 0 counter meta priority set ip daddr map @client_prio"
nft add rule inet limiter shaper counter ct mark set mark
nft "add rule inet limiter ifb_shaper meta mark 0 counter meta mark set ip daddr map @client_mark"
nft add rule inet limiter ifb_shaper counter ct mark set mark
}
function tc_by_ip(){
for i in $(seq 100 200);do
tc class add dev ${DEV} parent 1:1 classid 1:${i} hfsc sc rate ${DOWNRATE}kbit ul rate ${DOWNRATE}kbit
tc class add dev ${IFB} parent 2:1 classid 2:${i} hfsc sc rate ${UPRATE}kbit ul rate ${UPRATE}kbit
# mark
tc filter add dev ${IFB} parent 2: protocol ip handle ${i} fw flowid 2:${i}
done
}
function forward_tc(){
# Forward all ingress traffic on internet interface to the IFB device
tc qdisc add dev ${DEV} ingress handle ffff:
tc filter add dev ${DEV} parent ffff: protocol ip \
u32 match u32 0 0 \
action connmark \
action mirred egress redirect dev ${IFB} \
flowid ffff:1
}
function up_ifb(){
if [[ ! $(lsmod | grep ifb) ]];then
modprobe ifb
ip link set ifb0 name ${IFB}
elif [[ ! $(ip link show ${IFB} >/dev/null 2>&1) ]];then
# grab other ifb
other_ifb=($(ip link show | grep ifb'[0-9]' | awk '{print $2}' | sed 's/\://'))
ip link set ${other_ifb} name ${IFB}
fi
ip link set ${IFB} down
}
function stop_everything(){
# reset everything
tc qdisc del dev ${DEV} root &> /dev/null
tc qdisc del dev ${DEV} ingress &> /dev/null
tc qdisc del dev ${IFB} root &> /dev/null
tc qdisc del dev ${IFB} ingress &> /dev/null
ip link set ${IFB} down &> /dev/null
nft delete table inet limiter &> /dev/null
}
case ${ACTION} in
status)
echo "[qdisc]"
tc -s qdisc show dev ${DEV}
tc -s qdisc show dev ${IFB}
echo ""
echo "[class]"
tc -s class show dev ${DEV}
tc -s class show dev ${IFB}
echo ""
echo [filter]
# only ${IFB}
tc -s filter show dev ${IFB}
exit
;;
stop)
stop_everything
echo "Shaping removed on ${DEV}"
exit
;;
start)
stop_everything
up_ifb
set_tc
set_nft
tc_by_ip
forward_tc
;;
*)
echo "$(basename $0) [ACTION]"
echo "ACTION := { start [device] [downlink] [uplink] [downrate] [uprate] | stop | status }"
echo "device is for lan device"
echo "downlink/uplink is the device max [kbit]"
echo "downrate/uprate is limiter per ip [kbit]"
echo "if no parameters given (beside action),default is in the code"
echo "also, subnet is hardcoded atm"
exit
;;
esac
#!/usr/bin/env bash
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#set -x
DEV=${2:-extern0}
IFB=ifb_${DEV}
UPLINK=${3:-20000}
DOWNLINK=${4:-50000}
TCP_INTERACTIVE="ftp ssh domain mdns ldap ldaps mysql rndc 53000 domain-s"
UDP_INTERACTIVE="ftp ssh domain mdns ldap ldaps mysql 53000 ntp domain-s"
TCP_BROWSING="http https http-alt pcsync-https"
UDP_QUIC="http https"
TCP_LOWPRIO="xmpp-client"
UDP_LOWPRIO="ssdp vcom-tunnel"
TCP_LOWLATENCY="7889 17500 18081 5500-5700 8001 30000-30300 9000-9010"
UDP_LOWLATENCY="8011 9030 10010-10650 11000-14000 20000-20002 17000 5000-5200 5500-5700 30000-30300 9000-9010"
if [[ ! $(lsmod | grep ifb) ]];then
modprobe ifb
ip link set ifb0 name ${IFB}
elif [[ ! $(ip link show ${IFB}) ]];then
# grab other ifb
other_ifb=($(ip link show | grep ifb'[0-9]' | awk '{print $2}' | sed 's/\://'))
ip link set ${other_ifb} name ${IFB}
fi
ip link set ${IFB} down
# reset everything
tc qdisc del dev ${DEV} root 2>/dev/null
tc qdisc del dev ${DEV} ingress 2>/dev/null
tc qdisc del dev ${IFB} root 2>/dev/null
tc qdisc del dev ${IFB} ingress 2>/dev/null
nft delete table inet qos > /dev/null 2>&1
if [[ ${1} == "stop" ]];then
exit 0
fi
# Traffic classes:
# 1:1 Main rate limit class
# 1:2 Interactive
# 1:3 low latency
# 1:4 Browsing etc
# 1:5 low priority
# root, set default to 99
tc qdisc add dev ${DEV} root handle 1: hfsc default 99
# main rate limit class
tc class add dev ${DEV} parent 1: classid 1:1 hfsc sc rate $(( UPLINK*98/100 ))kbit ul rate $(( UPLINK*98/100 ))kbit
# Interactive traffic: guarantee realtime full uplink for 50ms, then 5/10 of the uplink
tc class add dev ${DEV} parent 1:1 classid 1:2 hfsc \
rt m1 ${UPLINK}kbit d 50ms m2 $(( 5*$UPLINK/10 ))kbit \
ls m1 ${UPLINK}kbit d 50ms m2 $(( 7*$UPLINK/10 ))kbit \
ul rate ${UPLINK}kbit
# low latency (voip etc)
# guarantee full uplink for 200ms, then 3/10
tc class add dev ${DEV} parent 1:1 classid 1:3 hfsc \
sc m1 ${UPLINK}kbit d 200ms m2 $(( 3*$UPLINK/10 ))kbit \
ul rate ${UPLINK}kbit
# Browsing: Don't guarantee anything for the first second, then guarantee 1/10
tc class add dev ${DEV} parent 1:1 classid 1:4 hfsc \
sc m1 0 d 1s m2 $(( 1*$UPLINK/10 ))kbit \
ul rate ${UPLINK}kbit
# low priority don't guarantee anything for the first 10 seconds, then guarantee 1/20
tc class add dev ${DEV} parent 1:1 classid 1:5 hfsc \
sc m1 0 d 10s m2 $(( 1*$UPLINK/20 ))kbit \
ul rate ${UPLINK}kbit
# default: don't guarantee anything for the first two seconds, then guarantee 1/20
tc class add dev ${DEV} parent 1:1 classid 1:99 hfsc \
sc m1 0 d 2s m2 $(( 1*$UPLINK/20 ))kbit \
ul rate ${UPLINK}kbit
# Do the same for ingress
ip link set ${IFB} up
# root, set default to 99
tc qdisc add dev ${IFB} root handle 2: hfsc default 99
# main rate limit class
tc class add dev ${IFB} parent 2: classid 2:1 hfsc sc rate $(( DOWNLINK*98/100 ))kbit ul rate $(( DOWNLINK*98/100 ))kbit
# Interactive traffic: guarantee realtime full uplink for 50ms, then 5/10 of the uplink
tc class add dev ${IFB} parent 2:1 classid 2:2 hfsc \
rt m1 ${DOWNLINK}kbit d 50ms m2 $(( 5*$UPLINK/10 ))kbit \
ls m1 ${DOWNLINK}kbit d 50ms m2 $(( 7*$UPLINK/10 ))kbit \
ul rate ${DOWNLINK}kbit
# low latency (voip etc)
# guarantee full uplink for 200ms, then 3/10
tc class add dev ${IFB} parent 2:1 classid 2:3 hfsc \
sc m1 ${DOWNLINK}kbit d 200ms m2 $(( 3*$UPLINK/10 ))kbit \
ul rate ${DOWNLINK}kbit
# Browsing: Don't guarantee anything for the first second, then guarantee 1/10
tc class add dev ${IFB} parent 2:1 classid 2:4 hfsc \
sc m1 0 d 1s m2 $(( 1*$DOWNLINK/10 ))kbit \
ul rate ${DOWNLINK}kbit
# low priority don't guarantee anything for the first 10 seconds, then guarantee 1/20
tc class add dev ${IFB} parent 2:1 classid 2:5 hfsc \
sc m1 0 d 10s m2 $(( 1*$DOWNLINK/20 ))kbit \
ul rate ${DOWNLINK}kbit
# default: don't guarantee anything for the first two seconds, then guarantee 1/20
tc class add dev ${IFB} parent 2:1 classid 2:99 hfsc \
sc m1 0 d 2s m2 $(( 1*$DOWNLINK/20 ))kbit \
ul rate ${DOWNLINK}kbit
# mark
tc filter add dev ${IFB} parent 2: protocol ip handle 2 fw flowid 2:2
tc filter add dev ${IFB} parent 2: protocol ip handle 3 fw flowid 2:3
tc filter add dev ${IFB} parent 2: protocol ip handle 4 fw flowid 2:4
tc filter add dev ${IFB} parent 2: protocol ip handle 5 fw flowid 2:5
# Forward all ingress traffic on internet interface to the IFB device
tc qdisc add dev ${DEV} ingress handle ffff:
tc filter add dev ${DEV} parent ffff: protocol ip \
u32 match u32 0 0 \
action connmark \
action mirred egress redirect dev ${IFB} \
flowid ffff:1
# nftables stuffs
nft add table inet qos
nft add chain inet qos shaper
nft add chain inet qos ifb_shaper
nft add chain inet qos post { type filter hook postrouting priority 0 \; policy accept \; }
nft add chain inet qos forward { type filter hook forward priority 0 \; policy accept \; }
nft add chain inet qos output { type filter hook output priority 0 \; policy accept \; }
nft insert rule inet qos post oifname "${DEV}" counter jump shaper
nft insert rule inet qos forward oifname "${DEV}" counter jump ifb_shaper
nft insert rule inet qos output oifname "${DEV}" counter jump ifb_shaper
# To speed up downloads while an upload is going on, put short ACK packets in the interactive class:
nft add rule "inet qos shaper tcp flags & (fin|syn|rst|ack) == ack meta length 0-64 counter meta priority set 1:2"
nft add rule "inet qos ifb_shaper tcp flags & (fin|syn|rst|ack) == ack meta length 0-64 counter meta mark set 0x2"
# put large (512+) icmp packets in browsing category
nft add rule "inet qos shaper ip protocol icmp meta length 512-65535 counter meta priority set 1:4"
nft add rule "inet qos ifb_shaper ip protocol icmp meta length 512-65535 counter meta mark set 0x4"
# ICMP (ip protocol 1) in the interactive class
nft add rule "inet qos shaper ip protocol icmp meta length 0-512 counter meta priority set 1:2"
nft add rule "inet qos ifb_shaper ip protocol icmp meta length 0-512 counter meta mark set 0x2"
nft add rule inet qos shaper counter ct mark set mark
nft add rule inet qos ifb_shaper counter ct mark set mark
nft "add map inet qos tcp_priorities { type inet_service : classid ; flags interval ; }"
nft "add map inet qos udp_priorities { type inet_service : classid ; flags interval ; }"
nft "add map inet qos tcp_marks { type inet_service : mark ; flags interval ; }"
nft "add map inet qos udp_marks { type inet_service : mark ; flags interval ; }"
nft "add rule inet qos shaper meta priority 0 counter meta priority set tcp dport map @tcp_priorities"
nft "add rule inet qos shaper meta priority 0 counter meta priority set udp dport map @udp_priorities"
nft "add rule inet qos ifb_shaper meta mark 0 counter meta mark set tcp dport map @tcp_marks"
nft "add rule inet qos ifb_shaper meta mark 0 counter meta mark set udp dport map @udp_marks"
for port in ${TCP_INTERACTIVE}; do
nft "add element inet qos tcp_priorities { ${port} : 1:2 }"
nft "add element inet qos tcp_marks { ${port} : 0x2 }"
done
for port in ${UDP_INTERACTIVE}; do
nft "add element inet qos udp_priorities { ${port} : 1:2 }"
nft "add element inet qos udp_marks { ${port} : 0x2 }"
done
for port in ${TCP_LOWLATENCY}; do
nft "add element inet qos tcp_priorities { ${port} : 1:3 }"
nft "add element inet qos tcp_marks { ${port} : 0x3 }"
done
for port in ${UDP_LOWLATENCY}; do
nft "add element inet qos udp_priorities { ${port} : 1:3 }"
nft "add element inet qos udp_marks { ${port} : 0x3 }"
done
for port in ${TCP_BROWSING}; do
nft "add element inet qos tcp_priorities { ${port} : 1:4 }"
nft "add element inet qos tcp_marks { ${port} : 0x4 }"
done
for port in ${TCP_LOWPRIO}; do
nft "add element inet qos tcp_priorities { ${port} : 1:5 }"
nft "add element inet qos tcp_marks { ${port} : 0x5 }"
done
for port in ${UDP_LOWPRIO}; do
nft "add element inet qos udp_priorities { ${port} : 1:5 }"
nft "add element inet qos udp_marks { ${port} : 0x5 }"
done
for port in ${UDP_QUIC}; do
nft "add element inet qos udp_priorities { ${port} : 1:5 }"
nft "add element inet qos udp_marks { ${port} : 0x5 }"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment