firehol/sbin/fireqos.in

3662 lines
90 KiB
Bash
Executable File

#!/bin/bash
#
# FireQOS - A traffic shaper for humans...
#
# Copyright
#
# Copyright (C) 2013-2014 Costa Tsaousis <costa@tsaousis.gr>
# Copyright (C) 2013-2014 Phil Whineray <phil@sanewall.org>
#
# License
#
# 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; either version 2 of the License, or
# (at your option) any later version.
#
# 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/>.
#
# See the file COPYING for details.
#
if [ $(( ${BASH_VERSINFO[0]} )) -lt 4 ]
then
echo >&2
echo >&2 "FireQOS requires BASH version 4 or later."
echo >&2 "You are running version: ${BASH_VERSION}"
echo >&2 "Please upgrade."
echo >&2
exit 1
fi
# make sure sbin is included in the path
# it seems that pppd ip-up.d script need this
export PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin"
get_version() {
GIT_REF='$Format:%d,commit-%h$'
local IFS=":(), "
set -- "$GIT_REF"
ver='$Id$'
for i in $@
do
case "$i" in
*[0-9].[0.9]*)
echo "$i" | sed -e 's/^v//'
return 0
;;
commit-[0-9a-zA-Z]*)
ver="$i"
;;
esac
done
echo "$ver"
return 0
}
VERSION=$(get_version)
# Make sure we don't get localized results
export LC_ALL=C
# let everyone read our status info
umask 022
PROGRAM_FILE="$0"
# enabled extended pattern matching in bash
shopt -s extglob
FIREQOS_SYSLOG_FACILITY="daemon"
FIREQOS_CONFIG=/etc/firehol/fireqos.conf
FIREQOS_LOCK_FILE=/var/run/fireqos.lock
FIREQOS_LOCK_FILE_TIMEOUT=600
FIREQOS_DIR=/var/run/fireqos
FIREQOS_SAVE="${FIREQOS_DIR}/.tmp.save.$$.$RANDOM"
# Gets set to 1 if this system cannot handle sub-second resolution
FIREQOS_LOWRES_TIMER=0
# Set it to 1 to see the tc commands generated.
# Set it in the config file to overwrite this default.
FIREQOS_DEBUG=0
# These are finer debugging options.
FIREQOS_DEBUG_STACK=0
FIREQOS_DEBUG_PORTS=0
FIREQOS_DEBUG_CLASS=0
FIREQOS_DEBUG_QDISC=0
FIREQOS_DEBUG_FILTER=0
FIREQOS_DEBUG_CMD=0
# The default and minimum rate for all classes is 1/100
# of the interface bandwidth
FIREQOS_MIN_RATE_DIVISOR=100
# if set to 1, it will print a line per match statement
FIREQOS_SHOW_MATCHES=0
# the classes priority in balanced mode
FIREQOS_BALANCED_PRIO=4
# step to increment between matches
FIREQOS_MATCHES_STEP=10
# the default class for all interfaces
# additional default classes may be added (incrementing
# this, when classes with device emulation are added)
FIREQOS_INTERFACE_DEFAULT_CLASSID=8000
# Enable colors
if [ -t 2 -a $[$(tput colors 2>/dev/null)] -ge 8 ]
then
COLOR_RESET="\e[0m"
COLOR_BLACK="\e[30m"
COLOR_RED="\e[31m"
COLOR_GREEN="\e[32m"
COLOR_YELLOW="\e[33m"
COLOR_BLUE="\e[34m"
COLOR_PURPLE="\e[35m"
COLOR_CYAN="\e[36m"
COLOR_WHITE="\e[37m"
COLOR_BGBLACK="\e[40m"
COLOR_BGRED="\e[41m"
COLOR_BGGREEN="\e[42m"
COLOR_BGYELLOW="\e[43m"
COLOR_BGBLUE="\e[44m"
COLOR_BGPURPLE="\e[45m"
COLOR_BGCYAN="\e[46m"
COLOR_BGWHITE="\e[47m"
COLOR_BOLD="\e[1m"
COLOR_DIM="\e[2m"
COLOR_UNDERLINED="\e[4m"
COLOR_BLINK="\e[5m"
COLOR_INVERTED="\e[7m"
fi
# Check for external commands
require_cmd() {
local c=`which $1 2>/dev/null`
eval "${1}_cmd=$c"
if [ -z "$c" ]
then
echo >&2 "Command '$1' is not found in this system."
return 1
fi
return 0
}
require_cmd tc || exit 1
require_cmd ip || exit 1
require_cmd modprobe 2> /dev/null
if [ $? -ne 0 ]
then
require_cmd insmod 2> /dev/null
if [ $? -ne 0 ]
then
echo >&2 "Neither modprobe nor insmod is found in this system."
exit 1
fi
modprobe_cmd=$insmod_cmd
fi
require_cmd flock || exit 1
require_cmd grep || exit 1
require_cmd egrep || exit 1
require_cmd cut || exit 1
require_cmd cat || exit 1
require_cmd sed || exit 1
require_cmd tr || exit 1
require_cmd touch || exit 1
require_cmd logger || exit 1
require_cmd mkdir || exit 1
require_cmd rm || exit 1
# service definitions
# taken from firehol, with:
#
# $cat_cmd firehol.sh | $egrep_cmd "^server_.*_ports="
#
server_AH_ports="51/any"
server_amanda_ports="udp/10080"
server_aptproxy_ports="tcp/9999"
server_apcupsd_ports="tcp/6544"
server_apcupsdnis_ports="tcp/3551"
server_asterisk_ports="tcp/5038"
server_cups_ports="tcp/631 udp/631"
server_cvspserver_ports="tcp/2401"
server_darkstat_ports="tcp/666"
server_daytime_ports="tcp/13"
server_dcc_ports="udp/6277"
server_dcpp_ports="tcp/1412 udp/1412"
server_dns_ports="udp/53 tcp/53"
server_dhcprelay_ports="udp/67"
server_dict_ports="tcp/2628"
server_distcc_ports="tcp/3632"
server_eserver_ports="tcp/4661 udp/4661 udp/4665"
server_ESP_ports="50/any"
server_echo_ports="tcp/7"
server_finger_ports="tcp/79"
server_ftp_ports="tcp/21"
server_gift_ports="tcp/4302 tcp/1214 tcp/2182 tcp/2472"
server_giftui_ports="tcp/1213"
server_gkrellmd_ports="tcp/19150"
server_GRE_ports="47/any"
server_h323_ports="tcp/1720"
server_heartbeat_ports="udp/690:699"
server_http_ports="tcp/80"
server_https_ports="tcp/443"
server_iax_ports="udp/5036"
server_iax2_ports="udp/5469 udp/4569"
server_ICMP_ports="icmp/any"
server_icmp_ports="icmp/any"
server_icp_ports="udp/3130"
server_ident_ports="tcp/113"
server_imap_ports="tcp/143"
server_imaps_ports="tcp/993"
server_irc_ports="tcp/6667"
server_isakmp_ports="udp/500"
server_ipsecnatt_ports="udp/4500"
server_jabber_ports="tcp/5222 tcp/5223"
server_jabberd_ports="tcp/5222 tcp/5223 tcp/5269"
server_l2tp_ports="udp/1701"
server_ldap_ports="tcp/389"
server_ldaps_ports="tcp/636"
server_lpd_ports="tcp/515"
server_microsoft_ds_ports="tcp/445"
server_ms_ds_ports="tcp/445"
server_mms_ports="tcp/1755 udp/1755"
server_msn_ports="tcp/6891"
server_mysql_ports="tcp/3306"
server_netbackup_ports="tcp/13701 tcp/13711 tcp/13720 tcp/13721 tcp/13724 tcp/13782 tcp/13783"
server_netbios_ns_ports="udp/137"
server_netbios_dgm_ports="udp/138"
server_netbios_ssn_ports="tcp/139"
server_nntp_ports="tcp/119"
server_nntps_ports="tcp/563"
server_ntp_ports="udp/123 tcp/123"
server_nut_ports="tcp/3493 udp/3493"
server_nxserver_ports="tcp/5000:5200"
server_oracle_ports="tcp/1521"
server_OSPF_ports="89/any"
server_pop3_ports="tcp/110"
server_pop3s_ports="tcp/995"
server_portmap_ports="udp/111 tcp/111"
server_postgres_ports="tcp/5432"
server_pptp_ports="tcp/1723"
server_privoxy_ports="tcp/8118"
server_radius_ports="udp/1812 udp/1813"
server_radiusproxy_ports="udp/1814"
server_radiusold_ports="udp/1645 udp/1646"
server_radiusoldproxy_ports="udp/1647"
server_rdp_ports="tcp/3389"
server_rndc_ports="tcp/953"
server_rsync_ports="tcp/873 udp/873"
server_rtp_ports="udp/10000:20000"
server_sane_ports="tcp/6566"
server_sip_ports="udp/5060"
server_socks_ports="tcp/1080 udp/1080"
server_squid_ports="tcp/3128"
server_smtp_ports="tcp/25"
server_smtps_ports="tcp/465"
server_snmp_ports="udp/161"
server_snmptrap_ports="udp/162"
server_ssh_ports="tcp/22"
server_stun_ports="udp/3478 udp/3479"
server_submission_ports="tcp/587"
server_sunrpc_ports="${server_portmap_ports}"
server_swat_ports="tcp/901"
server_syslog_ports="udp/514"
server_telnet_ports="tcp/23"
server_tftp_ports="udp/69"
server_time_ports="tcp/37 udp/37"
server_upnp_ports="udp/1900 tcp/2869"
server_uucp_ports="tcp/540"
server_whois_ports="tcp/43"
server_vmware_ports="tcp/902"
server_vmwareauth_ports="tcp/903"
server_vmwareweb_ports="tcp/8222 tcp/8333"
server_vnc_ports="tcp/5900:5903"
server_webcache_ports="tcp/8080"
server_webmin_ports="tcp/10000"
server_xdmcp_ports="udp/177"
# FireQOS only services
server_torrents_ports="tcp/6881:6999 udp/6881:6999"
server_facetime_ports="udp/3478:3497 udp/16384:16387 udp/16393:16402"
server_hangouts_ports="udp/19305:19309 tcp/19305:19309"
server_gtalk_ports="tcp/5222 tcp/5228"
server_teamviewer_ports="tcp/5938"
server_ping_ports="icmp/any"
server_tcp_ports="tcp/any"
server_udp_ports="udp/any"
server_surfing_ports="tcp/0:1023"
# -----------------------------------------------------------------------------
# Default FireHOL marks
declare -A MARKS_BITS='([connmark]="6" [usermark]="7" )'
declare -A MARKS_MASKS='([connmark]="0x0000003f" [usermark]="0x00001fc0" )'
declare -A MARKS_MAX='([connmark]="63" [usermark]="127" )'
declare -A MARKS_SHIFT='([connmark]="0" [usermark]="6" )'
FIREHOL_SPOOL_DIR="/var/spool/firehol"
if [ -f "${FIREHOL_SPOOL_DIR}/marks.conf" ]
then
source "${FIREHOL_SPOOL_DIR}/marks.conf" || exit 1
fi
mark_value() {
local name="${1}"; shift
local x=
if [ -z "${name}" ]
then
error "Cannot find the value of mark with name '${name}'."
return 1
fi
if [ -z "${1}" ]
then
error "Empty mark value given for mark ${name}."
return 1
fi
if [ -z "${MARKS_MASKS[$name]}" ]
then
error "Mark $name does not exist."
return 1
fi
for x in ${@//,/ }
do
local x=$[ x + 1 - 1 ]
if [ $x -gt ${MARKS_MAX[$name]} -o $x -lt 0 ]
then
error "Cannot get mark $name of value $x. Mark $name is configured to get values from 0 to ${MARKS_MAX[$name]}. Change firehol-defaults.conf to add more."
return 1
fi
#echo "$[ x << ${MARKS_SHIFT[$name]} ]/${MARKS_MASKS[$name]}"
printf "0x%08x/${MARKS_MASKS[$name]}\n" "$[ x << ${MARKS_SHIFT[$name]} ]"
done
return 0
}
# -----------------------------------------------------------------------------
save() {
[ ! $interface_save -eq 1 ] && return 0
local ipv=${force_ipv}
if [ ! -z "$ipv" ]
then
local ipv="ipv$ipv"
fi
config_line -ne
(
printf "FORCE_CONFIG_LINEID=\"${LAST_CONFIG_LINE}\"\n"
printf "%q " $ipv "$@"
printf "\n"
) >>"${FIREQOS_SAVE}"
}
simple_service() {
[ $interface_save -eq 1 ] && save ${FUNCNAME} "$@"
local direction="$1" service="$2" s= sports= reverse= p= proto= ports=
shift 2
for s in ${service//,/ }
do
eval "sports=\${server_${s}_ports}"
if [ -z "${sports}" ]
then
error "Service '${s}' is not defined."
exit 1
fi
# INPUT
# server_x_ports=tcp/SPORT
# server x src CLIENT dst SERVER ==> dport SPORT, src CLIENT, dst SERVER ==> dport SPORT, src CLIENT, dst SERVER
# client x src CLIENT dst SERVER ==> reverse dport SPORT, src CLIENT, dst SERVER ==> sport SPORT, dst CLIENT, src SERVER
# OUTPUT
# server_x_ports=tcp/SPORT
# server x src CLIENT dst SERVER ==> reverse dport SPORT, src CLIENT, dst SERVER ==> sport SPORT, dst CLIENT, src SERVER
# client x src CLIENT dst SERVER ==> dport SPORT, src CLIENT, dst SERVER ==> dport SPORT, src CLIENT, dst SERVER
# So:
# 1. use 'server' when you are the server
# 2. use 'client' when you are the client
# 3. use 'src' and 'dst' to match the REQUEST, i.e. src=CLIENT, dst=SERVER
# 4. forget about INPUT and OUTPUT interfaces, FireQOS will figure it out
case $direction in
server)
[ "${interface_inout}" = "output" ] && reverse="reverse"
;;
client)
[ "${interface_inout}" = "input" ] && reverse="reverse"
;;
*)
error "A service cannot be applied as '${direction}'."
exit 1
;;
esac
for p in ${sports}
do
proto=${p/\/*/}
ports=${p/*\//}
match -ns ${reverse} proto ${proto} dports ${ports} "${@}"
done
done
}
server4() { ipv4 server "${@}"; }
server6() { ipv6 server "${@}"; }
server46() { ipv46 server "${@}"; }
server() { simple_service server "${@}"; }
client4() { ipv4 client "${@}"; }
client6() { ipv6 client "${@}"; }
client46() { ipv46 client "${@}"; }
client() { simple_service client "${@}"; }
service() {
error "the 'service' match is no longer supported."
exit 1
}
fireqos_active_interfaces() {
ls $FIREQOS_DIR/ 2>/dev/null |\
$grep_cmd ".conf" |\
$tr_cmd "\n" " " |\
$sed_cmd -e "s/\.conf//g" -e "s/ \+/ /g"
}
FIREQOS_COMPLETED=
fireqos_exit() {
if [ "$FIREQOS_COMPLETED" = "0" ]
then
echo >&2 "FAILED TO ACTIVATE TRAFFIC CONTROL."
if [ ! -z "$interface_realdev" ]
then
# clear only the interface failed.
echo >&2
echo >&2 "Clearing failed interface: $interface_name ($interface_dev $interface_inout => $interface_realdev)..."
echo >&2
printf >&2 " %16.16s: " $interface_realdev
echo >&2 "cleared traffic control ${interface_inout}"
if [ $interface_inout = input ]
then
runcmd $tc_cmd qdisc del dev $interface_dev ingress >/dev/null 2>&1
runcmd $tc_cmd qdisc del dev $interface_realdev root >/dev/null 2>&1
if [ -f "$FIREQOS_DIR/ifbs/$interface_realdev" ]
then
printf >&2 " %16.16s: " $interface_realdev
echo >&2 "removed IFB device"
runcmd $ip_cmd del dev $interface_realdev name $interface_realdev type ifb >/dev/null 2>&1
fi
else
runcmd $tc_cmd qdisc del dev $interface_realdev root >/dev/null 2>&1
fi
$rm_cmd $FIREQOS_DIR/$interface_name.conf 2>/dev/null
local a=
local ifs="`fireqos_active_interfaces`"
if [ ! -z "$ifs" ]
then
local a="Traffic control on these interfaces is operational: $ifs"
else
local a="No traffic control is operational by FireQOS."
fi
echo >&2
echo >&2 "$a"
echo >&2
syslog error "FireQOS FAILED. Cleared all FireQOS changes on interface '$interface_realdev'. $a"
else
clear_everything
fi
elif [ "$FIREQOS_COMPLETED" = "1" ]
then
syslog info "QoS applied ok ($tc_count tc commands applied)"
fi
echo >&2 "bye..."
[ -f "${FIREQOS_LOCK_FILE}" ] && $rm_cmd -f "${FIREQOS_LOCK_FILE}" >/dev/null 2>&1
enable trap
enable exit
trap exit EXIT
if [ "$FIREQOS_COMPLETED" = "0" ]
then
exit 1
fi
exit 0
}
fireqos_concurrent_run_lock() {
# open the 200th file descriptor
exec 200>"${FIREQOS_LOCK_FILE}"
if [ $? -ne 0 ]
then
echo "Cannot setup file locking. Exiting..."
exit 1
fi
# open an exclusive lock on the 200th file descriptor
${flock_cmd} -n 200
if [ $? -ne 0 ]
then
echo >&2 "FireQOS is already running. Exiting..."
exit 1
fi
return 0
}
syslog() {
local p="$1"; shift
$logger_cmd -p ${FIREQOS_SYSLOG_FACILITY}.$p -t "FireQOS[$$]" "${@}"
return 0
}
# Find in the BASH execution stack, the line and the source file that has called us.
# Before first use the variable PROGRAM_FILE should be set to the file to be excluded.
# It also sets the variable LAST_CONFIG_LINE on each run.
FORCE_CONFIG_LINEID=
LAST_CONFIG_LINE=
config_line() {
if [ ! -z "${FORCE_CONFIG_LINEID}" ]
then
LAST_CONFIG_LINE="${FORCE_CONFIG_LINEID}"
else
# find the config line in the BASH stack
# start from 2
# 0 is this line
# 1 is the caller - our line for sure
# 2 is the caller's caller - possibly a config file line
local i= all=${#BASH_SOURCE}
for (( i = 2; i < $all; i++ ))
do
[ ! "${BASH_SOURCE[$i]}" = "${PROGRAM_FILE}" ] && break
done
LAST_CONFIG_LINE="${BASH_LINENO[$[i-1]]}@${BASH_SOURCE[$i]}: ${FUNCNAME[$[i-1]]}:"
fi
test ! "z$1" = "z-ne" && echo "${LAST_CONFIG_LINE}"
}
error() {
echo >&2 -e "$COLOR_RED$COLOR_BOLD"
echo >&2
echo >&2
echo >&2 "ERROR: $(config_line)"
echo -e >&2 "$@"
echo >&2
echo >&2 -e "$COLOR_RESET"
exit 1
}
warning() {
echo >&2
echo >&2 -e " ${COLOR_YELLOW}${COLOR_BOLD}WARNING: $(config_line)"
echo >&2 -e " $* ${COLOR_RESET}"
echo >&2
}
runcmd() {
local debug=$FIREQOS_DEBUG_CMD
if [ $debug -eq 1 ]
then
printf " %q" "${@}"
printf "\n"
[ $FIREQOS_DEBUG -eq 1 ] && return 0
fi
"${@}"
}
tc_count=0
tc() {
tc_count=$[tc_count + 1]
local noerror=0
if [ "$1" = "ignore-error" ]
then
local noerror=1
shift
fi
local debug=$FIREQOS_DEBUG
[ $FIREQOS_DEBUG_CLASS -eq 1 -a "$1" = "class" ] && local debug=1
[ $FIREQOS_DEBUG_QDISC -eq 1 -a "$1" = "qdisc" ] && local debug=1
[ $FIREQOS_DEBUG_FILTER -eq 1 -a "$1" = "filter" ] && local debug=1
if [ $debug -eq 1 ]
then
printf " %q" $tc_cmd "${@}"
printf "\n"
return 0
fi
if [ $noerror -eq 1 ]
then
$tc_cmd "${@}" >/dev/null 2>&1
else
$tc_cmd "${@}"
local ret=$?
if [ $ret -ne 0 ]
then
echo >&2 -e "$COLOR_RED$COLOR_BOLD"
echo >&2
echo >&2
echo >&2 "ERROR:"
echo >&2 "tc failed with error $ret, while executing the command:"
printf >&2 "%q " $tc_cmd "${@}"
echo >&2
echo >&2
echo >&2 -e "$COLOR_RESET"
exit 1
fi
fi
}
device_mtu() {
$ip_cmd link show dev "${1}" | $sed_cmd "s/^.* \(mtu [0-9]\+\) .*$/\1/g" | $grep_cmd ^mtu | $cut_cmd -d ' ' -f 2
}
rate2bps() {
local r="${1}" p="${2}" # is assumed to be the base rate in bytes per second
# # convert $r to kbit / sec ; the default rate in fireqos
# Gbit / sec
r="${r//gbit/ * 1000 * 1000}"
r="${r//Gbit/ * 1000 * 1000}"
# Mbit / sec
r="${r//mbit/ * 1000}"
r="${r//Mbit/ * 1000}"
# kbit / sec
r="${r//kbit/}"
r="${r//Kbit/}"
# GBytes / sec
r="${r//gbps/ * 8 * 1024 * 1024 * 1024 / 1000}"
r="${r//Gbps/ * 8 * 1024 * 1024 * 1024 / 1000}"
# MBytes / sec
r="${r//mbps/ * 8 * 1024 * 1024 / 1000}"
r="${r//Mbps/ * 8 * 1024 * 1024 / 1000}"
# kBytes / sec
r="${r//kbps/ * 8 * 1024 / 1000}"
r="${r//Kbps/ * 8 * 1024 / 1000}"
# Bytes / sec
r="${r//bps/ * 8 / 1000}"
r="${r//Bps/ * 8 / 1000}"
# percentage of $p
r="${r//%/ * 8 * $p / 100 / 1000}"
# convert $r to bytes / sec ; the default rate in tc
echo "$((r * 1000 / 8))"
# # calculate it in bits per second (highest resolution)
# case "$r" in
# +([0-9])kbps)
# local label="Kilobytes per second"
# local identifier="kbps"
# local multiplier=$((8 * 1024))
# ;;
# +([0-9])Kbps)
# local label="Kilobytes per second"
# local identifier="Kbps"
# local multiplier=$((8 * 1024))
# ;;
# +([0-9])mbps)
# local label="Megabytes per second"
# local identifier="mbps"
# local multiplier=$((8 * 1024 * 1024))
# ;;
# +([0-9])Mbps)
# local label="Megabytes per second"
# local identifier="Mbps"
# local multiplier=$((8 * 1024 * 1024))
# ;;
# +([0-9])gbps)
# local label="Gigabytes per second"
# local identifier="gbps"
# local multiplier=$((8 * 1024 * 1024 * 1024))
# ;;
# +([0-9])Gbps)
# local label="Gigabytes per second"
# local identifier="Gbps"
# local multiplier=$((8 * 1024 * 1024 * 1024))
# ;;
# +([0-9])bit)
# local label="bits per second"
# local identifier="bit"
# local multiplier=1
# ;;
# +([0-9])kbit)
# local label="Kilobits per second"
# local identifier="kbit"
# local multiplier=1000
# ;;
# +([0-9])Kbit)
# local label="Kilobits per second"
# local identifier="Kbit"
# local multiplier=1000
# ;;
# +([0-9])mbit)
# local label="Megabits per second"
# local identifier="mbit"
# local multiplier=1000000
# ;;
# +([0-9])Mbit)
# local label="Megabits per second"
# local identifier="Mbit"
# local multiplier=1000000
# ;;
# +([0-9])gbit)
# local label="Gigabits per second"
# local identifier="gbit"
# local multiplier=1000000000
# ;;
# +([0-9])Gbit)
# local label="Gigabits per second"
# local identifier="Gbit"
# local multiplier=1000000000
# ;;
# +([0-9])bps)
# local label="Bytes per second"
# local identifier="bps"
# local multiplier=8
# ;;
# +([0-9])%)
# local label="Percent"
# local identifier="bps"
# local multiplier=8
# #r=$((p * multiplier * `echo $r | $sed_cmd "s/%//g"` / 100))
# r=$((p * multiplier * ${r//%/} / 100))
# ;;
# +([0-9]))
# local label="Kilobits per second"
# local identifier="Kbit"
# local multiplier=1000
# r=$(( r * multiplier ))
# ;;
# *)
# echo >&2 "Invalid rate '${r}' given."
# return 1
# ;;
# esac
# #local n="`echo "$r" | $sed_cmd "s|$identifier| * $multiplier|g"`"
# #eval "local o=\$(($n / 8))"
# #echo "$o"
# # evaluate it in bytes per second (the default for a rate in tc)
# echo $(( ${r//$identifier/ * $multiplier} / 8))
return 0
}
calc_r2q() {
# r2q is by default 10
# It is used to find the default quantum (i.e. the size in bytes a class can burst above its ceiling).
# At the same time quantum cannot be smaller than a single packet (ptu).
# So, the default is good only if the minimum rate specified to any class is MTU * R2Q = 1500 * 10 = 15000 * 8(bits) = 120kbit
#
# To be adaptive, we allocate to the default classes 1/100 of the total bandwidth.
# This means that we need :
#
# rate = mtu * r2q
# or
# r2q = rate / mtu
#
# we expect the minimum rate that might be given
local rate="${1}" mtu="${2}" r2q=
shift 2
[ -z "$mtu" ] && mtu=1500
r2q=$(( rate / mtu ))
[ $r2q -lt 1 ] && r2q=1
# [ $r2q -gt 10 ] && r2q=10
echo $r2q
}
parse_class_params() {
local prefix="${1}" parent="${2}" ipv4= ipv6= priority_mode= \
prio= qdisc= qdisc_options= minrate= rate= ceil= r2q= \
burst= cburst= quantum= mtu= mpu= tsize= param= value= \
linklayer= linklayer_type= linklayer_encoding= overhead= diff= \
valid_options="$interface_inout" current_options="$interface_inout"
shift 2
eval local base_rate="\$${parent}_rate"
# we need the ceil_rate of the parent class
# so that the 'max/ceil' parameter can be a percentage
# of the parent's ceil_rate.
eval local ceil_rate="\$${parent}_ceil"
[ -z "${ceil}" ] && ceil_rate="${base_rate}"
case "$force_ipv" in
4)
ipv4=1
ipv6=0
;;
6)
ipv4=0
ipv6=1
;;
46)
ipv4=1
ipv6=1
;;
esac
# find all work_X arguments
while [ ! -z "${1}" ]
do
case "${1}" in
input|output)
current_options="${1}"
;;
priority|balanced)
priority_mode="${1}"
;;
prio)
prio="${2}"
shift
;;
qdisc)
qdisc="${2}"
qdisc_options="default"
if [ "$3" = "options" ]
then
qdisc_options="$4"
shift 2
fi
shift
;;
sfq|pfifo|bfifo|fq_codel|codel|none)
qdisc="${1}"
qdisc_options="default"
if [ "${2}" = "options" ]
then
qdisc_options="${3}"
shift 2
fi
;;
minrate)
[ "$prefix" = "class" ] && error "'$1' cannot be used in classes."
if [ $valid_options = $current_options ]
then
minrate="`rate2bps ${2} ${base_rate}`"
fi
shift
;;
rate|min|commit)
if [ $valid_options = $current_options ]
then
rate="`rate2bps $2 ${base_rate}`"
fi
shift
;;
ceil|max)
if [ $valid_options = $current_options ]
then
ceil="`rate2bps $2 ${ceil_rate}`"
fi
shift
;;
r2q)
[ "$prefix" = "class" ] && error "'$1' cannot be used in classes."
r2q="$2"
shift
;;
burst)
burst="$2"
shift
;;
cburst)
cburst="$2"
shift
;;
quantum)
# must be as small as possible, but larger than mtu
quantum="$2"
shift
;;
mtu)
mtu="$2"
shift
;;
mpu)
mpu="$2"
shift
;;
tsize)
tsize="$2"
shift
;;
overhead)
overhead="$2"
shift
;;
adsl)
linklayer="$1"
linklayer_type="$2"
linklayer_encoding="$3"
diff=0
case "$2" in
local) diff=0
;;
remote) diff=-14
;;
*) error "Unknown adsl option '$2'."
return 1
;;
esac
# default overhead values taken from http://ace-host.stuart.id.au/russell/files/tc/tc-atm/
case "$3" in
IPoA-VC/Mux|ipoa-vcmux|ipoa-vc|ipoa-mux)
overhead=$((8 + diff))
;;
IPoA-LLC/SNAP|ipoa-llcsnap|ipoa-llc|ipoa-snap)
overhead=$((16 + diff))
;;
Bridged-VC/Mux|bridged-vcmux|bridged-vc|bridged-mux)
overhead=$((24 + diff))
;;
Bridged-LLC/SNAP|bridged-llcsnap|bridged-llc|bridged-snap)
overhead=$((32 + diff))
;;
PPPoA-VC/Mux|pppoa-vcmux|pppoa-vc|pppoa-mux)
overhead=$((10 + diff))
[ "$2" = "remote" ] && mtu=1478
;;
PPPoA-LLC/SNAP|pppoa-llcsnap|pppoa-llc|pppoa-snap)
overhead=$((14 + diff))
;;
PPPoE-VC/Mux|pppoe-vcmux|pppoe-vc|pppoe-mux)
overhead=$((32 + diff))
;;
PPPoE-LLC/SNAP|pppoe-llcsnap|pppoe-llc|pppoe-snap)
overhead=$((40 + diff))
[ "$2" = "remote" ] && mtu=1492
;;
*)
error "Cannot understand adsl protocol '$3'."
return 1
;;
esac
shift 2
;;
atm|ethernet)
linklayer="$1"
linklayer_type=
linklayer_encoding=
;;
linklayer)
linklayer="$2"
linklayer_type=
linklayer_encoding=
shift
;;
*) error "Cannot understand what '${1}' means."
return 1
;;
esac
shift
done
# export our parameters for the caller
# for every parameter not set, use the parent value
# for every one set, use the set value
for param in ceil burst cburst quantum qdisc qdisc_options ipv4 ipv6 priority_mode
do
eval local value="\$$param"
if [ -z "$value" ]
then
eval export ${prefix}_${param}="\${${parent}_${param}}"
else
eval export ${prefix}_${param}="\$$param"
fi
done
# no inheritance for these parameters
for param in rate mtu mpu tsize overhead linklayer linklayer_type linklayer_encoding r2q prio minrate
do
eval export ${prefix}_${param}="\$$param"
done
return 0
}
parent_path=
parent_stack_size=0
parent_push() {
local prefix="${1}" param= \
vars="classid major sumrate default_class default_added filters_to name ceil burst cburst quantum qdisc rate mtu mpu tsize overhead linklayer linklayer_type linklayer_encoding r2q prio ipv4 ipv6 minrate priority_mode class_counter stab class_prio"
shift
if [ $FIREQOS_DEBUG_STACK -eq 1 ]
then
eval "local before=\$parent_stack_${parent_stack_size}"
echo "PUSH $prefix: OLD(${parent_stack_size}): $before"
fi
# refresh the existing parent_* values to stack
eval "parent_stack_${parent_stack_size}="
for param in $vars
do
eval "parent_stack_${parent_stack_size}=\"\${parent_stack_${parent_stack_size}}parent_$param=\$parent_$param;\""
done
if [ $FIREQOS_DEBUG_STACK -eq 1 ]
then
eval "local after=\$parent_stack_${parent_stack_size}"
echo "PUSH $prefix: REFRESHED(${parent_stack_size}): $after"
fi
# now push the new values into the stack
(( parent_stack_size += 1 ))
eval "parent_stack_${parent_stack_size}="
for param in $vars
do
eval "parent_$param=\$${prefix}_$param"
eval "parent_stack_${parent_stack_size}=\"\${parent_stack_${parent_stack_size}}parent_$param=\$${prefix}_$param;\""
done
if [ $FIREQOS_DEBUG_STACK -eq 1 ]
then
eval "local push=\$parent_stack_${parent_stack_size}"
echo "PUSH $prefix: NEW(${parent_stack_size}): $push"
#-- set | $grep_cmd ^parent
fi
if [ "$prefix" = "interface" ]
then
parent_path=
else
parent_path="$parent_path$parent_name/"
fi
[ $FIREQOS_DEBUG_STACK -eq 1 ] && echo "PARENT_PATH=$parent_path"
set_tabs
}
parent_pull() {
if [ $parent_stack_size -lt 1 ]
then
error "Cannot pull a not pushed set of values from stack."
exit 1
fi
parent_stack_size=$((parent_stack_size - 1))
eval "eval \${parent_stack_${parent_stack_size}}"
if [ $FIREQOS_DEBUG_STACK -eq 1 ]
then
eval "local pull=\$parent_stack_${parent_stack_size}"
echo "PULL(${parent_stack_size}): $pull"
#-- set | $grep_cmd ^parent
fi
if [ $parent_stack_size -gt 1 ]
then
parent_path="`echo $parent_path | $cut_cmd -d '/' -f 1-$((parent_stack_size - 1))`/"
else
parent_path=
fi
[ $FIREQOS_DEBUG_STACK -eq 1 ] && echo "PARENT_PATH=$parent_path"
set_tabs
}
parent_clear() {
parent_stack_size=0
set_tabs
}
class_tabs=
set_tabs() {
class_tabs=
local x=
for x in `seq 1 $parent_stack_size`
do
class_tabs="$class_tabs "
done
}
check_constrains() {
local prefix="$1"
eval "local mtu=\$${prefix}_mtu \
burst=\$${prefix}_burst \
cburst=\$${prefix}_cburst \
quantum=\$${prefix}_quantum \
rate=\$${prefix}_rate \
ceil=\$${prefix}_ceil \
minrate=\$${prefix}_minrate"
[ -z "$mtu" ] && eval "local mtu=${parent_mtu}"
[ -z "$mtu" ] && eval "local mtu=$interface_mtu"
# check the constrains
if [ ! -z "$mtu" ]
then
if [ ! -z "$quantum" ]
then
if [ $quantum -lt $mtu ]
then
warning "quantum ($quantum bytes) is less than MTU ($mtu bytes). Fixed it by setting quantum to MTU."
eval "${prefix}_quantum=$mtu"
fi
fi
if [ ! -z "$burst" ]
then
if [ $burst -lt $mtu ]
then
warning "burst ($burst bytes) is less than MTU ($mtu bytes). Fixed it by setting burst to MTU."
eval "${prefix}_burst=$mtu"
fi
fi
if [ ! -z "$cburst" ]
then
if [ $cburst -lt $mtu ]
then
warning "cburst ($cburst bytes) is less than MTU ($mtu bytes). Fixed it by setting cburst to MTU."
eval "${prefix}_cburst=$mtu"
fi
fi
if [ ! -z "$minrate" ]
then
if [ $minrate -lt $mtu ]
then
warning "minrate ($minrate bytes per second) is less than MTU ($mtu bytes). Fixed it by setting minrate to MTU."
eval "${prefix}_minrate=$mtu"
fi
fi
fi
if [ ! -z "$ceil" ]
then
if [ $ceil -lt $rate ]
then
warning "ceil ($((ceil * 8 / 1000))kbit) is less than rate ($((rate * 8 / 1000))kbit). Fixed it by setting ceil to rate."
eval "${prefix}_ceil=$rate"
fi
fi
[ "$prefix" = "interface" ] && return 0
if [ ! -z "$ceil" ]
then
if [ $ceil -gt $parent_ceil ]
then
warning "ceil ($((ceil * 8 / 1000))kbit) is higher than its parent's ceil ($((parent_ceil * 8 / 1000))kbit). Fixed it by settting ceil to parent's ceil."
eval "${prefix}_ceil=$parent_ceil"
fi
fi
if [ ! -z "$burst" -a ! -z "$parent_burst" ]
then
if [ $burst -gt $parent_burst ]
then
warning "burst ($burst bytes) is higher than its parent's burst ($parent_burst bytes). Fixed it by setting burst to parent's burst."
eval "${prefix}_burst=$parent_burst"
fi
fi
if [ ! -z "$cburst" -a ! -z "$parent_cburst" ]
then
if [ $cburst -gt $parent_cburst ]
then
warning "cburst ($cburst bytes) is higher than its parent's cburst ($parent_cburst bytes). Fixed it by setting cburst to parent's cburst."
eval "${prefix}_cburst=$parent_cburst"
fi
fi
return 0
}
check_committed_rate() {
if [ $parent_rate -ge ${parent_sumrate} ]
then
echo >&2 -e ": $class_tabs${COLOR_GREEN}${COLOR_BOLD}committed rate $((parent_sumrate * 8 / 1000))kbit ($((parent_sumrate * 100 / parent_rate))%), the remaining $(((parent_rate - parent_sumrate)*8/1000))kbit will be spare bandwidth.${COLOR_RESET}"
else
echo >&2 -e ": $class_tabs${COLOR_RED}${COLOR_BOLD}committed rate $((parent_sumrate * 8 / 1000))kbit, ($((parent_sumrate * 100 / parent_rate))%), overbooked by $((-(parent_rate - parent_sumrate)*8/1000))kbit. PLEASE FIX.${COLOR_RESET}"
fi
}
interface_major=
interface_dev=
interface_name=
interface_inout=
interface_realdev=
interface_minrate=
interface_global_class_counter=
interface_class_counter=
interface_class_prio=0
interface_qdisc_counter=
interface_default_added=
interface_default_class=${FIREQOS_INTERFACE_DEFAULT_CLASSID}
interface_classes=
interface_classes_ids=
interface_classes_monitor=
interface_sumrate=0
interface_classid=
interface_stab=
interface_save=0
interface_default_counter=0
interface_priority_mode=
ifb_interfaces=0
class_matchid=0
force_ipv=
interface_close() {
if [ -f "${FIREQOS_SAVE}" ]
then
# we have the output of a bidirectional interface to run
# debug info
# cat "${FIREQOS_SAVE}"
# move the exiting file to a new place, to avoid recursion
mv "${FIREQOS_SAVE}" "${FIREQOS_SAVE}.run"
# cat "${FIREQOS_SAVE}.run"
# close the existing, input, interface
interface_close
# run the new, output, interface
source "${FIREQOS_SAVE}.run"
FORCE_CONFIG_LINEID=
# delete the file, we don't need it any more
$rm_cmd "${FIREQOS_SAVE}.run"
# continue running this interface_close function, to close the output interface too
fi
if [ ! -z "$interface_dev" ]
then
# close all open class groups
while [ $parent_stack_size -gt 1 ]
do
class group end
done
# if we have not added the default class
# for the interface, add it now
if [ $parent_default_added -eq 0 ]
then
class default
parent_default_added=1
fi
check_committed_rate
# NOT NEEDED - the default for interfaces works via kernel.
# match all class default flowid $interface_major:${parent_default_class} prio 0xffff
FIREQOS_INTERFACES_COMPLETED="$interface_name $FIREQOS_INTERFACES_COMPLETED"
echo "interface_classes='TOTAL|${interface_major}:1 $interface_classes'" >>"${FIREQOS_DIR}/${interface_name}.conf"
echo "interface_classes_ids='${interface_major}_1 $interface_classes_ids'" >>"${FIREQOS_DIR}/${interface_name}.conf"
echo "interface_classes_monitor='$interface_classes_monitor'" >>"${FIREQOS_DIR}/${interface_name}.conf"
fi
echo >&2
parent_clear
interface_major=1
interface_dev=
interface_name=
interface_inout=
interface_realdev=
interface_minrate=
interface_global_class_counter=1
interface_class_counter=10
interface_class_prio=0
interface_qdisc_counter=10
interface_default_added=0
interface_default_class=${FIREQOS_INTERFACE_DEFAULT_CLASSID}
interface_default_counter=0
interface_classes=
interface_classes_ids=
interface_classes_monitor=
interface_sumrate=0
interface_classid=
interface_stab=1
interface_save=0
interface_priority_mode=
class_matchid=1
parent_stack_size=0
class_name=
class_filters_flowid=
class_classid=
class_major=
class_group=0
last_class_prio=0
return 0
}
ipv4() {
force_ipv="4"
"${@}"
force_ipv=
}
ipv6() {
force_ipv="6"
"${@}"
force_ipv=
}
ipv46() {
force_ipv="46"
"${@}"
force_ipv=
}
interface4() {
ipv4 interface "${@}"
}
interface6() {
ipv6 interface "${@}"
}
interface46() {
ipv46 interface "${@}"
}
#
## supports only 'xt'
#FIREQOS_CONNMARK_SAVE="${FIREQOS_CONNMARK_SAVE-}"
# supports either 'act_connmark' or empty
# 'act_connmark' needs kernel module act_connmark
FIREQOS_CONNMARK_RESTORE="${FIREQOS_CONNMARK_RESTORE-}"
FIREQOS_MARKS_ON_INPUT_USED=0
interface_count=0
interface() {
if [ "$3" = "both" -o "$3" = "bidirectional" ]
then
local a1="$1" a2="$2" a3="$3"
shift 3
interface "$a1" "${a2}-in" input "$@" || return $?
interface_save=1
save interface "$a1" "${a2}-out" output "$@"
return 0
fi
(( interface_count += 1 ))
interface_close
printf >&2 ": ${FUNCNAME} %s" "$*"
interface_dev="$1"; shift
interface_name="$1"; shift
interface_inout="$1"; shift
if [ "${interface_inout}" = "input" ]
then
ifb_interfaces=$((ifb_interfaces + 1))
interface_realdev=${interface_dev}-ifb
interface_realdev=${interface_realdev:0:15}
runcmd $ip_cmd link add dev ${interface_realdev} name ${interface_realdev} type ifb 2>/dev/null
if [ $? -ne 0 -a $? -ne 2 ]
then
error "Cannot add IFB device ${interface_realdev}."
exit 1
fi
# remember we used this interface
$touch_cmd $FIREQOS_DIR/ifbs/${interface_realdev}
runcmd $ip_cmd link set dev ${interface_realdev} up
if [ $? -ne 0 ]
then
error "Cannot bring device ${interface_realdev} UP. Do you have 'ifb' enabled in the kernel?"
exit 1
fi
else
# for 'output' interfaces, realdev is dev
interface_realdev=${interface_dev}
fi
# parse the parameters given
parse_class_params interface noparent "${@}"
[ -z "${interface_priority_mode}" ] && interface_priority_mode="priority"
# IPv, this is only used by matches
# here we just give the defaults for inheritance to work
if [ -z "${interface_ipv4}" -a -z "${interface_ipv6}" ]
then
interface_ipv4=1
interface_ipv6=0
elif [ -z "${interface_ipv4}" ]
then
interface_ipv4=0
elif [ -z "${interface_ipv6}" ]
then
interface_ipv6=0
fi
# check important arguments
if [ -z "${interface_rate}" ]
then
error "Cannot figure out the rate of interface '${interface_dev}'."
return 1
fi
if [ -z "${interface_mtu}" ]
then
# to find the mtu, we query the original device, not an ifb device
interface_mtu=`device_mtu ${interface_dev}`
if [ -z "${interface_mtu}" ]
then
interface_mtu=1500
warning "Device MTU cannot be detected. Setting it to 1500 bytes."
fi
fi
# fix stab
local stab=
if [ ! -z "${interface_linklayer}" ]
then
stab="stab"
test ! -z "${interface_linklayer}" && stab="$stab linklayer ${interface_linklayer}"
test ! -z "${interface_overhead}" && stab="$stab overhead ${interface_overhead}"
test ! -z "${interface_tsize}" && stab="$stab tsize ${interface_tsize}"
test ! -z "${interface_mtu}" && stab="$stab mtu ${interface_mtu}"
test ! -z "${interface_mpu}" && stab="$stab mpu ${interface_mpu}"
fi
# the default ceiling for the interface, is the rate of the interface
# if we don't respect this, all unclassified traffic will get just 1kbit!
[ -z "${interface_ceil}" ] && interface_ceil=${interface_rate}
# set the default qdisc for all classes
if [ -z "${interface_qdisc}" ]
then
interface_qdisc="${FIREQOS_DEFAULT_QDISC}"
interface_qdisc_options="${FIREQOS_DEFAULT_QDISC_OPTIONS}"
fi
# the desired minimum rate for all classes
[ -z "${interface_minrate}" ] && interface_minrate=$((interface_rate / FIREQOS_MIN_RATE_DIVISOR))
# calculate the default r2q for this interface
# *** THIS MAY NOT BE NEEDED ANYMORE, SINCE WE ALWAYS SET QUANTUM ***
if [ -z "${interface_r2q}" ]
then
interface_r2q=`calc_r2q ${interface_minrate} ${interface_mtu}`
fi
# the actual minimum rate we can get
local r=$((interface_r2q * interface_mtu))
[ ${r} -gt ${interface_minrate} ] && interface_minrate=${r}
# set the default quantum
[ -z "${interface_quantum}" ] && interface_quantum=${interface_mtu}
check_constrains interface
local rate="rate $((interface_rate * 8 / 1000))kbit" \
minrate="rate $((interface_minrate * 8 / 1000))kbit" \
ceil= burst= cburst= quantum= r2q=
[ ! -z "${interface_ceil}" ] && ceil="ceil $((interface_ceil * 8 / 1000))kbit"
[ ! -z "${interface_burst}" ] && burst="burst ${interface_burst}"
[ ! -z "${interface_cburst}" ] && cburst="cburst ${interface_cburst}"
[ ! -z "${interface_quantum}" ] && quantum="quantum ${interface_quantum}"
[ ! -z "${interface_r2q}" ] && r2q="r2q ${interface_r2q}"
echo >&2 -e " ${COLOR_BOLD}${COLOR_GREEN}(${interface_realdev}, $((interface_rate*8/1000))kbit, mtu ${interface_mtu}, quantum ${interface_quantum}, minrate $(( interface_minrate * 8 / 1000 ))kbit)${COLOR_RESET}"
# remember we used this interface
echo "$interface_name" >$FIREQOS_DIR/ifaces/${interface_realdev}
# Add root qdisc with proper linklayer and overheads
tc ignore-error qdisc del dev $interface_realdev root
tc qdisc add dev ${interface_realdev} root handle ${interface_major}: ${stab} htb default ${FIREQOS_INTERFACE_DEFAULT_CLASSID} ${r2q}
# redirect all incoming traffic to ifb
if [ ${interface_inout} = input ]
then
# Redirect all incoming traffic to ifb
# We then shape the traffic in the output of ifb
tc ignore-error qdisc del dev ${interface_dev} ingress
tc qdisc add dev ${interface_dev} ingress
local cm=
case "${FIREQOS_CONNMARK_RESTORE}" in
act_connmark)
cm="action connmark"
;;
#xt)
# cm="action xt -j CONNMARK --restore-mark"
# ;;
*)
if [ ! -z "${FIREQOS_CONNMARK_RESTORE}" ]
then
error "Unknown connmark restoration method '${FIREQOS_CONNMARK_RESTORE}'."
return 1
fi
;;
esac
tc filter add dev $interface_dev parent ffff: protocol all prio 39999 u32 match u32 0 0 ${cm} action mirred egress redirect dev ${interface_realdev}
fi
interface_classid="${interface_major}:1"
# Add the root class for the interface
tc class add dev ${interface_realdev} parent ${interface_major}: classid ${interface_classid} htb $rate $ceil $burst $cburst $quantum
#if [ $interface_inout = output ]
#then
# if [ "${FIREQOS_CONNMARK_SAVE}" = "xt" ]
# then
# tc filter add dev ${interface_realdev} parent ${interface_major}: protocol ip prio 0 u32 match u32 0 0 classid ${interface_classid} action xt -j CONNMARK --save-mark
# fi
#fi
interface_filters_to="${interface_major}:0"
parent_push interface
# default IPv for the classes
class_ipv4=$interface_ipv4
class_ipv6=$interface_ipv6
class_name=root
[ -f "${FIREQOS_DIR}/${interface_name}.conf" ] && $rm_cmd "${FIREQOS_DIR}/${interface_name}.conf"
$cat_cmd >"${FIREQOS_DIR}/${interface_name}.conf" <<EOF
interface_name="$interface_name"
interface_rate="$interface_rate"
interface_ceil="$interface_ceil"
interface_dev="$interface_dev"
interface_realdev="$interface_realdev"
interface_inout="$interface_inout"
interface_minrate="$interface_minrate"
interface_linklayer="$interface_linklayer"
interface_linklayer_type="$interface_linklayer_type"
interface_linklayer_encoding="$interface_linklayer_encoding"
interface_overhead="$interface_overhead"
interface_minrate="$interface_minrate"
interface_r2q="$interface_r2q"
interface_burst="$interface_burst"
interface_cburst="$interface_cburst"
interface_quantum="$interface_quantum"
interface_mtu="$interface_mtu"
interface_mpu="$interface_mpu"
interface_tsize="$interface_tsize"
interface_qdisc="$interface_qdisc"
interface_qdisc_options="$interface_qdisc_options"
class_${interface_major}_1_name="TOTAL"
class_${interface_major}_1_classid="CLASSID"
class_${interface_major}_1_priority="PRIORITY"
class_${interface_major}_1_rate="COMMIT"
class_${interface_major}_1_ceil="MAX"
class_${interface_major}_1_burst="BURST"
class_${interface_major}_1_cburst="CBURST"
class_${interface_major}_1_quantum="QUANTUM"
class_${interface_major}_1_qdisc="QDISC"
EOF
return 0
}
class_name=
class_classid=
class_major=
class_group=0
class4() {
ipv4 class "${@}"
}
class6() {
ipv6 class "${@}"
}
class46() {
ipv46 class "${@}"
}
class_count=0
class() {
[ ${interface_save} -eq 1 ] && save ${FUNCNAME} "$@"
(( class_count += 1 ))
# check if the have to push into the stack the last class (if it was a group class)
if [ ${class_group} -eq 1 ]
then
# the last class was a group
# filters have been added to it, and now we have reached its first child class
# we push the previous class, into the our parents stack
class_default_added=0
parent_push class
# the current command is the first child class
fi
# reset the values of the current class
class_name=
class_classid=
class_major=
class_group=0
# if this is a group class
if [ "${1}" = "group" ]
then
shift
# if this is the end of a group class
if [ "${1}" = "end" ]
then
shift
if [ ${parent_stack_size} -le 1 ]
then
error "No open class group to end."
exit 1
fi
# make sure we don't save these statements too
local isave=${interface_save}
interface_save=0
if [ ${parent_default_added} -eq 0 ]
then
class default
fi
# In nested classes, the default of the parent class is not respected
# by the kernel. This rule, sends all remaining traffic to the inner default.
match all class default flowid ${parent_major}:${parent_default_class} prio 0xfffe
check_committed_rate
# restore the previous save status
interface_save=${isave}
if [ ${parent_stab} -eq 1 ]
then
parent_pull
else
local pc=${parent_class_counter}
parent_pull
parent_class_counter=${pc}
fi
return 0
elif [ "${1}" = "default" ]
then
error "The default class cannot have subclasses."
exit 1
fi
class_group=1
fi
printf >&2 ": ${class_tabs}${FUNCNAME} %s" "$*"
class_name="${1}"; shift
# increase the interface global class counter
(( interface_global_class_counter += 1 ))
# increase the parent's class counter
# this is used for determining the minor of the class
(( parent_class_counter += 1 ))
# if this is the default class, use the pre-defined id,
# otherwise use the classid we just increased
if [ "${class_name}" = "default" ]
then
local id=${parent_default_class}
else
local id=${parent_class_counter}
fi
# the tc classid that we will create
# this is used for the parent of all child classed
class_classid="${parent_major}:${id}"
# the flowid the matches on this class will classify the packets
class_filters_flowid="${parent_major}:${id}"
# the id of the class in the config, for getting status info about it
local ncid="${parent_major}_${id}"
# the handle of the new qdisc we will create
(( interface_qdisc_counter += 1 ))
class_major=${interface_qdisc_counter}
parse_class_params class parent quantum "RESET" "${@}"
if [ "$class_quantum" = "RESET" ]
then
if [ ! -z "${class_mtu}" ]
then
class_quantum=${class_mtu}
else
class_quantum=${parent_quantum}
fi
fi
# the priority of this class, compared to the others in the same interface
if [ "${class_prio}" = "keep" -o "${class_prio}" = "last" ]
then
class_prio=${last_class_prio}
elif [ -z "${class_prio}" ]
then
if [ "${parent_priority_mode}" = "balanced" ]
then
class_prio=${FIREQOS_BALANCED_PRIO}
else
class_prio=${parent_class_prio}
# increase the parent's priority of subclasses
(( parent_class_prio += 1 ))
fi
else
parent_class_prio=$((class_prio + 1))
fi
if [ ${class_prio} -lt 0 ]
then
warning "Class '${class_name}' has been assigned priority ${class_prio}, but HTB allows 0-7. Setting it to 0."
class_prio=0
fi
if [ ${class_prio} -gt 7 ]
then
warning "Class '${class_name}' has been assigned priority ${class_prio}, but HTB allows 0-7. Setting it to 7."
class_prio=7
fi
# remember this prio, in case we need it later
last_class_prio="${class_prio}"
# if not specified, set the minimum rate
if [ -z "${class_rate}" ]
then
[ ${class_group} -eq 1 ] && error "Class group '${class_name}' does not specify a committed rate.\nClass groups should have a committed rate."
class_rate=${interface_minrate}
fi
# class rate cannot go bellow 1/100 of the interface rate
if [ ${class_rate} -lt ${interface_minrate} ]
then
warning "class rate ($((class_rate * 8 /1000))kbit) was less than the interface minrate ($((interface_minrate * 8 /1000))kbit). Fixed it by setting class rate to minrate."
class_rate=${interface_minrate}
fi
check_constrains class
[ ! -z "${class_rate}" ] && local rate="rate $((class_rate * 8 / 1000))kbit"
[ ! -z "$class_ceil" ] && local ceil="ceil $((class_ceil * 8 / 1000))kbit"
[ ! -z "$class_burst" ] && local burst="burst $class_burst"
[ ! -z "$class_cburst" ] && local cburst="cburst $class_cburst"
[ ! -z "$class_quantum" ] && local quantum="quantum $class_quantum"
# construct the stab for group class
# later we will check if this is accidentaly used in leaf classes
local stab=
if [ ! -z "${class_linklayer}" ]
then
[ -z "$class_mtu" ] && class_mtu="${parent_mtu}"
stab="stab"
test ! -z "${class_linklayer}" && stab="${stab} linklayer ${class_linklayer}"
test ! -z "${class_overhead}" && stab="${stab} overhead ${class_overhead}"
test ! -z "${class_tsize}" && stab="${stab} tsize ${class_tsize}"
test ! -z "${class_mtu}" && stab="${stab} mtu ${class_mtu}"
test ! -z "${class_mpu}" && stab="${stab} mpu ${class_mpu}"
fi
# check it the user overbooked the parent
parent_sumrate=$(( parent_sumrate + class_rate ))
local info_color="${COLOR_GREEN}"
[ ${parent_sumrate} -gt ${parent_rate} ] && local info_color="${COLOR_RED}"
if [ ${class_group} -eq 1 -a ! -z "${stab}" ]
then
echo >&2 -e " ${info_color}${COLOR_BOLD}(${class_classid}, $((class_rate*8/1000))/$((class_ceil*8/1000))kbit, prio ${class_prio}, MTU ${class_mtu}, quantum ${class_quantum})${COLOR_RESET}"
else
echo >&2 -e " ${info_color}${COLOR_BOLD}(${class_classid}, $((class_rate*8/1000))/$((class_ceil*8/1000))kbit, prio ${class_prio})${COLOR_RESET}"
fi
# keep track of all classes in the interface, so that the matches can name them to get their flowid
interface_classes="${interface_classes} ${class_name}|${class_filters_flowid}"
interface_classes_ids="${interface_classes_ids} ${ncid}"
class_default_class=
if [ ${class_group} -eq 1 ]
then
# this class will have subclasses
class_class_prio=0
(( interface_default_counter += 1 ))
# the default class that all unmatched traffic will be sent to
class_default_class="$((interface_default_class + interface_default_counter))"
# if the user added a stab, we need a qdisc and a slave class bellow the qdisc
if [ ! -z "${stab}" ]
then
# this is a group class with a linklayer
# we add a qdisc with the stab, and an HTB class bellow it
# add the class
tc class add dev ${interface_realdev} parent ${parent_classid} classid ${class_classid} htb ${rate} ${ceil} ${burst} ${cburst} prio ${class_prio} ${quantum}
# attach a qdisc
tc qdisc add dev ${interface_realdev} parent ${class_classid} handle ${class_major}: ${stab} htb default ${class_default_class}
# attach a class bellow the qdisc
tc class add dev ${interface_realdev} parent ${class_major}: classid ${class_major}:1 htb ${rate} ${ceil} ${burst} ${cburst} ${quantum}
# the parent of the child classes
class_classid="${class_major}:1"
# the qdisc the filters of all child classes should be attached to
class_filters_to="${class_major}:0"
class_class_counter=10
class_stab=1
else
# this is a group class without a linklayer
# add the class
tc class add dev ${interface_realdev} parent ${parent_classid} classid ${class_classid} htb ${rate} ${ceil} ${burst} ${cburst} prio ${class_prio} ${quantum}
# there is no need for a qdisc (HTB class directly attached to an HTB class)
class_major=${parent_major}
class_filters_to="${class_classid}"
class_class_counter=${parent_class_counter}
class_stab=0
fi
# if the user didn't give an mtu, set it to our parent's mtu.
# we do this, just for maintaining inheritance.
# (we don't set it to this class - will only be used by the subclasses)
[ -z "${class_mtu}" ] && class_mtu=${parent_mtu}
# this class will become a parent [parent_push()], as soon as we encounter the next class.
# we don't push it now as the parent, because we need to add filters to its parent, redirecting traffic to this class.
# so we add the filters and when we encounter the next class, we push it into the parents' stack, so that it becomes
# the parent for all classes following, until we encounter its matching 'class group end'.
else
# this is a leaf class (no child classes possible)
if [ ! -z "${stab}" ]
then
error "Linklayer can be used only in interfaces and group classes."
exit 1
fi
# add the class
tc class add dev ${interface_realdev} parent ${parent_classid} classid ${class_classid} htb ${rate} ${ceil} ${burst} ${cburst} prio ${class_prio} ${quantum}
# default qdisc options
if [ -z "${class_qdisc_options}" -o "${class_qdisc_options}" = "default" ]
then
# the user didn't give options to this class' qdisc
# find the global qdisc default
eval "local qdisc_options=\${FIREQOS_DEFAULT_QDISC_OPTIONS_$class_qdisc}"
if [ -z "${qdisc_options}" ]
then
# there is no global default
# check if we have an internal default for it
case "${class_qdisc}" in
sfq) qdisc_options="perturb 10 quantum ${class_quantum}"
;;
esac
fi
else
local qdisc_options="${class_qdisc_options}"
fi
local qdisc="${class_qdisc} ${qdisc_options}"
# attach a qdisc, if we have to
if [ ! "${class_qdisc}" = "none" ]
then
local qdisc_cmd="dev ${interface_realdev} ${stab} parent ${class_classid} handle ${class_major}: ${qdisc}"
tc qdisc add ${qdisc_cmd}
fi
# if this is the default, make sure we don't added again
if [ "${class_name}" = "default" ]
then
parent_default_added=1
interface_classes_monitor="${interface_classes_monitor} ${parent_path}${class_name}|$parent_path${class_name}|${class_classid}|${class_major}:"
else
interface_classes_monitor="${interface_classes_monitor} ${class_name}|$parent_path${class_name}|${class_classid}|${class_major}:"
fi
fi
local name="${class_name}"
[ ${parent_stack_size} -gt 1 ] && local name="${parent_name:0:2}/${class_name}"
# save the configuration
$cat_cmd >>"${FIREQOS_DIR}/${interface_name}.conf" <<EOF
class_${ncid}_name="${name}"
class_${ncid}_path="${parent_path}${class_name}"
class_${ncid}_dev="${interface_realdev}"
class_${ncid}_classid="${class_classid}"
class_${ncid}_priority="${class_prio}"
class_${ncid}_rate="${class_rate}"
class_${ncid}_ceil="${class_ceil}"
class_${ncid}_burst="${class_burst}"
class_${ncid}_cburst="${class_cburst}"
class_${ncid}_quantum="${class_quantum}"
class_${ncid}_qdisc="${class_qdisc}"
class_${ncid}_qdisc_parent="${class_classid}"
class_${ncid}_qdisc_handle="${class_major}:"
class_${ncid}_qdisc_stab="${stab}"
class_${ncid}_qdisc_options="${qdisc_options}"
class_${ncid}_qdisc_cmd="${qdisc_cmd}"
class_${ncid}_group="${class_group}"
EOF
return 0
}
find_port_masks() {
# echo >&2 "${FUNCNAME} ${@}"
local from=$(($1)) to=$(($2)) i= base= end= mask= next=
test -z "${from}" && from="${to}"
test -z "${from}" && return 1
if [ -z "${to}" ]
then
[ ${FIREQOS_DEBUG_PORTS} -eq 1 ] && echo >&2 "${from}/0xffff"
echo "${from}/0xffff"
return 0
fi
if [ ${from} -ge ${to} ]
then
[ ${FIREQOS_DEBUG_PORTS} -eq 1 ] && echo >&2 "${from}/0xffff"
echo "${from}/0xffff"
return 0
fi
# find the biggest power of two that fits in the range
# starting from $from
for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
do
base=$(( (from >> i) << i ))
end=$(( base + (1 << i) - 1 ))
[ ${FIREQOS_DEBUG_PORTS} -eq 1 ] && printf >&2 ": >>> examine bit %d, from 0x%04x (%s) to 0x%04x (%s), " ${i} ${base} ${base} ${end} ${end}
[ ${base} -ne ${from} ] && break
[ ${end} -gt ${to} ] && break
[ ${FIREQOS_DEBUG_PORTS} -eq 1 ] && echo >&2 " ok"
done
[ ${FIREQOS_DEBUG_PORTS} -eq 1 ] && echo >&2 " failed"
(( i += -1 ))
base=$(( (from >> i) << i ))
end=$(( base + (1 << i) - 1 ))
mask=$(( (0xffff >> i) << i ))
[ ${FIREQOS_DEBUG_PORTS} -eq 1 ] && printf >&2 ": 0x%04x (%d) to 0x%04x (%d), match 0x%04x (%d) to 0x%04x (%d) with mask 0x%04x \n" ${from} ${from} ${to} ${to} ${base} ${base} ${end} $$
printf "%d/0x%04x\n" ${base} ${mask}
if [ ${end} -lt ${to} ]
then
next=$[end + 1]
[ ${FIREQOS_DEBUG_PORTS} -eq 1 ] && printf >&2 "\n: next range 0x%04x (%d) to 0x%04x (%d)\n" ${next} ${next} ${to} ${to}
find_port_masks ${next} ${to}
fi
return 0
}
expand_ports() {
# echo >&2 "${FUNCNAME} ${@}"
local i=
for i in ${@//,/ }
do
case "${i}" in
any|all)
echo "${i}"
;;
*:*)
find_port_masks ${i//:/ }
;;
*-*)
find_port_masks ${i//-/ }
;;
*) find_port_masks ${i}
;;
esac
shift
done
return 0
}
match_count=0
match4() { ipv4 match "${@}"; }
match6() { ipv6 match "${@}"; }
match46() { ipv46 match "${@}"; }
match() {
# echo >&2 "${FUNCNAME} ${@}"
(( match_count += 1 ))
if [ "z${1}" = "z-ns" ]
then
shift
else
[ ${interface_save} -eq 1 ] && save ${FUNCNAME} "$@"
fi
[ ${FIREQOS_DEBUG} -eq 1 -o ${FIREQOS_SHOW_MATCHES} -eq 1 ] && echo >&2 -e "${COLOR_GREEN}: ${FUNCNAME} $*${COLOR_RESET}"
local proto=any port=any sport=any dport=any src=any dst=any ip=any tos=any mark= \
srcmac=any dstmac=any class=${class_name} flowid=${class_filters_flowid} \
ack=0 syn=0 at= custom= tcproto= ipv4=${class_ipv4} ipv6=${class_ipv6} \
reverse=0 estimator_interval= estimator_decay= police_arg= \
prio= limit_rate= t=
case "${force_ipv}" in
4)
ipv4=1
ipv6=0
;;
6)
ipv4=0
ipv6=1
;;
46)
ipv4=1
ipv6=1
;;
esac
while [ ! -z "${1}" ]
do
case "${1}" in
input|output)
reverse=1
[ "${interface_inout}" = "${1}" ] && reverse=0
;;
reverse)
reverse=1
;;
at)
at="$2"
shift
;;
root)
at="root"
;;
syn|syns)
syn=1
;;
ack|acks)
ack=1
;;
arp)
tcproto="$1"
;;
tcp|TCP|udp|UDP|icmp|ICMP|gre|GRE|ipv6|IPv6|all)
proto="$1"
;;
tos)
tos="${2//,/ }"
shift
;;
connmark|connmarks)
mark="${mark} $(mark_value connmark ${2//,/ })"
shift
;;
mark|marks)
mark="${mark} $(mark_value usermark ${2//,/ })"
shift
;;
custommark|custommarks)
mark="${mark} $(mark_value $2 ${3//,/ })"
shift 2
;;
rawmark|rawmarks)
mark="${mark} ${2//,/ }"
shift
;;
proto|protocol|protocols)
proto="${2//,/ }"
shift
;;
port|ports)
port="${2//,/ }"
shift
;;
sport|sports)
sport="${2//,/ }"
shift
;;
dport|dports)
dport="${2//,/ }"
shift
;;
src)
src="${2//,/ }"
shift
;;
dst)
dst="${2//,/ }"
shift
;;
prio)
prio="$2"
shift
;;
ip|ips|net|nets|host|hosts)
ip="${2//,/ }"
shift
;;
class)
class="$2"
shift
;;
flowid)
flowid="$2"
shift
;;
custom)
custom="$2"
shift
;;
estimator)
estimator_interval="$2"
estimator_decay="$3"
shift 2
;;
police)
police_arg="$2"
shift
;;
limit)
limit_rate="`rate2bps $2 ${class_rate}`"
estimator_interval="500msec"
estimator_decay="1sec"
police_arg="rate $[limit_rate * 8 / 1000]kbit burst 50kb continue"
shift
;;
srcmac|smac)
srcmac="${2//,/ }"
srcmac="${srcmac//:/}"
shift
;;
dstmac|dmac)
dstmac="${2//,/ }"
dstmac="${dstmac//:/}"
shift
;;
*) error "Cannot understand what the filter '${1}' is."
return 1
;;
esac
shift
done
if [ -z "${mark}" ]
then
mark="any"
else
if [ ${interface_inout} = input -a -z "${FIREQOS_CONNMARK_RESTORE}" ]
then
(( FIREQOS_MARKS_ON_INPUT_USED += 1 ))
fi
fi
# if reverse, flip src/dst sport/dport
if [ ${reverse} -eq 1 ]
then
t="${src}"
src="${dst}"
dst="${t}"
t="${sport}"
sport="${dport}"
dport="${t}"
t="${srcmac}"
srcmac="${dstmac}"
dstmac="${t}"
fi
if [ -z "${prio}" ]
then
prio=$((class_matchid * FIREQOS_MATCHES_STEP))
(( class_matchid += 1 ))
fi
port="$(expand_ports ${port})"
sport="$(expand_ports ${sport})"
dport="$(expand_ports ${dport})"
[ -z "${proto}" ] && error "Cannot accept empty protocol." && return 1
[ -z "${port}" ] && error "Cannot accept empty ports." && return 1
[ -z "${sport}" ] && error "Cannot accept empty source ports." && return 1
[ -z "${dport}" ] && error "Cannot accept empty destination ports." && return 1
[ -z "${src}" ] && error "Cannot accept empty source IPs." && return 1
[ -z "${dst}" ] && error "Cannot accept empty destination IPs." && return 1
[ -z "${ip}" ] && error "Cannot accept empty IPs." && return 1
[ -z "${tos}" ] && error "Cannot accept empty TOS." && return 1
[ -z "${mark}" ] && error "Cannot accept empty MARK." && return 1
[ -z "${srcmac}" ] && error "Cannot accept empty source MAC." && return 1
[ -z "${dstmac}" ] && error "Cannot accept empty destination MAC." && return 1
[ ! "${port}" = "any" -a ! "${sport}" = "any" ] && error "Cannot match 'port' and 'sport'." && exit 1
[ ! "${port}" = "any" -a ! "${dport}" = "any" ] && error "Cannot match 'port' and 'dport'." && exit 1
[ ! "${ip}" = "any" -a ! "${src}" = "any" ] && error "Cannot match 'ip' and 'src'." && exit 1
[ ! "${ip}" = "any" -a ! "${dst}" = "any" ] && error "Cannot match 'ip' and 'dst'." && exit 1
local device=${interface_realdev}
local parent="${parent_filters_to}"
if [ -z "${flowid}" ]
then
error "Please set 'flowid' for match statements above all classes."
exit 1
elif [ ! "${class}" = "${class_name}" ]
then
local c=
for c in ${interface_classes}
do
local cn="`echo $c | ${cut_cmd} -d '|' -f 1`"
local cf="`echo $c | ${cut_cmd} -d '|' -f 2`"
if [ "${class}" = "${cn}" ]
then
local flowid=${cf}
break
fi
done
if [ -z "${flowid}" ]
then
error "Cannot find class '${class}'"
exit 1
fi
fi
if [ ! -z "${at}" ]
then
case "${at}" in
root)
local parent="${interface_filters_to}"
;;
*)
local c=
for c in ${interface_classes}
do
local cn="`echo $c | ${cut_cmd} -d '|' -f 1`"
local cf="`echo $c | ${cut_cmd} -d '|' -f 2`"
if [ "${class}" = "${cn}" ]
then
local parent=${cf}
break
fi
done
if [ -z "${parent}" ]
then
error "Cannot find class '${class}'"
exit 1
fi
;;
esac
fi
if [ -z "${tcproto}" ]
then
[ ${ipv4} -eq 1 ] && tcproto="${tcproto} ip"
[ ${ipv6} -eq 1 ] && tcproto="${tcproto} ipv6"
fi
local ipvx= ether_type= ack_arg= syn_arg= proto_arg= tcproto_arg= tproto= \
pid= tip= mtip= otherip= ip_arg= tsrc= src_arg= tdst= dst_arg= \
tport= mtport= otherport= port_arg= mportmask= tsport= sport_arg= tdport= dport_arg= \
u32= ttos= tos_arg= tos_value= tos_mask= estimator= police= sm1= sm2= dm1= dm2= \
dmac= dmac_arg= smac= smac_arg= tmark= mark_arg=
# create all tc filter statements
for tcproto_arg in ${tcproto}
do
ipvx=
ether_type=
case ${tcproto_arg} in
ip)
ether_type="0x0800"
;;
ipv6)
ether_type="0x86DD"
ipvx="6"
;;
*)
esac
for tproto in $proto
do
ack_arg=
syn_arg=
proto_arg=
case ${tproto} in
any) ;;
all)
proto_arg="match ip${ipvx} protocol 0 0x00"
;;
ipv6|IPv6)
proto_arg="match ip${ipvx} protocol 41 0xff"
;;
icmp|ICMP)
if [ "${ipvx}" = "6" ]
then
proto_arg="match ip${ipvx} protocol 58 0xff"
else
proto_arg="match ip${ipvx} protocol 1 0xff"
fi
;;
tcp|TCP)
proto_arg="match ip${ipvx} protocol 6 0xff"
# http://www.lartc.org/lartc.html#LARTC.ADV-FILTER
if [ ${ack} -eq 1 ]
then
if [ "${ipvx}" = "6" ]
then
ack_arg="match u8 0x10 0xff at nexthdr+13"
error "I don't know how to match ACKs in ipv6"
exit 1
else
ack_arg="match u8 0x05 0x0f at 0 match u16 0x0000 0xffc0 at 2 match u8 0x10 0xff at 33"
fi
fi
if [ ${syn} -eq 1 ]
then
if [ "${ipvx}" = "6" ]
then
# I figured this out, based on the ACK match
syn_arg="match u8 0x02 0x02 at nexthdr+13"
error "I don't know how to match SYNs in ipv6"
exit 1
else
# I figured this out, based on the ACK match
syn_arg="match u8 0x02 0x02 at 33"
fi
fi
;;
udp|UDP)
proto_arg="match ip${ipvx} protocol 17 0xff"
;;
gre|GRE)
proto_arg="match ip${ipvx} protocol 47 0xff"
;;
+([0-9]))
proto_arg="match ip${ipvx} protocol ${tproto} 0xff"
;;
*) pid=`${cat_cmd} /etc/protocols | ${egrep_cmd} -i "^${tproto}[[:space:]]" | tail -n 1 | ${sed_cmd} "s/[[:space:]]\+/ /g" | ${cut_cmd} -d ' ' -f 2`
if [ -z "${pid}" ]
then
error "Cannot find protocol '${tproto}' in /etc/protocols."
return 1
fi
proto_arg="match ip${ipvx} protocol ${pid} 0xff"
;;
esac
mtip=src
otherip="dst ${ip}"
[ "${ip}" = "any" ] && otherip=
for tip in ${ip} ${otherip}
do
[ "${tip}" = "dst" ] && mtip="dst" && continue
ip_arg=
case "${tip}" in
any)
;;
all)
ip_arg="match ip${ipvx} $mtip 0.0.0.0/0"
;;
*) ip_arg="match ip${ipvx} ${mtip} ${tip}"
;;
esac
for tsrc in ${src}
do
src_arg=
case "${tsrc}" in
any) ;;
all)
src_arg="match ip${ipvx} src 0.0.0.0/0"
;;
*) src_arg="match ip${ipvx} src ${tsrc}"
;;
esac
for tdst in ${dst}
do
dst_arg=
case "${tdst}" in
any) ;;
all) dst_arg="match ip${ipvx} dst 0.0.0.0/0"
;;
*) dst_arg="match ip${ipvx} dst ${tdst}"
;;
esac
mtport=sport
otherport="dport ${port}"
[ "${port}" = "any" ] && otherport=
for tport in ${port} ${otherport}
do
[ "${tport}" = "dport" ] && mtport="dport" && continue
port_arg=
case "${tport}" in
any) ;;
all) port_arg="match ip${ipvx} ${mtport} 0 0x0000"
;;
*) mportmask=`echo ${tport} | ${tr_cmd} "/" " "`
port_arg="match ip${ipvx} ${mtport} ${mportmask}"
;;
esac
for tsport in ${sport}
do
sport_arg=
case "$tsport" in
any) ;;
all) sport_arg="match ip${ipvx} sport 0 0x0000"
;;
*) mportmask=`echo ${tsport} | ${tr_cmd} "/" " "`
sport_arg="match ip${ipvx} sport ${mportmask}"
;;
esac
for tdport in $dport
do
dport_arg=
case "${tdport}" in
any) ;;
all) dport_arg="match ip${ipvx} dport 0 0x0000"
;;
*) mportmask=`echo ${tdport} | $tr_cmd "/" " "`
dport_arg="match ip${ipvx} dport ${mportmask}"
;;
esac
for ttos in ${tos}
do
tos_arg=
tos_value=
tos_mask=
case "${ttos}" in
any) ;;
lowdelay|min-delay|minimize-delay|minimum-delay|low-delay|interactive)
tos_value="0x10"
tos_mask="0x10"
;;
throughput|maximize-throughput|maximum-throughput|max-throughput|high-throughput|bulk)
tos_value="0x08"
tos_mask="0x08"
;;
reliability|maximize-reliability|maximum-reliability|max-reliability|reliable)
tos_value="0x04"
tos_mask="0x04"
;;
mincost|min-cost|minimize-cost|minimum-cost|low-cost|cheap)
tos_value="0x02"
tos_mask="0x02"
;;
normal|normal-service)
tos_value="0x00"
tos_mask="0x1e"
;;
all)
tos_value="0x00"
tos_mask="0x00"
;;
*)
tos_value="`echo "${ttos}/" | ${cut_cmd} -d '/' -f 1`"
tos_mask="`echo "${ttos}/" | ${cut_cmd} -d '/' -f 2`"
[ -z "${tos_mask}" ] && tos_mask="0xff"
if [ -z "${tos_value}" ]
then
error "Empty TOS value is not allowed."
exit 1
fi
;;
esac
if [ ! -z "${tos_value}" -a ! -z "${tos_mask}" ]
then
if [ "$ipvx" = "6" ]
then
tos_arg="match ip6 priority ${tos_value} ${tos_mask}"
else
tos_arg="match ip tos ${tos_value} ${tos_mask}"
fi
fi
for tmark in ${mark}
do
# http://mailman.ds9a.nl/pipermail/lartc/2007q3/021364.html
mark_arg=
case "${tmark}" in
any) ;;
*)
# mark_arg="handle $tmark fw"
mark_arg="u32 match mark ${tmark//\// }"
;;
esac
for smac in ${srcmac}
do
smac_arg=
if [ ! "${smac}" = "any" ]
then
sm1=`echo "${smac}" | cut -b 1-8`
sm2=`echo "${smac}" | cut -b 9-12`
smac_arg="u32"
test ! -z "${ether_type}" && smac_arg="${smac_arg} match u16 ${ether_type} 0xFFFF at -2"
smac_arg="${smac_arg} match u16 0x${sm2} 0xFFFF at -4 match u32 0x${sm1} 0xFFFFFFFF at -8"
fi
for dmac in ${dstmac}
do
dmac_arg=
if [ ! "${dmac}" = "any" ]
then
dm1=`echo "${dmac}" | cut -b 1-4`
dm2=`echo "${dmac}" | cut -b 5-12`
dmac_arg="u32"
test ! -z "${ether_type}" && dmac_arg="${dmac_arg} match u16 ${ether_type} 0xFFFF at -2"
dmac_arg="${dmac_arg} match u32 0x${dm2} 0xFFFFFFFF at -12 match u16 0x${dm1} 0xFFFF at -14"
fi
if [ "${tcproto_arg}" = "arp" ]
then
u32="u32 match u32 0 0"
else
u32="u32"
[ -z "${proto_arg}${ip_arg}${src_arg}${dst_arg}${port_arg}${sport_arg}${dport_arg}${tos_arg}${ack_arg}${syn_arg}" ] && u32=
fi
# [ ! -z "${u32}" -a ! -z "${mark_arg}" ] && mark_arg="and ${mark_arg}"
estimator=
if [ ! -z "${estimator_interval}" -a ! -z "${estimator_decay}" ]
then
estimator="estimator ${estimator_interval} ${estimator_decay}"
fi
police=
if [ ! -z "${police_arg}" ]
then
police="police ${police_arg}"
fi
tc filter add \
dev ${device} parent ${parent} protocol ${tcproto_arg} prio ${prio} \
${estimator} \
${u32} \
${proto_arg} ${ip_arg} ${src_arg} ${dst_arg} ${port_arg} ${sport_arg} ${dport_arg} ${tos_arg} ${ack_arg} ${syn_arg} ${mark_arg} \
${smac_arg} \
${dmac_arg} \
${custom} \
flowid ${flowid} \
${police}
done # dstmac
done # srcmac
done # mark
done # tos
done # dport
done # sport
done # port
done # dst
done # src
done # ip
done # proto
# increase the counter between tc protocols
(( prio += 1 ))
done # tcproto (ipv4, ipv6)
return 0
}
clear_everything() {
local iface= found=
local fqifaces=
local ifaces=
local ifbs=
if [ ! -z "${*}" ]
then
for iface in ${*}
do
found=0
if [ -f "${FIREQOS_DIR}/ifaces/${iface}-ifb" ]
then
fqifaces="${fqifaces} `${cat_cmd} ${FIREQOS_DIR}/ifaces/${iface}-ifb`"
ifaces="${ifaces} ${iface}-ifb"
ifbs="${ifbs} ${iface}-ifb"
found=1
fi
if [ -f "${FIREQOS_DIR}/ifaces/${iface}" ]
then
fqifaces="${fqifaces} `${cat_cmd} ${FIREQOS_DIR}/ifaces/${iface}`"
ifaces="${ifaces} ${iface}"
found=1
fi
test ${found} -eq 1 && continue
echo >&2 "There is no device named ${iface} configured by FireQOS."
done
else
fqifaces="`fireqos_active_interfaces`"
ifaces="`ls ${FIREQOS_DIR}/ifaces/ 2>/dev/null`"
ifbs="`ls ${FIREQOS_DIR}/ifbs/ 2>/dev/null`"
fi
echo >&2
echo >&2 "Clearing FireQOS interface(s) ${fqifaces}..."
echo >&2
# remove qdiscs
for iface in ${ifaces}
do
test ! -f "${FIREQOS_DIR}/ifaces/${iface}" && continue
printf >&2 " %16.16s: " $iface
echo >&2 "cleared traffic control"
# remove existing qdisc from all devices
runcmd ${tc_cmd} qdisc del dev ${iface} ingress >/dev/null 2>&1
runcmd ${tc_cmd} qdisc del dev ${iface} root >/dev/null 2>&1
$rm_cmd "${FIREQOS_DIR}/ifaces/${iface}"
done
# remove IFB devices
for iface in ${ifbs}
do
test ! -f "${FIREQOS_DIR}/ifbs/${iface}" && continue
printf >&2 " %16.16s: " ${iface}
echo >&2 "removed IFB device"
runcmd ${ip_cmd} link del dev ${iface} name ${iface} type ifb >/dev/null 2>&1
${rm_cmd} "${FIREQOS_DIR}/ifbs/${iface}"
done
# remove FireQOS run status
for iface in ${fqifaces}
do
printf >&2 " %16.16s: " ${iface}
echo >&2 "cleared status info"
$rm_cmd ${FIREQOS_DIR}/${iface}.conf
done
echo >&2
syslog error "Cleared all FireQOS on ${fqifaces}"
return 0
}
clear_all_qos_on_all_interfaces() {
echo >&2
echo >&2 "Clearing all QoS on all interfaces..."
echo >&2
local dev=
for dev in `${cat_cmd} /proc/net/dev | ${grep_cmd} ':' | ${cut_cmd} -d ':' -f 1 | ${sed_cmd} "s/ //g" | ${grep_cmd} -v "^lo$"`
do
printf >&2 " %16.16s: " ${dev}
echo >&2 "cleared traffic control"
# remove existing qdisc from all devices
tc ignore-error qdisc del dev ${dev} ingress >/dev/null 2>&1
tc ignore-error qdisc del dev ${dev} root >/dev/null 2>&1
done
echo >&2
rmmod ifb 2>/dev/null
echo >&2 " - removed all IFB devices"
if [ -d ${FIREQOS_DIR} ]
then
cd ${FIREQOS_DIR}
if [ $? -eq 0 ]
then
${rm_cmd} interfaces *.conf 2>/dev/null
fi
echo >&2 " - cleared FireQOS status"
fi
return 0
}
check_root() {
if [ ! "${UID}" = 0 ]
then
echo >&2
echo >&2
echo >&2 "Only user root can run FireQOS."
echo >&2
exit 1
fi
}
show_interfaces() {
if [ -d ${FIREQOS_DIR} ]
then
echo >&2
echo >&2 "The following interfaces are available:"
fireqos_active_interfaces
echo >&2
else
echo >&2 "No interfaces have been configured."
fi
}
FIREQOS_STATS_ID="stats.$$.${RANDOM}"
cleanup_stats() {
local x=
for x in `ls ${FIREQOS_DIR}/${FIREQOS_STATS_ID}.* 2>/dev/null`
do
${rm_cmd} ${x}
done
}
stats_colors() {
local drops="${1}" overlimits="${2}" requeues="${3}" backlog="${4}" fcolor= bcolor=
[ $((backlog)) -gt 0 ] && fcolor="${COLOR_BOLD}${COLOR_YELLOW}"
[ $((requeues)) -gt 0 ] && bcolor="${COLOR_BGBLUE}"
[ $((overlimits)) -gt 0 ] && bcolor="${COLOR_BGPURPLE}"
[ $((drops)) -gt 0 ] && bcolor="${COLOR_BGRED}"
echo -e -n "${fcolor}${bcolor}"
}
htb_stats() {
local x=
require_cmd awk || exit 1
trap cleanup_stats EXIT
trap cleanup_stats SIGHUP
if [ `date +%N` = "%N" ]
then
warning "System has low-res time, stats may be inaccurate"
FIREQOS_LOWRES_TIMER=1
fi
if [ -z "$2" -o ! -f "${FIREQOS_DIR}/$2.conf" ]
then
echo >&2 "There is no interface named '$2' to show."
show_interfaces
exit 1
fi
# load the interface configuration
source "${FIREQOS_DIR}/$2.conf" || exit 1
# create the awk file to parse tc output
local title="UNSET" unit="Unknown/s" maxn="0" show_speeds=0 resolution=1 show_speeds=0 show=TCDROPS \
awk_script= number_digits= round= banner_every_lines=20 d= s= n= startedms=0 endedms=0
case "${1}" in
drops|dropped)
title="Packet Drops"
resolution=1
unit="packets/s"
maxn=99999
show_speeds=0
show=TCDROPS
;;
overlimits|over)
title="Packet Overlimits"
resolution=1
unit="packets/s"
maxn=99999
show_speeds=0
show=TCOVERS
;;
requeues)
title="Packet Requeues"
resolution=1
unit="packets/s"
maxn=99999
show_speeds=0
show=TCREQUEUES
;;
status)
title="Class Utilization"
show_speeds=1
show=TCSTATS
# pick the right unit for this interface (bit/s, Kbit, Mbit)
resolution=1
[ $((interface_rate * 8)) -gt $((100 * 1000)) ] && resolution=1000
[ $((interface_rate * 8)) -gt $((200 * 1000000)) ] && resolution=1000000
unit="bits/s"
[ ${resolution} = 1000 ] && unit="Kbit/s"
[ ${resolution} = 1000000 ] && unit="Mbit/s"
maxn="$(( interface_rate * 8 / resolution * 120 / 100))"
;;
*)
echo "Cannot understand what '$1' status is."
exit 1
;;
esac
shift
cat >${FIREQOS_DIR}/${FIREQOS_STATS_ID}.stats.awk <<EOF || exit 1
{
if( \$2 == "htb" ) {
value = \$13;
drops = \$18;
overs = \$20;
requeues = \$22;
backlog = \$28;
print "TCSTATS_" \$2 "_" \$3 "=\$(( (" value "*8) - OLD_TCSTATS_" \$2 "_" \$3 "));"
print "OLD_TCSTATS_" \$2 "_" \$3 "=\$((" value "*8));"
print "TCDROPS_" \$2 "_" \$3 "=\$(( (" drops ") - OLD_TCDROPS_" \$2 "_" \$3 "));"
print "OLD_TCDROPS_" \$2 "_" \$3 "=\$((" drops "));"
print "TCOVERS_" \$2 "_" \$3 "=\$(( (" overs ") - OLD_TCOVERS_" \$2 "_" \$3 "));"
print "OLD_TCOVERS_" \$2 "_" \$3 "=\$((" overs "));"
print "TCREQUEUES_" \$2 "_" \$3 "=\$(( (" requeues ") - OLD_TCREQUEUES_" \$2 "_" \$3 "));"
print "OLD_TCREQUEUES_" \$2 "_" \$3 "=\$((" requeues "));"
print "TCBACKLOG_" \$2 "_" \$3 "=\$((" backlog "));"
}
else {
print "# Cannot parse " \$2 " class " \$3;
value = 0;
}
}
EOF
awk_script="`cat ${FIREQOS_DIR}/${FIREQOS_STATS_ID}.stats.awk`"
$rm_cmd ${FIREQOS_DIR}/${FIREQOS_STATS_ID}.stats.awk
# attempt to shrink the list horizontally
# find how many digits we need
number_digits=${#maxn}
number_digits=$((number_digits + 1))
[ ${number_digits} -lt 6 ] && number_digits=6
# find what number we have to add, to round to closest number
# instead of round down (the only available in shell).
round=0
[ ${resolution} -gt 1 ] && round=$((resolution / 2))
getdata() {
eval "`${tc_cmd} -s class show dev ${1} | ${tr_cmd} "\n,()" "| " | ${sed_cmd} \
-e "s/[^|]|class /||class /g" \
-e "s/ \+/ /g" \
-e "s/ *| */|/g" \
-e "s/||/\n/g" \
-e "s/|/ /g" \
-e "s/\([0-9]\+\)Mbit /\1000000 /g" \
-e "s/\([0-9]\+\)Kbit /\1000 /g" \
-e "s/\([0-9]\+\)bit /\1 /g" \
-e "s/\([0-9]\+\)pps /\1 /g" \
-e "s/\([0-9]\+\)b /\1 /g" \
-e "s/\([0-9]\+\)p /\1 /g" \
-e "s/:/_/g" \
-e "s/ prio [0-9]\+ / /g" \
-e "s/ root / /g" \
-e "s/ parent [0-9]*_[0-9]* / /g" \
-e "s/ leaf [0-9]*_[0-9]* / /g" |\
${awk_cmd} "${awk_script}"`"
}
getms() {
d=`date +'%s.%N'`
s=`echo ${d} | ${cut_cmd} -d '.' -f 1`
n=`echo ${d} | ${cut_cmd} -d '.' -f 2 | ${cut_cmd} -b 1-3`
if [ ${FIREQOS_LOWRES_TIMER} -eq 1 ]
then
n=000
fi
echo "${s}${n}"
}
starttime() {
startedms=`getms`
}
endtime() {
endedms=`getms`
}
sleepms() {
local timetosleep="$1"
local diffms=$((endedms - startedms))
[ $diffms -gt $timetosleep ] && return 0
local sleepms=$((timetosleep - diffms))
local secs=$((sleepms / 1000))
local ms=$((sleepms - (secs * 1000)))
# echo "Sleeping for ${secs}.${ms} (started ${startedms}, ended ${endedms}, diffms ${diffms})"
if [ ${FIREQOS_LOWRES_TIMER} -eq 1 ]
then
sleep "${secs}"
else
sleep "${secs}.${ms}"
fi
}
echo
echo "$interface_name: $interface_dev $interface_inout => $interface_realdev, type: $interface_linklayer, overhead: $interface_overhead"
[ $show_speeds -eq 1 ] && echo "Rate: $((((interface_rate*8)+round)/resolution))$unit, min: $((((interface_minrate*8)+round)/resolution))$unit"
echo "Values in $unit"
echo
starttime
getdata $interface_realdev
# render the configuration
local x=
for x in $interface_classes_ids
do
eval local name="\${class_${x}_name}"
[ "$name" = "TOTAL" ] && local name="CLASS"
printf "% ${number_digits}.${number_digits}s " $name
done
echo
for x in $interface_classes_ids
do
eval local classid="\${class_${x}_classid}"
printf "% ${number_digits}.${number_digits}s " $classid
done
echo
if [ $show_speeds -eq 1 ]
then
for x in $interface_classes_ids
do
eval "local drops=\$TCDROPS_htb_${x}"
eval "local overlimits=\$TCOVERS_htb_${x}"
eval "local requeues=\$TCREQUEUES_htb_${x}"
stats_colors "$drops" "$overlimits" "$requeues" 0
eval local rate="\${class_${x}_rate}"
[ ! "${rate}" = "COMMIT" ] && local rate=$(( ((rate * 8) + round) / resolution ))
printf "% ${number_digits}.${number_digits}s " $rate
echo -e -n "$COLOR_RESET"
done
echo
for x in $interface_classes_ids
do
eval local ceil="\${class_${x}_ceil}"
[ ! "${ceil}" = "MAX" ] && local ceil=$(( ((ceil * 8) + round) / resolution ))
printf "% ${number_digits}.${number_digits}s " $ceil
done
echo
fi
echo
for x in $interface_classes_ids
do
eval local priority="\${class_${x}_priority}"
printf "% ${number_digits}.${number_digits}s " $priority
done
echo
for x in $interface_classes_ids
do
eval local qdisc="\${class_${x}_qdisc}"
printf "% ${number_digits}.${number_digits}s " $qdisc
done
echo
# the main loop
endtime
sleepms 1000
starttime
local c=$((banner_every_lines - 1))
while [ 1 = 1 ]
do
local c=$((c+1))
getdata $interface_realdev
if [ $c -eq ${banner_every_lines} ]
then
echo
if [ "$show" = "TCSTATS" ]
then
echo -n " color code (packets): "
stats_colors 0 0 0 1
echo -e -n " backlog ${COLOR_RESET} | "
stats_colors 1 0 0 0
echo -e -n " dropped ${COLOR_RESET} | "
stats_colors 0 1 0 0
echo -e -n " delayed ${COLOR_RESET} | "
stats_colors 0 0 1 0
echo -e " requeued ${COLOR_RESET}"
fi
echo " $title on $interface_name ($interface_dev $interface_inout => $interface_realdev) - values in $unit"
for x in $interface_classes_ids
do
eval local name="\${class_${x}_name}"
printf "% ${number_digits}.${number_digits}s " $name
done
echo
local c=0
fi
for x in $interface_classes_ids
do
eval "local y=\$${show}_htb_${x}"
if [ "$show" = "TCSTATS" ]
then
eval "local drops=\$TCDROPS_htb_${x}"
eval "local overlimits=\$TCOVERS_htb_${x}"
eval "local requeues=\$TCREQUEUES_htb_${x}"
eval "local backlog=\$TCBACKLOG_htb_${x}"
stats_colors "$drops" "$overlimits" "$requeues" "$backlog"
fi
if [ -z "$y" ]
then
printf "% ${number_digits}.${number_digits}s " ERROR
elif [ "$y" = "0" ]
then
printf "% ${number_digits}.${number_digits}s " "-"
elif [ "$y" -lt 0 ]
then
printf "% ${number_digits}.${number_digits}s " RESET
else
printf "% ${number_digits}d " $(( (y+round) / resolution ))
fi
[ "$show" = "TCSTATS" ] && echo -e -n "$COLOR_RESET"
done
echo
endtime
sleepms 1000
starttime
done
}
FIREQOS_MONITOR_ADDED=0
remove_monitor() {
if [ "$FIREQOS_MONITOR_ADDED" -eq 1 ]
then
runcmd $tc_cmd filter del dev $class_monitor_dev parent $class_monitor_qdisc_handle protocol all prio 1 u32 match u32 0 0 action mirred egress mirror dev fireqos_monitor
runcmd $ip_cmd link set dev fireqos_monitor down
runcmd $ip_cmd link del dev fireqos_monitor name fireqos_monitor type dummy
case "$class_monitor_qdisc" in
none)
runcmd $tc_cmd qdisc del dev $class_monitor_dev parent $class_monitor_qdisc_parent handle $class_monitor_qdisc_handle htb
;;
htb)
;;
*)
runcmd $tc_cmd qdisc del dev $class_monitor_dev parent $class_monitor_qdisc_parent handle $class_monitor_qdisc_handle htb
runcmd $tc_cmd qdisc add $class_monitor_qdisc_cmd
;;
esac
echo "FireQOS: monitor removed from device '$class_monitor_dev', qdisc '$class_monitor_qdisc_handle'."
FIREQOS_MONITOR_ADDED=0
fi
echo >&2 "bye..."
[ -f "${FIREQOS_LOCK_FILE}" ] && $rm_cmd -f "${FIREQOS_LOCK_FILE}" >/dev/null 2>&1
}
add_monitor() {
check_root
if [ -z "$class_monitor_dev" -o -z "$class_monitor_qdisc" -o -z "$class_monitor_qdisc_handle" ]
then
echo "Cannot setup monitor on device '$class_monitor_dev' for handle '$class_monitor_qdisc_handle'."
exit 1
fi
FIREQOS_LOCK_FILE_TIMEOUT=$((86400 * 30))
fireqos_concurrent_run_lock
trap remove_monitor EXIT
trap remove_monitor SIGHUP
runcmd $modprobe_cmd dummy numdummies=0 >/dev/null 2>&1
runcmd $ip_cmd link del dev fireqos_monitor name fireqos_monitor type dummy >/dev/null 2>&1
runcmd $ip_cmd link add dev fireqos_monitor name fireqos_monitor type dummy || exit 1
runcmd $ip_cmd link set dev fireqos_monitor up || exit 1
case "$class_monitor_qdisc" in
none)
runcmd $tc_cmd qdisc add dev $class_monitor_dev parent $class_monitor_qdisc_parent handle $class_monitor_qdisc_handle htb || exit 1
;;
htb)
;;
*)
runcmd $tc_cmd qdisc del $class_monitor_qdisc_cmd || exit 1
runcmd $tc_cmd qdisc add dev $class_monitor_dev parent $class_monitor_qdisc_parent handle $class_monitor_qdisc_handle htb || exit 1
;;
esac
FIREQOS_MONITOR_ADDED=1
runcmd $tc_cmd filter add dev $class_monitor_dev parent $class_monitor_qdisc_handle protocol all prio 1 u32 match u32 0 0 action mirred egress mirror dev fireqos_monitor || exit 1
echo "FireQOS: monitor added to device '$class_monitor_dev', class '$class_monitor_classid', qdisc '$class_monitor_qdisc_handle'."
}
monitor() {
require_cmd tcpdump
if [ -z "$1" -o ! -f "${FIREQOS_DIR}/$1.conf" ]
then
echo >&2 "There is no interface named '$1' to show."
show_interfaces
exit 1
fi
# load the interface configuration
source "${FIREQOS_DIR}/$1.conf" || exit 1
local x=
local foundname=
local foundflow=
for x in $interface_classes_monitor
do
local name=`echo "$x|" | $cut_cmd -d '|' -f 1`
local name2=`echo "$x|" | $cut_cmd -d '|' -f 2`
local flow=`echo "$x|" | $cut_cmd -d '|' -f 3`
local monitor=`echo "$x|" | $cut_cmd -d '|' -f 4`
if [ "$name" = "$2" -o "$flow" = "$2" -o "$name2" = "$2" -o "$monitor" = "$2" ]
then
local foundname="$name"
local foundname2="$name2"
local foundflow="$flow"
local foundmonitor="$monitor"
local foundncid="`echo $foundflow | $tr_cmd ":" "_"`"
break
fi
done
if [ -z "$foundname" ]
then
echo
echo "No class found with name '$2' in interface '$1'."
echo
echo "Use one of the following names, class ids or qdisc handles:"
local x=
for x in `echo "$interface_classes_monitor" | $tr_cmd ' ' '\n' | $grep_cmd -v "^$"`
do
echo "$x" | (
local name=
local name2=
local flow=
local monitor=
IFS="|" read name name2 flow monitor
if [ "$name" = "$name2" -o "$name" = "default" ]
then
echo -e " $COLOR_BOLD$COLOR_YELLOW $name2 $COLOR_RESET or classid $COLOR_BOLD$COLOR_YELLOW $flow $COLOR_RESET or handle $COLOR_BOLD$COLOR_YELLOW $monitor $COLOR_RESET"
else
echo -e " $COLOR_BOLD$COLOR_YELLOW $name $COLOR_RESET or $COLOR_BOLD$COLOR_YELLOW $name2 $COLOR_RESET or classid $COLOR_BOLD$COLOR_YELLOW $flow $COLOR_RESET or handle $COLOR_BOLD$COLOR_YELLOW $monitor $COLOR_RESET"
fi
)
done
exit 1
fi
shift 2
# make all class variables available as class_monitor_*
eval "`set | grep "^class_${foundncid}_" | sed "s/^class_${foundncid}_/class_monitor_/g"`"
if [ $class_monitor_group -eq 1 ]
then
echo "Class $class_monitor_path is a class group. Please give a leaf class."
exit 1
fi
local warning=
case "$class_monitor_qdisc" in
none)
local warning="$COLOR_BOLD$COLOR_BGRED WARNING $COLOR_RESET\\nThe class '$class_monitor_path' does not have a qdisc attached.\\nTo monitor its traffic, FireQOS will attach an 'htb' qdisc to this class.\\nThis qdisc will be removed once you stop monitoring the traffic."
;;
htb)
;;
*)
local warning="$COLOR_BOLD$COLOR_BGRED WARNING $COLOR_RESET\\nThe class '$class_monitor_path' cannot be monitored directly.\\nFireQOS will REMOVE the existing '$class_monitor_qdisc' qdisc from the class\\nand temporarily attach an 'htb' qdisc, to allow monitoring the traffic.\\nThe original qdisc will be restored once you stop monitoring the traffic."
;;
esac
if [ "$interface_linklayer" = "adsl" -a "$interface_linklayer_type" = "local" -a "$interface_inout" = "output" ]
then
[ ! -z "$warning" ] && warning="$warning\\n"
local warning="$warning\\n$COLOR_BOLD$COLOR_BGRED WARNING $COLOR_RESET\\nWhen monitoring the packets sent by a PPPoE device, tcpdump sees the\\npackets encapsulated in something that is not PPPoE or Ethernet frames.\\nTherefore they cannot be decoded by tcpdump, wireshark or other tools."
fi
if [ ! -z "$warning" ]
then
echo
echo -e "$warning"
echo
echo -n "Press ENTER to continue, or Control-C to stop now > "
read
fi
FIREQOS_DEBUG_CMD=1
echo "Monitoring qdisc '$class_monitor_qdisc_handle' for class '$class_monitor_path' ($class_monitor_classid)..."
add_monitor || exit 1
echo
runcmd $tcpdump_cmd -i fireqos_monitor "${@}"
echo
# add_monitor() adds a trap that will remove the monitor on exit
}
$cat_cmd >&2 <<EOF
FireQOS $VERSION
(C) 2013-2014 Costa Tsaousis, GPL
EOF
show_usage() {
local msg="
${COLOR_YELLOW}${COLOR_BOLD}${PROGRAM_FILE}${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}action${COLOR_RESET}
${COLOR_BLUE}${COLOR_BOLD}action${COLOR_RESET} can be one of:
${COLOR_YELLOW}${COLOR_BOLD}start${COLOR_RESET} [${COLOR_BLUE}${COLOR_BOLD}filename${COLOR_RESET}] [${COLOR_YELLOW}${COLOR_BOLD}--${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}options${COLOR_RESET}]
or
[${COLOR_BLUE}${COLOR_BOLD}filename${COLOR_RESET}] ${COLOR_YELLOW}${COLOR_BOLD}start${COLOR_RESET} [${COLOR_YELLOW}${COLOR_BOLD}--${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}options${COLOR_RESET}]
activates traffic shapping rules according to rules given in
${COLOR_BLUE}${COLOR_BOLD}${FIREQOS_CONFIG}${COLOR_RESET}
if ${COLOR_BLUE}${COLOR_BOLD}filename${COLOR_RESET} is given, it will be used instead of the
default ${COLOR_BLUE}${COLOR_BOLD}${FIREQOS_CONFIG}${COLOR_RESET}
all ${COLOR_BLUE}${COLOR_BOLD}options${COLOR_RESET} after ${COLOR_YELLOW}${COLOR_BOLD}--${COLOR_RESET} will be given as options to the config
file when it will be executed by FireQOS
${COLOR_YELLOW}${COLOR_BOLD}debug${COLOR_RESET} [${COLOR_BLUE}${COLOR_BOLD}filename${COLOR_RESET}] [${COLOR_YELLOW}${COLOR_BOLD}--${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}options${COLOR_RESET}]
or
[${COLOR_BLUE}${COLOR_BOLD}filename${COLOR_RESET}] ${COLOR_YELLOW}${COLOR_BOLD}debug${COLOR_RESET} [${COLOR_YELLOW}${COLOR_BOLD}--${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}options${COLOR_RESET}]
same as ${COLOR_YELLOW}${COLOR_BOLD}start${COLOR_RESET}, but shows also the generated tc commands
${COLOR_YELLOW}${COLOR_BOLD}stop${COLOR_RESET}
stops all traffic shapping applied by FireQOS
(it does not touch QoS on other interfaces and IFBs used by
other tools)
${COLOR_YELLOW}${COLOR_BOLD}clear_all_qos${COLOR_RESET}
- stops all traffic shapping on all network interfaces
- removes all IFB devices from the system
(clears even QoS applied by other tools)
${COLOR_YELLOW}${COLOR_BOLD}status${COLOR_RESET} [${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET} [ ${COLOR_YELLOW}${COLOR_BOLD}dump${COLOR_RESET} [${COLOR_BLUE}${COLOR_BOLD}class${COLOR_RESET}] ] ]
shows live class utilization for the interface ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET} the
name given mathes the name of an interface statement given
in the config
if ${COLOR_YELLOW}${COLOR_BOLD}dump' is specified, it tcpdumps the traffic in the
${COLOR_BLUE}${COLOR_BOLD}class${COLOR_RESET} of interface ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
${COLOR_YELLOW}${COLOR_BOLD}tcpdump${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}class${COLOR_RESET}
or
${COLOR_YELLOW}${COLOR_BOLD}dump${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}class${COLOR_RESET}
tcpdumps all traffic in the ${COLOR_BLUE}${COLOR_BOLD}class${COLOR_RESET} of interface ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
${COLOR_YELLOW}${COLOR_BOLD}drops${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
shows packets dropped per second, per class for the
interface ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
${COLOR_YELLOW}${COLOR_BOLD}overlimits${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
shows packets delayed per second, per class for the
interface ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
${COLOR_YELLOW}${COLOR_BOLD}requeues${COLOR_RESET} ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
shows packets requeued per second, per class for the
interface ${COLOR_BLUE}${COLOR_BOLD}name${COLOR_RESET}
"
echo -e "$msg"
}
FIREQOS_MODE=
while [ ! -z "$1" ]
do
case "$1" in
clear_all_qos)
clear_all_qos_on_all_interfaces
syslog info "Cleared all QOS on all interfaces"
exit 0
;;
stop)
shift
clear_everything "${@}"
syslog info "Cleared all FireQOS changes"
exit 0
;;
status)
shift
if [ "$2" = "dump" -o "$2" = "tcpdump" ]
then
iface="$1"
shift 2
monitor $iface "$@"
else
htb_stats status "$@"
fi
exit 0
;;
drops|overlimits|requeues)
htb_stats "$@"
exit 0
;;
dump|tcpdump)
shift
monitor "$@"
exit $?
;;
debug)
FIREQOS_MODE=START
FIREQOS_DEBUG=1
;;
start)
FIREQOS_MODE=START
;;
--)
shift
break;
;;
--help|-h)
FIREQOS_MODE=
break;
;;
*)
echo >&2 "Using file '$1' for FireQOS configuration..."
FIREQOS_CONFIG="$1"
;;
esac
shift
done
if [ -z "$FIREQOS_MODE" ]
then
show_usage
exit 1
fi
check_root
# ----------------------------------------------------------------------------
# Normal startup
if [ ! -f "${FIREQOS_CONFIG}" ]
then
error "Cannot find file '${FIREQOS_CONFIG}'."
exit 1
fi
if [ ! -d "${FIREQOS_DIR}" ]
then
$mkdir_cmd -p "${FIREQOS_DIR}" || exit 1
fi
if [ ! -d "${FIREQOS_DIR}/ifbs" ]
then
$mkdir_cmd -p "${FIREQOS_DIR}/ifbs" || exit 1
fi
if [ ! -d "${FIREQOS_DIR}/ifaces" ]
then
$mkdir_cmd -p "${FIREQOS_DIR}/ifaces" || exit 1
fi
FIREQOS_DEFAULT_QDISC="fq_codel"
FIREQOS_DEFAULT_QDISC_OPTIONS="default"
# check if this system has fq_codel
runcmd $modprobe_cmd sch_$FIREQOS_DEFAULT_QDISC >/dev/null 2>&1
[ $? -ne 0 ] && FIREQOS_DEFAULT_QDISC="codel"
# check if this system has codel
runcmd $modprobe_cmd sch_$FIREQOS_DEFAULT_QDISC >/dev/null 2>&1
[ $? -ne 0 ] && FIREQOS_DEFAULT_QDISC="sfq"
# make sure we are not running in parallel
fireqos_concurrent_run_lock
# enable cleanup in case of failure
FIREQOS_COMPLETED=0
trap fireqos_exit EXIT
trap fireqos_exit SIGHUP
trap fireqos_exit INT
# load the IFB kernel module
runcmd $modprobe_cmd ifb numifbs=0 >/dev/null 2>&1
# Run the configuration
enable -n trap # Disable the trap buildin shell command.
{ source ${FIREQOS_CONFIG} "$@"; } # Run the configuration as a normal script.
ret=$?
enable trap # Enable the trap buildin shell command.
if [ $ret -ne 0 ]
then
error "Processing of '${FIREQOS_CONFIG}' failed with code $ret."
exit 1
fi
interface_close # close the last interface.
if [ ${FIREQOS_MARKS_ON_INPUT_USED} -gt 0 ]
then
echo >&2
echo >&2 -e "${COLOR_BGRED}${COLOR_WHITE} WARNING ${COLOR_RESET}"
echo >&2 -e "There have been encounted ${FIREQOS_MARKS_ON_INPUT_USED} rule(s) than match MARKs in incoming traffic."
echo >&2 -e "These statements will not match incoming packets without adding:${COLOR_BOLD}"
echo >&2 -e "FIREQOS_CONNMARK_RESTORE=\"act_connmark\""
echo >&2 -e "${COLOR_RESET}in your config. This however requires the act_connmark kernel module."
echo >&2 -e "For more information check: https://github.com/ktsaou/firehol/issues/49"
echo >&2 -e "${COLOR_RESET}"
fi
echo >&2
echo >&2 " Traffic is classified:"
echo >&2
echo >&2 " - on $interface_count interfaces"
echo >&2 " - to $class_count classes"
echo >&2 " - by $match_count FireQOS matches"
echo >&2
echo >&2 " $tc_count TC commands executed"
echo >&2
echo >&2 "All Done! Enjoy..."
# inform the trap everything is ok
FIREQOS_COMPLETED=1
exit 0