Last active
October 31, 2022 00:13
-
-
Save alexforsale/f02615e4c4ac94670c41834d5ea8abf6 to your computer and use it in GitHub Desktop.
My first attempt at creating hfsc shaper for lan & wan
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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