diff options
author | Rosy Song <rosysong@rosinson.com> | 2018-08-03 12:17:59 +0800 |
---|---|---|
committer | Rosy Song <rosysong@rosinson.com> | 2018-11-06 16:13:28 +0800 |
commit | 6649ac91e9ebaf92b1a8f7fb51147c04b437aed2 (patch) | |
tree | 319c942202a063b187f60e25404eb9192215c753 /net/nft-qos/files/lib | |
parent | 9753c21d5c73a2a53a866fd8ee706acdddf90d6d (diff) |
nft-qos: add new package
This is the nftables implementation for qos on OpenWrt,
Currently, it has below features:
* Static QoS : setting limit rate for devices or global network.
* Dynamic/Auto QoS : setting limit rate according to the network
bandwidth and adjust itself automatically (hotplug event).
* Traffic Priority : this feature is like traffic shaping under tc,
it uses ingress hook to handle to packets here.
Signed-off-by: Rosy Song <rosysong@rosinson.com>
Diffstat (limited to 'net/nft-qos/files/lib')
-rw-r--r-- | net/nft-qos/files/lib/core.sh | 93 | ||||
-rw-r--r-- | net/nft-qos/files/lib/dynamic.sh | 89 | ||||
-rw-r--r-- | net/nft-qos/files/lib/monitor.sh | 39 | ||||
-rw-r--r-- | net/nft-qos/files/lib/priority.sh | 90 | ||||
-rw-r--r-- | net/nft-qos/files/lib/static.sh | 73 |
5 files changed, 384 insertions, 0 deletions
diff --git a/net/nft-qos/files/lib/core.sh b/net/nft-qos/files/lib/core.sh new file mode 100644 index 000000000..d3c9d641f --- /dev/null +++ b/net/nft-qos/files/lib/core.sh @@ -0,0 +1,93 @@ +#!/bin/sh +# +# Copyright (C) 2018 rosysong@rosinson.com +# + +# for uci_validate_section() +. /lib/functions/procd.sh + +NFT_QOS_HAS_BRIDGE= +NFT_QOS_INET_FAMILY=ip +NFT_QOS_SCRIPT_TEXT= +NFT_QOS_SCRIPT_FILE=/tmp/qos.nft + +qosdef_appendx() { # <string to be appended> + NFT_QOS_SCRIPT_TEXT="$NFT_QOS_SCRIPT_TEXT""$1" +} + +qosdef_append_chain_def() { # <type> <hook> <priority> <policy> + qosdef_appendx "\t\ttype $1 hook $2 priority $3; policy $4;\n" +} + +qosdef_append_chain_ingress() { # <type> <device> <priority> <policy> + qosdef_appendx "\t\ttype $1 hook ingress device $2 priority $3; policy $4;\n" +} + +# qosdef_append_rule_{MATCH}_{STATEMENT} +qosdef_append_rule_ip_limit() { # <ipaddr> <operator> <unit> <rate> + local ipaddr=$1 + local operator=$2 + local unit=$3 + local rate=$4 + + qosdef_appendx \ + "\t\tip $operator $ipaddr limit rate over $rate $unit/second drop\n" +} + +# qosdef_append_rule_{MATCH}_{POLICY} +qosdef_append_rule_ip_policy() { # <operator> <ipaddr> <policy> + qosdef_appendx "\t\tip $1 $2 $3\n" +} + +_handle_limit_whitelist() { # <value> <chain> + local ipaddr=$1 + local operator + + [ -z "$ipaddr" ] && return + + case "$2" in + download) operator="daddr";; + upload) operator="saddr";; + esac + + qosdef_append_rule_ip_policy $operator $ipaddr accept +} + +qosdef_append_rule_limit_whitelist() { # <chain> + config_list_foreach default limit_whitelist _handle_limit_whitelist $1 +} + +qosdef_flush_table() { # <family> <table> + nft flush table $1 $2 2>/dev/null +} + +qosdef_remove_table() { # <family> <table> + nft delete table $1 $2 2>/dev/null +} + +qosdef_init_header() { # add header for nft script + qosdef_appendx "#!/usr/sbin/nft -f\n" + qosdef_appendx "# Copyright (C) 2018 rosysong@rosinson.com\n" + qosdef_appendx "#\n\n" +} + +qosdef_init_env() { + # check interface type of lan + local lt="$(uci_get "network.lan.type")" + [ "$lt" = "bridge" ] && export NFT_QOS_HAS_BRIDGE="y" + + # check if ipv6 support + [ -e /proc/sys/net/ipv6 ] && export NFT_QOS_INET_FAMILY="inet" +} + +qosdef_clean_cache() { + rm -f $NFT_QOS_SCRIPT_FILE +} + +qosdef_init_done() { + echo -e $NFT_QOS_SCRIPT_TEXT > $NFT_QOS_SCRIPT_FILE 2>/dev/null +} + +qosdef_start() { + nft -f $NFT_QOS_SCRIPT_FILE 2>/dev/null +} diff --git a/net/nft-qos/files/lib/dynamic.sh b/net/nft-qos/files/lib/dynamic.sh new file mode 100644 index 000000000..960ca5282 --- /dev/null +++ b/net/nft-qos/files/lib/dynamic.sh @@ -0,0 +1,89 @@ +#!/bin/sh +# +# Copyright (C) 2018 rosysong@rosinson.com +# + +. /lib/nft-qos/core.sh + +# return average rate for dhcp leases +qosdef_dynamic_rate() { # <bandwidth> + local c=0 c6=0 + + [ ! -e /tmp/dhcp.leases -a \ + ! -e /var/dhcp6.leases ] && return + + [ -e /tmp/dhcp.leases ] && \ + c=$(wc -l < /tmp/dhcp.leases 2>/dev/null) + [ -e /var/dhcp6.leases ] && \ + c6=$(wc -l < /var/dhcp6.leases 2>/dev/null) + [ $c -eq 0 -a $c6 -eq 0 ] && \ + { echo 12500; return; } + + echo $(($1 / ($c + $c6))) +} + +qosdef_append_chain_dym() { # <hook> <name> <bandwidth> + local cidr cidr6 + local operator rate + local hook=$1 name=$2 bandwidth=$3 + + config_get cidr default 'dynamic_cidr' + config_get cidr6 default 'dynamic_cidr6' + + [ -z "$cidr" -a -z "$cidr6" ] && return + + case "$2" in + download) operator=daddr;; + upload) operator=saddr;; + esac + + rate=$(qosdef_dynamic_rate $bandwidth) + + qosdef_appendx "\tchain $name {\n" + qosdef_append_chain_def filter $hook 0 accept + qosdef_append_rule_limit_whitelist $name + [ -n "$cidr" ] && \ + qosdef_append_rule_ip_limit $cidr $operator kbytes $rate + [ -n "$cidr6" ] && \ + qosdef_append_rule_ip_limit $cidr6 $operator kbytes $rate + qosdef_appendx "\t}\n" +} + +qosdef_flush_dynamic() { + qosdef_flush_table "$NFT_QOS_INET_FAMILY" nft-qos-dynamic +} + +# init dynamic qos +qosdef_init_dynamic() { + local dynamic_bw_up dynamic_bw_down limit_enable limit_type + local hook_ul="input" hook_dl="postrouting" + + uci_validate_section nft-qos default default \ + 'limit_enable:bool:0' \ + 'limit_type:maxlength(8)' \ + 'dynamic_bw_up:uinteger:100' \ + 'dynamic_bw_down:uinteger:100' + + [ $? -ne 0 ] && { + logger -t nft-qos-dynamic "validation failed" + return 1 + } + + [ $limit_enable -eq 0 -o \ + "$limit_type" = "static" ] && return 1 + + # Transfer mbits/s to mbytes/s + # e.g. 100,000 kbits == 12,500 kbytes + dynamic_bw_up=$(($dynamic_bw_up * 1000 / 8)) + dynamic_bw_down=$(($dynamic_bw_down * 1000 / 8)) + + [ -z "$NFT_QOS_HAS_BRIDGE" ] && { + hook_ul="postrouting" + hook_dl="input" + } + + qosdef_appendx "table $NFT_QOS_INET_FAMILY nft-qos-dynamic {\n" + qosdef_append_chain_dym $hook_ul upload $dynamic_bw_up + qosdef_append_chain_dym $hook_dl download $dynamic_bw_down + qosdef_appendx "}\n" +} diff --git a/net/nft-qos/files/lib/monitor.sh b/net/nft-qos/files/lib/monitor.sh new file mode 100644 index 000000000..d05943ae2 --- /dev/null +++ b/net/nft-qos/files/lib/monitor.sh @@ -0,0 +1,39 @@ +#!/bin/sh +# +# Copyright (C) 2018 rosysong@rosinson.com +# + +. /lib/nft-qos/core.sh + +qosdef_monitor_get_ip_handle() { # <family> <chain> <ip> + echo $(nft list chain $1 nft-qos-monitor $2 -a 2>/dev/null | grep $3 | awk '{print $11}') +} + +qosdef_monitor_add() { # <mac> <ip> <hostname> + handle_dl=$(qosdef_monitor_get_ip_handle $NFT_QOS_INET_FAMILY download $2) + [ -z "$handle_dl" ] && nft add rule $NFT_QOS_INET_FAMILY nft-qos-monitor download ip daddr $2 counter + handle_ul=$(qosdef_monitor_get_ip_handle $NFT_QOS_INET_FAMILY upload $2) + [ -z "$handle_ul" ] && nft add rule $NFT_QOS_INET_FAMILY nft-qos-monitor upload ip saddr $2 counter +} + +qosdef_monitor_del() { # <mac> <ip> <hostname> + local handle_dl handle_ul + handle_dl=$(qosdef_monitor_get_ip_handle $NFT_QOS_INET_FAMILY download $2) + handle_ul=$(qosdef_monitor_get_ip_handle $NFT_QOS_INET_FAMILY upload $2) + [ -n "$handle_dl" ] && nft delete handle $handle_dl + [ -n "$handle_ul" ] && nft delete handle $handle_ul +} + +# init qos monitor +qosdef_init_monitor() { + local hook_ul="input" hook_dl="postrouting" + + [ -z "$NFT_QOS_HAS_BRIDGE" ] && { + hook_ul="postrouting" + hook_dl="input" + } + + nft add table $NFT_QOS_INET_FAMILY nft-qos-monitor + nft add chain $NFT_QOS_INET_FAMILY nft-qos-monitor upload { type filter hook $hook_ul priority 0\; } + nft add chain $NFT_QOS_INET_FAMILY nft-qos-monitor download { type filter hook $hook_dl priority 0\; } +} diff --git a/net/nft-qos/files/lib/priority.sh b/net/nft-qos/files/lib/priority.sh new file mode 100644 index 000000000..59288b85c --- /dev/null +++ b/net/nft-qos/files/lib/priority.sh @@ -0,0 +1,90 @@ +#!/bin/sh +# +# Copyright (C) 2018 rosysong@rosinson.com +# + +. /lib/functions/network.sh +. /lib/nft-qos/core.sh + +P1=""; P2=""; P3=""; P4=""; P5=""; P6=""; +P7=""; P8=""; P9=""; P10=""; P11=""; + +_qosdef_handle_protox() { # <priority> <rule> + case "$1" in + -400) P1="$P1""$2";; + -300) P2="$P2""$2";; + -225) P3="$P3""$2";; + -200) P4="$P4""$2";; + -150) P5="$P5""$2";; + -100) P6="$P6""$2";; + 0) P7="$P7""$2";; + 50) P8="$P8""$2";; + 100) P9="$P9""$2";; + 225) P10="$P10""$2";; + 300) P11="$P11""$2";; + esac +} + +qosdef_handle_protox() { # <section> + local proto prio srv + + config_get proto $1 'protocol' + config_get prio $1 'priority' + config_get srv $1 'service' + + [ -z "$proto" -o \ + -z "$prio" -o \ + -z "$srv" ] && return + + _qosdef_handle_protox $prio \ + "\t\t$proto dport { $srv } accept\n" +} + +qosdef_append_rule_protox() { # <section> + config_foreach qosdef_handle_protox $1 + qosdef_appendx \ + "${P1}${P2}${P3}${P4}${P5}${P6}${P7}${P8}${P9}${P10}${P11}" +} + +qosdef_append_chain_priority() { # <name> <section> <device> + local name=$1 device=$3 + + qosdef_appendx "\tchain $name {\n" + qosdef_append_chain_ingress filter $device 0 accept + qosdef_append_rule_protox $2 + qosdef_appendx "\t}\n" +} + +qosdef_remove_priority() { + qosdef_remove_table netdev nft-qos-priority +} + +# init traffic priority +qosdef_init_priority() { + local priority_enable priority_netdev ifname="br-lan" + + uci_validate_section nft-qos default default \ + 'priority_enable:bool:0' \ + 'priority_netdev:maxlength(8)' + + [ $? -ne 0 ] && { + logger -t nft-qos-priority "validation failed" + return 1 + } + + [ $priority_enable -eq 0 ] && return 1 + + case "$priority_netdev" in + lan) [ "$(uci_get network.lan.type)" != "bridge" ] && { + network_get_device ifname "$priority_netdev" || \ + ifname="$(uci_get network.lan.ifname)" + } + ;; + wan*) network_get_device ifname "$priority_netdev" || \ + ifname="$(uci_get network.$priority_netdev.ifname)" + esac + + qosdef_appendx "table netdev nft-qos-priority {\n" + qosdef_append_chain_priority filter priority $ifname + qosdef_appendx "}\n" +} diff --git a/net/nft-qos/files/lib/static.sh b/net/nft-qos/files/lib/static.sh new file mode 100644 index 000000000..cb56b4925 --- /dev/null +++ b/net/nft-qos/files/lib/static.sh @@ -0,0 +1,73 @@ +#!/bin/sh +# +# Copyright (C) 2018 rosysong@rosinson.com +# + +. /lib/nft-qos/core.sh + +# append rule for static qos +qosdef_append_rule_sta() { # <section> <operator> <default-unit> <default-rate> + local ipaddr unit rate + local operator=$2 + + config_get ipaddr $1 ipaddr + config_get unit $1 unit $3 + config_get rate $1 rate $4 + + [ -z "$ipaddr" ] && return + + qosdef_append_rule_ip_limit $ipaddr $operator $unit $rate +} + +# append chain for static qos +qosdef_append_chain_sta() { # <hook> <name> <section> <unit> <rate> + local hook=$1 name=$2 + local config=$3 operator + + case "$name" in + download) operator="daddr";; + upload) operator="saddr";; + esac + + qosdef_appendx "\tchain $name {\n" + qosdef_append_chain_def filter $hook 0 accept + qosdef_append_rule_limit_whitelist $name + config_foreach qosdef_append_rule_sta $config $operator $4 $5 + qosdef_appendx "\t}\n" +} + +qosdef_flush_static() { + qosdef_flush_table "$NFT_QOS_INET_FAMILY" nft-qos-static +} + +# static limit rate init +qosdef_init_static() { + local unit_dl unit_ul rate_dl rate_ul + local limit_enable limit_type hook_ul="input" hook_dl="postrouting" + + uci_validate_section nft-qos default default \ + 'limit_enable:bool:0' \ + 'limit_type:maxlength(8)' \ + 'static_unit_dl:string:kbytes' \ + 'static_unit_ul:string:kbytes' \ + 'static_rate_dl:uinteger:50' \ + 'static_rate_ul:uinteger:50' + + [ $? -ne 0 ] && { + logger -t nft-qos-static "validation failed" + return 1 + } + + [ $limit_enable -eq 0 -o \ + $limit_type = "dynamic" ] && return 1 + + [ -z "$NFT_QOS_HAS_BRIDGE" ] && { + hook_ul="postrouting" + hook_dl="input" + } + + qosdef_appendx "table $NFT_QOS_INET_FAMILY nft-qos-static {\n" + qosdef_append_chain_sta $hook_ul upload upload $unit_ul $rate_ul + qosdef_append_chain_sta $hook_dl download download $unit_dl $rate_dl + qosdef_appendx "}\n" +} |