firehol/sbin/fireqos.in
Philip Whineray 2c9a2d4000 Extract common functions to functions.common.sh
Version number detection, command detection, terminal setup and a few
other bits have moved.

The processed (not .in) scripts will look for it in e.g. /usr/local/lib/firehol
or wherever the system will install it. The .in scripts will look for it in
their own directory.

Updated the configure system so that it correctly replaces paths rather
via the Makefile rather than trying to subsitute NONE in configire.ac.

Extracted all of the configure-time command substitutions to a single
sed file which is used to process the script.in files. Extended the
package checks to cover this file.
2015-11-25 23:36:29 +00:00

3754 lines
92 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
PROGRAM_FILE="${0}"
PROGRAM_DIR="${0%/*}"
if [ "$PROGRAM_DIR" = "$0" ]; then PROGRAM_DIR="."; fi
PROGRAM_PWD="${PWD}"
declare -a PROGRAM_ORIGINAL_ARGS=("${@}")
# Start defaults before configure
prefix_POST=/usr
sysconfdir_POST=/etc
localstatedir_POST=/var
libdir_POST=$PROGRAM_DIR
# End defaults before configure
for functions_file in $libdir_POST/functions.common.sh
do
if [ -r $functions_file ]
then
source $functions_file
else
1>&2 echo "Cannot access $functions_file"
exit 1
fi
done
FIREHOL_CONFIG_DIR="$sysconfdir_POST/firehol"
common_disable_localization || exit
common_public_umask || exit
common_require_root || exit
# 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"
# 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_COMMAND=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
# load the defaults if they exist, ignoring any mark definitions
marksreset() { :; }
markdef() { :; }
if [ -r "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" ]
then
source "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" || exit 1
fi
common_load_commands $PROGRAM_FILE @AUTOCONF_RUN@ <<-!
N|TPUT_CMD|@TPUT@|tput
Y|IP_CMD|@IP@|ip
Y|MODPROBE_CMD|@MODPROBE@|'modprobe -q' insmod
Y|RMMOD_CMD|@RMMOD@|rmmod
Y|FLOCK_CMD|@FLOCK@|flock
Y|GREP_CMD|@GREP@|grep
Y|EGREP_CMD|@EGREP@|egrep 'grep -E'
Y|CAT_CMD|@CAT@|cat
Y|CUT_CMD|@CUT@|cut
Y|SED_CMD|@SED@|sed
Y|TOUCH_CMD|@TOUCH@|touch
Y|TR_CMD|@TR@|tr
Y|MV_CMD|@MV@|mv
Y|LOGGER_CMD|@LOGGER@|logger
Y|MKDIR_CMD|@MKDIR@|mkdir
Y|SLEEP_CMD|@SLEEP@|sleep
Y|RM_CMD|@RM@|rm
Y|TC_CMD|@TC@|tc
N|GAWK_CMD|@GAWK@|gawk awk
N|TCPDUMP_CMD|@TCPDUMP@|tcpdump
Y|SEQ_CMD|@SEQ@|seq
Y|LS_CMD|@LS@|ls
Y|DATE_CMD|@DATE@|date
Y|TAIL_CMD|@TAIL@|tail
!
status=$?
test $status -eq 0 || exit $status
VERSION=$(common_get_version '$Id$')
RUNNING_ON_TERMINAL=0
if [ "z$1" = "z-nc" ]
then
shift
else
common_setup_terminal && RUNNING_ON_TERMINAL=1
fi
# 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="${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_CMD $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_COMMAND
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_CMD 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_CMD "${FIREQOS_SAVE}"
# move the exiting file to a new place, to avoid recursion
$MV_CMD "${FIREQOS_SAVE}" "${FIREQOS_SAVE}.run"
# $CAT_CMD "${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_command="dev ${interface_realdev} ${stab} parent ${class_classid} handle ${class_major}: ${qdisc}"
tc qdisc add ${qdisc_command}
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_command="${qdisc_command}"
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 dscp=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
;;
dscp)
dscp="${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 "${dscp}" ] && error "Cannot accept empty DSCP." && 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
[ ! "${dscp}" = "any" -a ! "${tos}" = "any" ] && error "Cannot match both 'dscp' and 'tos''." && 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= tdscp= 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_CMD -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 tdscp in ${dscp}
do
dscp_value=
tos_value=
tos_mask=
case "${tdscp}" in
any) ;;
CS1)
tos_value="0x20"
tos_mask="0x20"
;;
CS2)
tos_value="0x40"
tos_mask="0x40"
;;
CS3)
tos_value="0x60"
tos_mask="0x60"
;;
CS4)
tos_value="0x80"
tos_mask="0x80"
;;
CS5)
tos_value="0xA0"
tos_mask="0xA0"
;;
CS6)
tos_value="0xC0"
tos_mask="0xC0"
;;
CS7)
tos_value="0xE0"
tos_mask="0xE0"
;;
AF11)
tos_value="0x28"
tos_mask="0x28"
;;
AF12)
tos_value="0x30"
tos_mask="0x30"
;;
AF13)
tos_value="0x38"
tos_mask="0x38"
;;
AF21)
tos_value="0x48"
tos_mask="0x48"
;;
AF22)
tos_value="0x50"
tos_mask="0x50"
;;
AF23)
tos_value="0x58"
tos_mask="0x58"
;;
AF31)
tos_value="0x68"
tos_mask="0x68"
;;
AF32)
tos_value="0x70"
tos_mask="0x70"
;;
AF33)
tos_value="0x78"
tos_mask="0x78"
;;
AF41)
tos_value="0x88"
tos_mask="0x88"
;;
AF42)
tos_value="0x90"
tos_mask="0x90"
;;
AF43)
tos_value="0x98"
tos_mask="0x98"
;;
EF)
tos_value="0xB8"
tos_mask="0xB8"
;;
*)
if [ -z "${tdscp}" ]
then
error "Invalid DSCP value found."
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_CMD -b 1-8`
sm2=`echo "${smac}" | $CUT_CMD -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_CMD -b 1-4`
dm2=`echo "${dmac}" | $CUT_CMD -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 # dscp
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_CMD ${FIREQOS_DIR}/ifaces/ 2>/dev/null`"
ifbs="`$LS_CMD ${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_CMD 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_CMD ${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=
common_require_cmd $PROGRAM_FILE GAWK_CMD
trap cleanup_stats EXIT
trap cleanup_stats SIGHUP
if [ `$DATE_CMD +%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_CMD >${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_CMD ${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" |\
${GAWK_CMD} "${awk_script}"`"
}
getms() {
d=`$DATE_CMD +'%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_CMD "${secs}"
else
$SLEEP_CMD "${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_command
;;
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_command || 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() {
common_require_cmd $PROGRAM_FILE TCPDUMP_CMD
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_CMD "^class_${foundncid}_" | $SED_CMD "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_COMMAND=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