add qos-gargoyle

This commit is contained in:
CN_SZTL 2019-07-29 03:03:28 +08:00
parent 6df495760a
commit 2cb9217c77
No known key found for this signature in database
GPG Key ID: 6850B6345C862176
81 changed files with 17137 additions and 0 deletions

View File

@ -0,0 +1,71 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=gargoyle-firewall-util
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/gargoyle-firewall-util
SECTION:=net
CATEGORY:=Gargoyle
SUBMENU:=Network
TITLE:=A couple of shell script routines for firewall initialization
DEPENDS:=+ebtables +libericstools +uci +libiptbwctl +iptables-mod-filter \
+iptables-mod-ipopt +iptables-mod-conntrack-extra +iptables-mod-nat-extra \
+iptables-mod-extra +iptables-mod-iprange +iptables-mod-bandwidth \
+iptables-mod-timerange +iptables-mod-weburl +kmod-gre +kmod-pptp \
+kmod-tun +kmod-nf-nathelper +kmod-nf-nathelper-extra
MAINTAINER:=Eric Bishop <eric@gargoyle-router.com>
endef
define Package/gargoyle-firewall-util/description
A couple of shell script routines for firewall initialization
endef
define Build/Prepare
echo PACKAGE BUILD DIR = $(PACKAGE_BUILD_DIR)
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Build/Configure
endef
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) \
$(TARGET_CONFIGURE_OPTS) \
STAGING_DIR="$(STAGING_DIR)" \
CFLAGS="$(TARGET_CFLAGS) -I $(STAGING_DIR)/usr/include" \
LDFLAGS="$(TARGET_LDFLAGS) -L $(STAGING_DIR)/usr/lib"
endef
define Package/gargoyle-firewall-util/postinst
#!/bin/sh
included=$$(cat $${IPKG_INSTROOT}/etc/config/firewall | grep 'gargoyle_additions.firewall' )
if [ -z "$$included" ] ; then printf "config include\n\toption type script\n\toption path /usr/lib/gargoyle_firewall_util/gargoyle_additions.firewall\n\toption family IPv4\n\toption reload 1\n\n" >> $${IPKG_INSTROOT}/etc/config/firewall ; fi
endef
define Package/gargoyle-firewall-util/install
$(INSTALL_DIR) $(1)/usr/lib/gargoyle_firewall_util/
$(INSTALL_DIR) $(1)/etc/hotplug.d/iface/
$(INSTALL_DIR) $(1)/usr/bin/
$(INSTALL_DIR) $(1)/etc/init.d/
$(INSTALL_DIR) $(1)/etc/ppp/ip-up.d/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/make_iptables_rules $(1)/usr/bin/make_iptables_rules
$(INSTALL_BIN) $(PKG_BUILD_DIR)/delete_chain_from_table $(1)/usr/bin/delete_chain_from_table
$(INSTALL_BIN) $(PKG_BUILD_DIR)/backup_quotas $(1)/usr/bin/backup_quotas
$(INSTALL_BIN) $(PKG_BUILD_DIR)/restore_quotas $(1)/usr/bin/restore_quotas
$(INSTALL_BIN) $(PKG_BUILD_DIR)/print_quotas $(1)/usr/bin/print_quotas
$(INSTALL_BIN) ./files/gargoyle_firewall_util.sh $(1)/usr/lib/gargoyle_firewall_util/gargoyle_firewall_util.sh
$(INSTALL_BIN) ./files/gargoyle_additions.firewall $(1)/usr/lib/gargoyle_firewall_util/gargoyle_additions.firewall
$(INSTALL_BIN) ./files/gargoyle_firewall.hotplug $(1)/etc/hotplug.d/iface/21-gargoyle_firewall
$(INSTALL_BIN) ./files/set_kernel_timezone.init $(1)/etc/init.d/set_kernel_timezone
$(INSTALL_BIN) ./files/modemaccess.pppoe $(1)/etc/ppp/ip-up.d/modemaccess.sh
endef
$(eval $(call BuildPackage,gargoyle-firewall-util))

View File

@ -0,0 +1,2 @@
. /usr/lib/gargoyle_firewall_util/gargoyle_firewall_util.sh
initialize_firewall

View File

@ -0,0 +1,42 @@
#!/bin/sh
if [ "$INTERFACE" = "wan" ]; then
. /usr/lib/gargoyle_firewall_util/gargoyle_firewall_util.sh
if [ "$ACTION" = "ifup" ]; then
# previously we waited until firewall was up here, testing firewall.core.loaded in /var/state
# unfortunately that was removed in barrier breaker, but new firewall (fw3) loads very FAST as it's a binary
# So... just wait 2 seconds
sleep 2
#Bring up the parts of the firewall that depend on device name and WAN IP address.
ifup_firewall
#Start up the bandwidth monitor which depends on the device name
if [ -h /etc/rc.d/S55bwmon_gargoyle ]; then
/etc/init.d/bwmon_gargoyle restart
fi
fi
if [ "$ACTION" = "ifdown" ]; then
quota_chains_exist=$(iptables -t mangle -L combined_quotas 2>/dev/null)
if [ -n "$quota_chains_exist" ]; then
backup_quotas
fi
fi
fi
if [ "$INTERFACE" = "lan" ]; then
wan_exists=$(uci -q get network.wan)
if [ -z "$wan_exists" ]; then
if [ "$ACTION" = "ifup" ]; then
/etc/init.d/bwmon_gargoyle restart
fi
if [ "$ACTION" = "ifdown" ]; then
/etc/init.d/bwmon_gargoyle stop
fi
fi
fi

View File

@ -0,0 +1,585 @@
# Copyright Eric Bishop, 2008-2010
# This is free software licensed under the terms of the GNU GPL v2.0
#
. /lib/functions.sh
include /lib/network
ra_mask="0x0080"
ra_mark="$ra_mask/$ra_mask"
death_mask=0x8000
death_mark="$death_mask"
wan_if=""
mask_to_cidr()
{
mask="$1"
bits=0;
mask_parts=$(echo $mask | sed 's/\./ /g')
for p in $mask_parts ; do
case $p in
255)
bits=$(($bits + 8)) ;;
254)
bits=$(($bits + 7)) ;;
252)
bits=$(($bits + 6)) ;;
248)
bits=$(($bits + 5)) ;;
240)
bits=$(($bits + 4)) ;;
224)
bits=$(($bits + 3)) ;;
192)
bits=$(($bits + 2)) ;;
128)
bits=$(($bits + 1)) ;;
esac
done
echo $bits
}
define_wan_if()
{
if [ -z "$wan_if" ]; then
#Wait for up to 15 seconds for the wan interface to indicate it is up.
wait_sec=15
while [ -z "$(uci -P /var/state get network.wan.up 2>/dev/null)" ] && [ "$wait_sec" -gt 0 ]; do
sleep 1
wait_sec=$(($wait_sec - 1))
done
#The interface name will depend on if pppoe is used or not. If pppoe is used then
#the name we are looking for is in network.wan.ifname. If there is nothing there
#use the device named by network.wan.device
wan_if=$(uci -P /var/state get network.wan.ifname 2>/dev/null)
if [ -z "$wan_if" ]; then
wan_if=$(uci -P /var/state get network.wan.device 2>/dev/null)
fi
fi
}
# parse remote_accept sections in firewall config and add necessary rules
insert_remote_accept_rules()
{
local config_name="firewall"
local section_type="remote_accept"
ssh_max_attempts=$(uci -q get dropbear.@dropbear[0].max_remote_attempts)
ssh_port=$(uci -q get dropbear.@dropbear[0].Port)
if [ -z "$ssh_max_attempts" ] || [ "$ssh_max_attempts" = "unlimited" ]; then
ssh_max_attempts=""
else
ssh_max_attempts=$(( $ssh_max_attempts + 1 ))
fi
#add rules for remote_accepts
parse_remote_accept_config()
{
vars="local_port remote_port start_port end_port proto zone"
proto="tcp udp"
zone="wan"
for var in $vars ; do
config_get $var $1 $var
done
if [ "$proto" = "tcpudp" ] || [ -z "$proto" ]; then
proto="tcp udp"
fi
for prot in $proto ; do
if [ -n "$local_port" ]; then
if [ -z "$remote_port" ]; then
remote_port="$local_port"
fi
#Discourage brute force attacks on ssh from the WAN by limiting failed conneciton attempts.
#Each attempt gets a maximum of 10 password tries by dropbear.
if [ -n "$ssh_max_attempts" ] && [ "$local_port" = "$ssh_port" ] && [ "$prot" = "tcp" ]; then
iptables -t filter -A "input_${zone}_rule" -p "$prot" --dport $ssh_port -m recent --set --name SSH_CHECK
iptables -t filter -A "input_${zone}_rule" -m recent --update --seconds 300 --hitcount $ssh_max_attempts --name SSH_CHECK -j DROP
fi
if [ "$remote_port" != "$local_port" ]; then
#since we're inserting with -I, insert redirect rule first which will then be hit second, after setting connmark
iptables -t nat -I "zone_"$zone"_prerouting" -p "$prot" --dport "$remote_port" -j REDIRECT --to-ports "$local_port"
iptables -t nat -I "zone_"$zone"_prerouting" -p "$prot" --dport "$remote_port" -j CONNMARK --set-mark "$ra_mark"
iptables -t filter -A "input_${zone}_rule" -p $prot --dport "$local_port" -m connmark --mark "$ra_mark" -j ACCEPT
else
iptables -t nat -I "zone_"$zone"_prerouting" -p "$prot" --dport "$remote_port" -j REDIRECT --to-ports "$local_port"
iptables -t filter -A "input_${zone}_rule" -p "$prot" --dport "$local_port" -j ACCEPT
fi
elif [ -n "$start_port" ] && [ -n "$end_port" ]; then
iptables -t nat -I "zone_"$zone"_prerouting" -p "$prot" --dport "$start_port:$end_port" -j REDIRECT
iptables -t filter -A "input_${zone}_rule" -p "$prot" --dport "$start_port:$end_port" -j ACCEPT
fi
done
}
config_load "$config_name"
config_foreach parse_remote_accept_config "$section_type"
}
insert_pf_loopback_rules()
{
config_name="firewall"
section_type="redirect"
#Need to always delete the old chains first.
delete_chain_from_table "nat" "pf_loopback_A"
delete_chain_from_table "filter" "pf_loopback_B"
delete_chain_from_table "nat" "pf_loopback_C"
define_wan_if
if [ -z "$wan_if" ] ; then return ; fi
wan_ip=$(uci -p /tmp/state get network.wan.ipaddr)
lan_mask=$(uci -p /tmp/state get network.lan.netmask)
if [ -n "$wan_ip" ] && [ -n "$lan_mask" ]; then
iptables -t nat -N "pf_loopback_A"
iptables -t filter -N "pf_loopback_B"
iptables -t nat -N "pf_loopback_C"
iptables -t nat -I zone_lan_prerouting -d $wan_ip -j pf_loopback_A
iptables -t filter -I zone_lan_forward -j pf_loopback_B
iptables -t nat -I postrouting_rule -o br-lan -j pf_loopback_C
add_pf_loopback()
{
local vars="src dest proto src_dport dest_ip dest_port"
local all_defined="1"
for var in $vars ; do
config_get $var $1 $var
loaded=$(eval echo "\$$var")
#echo $var = $loaded
if [ -z "$loaded" ] && [ ! "$var" = "$src_dport" ]; then
all_defined="0"
fi
done
if [ -z "$src_dport" ]; then
src_dport=$dest_port
fi
sdp_dash=$src_dport
sdp_colon=$(echo $sdp_dash | sed 's/\-/:/g')
dp_dash=$dest_port
dp_colon=$(echo $dp_dash | sed 's/\-/:/g')
if [ "$all_defined" = "1" ] && [ "$src" = "wan" ] && [ "$dest" = "lan" ] ; then
iptables -t nat -A pf_loopback_A -p $proto --dport $sdp_colon -j DNAT --to-destination $dest_ip:$dp_dash
iptables -t filter -A pf_loopback_B -p $proto --dport $dp_colon -d $dest_ip -j ACCEPT
iptables -t nat -A pf_loopback_C -p $proto --dport $dp_colon -d $dest_ip -s $dest_ip/$lan_mask -j MASQUERADE
fi
}
config_load "$config_name"
config_foreach add_pf_loopback "$section_type"
fi
}
insert_dmz_rule()
{
local config_name="firewall"
local section_type="dmz"
#add rules for remote_accepts
parse_dmz_config()
{
vars="to_ip from"
for var in $vars ; do
config_get $var $1 $var
done
if [ -n "$from" ]; then
from_if=$(uci -q -p /tmp/state get network.$from.ifname)
fi
# echo "from_if = $from_if"
if [ -n "$to_ip" ] && [ -n "$from" ] && [ -n "$from_if" ]; then
iptables -t nat -A "zone_"$from"_prerouting" -i $from_if -j DNAT --to-destination $to_ip
# echo "iptables -t nat -A "prerouting_"$from -i $from_if -j DNAT --to-destination $to_ip"
iptables -t filter -I "zone_"$from"_forward" -d $to_ip -j ACCEPT
fi
}
config_load "$config_name"
config_foreach parse_dmz_config "$section_type"
}
insert_restriction_rules()
{
define_wan_if
if [ -z "$wan_if" ] ; then return ; fi
if [ -e /tmp/restriction_init.lock ]; then return ; fi
touch /tmp/restriction_init.lock
egress_exists=$(iptables -t filter -L egress_restrictions 2>/dev/null)
ingress_exists=$(iptables -t filter -L ingress_restrictions 2>/dev/null)
if [ -n "$egress_exists" ]; then
delete_chain_from_table filter egress_whitelist
delete_chain_from_table filter egress_restrictions
fi
if [ -n "$ingress_exists" ]; then
delete_chain_from_table filter ingress_whitelist
delete_chain_from_table filter ingress_restrictions
fi
iptables -t filter -N egress_restrictions
iptables -t filter -N ingress_restrictions
iptables -t filter -N egress_whitelist
iptables -t filter -N ingress_whitelist
iptables -t filter -I FORWARD -o $wan_if -j egress_restrictions
iptables -t filter -I FORWARD -i $wan_if -j ingress_restrictions
iptables -t filter -I egress_restrictions -j egress_whitelist
iptables -t filter -I ingress_restrictions -j ingress_whitelist
package_name="firewall"
parse_rule_config()
{
section=$1
section_type=$(uci get "$package_name"."$section")
config_get "enabled" "$section" "enabled"
if [ -z "$enabled" ]; then enabled="1" ; fi
if [ "$enabled" = "1" ] && ( [ "$section_type" = "restriction_rule" ] || [ "$section_type" = "whitelist_rule" ] ) ; then
#convert app_proto && not_app_proto to connmark here
config_get "app_proto" "$section" "app_proto"
config_get "not_app_proto" "$section" "not_app_proto"
if [ -n "$app_proto" ]; then
app_proto_connmark=$(cat /etc/l7marker.marks 2>/dev/null | grep $app_proto | awk '{ print $2 ; }' )
app_proto_mask=$(cat /etc/l7marker.marks 2>/dev/null | grep $app_proto | awk '{ print $3 ; }' )
uci set "$package_name"."$section".connmark="$app_proto_connmark/$app_proto_mask"
fi
if [ -n "$not_app_proto" ]; then
not_app_proto_connmark=$(cat /etc/l7marker.marks 2>/dev/null | grep "$not_app_proto" | awk '{ print $2 }')
not_app_proto_mask=$(cat /etc/l7marker.marks 2>/dev/null | grep "$not_app_proto" | awk '{ print $3 }')
uci set "$package_name"."$section".not_connmark="$not_app_proto_connmark/$not_app_proto_mask"
fi
table="filter"
chain="egress_restrictions"
ingress=""
target="REJECT"
config_get "is_ingress" "$section" "is_ingress"
if [ "$is_ingress" = "1" ]; then
ingress=" -i "
if [ "$section_type" = "restriction_rule" ]; then
chain="ingress_restrictions"
else
chain="ingress_whitelist"
fi
else
if [ "$section_type" = "restriction_rule" ]; then
chain="egress_restrictions"
else
chain="egress_whitelist"
fi
fi
if [ "$section_type" = "whitelist_rule" ]; then
target="ACCEPT"
fi
make_iptables_rules -p "$package_name" -s "$section" -t "$table" -c "$chain" -g "$target" $ingress
make_iptables_rules -p "$package_name" -s "$section" -t "$table" -c "$chain" -g "$target" $ingress -r
uci del "$package_name"."$section".connmark 2>/dev/null
uci del "$package_name"."$section".not_connmark 2>/dev/null
fi
}
config_load "$package_name"
config_foreach parse_rule_config "whitelist_rule"
config_foreach parse_rule_config "restriction_rule"
rm -rf /tmp/restriction_init.lock
}
initialize_quotas()
{
define_wan_if
if [ -z "$wan_if" ] ; then return ; fi
if [ -e /tmp/quota_init.lock ]; then return ; fi
touch /tmp/quota_init.lock
lan_mask=$(uci -p /tmp/state get network.lan.netmask)
lan_ip=$(uci -p /tmp/state get network.lan.ipaddr)
full_qos_enabled=$(ls /etc/rc.d/*qos_gargoyle 2>/dev/null)
if [ -n "$full_qos_enabled" ]; then
full_up=$(uci get qos_gargoyle.upload.total_bandwidth 2>/dev/null)
full_down=$(uci get qos_gargoyle.download.total_bandwidth 2>/dev/null)
if [ -z "$full_up" ] && [ -z "$full_down" ]; then
full_qos_enabled=""
fi
fi
# restore_quotas does the hard work of building quota chains & rebuilding crontab file to do backups
#
# this initializes qos functions ONLY if we have quotas that
# have up and down speeds defined for when quota is exceeded
# and full qos is not enabled
if [ -z "$full_qos_enabled" ]; then
restore_quotas -w $wan_if -d $death_mark -m $death_mask -s "$lan_ip/$lan_mask" -c "0 0,4,8,12,16,20 * * * /usr/bin/backup_quotas >/dev/null 2>&1"
initialize_quota_qos
else
restore_quotas -q -w $wan_if -d $death_mark -m $death_mask -s "$lan_ip/$lan_mask" -c "0 0,4,8,12,16,20 * * * /usr/bin/backup_quotas >/dev/null 2>&1"
cleanup_old_quota_qos
fi
#enable cron, but only restart cron if it is currently running
#since we initialize this before cron, this will
#make sure we don't start cron twice at boot
/etc/init.d/cron enable
cron_active=$(ps | grep "crond" | grep -v "grep" )
if [ -n "$cron_active" ]; then
/etc/init.d/cron restart
fi
rm -rf /tmp/quota_init.lock
}
load_all_config_sections()
{
local config_name="$1"
local section_type="$2"
all_config_sections=""
section_order=""
config_cb()
{
if [ -n "$2" ] || [ -n "$1" ]; then
if [ -n "$section_type" ]; then
if [ "$1" = "$section_type" ]; then
all_config_sections="$all_config_sections $2"
fi
else
all_config_sections="$all_config_sections $2"
fi
fi
}
config_load "$config_name"
echo "$all_config_sections"
}
cleanup_old_quota_qos()
{
for iface in $(tc qdisc show | awk '{print $5}' | sort -u ); do
tc qdisc del dev "$iface" root >/dev/null 2>&1
done
}
initialize_quota_qos()
{
cleanup_old_quota_qos
#speeds should be in kbyte/sec, units should NOT be present in config file (unit processing should be done by front-end)
quota_sections=$(load_all_config_sections "firewall" "quota")
upload_speeds=""
download_speeds=""
config_load "firewall"
for q in $quota_sections ; do
config_get "exceeded_up_speed" $q "exceeded_up_speed"
config_get "exceeded_down_speed" $q "exceeded_down_speed"
if [ -n "$exceeded_up_speed" ] && [ -n "$exceeded_down_speed" ]; then
if [ $exceeded_up_speed -gt 0 ] && [ $exceeded_down_speed -gt 0 ]; then
upload_speeds="$exceeded_up_speed $upload_speeds"
download_speeds="$exceeded_down_speed $download_speeds"
fi
fi
done
#echo "upload_speeds = $upload_speeds"
unique_up=$( printf "%d\n" $upload_speeds 2>/dev/null | sort -u -n)
unique_down=$( printf "%d\n" $download_speeds 2>/dev/null | sort -u -n)
#echo "unique_up = $unique_up"
num_up_bands=1
num_down_bands=1
if [ -n "$upload_speeds" ]; then
num_up_bands=$((1 + $(printf "%d\n" $upload_speeds 2>/dev/null | sort -u -n | wc -l) ))
fi
if [ -n "$download_speeds" ]; then
num_down_bands=$((1 + $(printf "%d\n" $download_speeds 2>/dev/null | sort -u -n | wc -l) ))
fi
#echo "num_up_bands=$num_up_bands"
#echo "num_down_bands=$num_down_bands"
if [ -n "$wan_if" ] && [ $num_up_bands -gt 1 ] && [ $num_down_bands -gt 1 ]; then
insmod sch_prio >/dev/null 2>&1
insmod sch_tbf >/dev/null 2>&1
insmod cls_fw >/dev/null 2>&1
ifconfig imq0 down >/dev/null 2>&1
ifconfig imq1 down >/dev/null 2>&1
rmmod imq >/dev/null 2>&1
insmod imq numdevs=1 hook_chains="INPUT,FORWARD" hook_tables="mangle,mangle" >/dev/null 2>&1
ip link set imq0 up
#egress/upload
tc qdisc del dev $wan_if root >/dev/null 2>&1
tc qdisc add dev $wan_if handle 1:0 root prio bands $num_up_bands priomap 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
cur_band=2
upload_shift=0
for rate_kb in $unique_up ; do
kbit=$(echo $((rate_kb*8))kbit)
mark=$(($cur_band << $upload_shift))
tc filter add dev $wan_if parent 1:0 prio $cur_band protocol ip handle $mark fw flowid 1:$cur_band
tc qdisc add dev $wan_if parent 1:$cur_band handle $cur_band: tbf rate $kbit burst $kbit limit $kbit
cur_band=$(($cur_band+1))
done
#ingress/download
tc qdisc del dev imq0 root >/dev/null 2>&1
tc qdisc add dev imq0 handle 1:0 root prio bands $num_down_bands priomap 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
cur_band=2
download_shift=8
for rate_kb in $unique_down ; do
kbit=$(echo $((rate_kb*8))kbit)
mark=$(($cur_band << $download_shift))
tc filter add dev imq0 parent 1:0 prio $cur_band protocol ip handle $mark fw flowid 1:$cur_band
tc qdisc add dev imq0 parent 1:$cur_band handle $cur_band: tbf rate $kbit burst $kbit limit $kbit
cur_band=$(($cur_band+1))
done
iptables -t mangle -I ingress_quotas -i $wan_if -j IMQ --todev 0
#tc -s qdisc show dev $wan_if
#tc -s qdisc show dev imq0
fi
}
enforce_dhcp_assignments()
{
enforce_assignments=$(uci -q get firewall.@defaults[0].enforce_dhcp_assignments)
delete_chain_from_table "filter" "lease_mismatch_check"
local pairs1
local pairs2
local pairs
pairs1=""
pairs2=""
if [ -e /tmp/dhcp.leases ]; then
pairs1=$(cat /tmp/dhcp.leases | sed '/^[ \t]*$/d' | awk ' { print $2"^"$3"\n" ; } ' )
fi
if [ -e /etc/ethers ]; then
pairs2=$(cat /etc/ethers | sed '/^[ \t]*$/d' | awk ' { print $1"^"$2"\n" ; } ' )
fi
pairs=$( printf "$pairs1\n$pairs2\n" | sort | uniq )
if [ "$enforce_assignments" = "1" ] && [ -n "$pairs" ]; then
iptables -t filter -N lease_mismatch_check
local p
for p in $pairs ; do
local mac
local ip
mac=$(echo $p | sed 's/\^.*$//g')
ip=$(echo $p | sed 's/^.*\^//g')
if [ -n "$ip" ] && [ -n "$mac" ]; then
iptables -t filter -A lease_mismatch_check ! -s "$ip" -m mac --mac-source "$mac" -j REJECT
iptables -t filter -A lease_mismatch_check -s "$ip" -m mac ! --mac-source "$mac" -j REJECT
fi
done
iptables -t filter -I delegate_forward -j lease_mismatch_check
fi
}
force_router_dns()
{
force_router_dns=$(uci get firewall.@defaults[0].force_router_dns 2> /dev/null)
if [ "$force_router_dns" = "1" ]; then
iptables -t nat -I zone_lan_prerouting -p tcp --dport 53 -j REDIRECT
iptables -t nat -I zone_lan_prerouting -p udp --dport 53 -j REDIRECT
fi
}
add_adsl_modem_routes()
{
wan_proto=$(uci -q get network.wan.proto)
if [ "$wan_proto" = "pppoe" ]; then
wan_dev=$(uci -q get network.wan.ifname) #not really the interface, but the device
iptables -A postrouting_rule -t nat -o $wan_dev -j MASQUERADE
iptables -A forwarding_rule -o $wan_dev -j ACCEPT
/etc/ppp/ip-up.d/modemaccess.sh firewall $wan_dev
fi
}
initialize_firewall()
{
iptables -I zone_lan_forward -i br-lan -o br-lan -j ACCEPT
insert_remote_accept_rules
insert_dmz_rule
enforce_dhcp_assignments
force_router_dns
add_adsl_modem_routes
isolate_guest_networks
}
guest_mac_from_uci()
{
local is_guest_network
local macaddr
config_get is_guest_network "$1" is_guest_network
if [ "$is_guest_network" = "1" ]; then
config_get macaddr "$1" macaddr
echo "$macaddr"
fi
}
get_guest_macs()
{
config_load "wireless"
config_foreach guest_mac_from_uci "wifi-iface"
}
isolate_guest_networks()
{
ebtables -t filter -F FORWARD
ebtables -t filter -F INPUT
local guest_macs=$( get_guest_macs )
if [ -n "$guest_macs" ]; then
local lanifs=`brctl show br-lan 2>/dev/null | awk ' $NF !~ /interfaces/ { print $NF } '`
local lif
local lan_ip=$(uci -p /tmp/state get network.lan.ipaddr)
for lif in $lanifs ; do
for gmac in $guest_macs ; do
local is_guest=$(ifconfig "$lif" 2>/dev/null | grep -i "$gmac")
if [ -n "$is_guest" ]; then
echo "$lif with mac $gmac is wireless guest"
#Allow access to WAN but not other LAN hosts for anyone on guest network
ebtables -t filter -A FORWARD -i "$lif" --logical-out br-lan -j DROP
#Only allow DHCP/DNS access to router for anyone on guest network
ebtables -t filter -A INPUT -i "$lif" -p ARP -j ACCEPT
ebtables -t filter -A INPUT -i "$lif" -p IPV4 --ip-protocol UDP --ip-destination-port 53 -j ACCEPT
ebtables -t filter -A INPUT -i "$lif" -p IPV4 --ip-protocol UDP --ip-destination-port 67 -j ACCEPT
ebtables -t filter -A INPUT -i "$lif" -p IPV4 --ip-destination $lan_ip -j DROP
fi
done
done
fi
}
ifup_firewall()
{
insert_restriction_rules
initialize_quotas
insert_pf_loopback_rules
}

View File

@ -0,0 +1,49 @@
#!/bin/sh
#This script allows access to the ADSL modem web interface when pppoe is used.
#For this to work configure your modem in bridge mode with DHCP enabled on the modem.
#This will cause the modem to dish an address to the router interface when requested below.
#
#Alternatively you can manually set the below variable ROUTER_IP with the IP address
#you want to use. Make sure the IP address is on the same network as the modem.
#ROUTER_IP=10.0.0.2
#Main case statement used only by udhcp which only calls with one of the
#following four key words in parameter 1.
case "$1" in
deconfig)
ifconfig "$interface" 0.0.0.0
exit 0
;;
renew)
ifconfig $interface $ip netmask ${subnet:-255.255.255.0} broadcast ${broadcast:-+}
exit 0
;;
bound)
ifconfig $interface $ip netmask ${subnet:-255.255.255.0} broadcast ${broadcast:-+}
exit 0
;;
nak)
exit 0
;;
leasefail)
exit 0
;;
esac
#if we get here then udhcp did not call us. Must be from pppd or /usr/lib/gargoyle_firewall_util
#configure the ethernet interface.
if [ -n "$ROUTER_IP" ]; then
#In manual mode the user gave us an IP address for our interface
ifconfig $2 $ROUTER_IP netmask 255.255.255.0
else
#In auto mode we first check if we have an ip address already.
ROUTER_IP=$(ifconfig $2 | grep "inet addr:")
if [ -z "$ROUTER_IP" ]; then
#Dont have one so try an get one.
udhcpc -f -i $2 -n -q -s /etc/ppp/ip-up.d/modemaccess.sh
fi
fi
exit 0

View File

@ -0,0 +1,20 @@
#!/bin/sh /etc/rc.common
START=30
start()
{
/usr/bin/set_kernel_timezone
touch /etc/crontabs/root
if ! grep -q "set_kernel_timezone" /etc/crontabs/root; then
echo '0,1,11,21,31,41,51 * * * * /usr/bin/set_kernel_timezone >/dev/null 2>&1' >> /etc/crontabs/root
/etc/init.d/cron enable
#only restart cron if it is currently running
#since we initialize this before cron, this will
#make sure we don't start cron twice at boot
cron_active=$(ps | grep "crond" | grep -v "grep" )
if [ -n "$cron_active" ]; then
/etc/init.d/cron restart
fi
fi
}

View File

@ -0,0 +1,19 @@
all: make_iptables_rules delete_chain_from_table backup_quotas restore_quotas print_quotas
print_quotas: print_quotas.c
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ -lericstools -luci -liptbwctl
restore_quotas: restore_quotas.c
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ -lericstools -luci -liptbwctl
backup_quotas: backup_quotas.c
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ -lericstools -luci -liptbwctl
delete_chain_from_table: delete_chain_from_table.c
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ -lericstools
make_iptables_rules: make_iptables_rules.c
$(CC) $(CFLAGS) $(LDFLAGS) make_iptables_rules.c -o make_iptables_rules -lericstools -luci -lm
clean:
rm -rf make_iptables_rules delete_chain_from_table print_quotas backup_quotas restore_quotas *.o *~ .*sw*

View File

@ -0,0 +1,217 @@
/* backup_quotas -- Used to backup quota data from iptables rules that use the "bandwidth" module
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2009 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <erics_tools.h>
#include <uci.h>
#include <ipt_bwctl.h>
#define malloc safe_malloc
#define strdup safe_strdup
list* get_all_sections_of_type(struct uci_context *ctx, char* package, char* section_type);
void backup_quota(char* quota_id, char* quota_backup_dir);
char* get_uci_option(struct uci_context* ctx,char* package_name, char* section_name, char* option_name);
char* get_option_value_string(struct uci_option* uopt);
int main(void)
{
struct uci_context *ctx = uci_alloc_context();
list* quota_sections = get_all_sections_of_type(ctx, "firewall", "quota");
system("mkdir -p /usr/data/quotas");
unlock_bandwidth_semaphore_on_exit();
while(quota_sections->length > 0)
{
char* next_quota = shift_list(quota_sections);
char* ignore_backup = get_uci_option(ctx, "firewall", next_quota, "ignore_backup_at_next_restore");
int do_backup = 1;
if(ignore_backup != NULL)
{
if(strcmp(ignore_backup, "1") == 0)
{
do_backup = 0;
}
free(ignore_backup);
}
if(do_backup)
{
//do backup
/* base id for quota is the ip associated with it*/
char* backup_id = get_uci_option(ctx, "firewall", next_quota, "id");
char* ip = get_uci_option(ctx, "firewall", next_quota, "ip");
if(ip == NULL)
{
ip = strdup("ALL");
}
else if(strcmp(ip, "") == 0)
{
free(ip);
ip = strdup("ALL");
}
if(backup_id == NULL)
{
backup_id = strdup(ip);
}
else if(strcmp(backup_id, "") == 0)
{
free(backup_id);
backup_id = strdup(ip);
}
char* types[] = { "ingress_limit", "egress_limit", "combined_limit" };
char* postfixes[] = { "_ingress", "_egress", "_combined" };
int type_index;
for(type_index=0; type_index < 3; type_index++)
{
char* defined = get_uci_option(ctx, "firewall", next_quota, types[type_index]);
if(defined != NULL)
{
char* type_id = dynamic_strcat(2, backup_id, postfixes[type_index]);
backup_quota(type_id, "/usr/data/quotas" );
free(type_id);
free(defined);
}
}
free(backup_id);
free(ip);
}
free(next_quota);
}
unsigned long num;
destroy_list(quota_sections, DESTROY_MODE_FREE_VALUES, &num);
uci_free_context(ctx);
return 0;
}
list* get_all_sections_of_type(struct uci_context *ctx, char* package, char* section_type)
{
struct uci_package *p = NULL;
struct uci_element *e = NULL;
list* sections_of_type = initialize_list();
if(uci_load(ctx, package, &p) == UCI_OK)
{
uci_foreach_element( &p->sections, e)
{
struct uci_section *section = uci_to_section(e);
if(safe_strcmp(section->type, section_type) == 0)
{
push_list(sections_of_type, strdup(section->e.name));
}
}
}
return sections_of_type;
}
void backup_quota(char* id, char* quota_backup_dir)
{
/* if we ever bother to allow quotas to apply to subnets
* specified with '/', this may be necessary
*/
char* quota_file_name;
if(strstr(id, "/") != NULL)
{
char* quota_file_name = dynamic_replace(id, "/", "_");
}
else
{
quota_file_name = strdup(id);
}
char* quota_file_path = dynamic_strcat(3, quota_backup_dir, "/quota_", quota_file_name);
unsigned long num_ips;
ip_bw *ip_buf = NULL;
int query_succeeded = get_all_bandwidth_usage_for_rule_id(id, &num_ips, &ip_buf, 5000);
if(query_succeeded)
{
save_usage_to_file(ip_buf, num_ips, quota_file_path);
free(ip_buf);
}
free(quota_file_path);
free(quota_file_name);
}
char* get_uci_option(struct uci_context* ctx, char* package_name, char* section_name, char* option_name)
{
char* option_value = NULL;
struct uci_ptr ptr;
char* lookup_str = dynamic_strcat(5, package_name, ".", section_name, ".", option_name);
int ret_value = uci_lookup_ptr(ctx, &ptr, lookup_str, 1);
if(ret_value == UCI_OK)
{
if( !(ptr.flags & UCI_LOOKUP_COMPLETE))
{
ret_value = UCI_ERR_NOTFOUND;
}
else
{
struct uci_element *e = (struct uci_element*)ptr.o;
option_value = get_option_value_string(uci_to_option(e));
}
}
free(lookup_str);
return option_value;
}
// this function dynamically allocates memory for
// the option string, but since this program exits
// almost immediately (after printing variable info)
// the massive memory leak we're opening up shouldn't
// cause any problems. This is your reminder/warning
// that this might be an issue if you use this code to
// do anything fancy.
char* get_option_value_string(struct uci_option* uopt)
{
char* opt_str = NULL;
if(uopt->type == UCI_TYPE_STRING)
{
opt_str = strdup(uopt->v.string);
}
if(uopt->type == UCI_TYPE_LIST)
{
struct uci_element* e;
uci_foreach_element(&uopt->v.list, e)
{
if(opt_str == NULL)
{
opt_str = strdup(e->name);
}
else
{
char* tmp;
tmp = dynamic_strcat(3, opt_str, " ", e->name);
free(opt_str);
opt_str = tmp;
}
}
}
return opt_str;
}

View File

@ -0,0 +1,76 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <erics_tools.h>
#define malloc safe_malloc
#define strdup safe_strdup
void free_split_pieces(char** split_pieces);
int main(int argc, char **argv)
{
char *table = argv[1];
char *delete_chain = argv[2];
if(argc != 3)
{
printf("USAGE: %s [TABLE] [CHAIN TO DELETE]\n\n", argv[0]);
return 0;
}
char *command = dynamic_strcat(3, "iptables -t ", table, " -L -n --line-numbers 2>/dev/null");
unsigned long num_lines = 0;
char** table_dump = get_shell_command_output_lines(command, &num_lines);
free(command);
unsigned long line_index;
char* current_chain = NULL;
list* delete_commands = initialize_list();
for(line_index=0; line_index < num_lines; line_index++)
{
char* line = table_dump[line_index];
unsigned long num_pieces = 0;
char whitespace[] = { '\t', ' ', '\r', '\n' };
char** line_pieces = split_on_separators(line, whitespace, 4, -1, 0, &num_pieces);
if(strcmp(line_pieces[0], "Chain") == 0)
{
if(current_chain != NULL) { free(current_chain); }
current_chain = strdup(line_pieces[1]);
}
else
{
unsigned long line_num;
int read = sscanf(line_pieces[0], "%ld", &line_num);
if(read > 0 && current_chain != NULL && num_pieces >1)
{
if(strcmp(line_pieces[1], delete_chain) == 0)
{
char* delete_command = dynamic_strcat(7, "iptables -t ", table, " -D ", current_chain, " ", line_pieces[0], " 2>/dev/null");
push_list(delete_commands, delete_command);
}
}
}
//free line_pieces
free_null_terminated_string_array(line_pieces);
}
free_null_terminated_string_array(table_dump);
/* final two commands to flush chain being deleted and whack it */
unshift_list(delete_commands, dynamic_strcat(5, "iptables -t ", table, " -F ", delete_chain, " 2>/dev/null"));
unshift_list(delete_commands, dynamic_strcat(5, "iptables -t ", table, " -X ", delete_chain, " 2>/dev/null"));
/* run delete commands */
while(delete_commands->length > 0)
{
char *next_command = (char*)pop_list(delete_commands);
char **out = get_shell_command_output_lines(next_command, &num_lines);
free_null_terminated_string_array(out);
}
return 0;
}

View File

@ -0,0 +1,984 @@
/* make_iptables_rules -- A tool to generate firewall rules from options in UCI config files
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2009 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <math.h>
#include <erics_tools.h>
#include <uci.h>
#define malloc safe_malloc
#define strdup safe_strdup
/* these are indices don't change! */
#define MATCH_IP_INDEX 0
#define MATCH_IP_RANGE_INDEX 1
#define MATCH_MAC_INDEX 2
string_map* get_rule_definition(char* config, char* section);
char* get_option_value_string(struct uci_option* uopt);
int parse_option(char* option_name, char* option_value, string_map* definition);
char*** parse_ips_and_macs(char* addr_str);
char** parse_ports(char* port_str);
char** parse_marks(char* list_str, unsigned long max_mask);
list* parse_quoted_list(char* list_str, char quote_char, char escape_char, char add_remainder_if_uneven_quotes);
int truncate_if_starts_with(char* test_str, char* prefix);
char** compute_rules(string_map *rule_def, char* table, char* chain, int is_ingress, char* target, char* target_options);
int compute_multi_rules(char** def, list* multi_rules, char** single_check, int never_single, char* rule_prefix, char* test_prefix1, char* test_prefix2, int is_negation, int mask_byte_index, char* proto, int requires_proto, int quoted_args);
int main(int argc, char **argv)
{
int c;
char* package = NULL;
char* section = NULL;
char* table = NULL;
char* chain = NULL;
char* target = NULL;
char* target_options = NULL;
int is_ingress = 0;
int run_commands = 0;
int usage_printed = 0;
while((c = getopt(argc, argv, "P:p:S:s:T:t:C:c:G:g:O:o:IiRrUu")) != -1) //section, page, css includes, javascript includes, title, output interface variables
{
switch(c)
{
case 'P':
case 'p':
package = strdup(optarg);
break;
case 'S':
case 's':
section = strdup(optarg);
break;
case 'T':
case 't':
table = strdup(optarg);
break;
case 'C':
case 'c':
chain = strdup(optarg);
break;
case 'G':
case 'g':
target = strdup(optarg);
break;
case 'O':
case 'o':
target_options = strdup(optarg);
break;
case 'I':
case 'i':
is_ingress = 1;
break;
case 'R':
case 'r':
run_commands = 1;
break;
case 'U':
case 'u':
default:
fprintf(stderr, "USAGE: %s -p [PACKAGE] -s [SECTION] -t [TABLE] -c [CHAIN] -g [TARGET] [OPTIONS]\n -o [TARGET_OPTIONS]\n -i indicates that this rule applies to ingress packets\n -r implies computed commands should be executed instead of just printed\n -u print usage and exit\n\n", argv[0]);
usage_printed = 1;
break;
}
}
if(package != NULL && section != NULL && table != NULL && chain != NULL && target != NULL)
{
string_map* def = get_rule_definition(package, section);
if(def != NULL)
{
char** rules = compute_rules(def, table, chain, 0, target, target_options);
int rindex = 0;
for(rindex=0; rules[rindex] != NULL; rindex++)
{
if(run_commands == 0)
{
printf("%s\n", rules[rindex]);
}
else
{
system(rules[rindex]);
}
}
}
else
{
fprintf(stderr, "ERROR: Invalid package / section\n");
}
}
else if(!usage_printed)
{
fprintf(stderr, "USAGE: %s -p [PACKAGE] -s [SECTION] -t [TABLE] -c [CHAIN] -g [TARGET] [OPTIONS]\n -o [TARGET_OPTIONS]\n -i indicates that this rule applies to ingress packets\n -r implies computed commands should be executed instead of just printed\n -u print usage and exit\n\n", argv[0]);
}
return 0;
}
/*
* Note we've currently maxed out out one whole byte of address space
* in the connmark at this point. If we want to match in
* further dimensions, we will have to be greedy and take
* even more address space
*/
char** compute_rules(string_map *rule_def, char* table, char* chain, int is_ingress, char* target, char* target_options)
{
list* multi_rules = initialize_list();
char* single_check = strdup("");
char* rule_prefix = dynamic_strcat(5, "iptables -t ", table, " -A ", chain, " ");
target = strdup(target);
to_uppercase(target);
/* get timerange vars first */
char* active_hours = get_map_element(rule_def, "active_hours");
char* active_weekdays = get_map_element(rule_def, "active_weekdays");
char* active_weekly_ranges = get_map_element(rule_def, "active_weekly_ranges");
if(active_weekly_ranges != NULL)
{
char* tmp = dynamic_strcat(3, " -m timerange --weekly_ranges \"", active_weekly_ranges, "\" " );
dcat_and_free(&single_check, &tmp, 1, 1);
}
else if(active_hours != NULL && active_weekdays != NULL)
{
char* tmp = dynamic_strcat(5, " -m timerange --hours \"", active_hours, "\" --weekdays \"", active_weekdays, "\" " );
dcat_and_free(&single_check, &tmp, 1, 1);
}
else if(active_hours != NULL)
{
char* tmp = dynamic_strcat(3, " -m timerange --hours \"", active_hours, "\" " );
dcat_and_free(&single_check, &tmp, 1, 1);
}
else if(active_weekdays != NULL)
{
char* tmp = dynamic_strcat(3, " -m timerange --weekdays \"", active_weekdays, "\" " );
dcat_and_free(&single_check, &tmp, 1, 1);
}
/*
* layer7 && ipp2p can not be negated. To negate them
* set a mark/connmark and negate that
*/
char* layer7_def = get_map_element(rule_def, "layer7");
if(layer7_def != NULL)
{
char* tmp = dynamic_strcat(2, " -m layer7 --l7proto ", layer7_def );
dcat_and_free(&single_check, &tmp, 1, 1);
}
char* ipp2p_def = get_map_element(rule_def, "ipp2p");
if(ipp2p_def != NULL)
{
char* tmp = dynamic_strcat(2, " -m ipp2p --", ipp2p_def );
dcat_and_free(&single_check, &tmp, 1, 1);
}
char* min_def = get_map_element(rule_def, "min_pkt_size");
char* max_def = get_map_element(rule_def, "max_pkt_size");
if(min_def != NULL && max_def != NULL)
{
min_def = min_def == NULL ? "0" : min_def;
max_def = max_def == NULL ? "3000" : max_def; //typical max transmission size is 1500, let's make default max 2x that
char* tmp = dynamic_strcat(5, " -m length --length ", min_def, ":", max_def, " " );
dcat_and_free(&single_check, &tmp, 1, 1);
}
/* make sure proto is lower case */
char* proto = get_map_element(rule_def, "proto");
if(proto == NULL)
{
proto = strdup("both");
set_map_element(rule_def, "proto", proto);
}
to_lowercase(proto);
if( safe_strcmp(proto, "udp") != 0 && safe_strcmp(proto, "tcp") != 0 && safe_strcmp(proto, "both") != 0 )
{
char* tmp;
tmp = set_map_element(rule_def, "proto", strdup("both"));
free(tmp);
proto = (char*)get_map_element(rule_def, "proto");
}
int include_proto = strcmp(proto, "both") == 0 ? 0 : 1;
/* parse multi rules */
int mask_byte_index = 0;
list* initial_mask_list = initialize_list();
list* final_mask_list = initialize_list();
/* url matches are a bit of a special case, handle them first */
/* we have to save this mask_byte_index specially, because it must be set separately, so it only gets set if packet is http request */
int url_mask_byte_index = mask_byte_index;
char* url_match_vars[] = { "url_contains", "url_regex", "url_exact", "url_domain_contains", "url_domain_regex", "url_domain_exact" };
char* url_neg_match_vars[] = { "not_url_contains", "not_url_regex", "not_url_exact", "not_url_domain_contains", "not_url_domain_regex", "not_url_domain_exact" };
char* url_prefixes[] = { " -m weburl --contains ", " -m weburl --contains_regex ", " -m weburl --matches_exactly ", " -m weburl --domain_only --contains ", " -m weburl --domain_only --contains_regex ", " -m weburl --domain_only --matches_exactly " };
list* url_lists[6];
int url_var_index=0;
int url_rule_count=0;
int url_is_negated=0;
for(url_is_negated=0; url_is_negated < 2 && url_rule_count == 0; url_is_negated++)
{
char** url_vars = url_is_negated ? url_neg_match_vars : url_match_vars;
for(url_var_index=0; url_var_index < 6; url_var_index++)
{
list* url_list = get_map_element(rule_def, url_vars[url_var_index]);
if(url_list != NULL)
{
url_rule_count = url_rule_count + url_list->length;
}
url_lists[url_var_index] = url_list;
}
}
url_is_negated--;
proto = url_rule_count > 0 ? "tcp" : proto;
int url_is_multi = url_rule_count <= 1 ? 0 : 1;
for(url_var_index=0; url_var_index < 6; url_var_index++)
{
list* url_list = url_lists[url_var_index];
if(url_list != NULL)
{
if(url_list->length > 0)
{
unsigned long num_vals;
char** url_def = (char**)get_list_values(url_list, &num_vals);
compute_multi_rules( url_def, multi_rules, &single_check, url_is_multi, rule_prefix, url_prefixes[url_var_index], "", url_is_negated, mask_byte_index, proto, include_proto, 1 );
free(url_def);
}
}
}
push_list(initial_mask_list, (void*)&url_is_negated);
push_list(final_mask_list, (void*)&url_is_multi);
mask_byte_index++;
/* mark matches */
char** mark_def = get_map_element(rule_def, "mark");
int mark_is_negated = mark_def == NULL ? 1 : 0;
mark_def = mark_def == NULL ? get_map_element(rule_def, "not_mark") : mark_def;
mark_is_negated = mark_def == NULL ? 0 : mark_is_negated;
/* we can't do single negation with mark match, so always add seperate multi-match if mark is negated */
int mark_is_multi = compute_multi_rules(mark_def, multi_rules, &single_check, mark_is_negated, rule_prefix, " -m mark ", " --mark ", mark_is_negated, mask_byte_index, proto, include_proto, 0) == 2;
push_list(initial_mask_list, (void*)&mark_is_negated);
push_list(final_mask_list, (void*)&mark_is_multi);
mask_byte_index++;
/* connmark matches */
char** connmark_def = get_map_element(rule_def, "connmark");
int connmark_is_negated = connmark_def == NULL ? 1 : 0;
connmark_def = connmark_def == NULL ? get_map_element(rule_def, "not_connmark") : connmark_def;
connmark_is_negated = connmark_def == NULL ? 0 : connmark_is_negated;
int connmark_is_multi = compute_multi_rules(connmark_def, multi_rules, &single_check, 0, rule_prefix, " -m connmark ", " --mark ", connmark_is_negated, mask_byte_index, proto, include_proto, 0) == 2;
push_list(initial_mask_list, (void*)&connmark_is_negated);
push_list(final_mask_list, (void*)&connmark_is_multi);
mask_byte_index++;
/*
* for ingress source = remote, destination = local
* for egress source = local, destination = remote
*
* addresses are a bit tricky, since we need to handle 3 different kinds of matches: ips, ip ranges and macs
*/
char*** src_def = get_map_element(rule_def, (is_ingress ? "remote_addr" : "local_addr"));
char*** not_src_def = get_map_element(rule_def, (is_ingress ? "not_remote_addr" : "not_local_addr"));
int src_is_negated = src_def == NULL && not_src_def != NULL ? 1 : 0;
src_def = src_is_negated == 1 ? not_src_def : src_def;
char*** dst_def = get_map_element(rule_def, (is_ingress ? "local_addr" : "remote_addr"));
char*** not_dst_def = get_map_element(rule_def, (is_ingress ? "not_local_addr" : "not_remote_addr"));
int dst_is_negated = dst_def == NULL && not_dst_def != NULL ? 1 : 0;
dst_def = dst_is_negated == 1 ? not_dst_def : dst_def;
char*** addr_defs[2] = { src_def, dst_def };
int addr_negated[2] = { src_is_negated, dst_is_negated };
char* addr_prefix1[2][3] = { { " -s ", " -m iprange ", " -m mac --mac-source " }, { " -d", "-m iprange ", NULL } };
char* addr_prefix2[2][3] = { {"", " --src-range ", "" }, { "", " --dst-range ", NULL } };
int addr_index = 0;
int is_true = 1;
int is_false = 0;
for(addr_index = 0; addr_index < 2; addr_index++)
{
char*** addrs = addr_defs[addr_index];
if(addrs != NULL)
{
int total_rules = 0;
int test_list_index;
for(test_list_index=0; test_list_index < 3; test_list_index++)
{
char** test_list = addrs[test_list_index];
if(test_list != NULL && addr_prefix1[addr_index][test_list_index] != NULL)
{
while(test_list[0] != NULL){ test_list++; total_rules++; }
}
}
int is_multi = total_rules > 1 ? 1 : 0;
int is_negated = addr_negated[addr_index];
//printf("is negated = %d for addr_index = %d\n", is_negated, addr_index);
for(test_list_index=0; test_list_index < 3; test_list_index++)
{
char** test_list = addrs[test_list_index];
if(test_list != NULL && addr_prefix1[addr_index][test_list_index] != NULL)
{
if(test_list[0] != NULL)
{
compute_multi_rules(test_list, multi_rules, &single_check, is_multi, rule_prefix, addr_prefix1[addr_index][test_list_index], addr_prefix2[addr_index][test_list_index],is_negated, mask_byte_index, proto, include_proto, 0);
}
}
}
push_list(initial_mask_list, (void*)(addr_negated + addr_index));
push_list(final_mask_list, (void*)(is_multi == 1 ? &is_true : &is_false) );
mask_byte_index++;
}
}
char** sport_def = get_map_element(rule_def, (is_ingress ? "remote_port" : "local_port"));
int sport_is_negated = sport_def == NULL ? 1 : 0;
sport_def = sport_def == NULL ? get_map_element(rule_def, (is_ingress ? "not_remote_port" : "not_local_port")) : sport_def;
sport_is_negated = sport_def == NULL ? 0 : sport_is_negated;
int sport_is_multi = compute_multi_rules(sport_def, multi_rules, &single_check, 0, rule_prefix, " --sport ", "", sport_is_negated, mask_byte_index, proto, 1, 0) == 2;
push_list(initial_mask_list, (void*)&sport_is_negated);
push_list(final_mask_list, (void*)&sport_is_multi);
mask_byte_index++;
char** dport_def = get_map_element(rule_def, (is_ingress ? "local_port" : "remote_port"));
int dport_is_negated = dport_def == NULL ? 1 : 0;
dport_def = dport_def == NULL ? get_map_element(rule_def, (is_ingress ? "not_local_port" : "not_remote_port")) : dport_def;
dport_is_negated = dport_def == NULL ? 0 : dport_is_negated;
int dport_is_multi = compute_multi_rules(dport_def, multi_rules, &single_check, 0, rule_prefix, " --dport ", "", dport_is_negated, mask_byte_index, proto, 1, 0) == 2;
push_list(initial_mask_list, (void*)&dport_is_negated);
push_list(final_mask_list, (void*)&dport_is_multi);
mask_byte_index++;
list* all_rules = initialize_list();
//if no target_options specified, make sure it's an empty string, not null
target_options = (target_options == NULL) ? "" : target_options;
//if target_options is empty and we're rejecting and proto is tcp, set options to --reject-with tcp-reset instead of default
target_options = strlen(target_options) == 0 && (strcmp(proto, "tcp") == 0) && strcmp(target, "REJECT") == 0 ? " --reject-with tcp-reset " : target_options;
if(multi_rules->length > 0)
{
if(strlen(single_check) > 0)
{
char* dummy_multi[] = { single_check, NULL };
int requires_proto = strcmp(proto, "both") == 0 && sport_def == NULL && dport_def == NULL ? 0 : 1;
compute_multi_rules(dummy_multi, multi_rules, &single_check, 1, rule_prefix, " ", "", 0, mask_byte_index, proto, requires_proto, 0);
mask_byte_index++;
}
/*
printf("final mask length = %ld\n", final_mask_list->length);
printf("src is multi = %d\n", src_is_multi);
unsigned long mi;
int* one = shift_list(final_mask_list);
int* two = shift_list(final_mask_list);
printf("one = %d, two = %d\n", *one, *two);
unshift_list(final_mask_list, two);
unshift_list(final_mask_list, one);
*/
unsigned long initial_url_mark = 0x01000000 * url_is_negated * url_is_multi;
unsigned long initial_main_mark = 0;
unsigned long final_match = 0;
int next_mask_index;
for(next_mask_index = 0; next_mask_index <mask_byte_index ; next_mask_index++)
{
int tmp = 1;
int* next_is_multi = &tmp;
if(final_mask_list->length > 0)
{
next_is_multi = shift_list(final_mask_list);
}
else
{
*next_is_multi = 1;
}
unsigned long next_mark_bit = 0x01000000 * (unsigned long)pow(2, next_mask_index) * (*next_is_multi);
final_match = final_match + next_mark_bit;
if(initial_mask_list->length > 0)
{
int* is_negation = (int*)shift_list(initial_mask_list);
if(*is_negation == 1 && next_mask_index != url_mask_byte_index )
{
//printf("nonzero byte index for main mark = %d\n", next_mask_index);
initial_main_mark = initial_main_mark + next_mark_bit;
}
}
/* else it's last single_check mark which is never initialized to one */
}
if(initial_main_mark > 0)
{
//set main_mark unconditionally
char mark[12];
sprintf(mark, "0x%lX", initial_main_mark);
push_list(all_rules, dynamic_strcat(4, rule_prefix, " -j CONNMARK --set-mark ", mark, "/0xFF000000" ));
}
if(initial_url_mark > 0) //do url_mark second since because in order to set main mark we use full mask of 0xFF000000
{
//set proper mark if this is an http request
char mark[12];
sprintf(mark, "0x%lX", initial_url_mark);
push_list(all_rules, dynamic_strcat(5, rule_prefix, " -p tcp -m weburl --contains http -j CONNMARK --set-mark ", mark, "/", mark));
}
//put all rules in place from multi_rules list
while(multi_rules->length > 0)
{
push_list(all_rules, shift_list(multi_rules));
}
unsigned long tmp_length;
destroy_list(multi_rules, DESTROY_MODE_IGNORE_VALUES, &tmp_length);
//if final mark matches perfectly with mask of 0xFF000000, jump to (REJECT/ACCEPT) target
char final_match_str[12];
sprintf(final_match_str, "0x%lX", final_match);
//if we're rejecting, no target options are specified, and no proto is specified add two rules: one for tcp with tcp-reject, and one for everything else
if(safe_strcmp(target, "REJECT") == 0 && safe_strcmp(target_options, "") == 0 && safe_strcmp(proto, "both"))
{
push_list(all_rules, dynamic_strcat(4, rule_prefix, " -p tcp -m connmark --mark ", final_match_str, "/0xFF000000 -j REJECT --reject-with tcp-reset"));
push_list(all_rules, dynamic_strcat(4, rule_prefix, " -m connmark --mark ", final_match_str, "/0xFF000000 -j REJECT"));
}
else
{
char* final_proto = strstr(target_options, "tcp-reset") == NULL ? "" : " -p tcp ";
push_list(all_rules, dynamic_strcat(7, rule_prefix, final_proto, " -m connmark --mark ", final_match_str, "/0xFF000000 -j ", target, target_options ));
}
//if final mark does not match (i.e. we didn't reject), unconditionally reset mark to 0x0 with mask of 0xFF000000
push_list(all_rules, dynamic_strcat(2, rule_prefix, " -j CONNMARK --set-mark 0x0/0xFF000000" ));
}
else
{
if( strcmp(proto, "both") == 0 )
{
if( dport_def == NULL && sport_def == NULL )
{
if(safe_strcmp(target, "REJECT") == 0 && safe_strcmp(target_options, "") == 0 )
{
push_list(all_rules, dynamic_strcat(4, rule_prefix, " -p tcp ", single_check, " -j REJECT --reject-with tcp-reset"));
}
push_list(all_rules, dynamic_strcat(5, rule_prefix, single_check, " -j ",target, target_options ));
}
else
{
if(safe_strcmp(target, "REJECT") == 0 && safe_strcmp(target_options, "") == 0 )
{
push_list(all_rules, dynamic_strcat(4, rule_prefix, " -p tcp ", single_check, " -j REJECT --reject-with tcp-reset" ));
}
else
{
push_list(all_rules, dynamic_strcat(6, rule_prefix, " -p tcp ", single_check, " -j ", target, target_options ));
}
push_list(all_rules, dynamic_strcat(6, rule_prefix, " -p udp ", single_check, " -j ", target, target_options ));
}
}
else
{
push_list(all_rules, dynamic_strcat(8, rule_prefix, " -p ", proto, " ", single_check, " -j ", target, target_options ));
}
}
/* handle very special case: if we're white-listing a URL we need to make
* sure other, non-request packets in connection get through too. So, we allow all traffic
* with a destination port of 80 that is NOT an HTTP request. This should allow
* HTTP connections to persist but prevent connections to any websites but those
* specified
*/
if(url_rule_count > 0 && safe_strcmp(target, "ACCEPT") == 0 && is_ingress == 0)
{
push_list(all_rules, dynamic_strcat(2, rule_prefix, " -p tcp -m weburl --contains http -j CONNMARK --set-mark 0xFF000000/0xFF000000" ));
push_list(all_rules, dynamic_strcat(2, rule_prefix, " -p tcp --dport 80 -m connmark ! --mark 0xFF000000/0xFF000000 -j ACCEPT " ));
push_list(all_rules, dynamic_strcat(2, rule_prefix, " -j CONNMARK --set-mark 0x0/0xFF000000" ));
}
unsigned long num_rules;
char** block_rule_list = (char**) destroy_list( all_rules, DESTROY_MODE_RETURN_VALUES, &num_rules);
return block_rule_list;
}
/* returns 0 if no rules found, 1 if one rule found AND included in single_check, otherwise 2 */
int compute_multi_rules(char** def, list* multi_rules, char** single_check, int never_single, char* rule_prefix, char* test_prefix1, char* test_prefix2, int is_negation, int mask_byte_index, char* proto, int requires_proto, int quoted_args)
{
int parse_type = 0;
if(def != NULL)
{
int num_rules;
for(num_rules=0; def[num_rules] != NULL; num_rules++){}
if(num_rules == 1 && !never_single)
{
parse_type = 1;
char* tmp = dynamic_strcat(7, " ", test_prefix1, (is_negation ? " ! " : " "), test_prefix2, (quoted_args ? " \"" : " "), def[0], (quoted_args ? "\" " : " ") );
dcat_and_free(&tmp, single_check, 1, 1 );
}
else
{
parse_type = 2;
unsigned long mask = 0x01000000 * (unsigned long)pow(2, mask_byte_index);
char mask_str[12];
sprintf(mask_str, "0x%lX", mask);
char* connmark_part = dynamic_strcat(4, " -j CONNMARK --set-mark ", (is_negation ? "0x0" : mask_str), "/", mask_str);
int rule_index =0;
for(rule_index=0; def[rule_index] != NULL; rule_index++)
{
char* common_part = dynamic_strcat(7, test_prefix1, " ", test_prefix2, (quoted_args ? " \"" : " "), def[rule_index], (quoted_args ? "\" " : " "), connmark_part);
if(strcmp(proto, "both") == 0)
{
if(requires_proto)
{
push_list(multi_rules, dynamic_strcat(3, rule_prefix, " -p tcp ", common_part));
push_list(multi_rules, dynamic_strcat(3, rule_prefix, " -p udp ", common_part));
}
else
{
push_list(multi_rules, dynamic_strcat(3, rule_prefix, " ", common_part ));
}
}
else
{
push_list(multi_rules, dynamic_strcat(5, rule_prefix, " -p ", proto, " ", common_part));
}
free(common_part);
}
free(connmark_part);
}
}
return parse_type;
}
string_map* get_rule_definition(char* package, char* section)
{
string_map* definition = NULL;
struct uci_context *ctx;
struct uci_package *p = NULL;
ctx = uci_alloc_context();
if(uci_load(ctx, package, &p) == UCI_OK)
{
struct uci_ptr ptr;
char* lookup_str = dynamic_strcat(3, package, ".", section);
int ret_value = uci_lookup_ptr(ctx, &ptr, lookup_str, 1);
if(ret_value == UCI_OK)
{
struct uci_section *s = ptr.s;
if(s != NULL)
{
struct uci_element *e;
definition = initialize_string_map(1);
uci_foreach_element(&s->options, e)
{
char* option_name = strdup(e->name);
to_lowercase(option_name);
char* option_value = get_option_value_string(uci_to_option(e));
parse_option(option_name, option_value, definition);
free(option_name);
free(option_value);
}
}
}
}
uci_free_context(ctx);
return definition;
}
int parse_option(char* option_name, char* option_value, string_map* definition)
{
int valid_option = 0;
if( safe_strcmp(option_name, "proto") == 0 ||
safe_strcmp(option_name, "layer7") == 0 ||
safe_strcmp(option_name, "ipp2p") == 0 ||
safe_strcmp(option_name, "max_pkt_size") == 0 ||
safe_strcmp(option_name, "min_pkt_size") ==0
)
{
valid_option = 1;
set_map_element(definition, option_name, strdup(option_value));
}
else if( safe_strcmp(option_name, "active_hours") == 0 ||
safe_strcmp(option_name, "active_weekly_ranges") == 0 ||
safe_strcmp(option_name, "active_weekdays") == 0
)
{
to_lowercase(option_value);
if( safe_strcmp(option_value, "all") != 0 )
{
valid_option = 1;
set_map_element(definition, option_name, strdup(option_value));
}
}
else if( safe_strcmp(option_name, "mark") == 0 ||
safe_strcmp(option_name, "connmark") == 0 ||
safe_strcmp(option_name, "not_mark") == 0 ||
safe_strcmp(option_name, "not_connmark") == 0
)
{
valid_option = 1;
set_map_element(definition, option_name, parse_marks(option_value, 0xFFFFFFFF));
}
else if(safe_strcmp(option_name, "remote_addr") == 0 ||
safe_strcmp(option_name, "local_addr") == 0 ||
safe_strcmp(option_name, "not_remote_addr") == 0 ||
safe_strcmp(option_name, "not_local_addr") == 0)
{
char*** parsed_addr = parse_ips_and_macs(option_value);
if(parsed_addr != NULL)
{
valid_option = 1;
if( safe_strcmp(option_name, "not_remote_addr") == 0 || safe_strcmp(option_name, "remote_addr") == 0 )
{
parsed_addr[MATCH_MAC_INDEX][0] = NULL; //doesn't make sense to match remote MAC address
}
set_map_element(definition, option_name, parsed_addr);
}
}
else if(safe_strcmp(option_name, "remote_port") == 0 ||
safe_strcmp(option_name, "local_port") == 0 ||
safe_strcmp(option_name, "not_remote_port") == 0 ||
safe_strcmp(option_name, "not_local_port") == 0)
{
char** parsed_ports = parse_ports(option_value);
if(parsed_ports != NULL)
{
valid_option = 1;
set_map_element(definition, option_name, parsed_ports);
}
}
else if(truncate_if_starts_with(option_name, "url_contains") ||
truncate_if_starts_with(option_name, "url_regex") ||
truncate_if_starts_with(option_name, "url_exact") ||
truncate_if_starts_with(option_name, "url_domain_contains") ||
truncate_if_starts_with(option_name, "url_domain_regex") ||
truncate_if_starts_with(option_name, "url_domain_exact") ||
truncate_if_starts_with(option_name, "not_url_contains") ||
truncate_if_starts_with(option_name, "not_url_regex") ||
truncate_if_starts_with(option_name, "not_url_exact") ||
truncate_if_starts_with(option_name, "not_url_domain_contains") ||
truncate_if_starts_with(option_name, "not_url_domain_regex") ||
truncate_if_starts_with(option_name, "not_url_domain_exact"))
{
/*
* may be a quoted list of urls to block, so attempt to parse this
* if no quotes found, match on unquoted expresssion
* we don't need to de-escape quotes because when we define rule,
* we call iptables from system, and through the shell, which will de-escape quotes for us
*/
list* parsed_quoted = parse_quoted_list(option_value, '\"', '\\', 0);
list* old_parsed = get_map_element(definition, option_name);
if(old_parsed != NULL)
{
while(parsed_quoted->length > 0)
{
push_list(old_parsed, shift_list(parsed_quoted));
}
free(parsed_quoted);
parsed_quoted = old_parsed;
}
if(parsed_quoted->length > 0)
{
valid_option = 1;
set_map_element(definition, option_name, parsed_quoted);
}
}
return valid_option;
}
// this function dynamically allocates memory for
// the option string, but since this program exits
// almost immediately (after printing variable info)
// the massive memory leak we're opening up shouldn't
// cause any problems. This is your reminder/warning
// that this might be an issue if you use this code to
// do anything fancy.
char* get_option_value_string(struct uci_option* uopt)
{
char* opt_str = NULL;
if(uopt->type == UCI_TYPE_STRING)
{
opt_str = strdup(uopt->v.string);
}
if(uopt->type == UCI_TYPE_LIST)
{
struct uci_element* e;
uci_foreach_element(&uopt->v.list, e)
{
if(opt_str == NULL)
{
opt_str = strdup(e->name);
}
else
{
char* tmp;
tmp = dynamic_strcat(3, opt_str, " ", e->name);
free(opt_str);
opt_str = tmp;
}
}
}
return opt_str;
}
char*** parse_ips_and_macs(char* addr_str)
{
unsigned long num_pieces;
char** addr_parts = split_on_separators(addr_str, ",", 1, -1, 0, &num_pieces);
list* ip_list = initialize_list();
list* ip_range_list = initialize_list();
list* mac_list = initialize_list();
int ip_part_index;
for(ip_part_index=0; addr_parts[ip_part_index] != NULL; ip_part_index++)
{
char* next_str = addr_parts[ip_part_index];
if(strchr(next_str, ':'))
{
trim_flanking_whitespace(next_str);
if(strlen(next_str) == 17)
{
push_list(mac_list, trim_flanking_whitespace(next_str));
}
}
else if(strchr(next_str, '-') != NULL)
{
char** range_parts = split_on_separators(next_str, "-", 1, 2, 1, &num_pieces);
char* start = trim_flanking_whitespace(range_parts[0]);
char* end = trim_flanking_whitespace(range_parts[1]);
int start_ip[4];
int end_ip[4];
int start_valid = sscanf(start, "%d.%d.%d.%d", start_ip, start_ip+1, start_ip+2, start_ip+3);
int end_valid = sscanf(end, "%d.%d.%d.%d", end_ip, end_ip+1, end_ip+2, end_ip+3);
if(start_valid == 4 && end_valid == 4)
{
//get_ip_range_strs(start_ip, end_ip, "", 4, ip_list);
push_list(ip_range_list, trim_flanking_whitespace(next_str));
}
free(start);
free(end);
free(range_parts);
//free(next_str);
}
else
{
int parsed_ip[4];
int valid = sscanf(next_str, "%d.%d.%d.%d", parsed_ip, parsed_ip+1, parsed_ip+2, parsed_ip+3);
if(valid == 4)
{
push_list(ip_list, trim_flanking_whitespace(next_str));
}
}
}
free(addr_parts);
unsigned long num1, num2, num3;
char*** return_value = (char***)malloc(3*sizeof(char**));
return_value[MATCH_IP_INDEX] = (char**)destroy_list(ip_list, DESTROY_MODE_RETURN_VALUES, &num1);
return_value[MATCH_IP_RANGE_INDEX] = (char**)destroy_list(ip_range_list, DESTROY_MODE_RETURN_VALUES, &num2);
return_value[MATCH_MAC_INDEX] = (char**)destroy_list(mac_list, DESTROY_MODE_RETURN_VALUES, &num3);
if(num1 + num2 + num3 == 0)
{
free(return_value[0]);
free(return_value[1]);
free(return_value[2]);
free(return_value);
return_value = NULL;
}
return return_value;
}
char** parse_ports(char* port_str)
{
unsigned long num_pieces;
char** ports = split_on_separators(port_str, ",", 1, -1, 0, &num_pieces);
int port_index = 0;
for(port_index=0; ports[port_index] != NULL; port_index++)
{
char* dash_ptr;
while((dash_ptr=strchr(ports[port_index], '-')) != NULL)
{
dash_ptr[0] = ':';
}
trim_flanking_whitespace( ports[port_index] );
}
return ports;
}
/*
* parses a list of marks/connmarks
* the max_mask parameter specfies a maximal mask that will be used
* when matching marks/connmarks. If a user-defined mask is specified
* (by defining [mark]/[mask]) this is bitwise-anded with the maximum
* mask to get the final mask. This is especially necessary for
* connmarks, because the mechanism to handle negation when multiple
* test rules are needed uses the last (high) byte of the connmark
* address space, so this HAS to be masked out when matching
* connmarks, using max_mask=0x00FFFFFF
*/
char** parse_marks(char* list_str, unsigned long max_mask)
{
char** marks = NULL;
if(list_str != NULL)
{
unsigned long num_pieces;
marks = split_on_separators(list_str, ",", 1, -1, 0, &num_pieces);
if(marks[0] == NULL)
{
free(marks);
marks = NULL;
}
else
{
int mark_index;
for(mark_index = 0; marks[mark_index] != NULL; mark_index++)
{
trim_flanking_whitespace(marks[mark_index]);
if(max_mask != 0xFFFFFFFF)
{
char* m = marks[mark_index];
char* mask_start;
if( (mask_start = strchr(m, '/')) != NULL )
{
unsigned long mask = 0xFFFFFFFF;
mask_start++;
sscanf(mask_start, "%lX", &mask);
mask = mask & max_mask;
*(mask_start) = '\0';
char new_mask_str[12];
sprintf(new_mask_str, "0x%lX", mask);
marks[mark_index] = dynamic_strcat(2, m, new_mask_str);
}
else
{
char new_mask_str[12];
sprintf(new_mask_str, "0x%lX", max_mask);
marks[mark_index] = dynamic_strcat(3, m, "/", new_mask_str);
}
free(m);
}
}
}
}
return marks;
}
/*
* parses list of quoted strings, ignoring escaped quote characters that are not themselves escaped
* Note that we don't de-escape anything here. If necessary that should be done elsewhere.
*/
list* parse_quoted_list(char* list_str, char quote_char, char escape_char, char add_remainder_if_uneven_quotes)
{
long num_quotes = 0;
long list_index = 0;
char previous_is_quoted = 0;
for(list_index=0; list_str[list_index] != '\0'; list_index++)
{
num_quotes = num_quotes + ( list_str[list_index] == quote_char && !previous_is_quoted ? 1 : 0);
previous_is_quoted = list_str[list_index] == escape_char && !previous_is_quoted ? 1 : 0;
}
char** pieces = (char**)malloc( ((long)(num_quotes/2)+2) * sizeof(char*) );
long piece_index = 0;
long next_start_index=-1;
previous_is_quoted = 0;
for(list_index=0; list_str[list_index] != '\0'; list_index++)
{
if( list_str[list_index] == quote_char && !previous_is_quoted )
{
if(next_start_index < 0)
{
next_start_index = list_index+1;
}
else
{
long length = list_index-next_start_index;
char* next_piece = (char*)malloc( (1+length)*sizeof(char) );
memcpy(next_piece, list_str+next_start_index, length);
next_piece[length] = '\0';
pieces[piece_index] = next_piece;
piece_index++;
next_start_index = -1;
}
}
previous_is_quoted = list_str[list_index] == escape_char && !previous_is_quoted ? 1 : 0;
}
if(add_remainder_if_uneven_quotes && next_start_index >= 0)
{
long length = 1+list_index-next_start_index;
char* next_piece = (char*)malloc( (1+length)*sizeof(char) );
memcpy(next_piece, list_str+next_start_index, length);
next_piece[length] = '\0';
pieces[piece_index] = next_piece;
piece_index++;
}
pieces[piece_index] = NULL;
list* quoted_list = initialize_list();
if(pieces[0] != NULL)
{
for(piece_index=0; pieces[piece_index] != NULL; piece_index++)
{
push_list(quoted_list, pieces[piece_index]);
//don't free pieces[piece], we're just putting it in list
}
}
else
{
//if no quotes at all, just return list_str
push_list(quoted_list, strdup(list_str));
}
free(pieces);//but do free array of char* pointers, we don't need it anymore
return quoted_list;
}
int truncate_if_starts_with(char* test_str, char* prefix)
{
int prefix_length = strlen(prefix);
int test_length = strlen(test_str);
int matches = 0;
if(prefix_length <= test_length)
{
matches = strncmp(test_str, prefix, prefix_length) == 0 ? 1 : 0;
if(matches)
{
test_str[prefix_length] = '\0';
}
}
return matches;
}

View File

@ -0,0 +1,410 @@
/* print_quotas -- Used to print quota data from iptables rules that use the "bandwidth" module
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2009-2010 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <erics_tools.h>
#include <uci.h>
#include <ipt_bwctl.h>
#define malloc safe_malloc
#define strdup safe_strdup
list* get_all_sections_of_type(struct uci_context *ctx, char* package, char* section_type);
void backup_quota(char* quota_id, char* quota_backup_dir);
char* get_uci_option(struct uci_context* ctx,char* package_name, char* section_name, char* option_name);
char* get_option_value_string(struct uci_option* uopt);
int main(void)
{
struct uci_context *ctx = uci_alloc_context();
list* quota_sections = get_all_sections_of_type(ctx, "firewall", "quota");
unlock_bandwidth_semaphore_on_exit();
/* for each ip have uint64_t[6], */
string_map *id_ip_to_bandwidth = initialize_string_map(1);
string_map *id_ip_to_percents = initialize_string_map(1);
string_map *id_ip_to_limits = initialize_string_map(1);
list *id_to_time = initialize_list();
while(quota_sections->length > 0)
{
char* next_quota = shift_list(quota_sections);
/* base id for quota is the ip associated with it*/
char *id = get_uci_option(ctx, "firewall", next_quota, "id");
char* ip = get_uci_option(ctx, "firewall", next_quota, "ip");
if(ip == NULL)
{
ip = strdup("ALL");
}
else if(strcmp(ip, "") == 0)
{
free(ip);
ip = strdup("ALL");
}
if(id == NULL)
{
id = strdup(ip);
}
else if(strcmp(id, "") == 0)
{
free(id);
id = strdup(ip);
}
string_map* ip_to_bandwidth = get_string_map_element(id_ip_to_bandwidth, id);
ip_to_bandwidth = ip_to_bandwidth == NULL ? initialize_string_map(1) : ip_to_bandwidth;
set_string_map_element(id_ip_to_bandwidth, id, ip_to_bandwidth);
string_map* ip_to_percents = get_string_map_element(id_ip_to_percents, id);
ip_to_percents = ip_to_percents == NULL ? initialize_string_map(1) : ip_to_percents;
set_string_map_element(id_ip_to_percents, id, ip_to_percents);
string_map* ip_to_limits = get_string_map_element(id_ip_to_limits, id);
ip_to_limits = ip_to_limits == NULL ? initialize_string_map(1) : ip_to_limits;
set_string_map_element(id_ip_to_limits, id, ip_to_limits);
char* offpeak_hours = get_uci_option(ctx, "firewall", next_quota, "offpeak_hours");
char* offpeak_weekdays = get_uci_option(ctx, "firewall", next_quota, "offpeak_weekdays");
char* offpeak_weekly_ranges = get_uci_option(ctx, "firewall", next_quota, "offpeak_weekly_ranges");
char* onpeak_hours = get_uci_option(ctx, "firewall", next_quota, "onpeak_hours");
char* onpeak_weekdays = get_uci_option(ctx, "firewall", next_quota, "onpeak_weekdays");
char* onpeak_weekly_ranges = get_uci_option(ctx, "firewall", next_quota, "onpeak_weekly_ranges");
if(offpeak_hours != NULL || offpeak_weekdays != NULL || offpeak_weekly_ranges != NULL || onpeak_hours != NULL || onpeak_weekdays != NULL || onpeak_weekly_ranges != NULL)
{
unsigned char is_off_peak = (offpeak_hours != NULL || offpeak_weekdays != NULL || offpeak_weekly_ranges != NULL) ? 1 : 0;
char* hours_var = is_off_peak ? offpeak_hours : onpeak_hours;
char* weekdays_var = is_off_peak ? offpeak_weekdays : onpeak_weekdays;
char* weekly_ranges_var = is_off_peak ? offpeak_weekly_ranges : onpeak_weekly_ranges;
char* active_var = is_off_peak ? strdup("except") : strdup("only");
if(weekly_ranges_var != NULL)
{
if(hours_var != NULL) { free(hours_var); hours_var=NULL; }
if(weekdays_var != NULL) { free(weekly_ranges_var); weekly_ranges_var=NULL; }
}
hours_var = hours_var == NULL ? strdup("") : hours_var;
weekdays_var = weekdays_var == NULL ? strdup("") : weekdays_var;
weekly_ranges_var = weekly_ranges_var == NULL ? strdup("") : weekly_ranges_var;
push_list(id_to_time, dynamic_strcat(11, "quotaTimes[\"", id, "\"] = [\"", hours_var, "\", \"", weekdays_var, "\", \"", weekly_ranges_var ,"\", \"", active_var, "\"];"));
free(hours_var);
free(weekdays_var);
free(weekly_ranges_var);
free(active_var);
}
else
{
push_list(id_to_time, dynamic_strcat(3, "quotaTimes[\"", id, "\"] = [\"\", \"\", \"\", \"always\"];"));
}
char* types[] = { "combined_limit", "ingress_limit", "egress_limit" };
char* postfixes[] = { "_combined", "_ingress", "_egress" };
int type_index;
for(type_index=0; type_index < 3; type_index++)
{
char* limit = get_uci_option(ctx, "firewall", next_quota, types[type_index]);
if(limit != NULL)
{
char* type_id = dynamic_strcat(2, id, postfixes[type_index]);
ip_bw* ip_buf;
unsigned long num_ips = 0;
int query_succeeded = get_all_bandwidth_usage_for_rule_id(type_id, &num_ips, &ip_buf, 5000);
if(query_succeeded && num_ips > 0)
{
unsigned long ip_index = 0;
for(ip_index = 0; ip_index < num_ips; ip_index++)
{
ip_bw next = ip_buf[ip_index];
char* next_ip = NULL;
if(next.ip == 0)
{
next_ip = strdup(ip);
}
else
{
struct in_addr addr;
addr.s_addr = next.ip;
next_ip = strdup(inet_ntoa(addr));
}
uint64_t *bw_list = get_string_map_element(ip_to_bandwidth,next_ip);
if(bw_list == NULL)
{
bw_list = (uint64_t*)malloc(sizeof(uint64_t)*6);
bw_list[0] = 0;
bw_list[1] = 0;
bw_list[2] = 0;
bw_list[3] = 0;
bw_list[4] = 0;
bw_list[5] = 0;
set_string_map_element(ip_to_bandwidth, next_ip, bw_list);
}
bw_list[type_index] = 1;
bw_list[type_index+3] = next.bw;
char bw_str[50];
sprintf(bw_str, "%lld", next.bw);
double bw_percent;
double bw_limit;
uint64_t bw_limit_64;
sscanf(bw_str, "%lf", &bw_percent);
sscanf(limit, "%lf", &bw_limit);
sscanf(limit, "%lld", &bw_limit_64);
if(bw_limit > 0)
{
bw_percent = (bw_percent*100.0)/bw_limit;
bw_percent = bw_percent > 100.0 ? 100.0 : bw_percent;
}
else
{
bw_percent = 100.0;
}
double* percent_list = get_string_map_element(ip_to_percents, next_ip);
if(percent_list == NULL)
{
percent_list = (double*)malloc(sizeof(double)*3);
percent_list[0] = -1;
percent_list[1] = -1;
percent_list[2] = -1;
set_string_map_element(ip_to_percents, next_ip, percent_list);
}
percent_list[type_index] = bw_percent;
uint64_t* limit_list = get_string_map_element(ip_to_limits, next_ip);
if(limit_list == NULL)
{
limit_list = (uint64_t*)malloc(sizeof(uint64_t)*3);
limit_list[0] = -1;
limit_list[1] = -1;
limit_list[2] = -1;
set_string_map_element(ip_to_limits, next_ip, limit_list);
}
limit_list[type_index] = bw_limit_64;
}
}
free(type_id);
free(limit);
}
}
free(id);
free(ip);
free(next_quota);
}
unsigned long num_ids;
char** id_list = (char**)get_string_map_keys(id_ip_to_bandwidth, &num_ids);
printf("var quotaIdList = [ ");
char print_comma[10] = "";
unsigned long id_index;
for(id_index=0; id_index < num_ids; id_index++)
{
printf("%s\"%s\"", print_comma, id_list[id_index]);
sprintf(print_comma, ", ");
}
printf(" ];\n");
printf("var quotaIpLists = [];\n");
for(id_index=0; id_index < num_ids; id_index++)
{
string_map* ip_to_bandwidth = get_string_map_element(id_ip_to_bandwidth, id_list[id_index]);
if(ip_to_bandwidth != NULL)
{
unsigned long num_ips = 0;
unsigned long ip_index = 0;
char** ip_list = (char**)get_string_map_keys(ip_to_bandwidth, &num_ips);
printf("quotaIpLists[\"%s\"] = [ ", id_list[id_index]);
sprintf(print_comma, "");
for(ip_index=0; ip_index < num_ips; ip_index++)
{
printf("%s\"%s\"", print_comma, ip_list[ip_index]);
sprintf(print_comma, ", ");
}
printf("];\n");
}
}
printf("var quotaTimes = new Array();\n");
printf("var quotaUsed = new Array();\n");
printf("var quotaLimits = new Array();\n");
printf("var quotaPercents = new Array();\n");
char* next_time = shift_list(id_to_time);
while(next_time != NULL)
{
printf("%s\n", next_time);
next_time = shift_list(id_to_time);
}
for(id_index=0; id_index < num_ids; id_index++)
{
char* next_id = id_list[id_index];
string_map* ip_to_bandwidth = get_string_map_element(id_ip_to_bandwidth, next_id);
string_map* ip_to_percents = get_string_map_element(id_ip_to_percents, next_id);
string_map* ip_to_limits = get_string_map_element(id_ip_to_limits, next_id);
printf("quotaUsed[ \"%s\" ] = [];\n", next_id);
printf("quotaPercents[ \"%s\" ] = [];\n", next_id);
printf("quotaLimits[ \"%s\" ] = [];\n", next_id);
if(ip_to_bandwidth != NULL)
{
unsigned long num_ips;
unsigned long ip_index = 0;
char** ip_list = (char**)get_string_map_keys(ip_to_bandwidth, &num_ips);
for(ip_index=0; ip_index < num_ips; ip_index++)
{
char* next_ip = ip_list[ip_index];
uint64_t* used = (uint64_t*)get_string_map_element(ip_to_bandwidth, next_ip);
double* percents = (double*)get_string_map_element(ip_to_percents, next_ip);
uint64_t* limits = (uint64_t*)get_string_map_element(ip_to_limits, next_ip);
if(used != NULL)
{
int type_index;
printf("quotaUsed[ \"%s\" ][ \"%s\" ] = [ ", next_id, next_ip);
sprintf(print_comma, "");
for(type_index=0; type_index < 3; type_index++)
{
if(!used[type_index])
{
printf("%s-1", print_comma);
}
else
{
printf("%s%lld", print_comma, (long long int)used[type_index+3]);
}
sprintf(print_comma, ", ");
}
printf(" ];\n");
printf("quotaPercents[ \"%s\" ][ \"%s\" ] = [ ", next_id, next_ip);
sprintf(print_comma, "");
for(type_index=0; type_index < 3; type_index++)
{
printf("%s%6.3lf", print_comma, percents[type_index]);
sprintf(print_comma, ", ");
}
printf(" ];\n");
printf("quotaLimits[ \"%s\" ][ \"%s\" ] = [ ", next_id, next_ip);
sprintf(print_comma, "");
for(type_index=0; type_index < 3; type_index++)
{
printf("%s%lld", print_comma,limits[type_index]);
sprintf(print_comma, ", ");
}
printf(" ];\n");
}
}
}
}
unsigned long num;
destroy_list(quota_sections, DESTROY_MODE_FREE_VALUES, &num);
uci_free_context(ctx);
return 0;
}
list* get_all_sections_of_type(struct uci_context *ctx, char* package, char* section_type)
{
struct uci_package *p = NULL;
struct uci_element *e = NULL;
list* sections_of_type = initialize_list();
if(uci_load(ctx, package, &p) == UCI_OK)
{
uci_foreach_element( &p->sections, e)
{
struct uci_section *section = uci_to_section(e);
if(safe_strcmp(section->type, section_type) == 0)
{
push_list(sections_of_type, strdup(section->e.name));
}
}
}
return sections_of_type;
}
char* get_uci_option(struct uci_context* ctx, char* package_name, char* section_name, char* option_name)
{
char* option_value = NULL;
struct uci_ptr ptr;
char* lookup_str = dynamic_strcat(5, package_name, ".", section_name, ".", option_name);
int ret_value = uci_lookup_ptr(ctx, &ptr, lookup_str, 1);
if(ret_value == UCI_OK)
{
if( !(ptr.flags & UCI_LOOKUP_COMPLETE))
{
ret_value = UCI_ERR_NOTFOUND;
}
else
{
struct uci_element *e = (struct uci_element*)ptr.o;
option_value = get_option_value_string(uci_to_option(e));
}
}
free(lookup_str);
return option_value;
}
// this function dynamically allocates memory for
// the option string, but since this program exits
// almost immediately (after printing variable info)
// the massive memory leak we're opening up shouldn't
// cause any problems. This is your reminder/warning
// that this might be an issue if you use this code to
// do anything fancy.
char* get_option_value_string(struct uci_option* uopt)
{
char* opt_str = NULL;
if(uopt->type == UCI_TYPE_STRING)
{
opt_str = strdup(uopt->v.string);
}
if(uopt->type == UCI_TYPE_LIST)
{
struct uci_element* e;
uci_foreach_element(&uopt->v.list, e)
{
if(opt_str == NULL)
{
opt_str = strdup(e->name);
}
else
{
char* tmp;
tmp = dynamic_strcat(3, opt_str, " ", e->name);
free(opt_str);
opt_str = tmp;
}
}
}
return opt_str;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
#
# Copyright (C) 2006 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
# $Id: Makefile 9907 2007-12-25 01:59:55Z nbd $
include $(TOPDIR)/rules.mk
PKG_NAME:=libericstools
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/libericstools
SECTION:=libs
CATEGORY:=Gargoyle
SUBMENU:=Libraries
TITLE:=Erics Tools
URL:=http://www.gargoyle-router.com
MAINTAINER:=Eric Bishop <eric@gargoyle-router.com>
endef
define Package/libericstools/description
A bunch of routines/utilities written by Eric Bishop,
a library primarily used in Gargoyle Web Interface for OpenWrt
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)
endef
define Build/Configure
endef
define Build/InstallDev
mkdir -p $(STAGING_DIR)/usr/include/
$(CP) $(PKG_BUILD_DIR)/*.h $(STAGING_DIR)/usr/include/
mkdir -p $(STAGING_DIR)/usr/lib
$(CP) $(PKG_BUILD_DIR)/*.so* $(STAGING_DIR)/usr/lib/
endef
define Package/libericstools/install
$(INSTALL_DIR) $(1)/usr/lib
$(CP) $(PKG_BUILD_DIR)/*.so* $(1)/usr/lib/
endef
$(eval $(call BuildPackage,libericstools))

View File

@ -0,0 +1,78 @@
all: erics_tools
VERSION=1
ifeq ($(CC),)
CC=gcc
endif
ifeq ($(LD),)
LD=ld
endif
ifeq ($(AR),)
AR=ar
endif
ifeq ($(RANLIB),)
RANLIB=ranlib
endif
CFLAGS:=$(CFLAGS) -Os
WARNING_FLAGS=-Wall -Wstrict-prototypes -pedantic
MINIMAL_WARNING_FLAGS=-Wall -Wstrict-prototypes
OS=$(shell uname)
ifeq ($(OS),Darwin)
LINK=$(LD)
SHLIB_EXT=dylib
SHLIB_FLAGS=-dylib
SHLIB_FILE=libericstools.$(SHLIB_EXT).$(VERSION)
CFLAGS:=$(CFLAGS) -arch i386
else
LINK=$(CC)
SHLIB_EXT=so
SHLIB_FILE=libericstools.$(SHLIB_EXT).$(VERSION)
SHLIB_FLAGS=-shared -Wl,-soname,$(SHLIB_FILE)
endif
test_list_and_queue: test_list_and_queue.c libericstools.a
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^
test_list_and_queue.o: test_list_and_queue.c
$(CC) $(CFLAGS) $(MINIMAL_WARNING_FLAGS) -o $@ -c $^
test_string: test_string.o libericstools.a
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^
test_string.o: test_string.c
$(CC) $(CFLAGS) $(MINIMAL_WARNING_FLAGS) -c $^ -o $@
test_map: test_map.o libericstools.a
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^
test_map.o: test_map.c
$(CC) $(CFLAGS) $(MINIMAL_WARNING_FLAGS) -c $^ -o $@
all: erics_tools
erics_tools: libericstools.$(SHLIB_EXT) libericstools.a
libericstools.a: list_static.o priority_queue_static.o tree_map_static.o string_util_static.o file_util_static.o safe_malloc_static.o
if [ -e $@ ] ; then rm $@ ; fi
$(AR) rc $@ $^
$(RANLIB) $@
libericstools.$(SHLIB_EXT) : list_dyn.o priority_queue_dyn.o tree_map_dyn.o string_util_dyn.o file_util_dyn.o safe_malloc_dyn.o
if [ -e libericstools.$(SHLIB_EXT) ] ; then rm libericstools.$(SHLIB_EXT)* ; fi
$(LINK) $(LDFLAGS) $(SHLIB_FLAGS) -o $(SHLIB_FILE) $^ -lc
ln -s $(SHLIB_FILE) libericstools.$(SHLIB_EXT)
%_dyn.o: %.c
$(CC) $(CFLAGS) -fPIC $(WARNING_FLAGS) -o $@ -c $^
%_static.o: %.c
$(CC) $(CFLAGS) $(WARNING_FLAGS) -o $@ -c $^
clean:
rm -rf *.a *.o .*sw* *~ test_map test_string test_list_and_queue
if [ "$(SHLIB_EXT)" != "" ] ; then rm -rf *.$(SHLIB_EXT)* ; fi
install:
cp *.h /usr/include
cp *.$(SHLIB_EXT)* /usr/lib

View File

@ -0,0 +1,276 @@
/*
* Copyright © 2008 by Eric Bishop <eric@gargoyle-router.com>
*
* This work 'as-is' we provide.
* No warranty, express or implied.
* We've done our best,
* to debug and test.
* Liability for damages denied.
*
* Permission is granted hereby,
* to copy, share, and modify.
* Use as is fit,
* free or for profit.
* On this notice these rights rely.
*
*
*
* Note that unlike other portions of Gargoyle this code
* does not fall under the GPL, but the rather whimsical
* 'Poetic License' above.
*
* Basically, this library contains a bunch of utilities
* that I find useful. I'm sure other libraries exist
* that are just as good or better, but I like these tools
* because I personally wrote them, so I know their quirks.
* (i.e. I know where the bodies are buried). I want to
* make sure that I can re-use these utilities for whatever
* code I may want to write in the future be it
* proprietary or open-source, so I've put them under
* a very, very permissive license.
*
* If you find this code useful, use it. If not, don't.
* I really don't care.
*
*/
#ifndef ERICS_TOOLS_H
#define ERICS_TOOLS_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <regex.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#ifndef stricmp
#define stricmp strcasecmp
#endif
/* tree_map structs / prototypes */
typedef struct long_tree_map_node
{
unsigned long key;
void* value;
signed char balance;
struct long_tree_map_node* left;
struct long_tree_map_node* right;
} long_map_node;
typedef struct
{
long_map_node* root;
unsigned long num_elements;
} long_map;
typedef struct
{
long_map lm;
unsigned char store_keys;
unsigned long num_elements;
} string_map;
/* long map functions */
extern long_map* initialize_long_map(void);
extern void* get_long_map_element(long_map* map, unsigned long key);
void* get_smallest_long_map_element(long_map* map, unsigned long* smallest_key);
void* get_largest_long_map_element(long_map* map, unsigned long* largest_key);
void* remove_smallest_long_map_element(long_map* map, unsigned long* smallest_key);
void* remove_largest_long_map_element(long_map* map, unsigned long* largest_key);
extern void* set_long_map_element(long_map* map, unsigned long key, void* value);
extern void* remove_long_map_element(long_map* map, unsigned long key);
extern unsigned long* get_sorted_long_map_keys(long_map* map, unsigned long* num_keys_returned);
extern void** get_sorted_long_map_values(long_map* map, unsigned long* num_values_returned);
extern void** destroy_long_map(long_map* map, int destruction_type, unsigned long* num_destroyed);
extern void apply_to_every_long_map_value(long_map* map, void (*apply_func)(unsigned long key, void* value));
/* string map functions */
extern string_map* initialize_string_map(unsigned char store_keys);
extern void* get_string_map_element(string_map* map, const char* key);
extern void* set_string_map_element(string_map* map, const char* key, void* value);
extern void* remove_string_map_element(string_map* map, const char* key);
extern char** get_string_map_keys(string_map* map, unsigned long* num_keys_returned);
extern void** get_string_map_values(string_map* map, unsigned long* num_values_returned);
extern void** destroy_string_map(string_map* map, int destruction_type, unsigned long* num_destroyed);
extern void apply_to_every_string_map_value(string_map* map, void (*apply_func)(char* key, void* value));
/*
* three different ways to deal with values when data structure is destroyed
*/
#define DESTROY_MODE_RETURN_VALUES 20
#define DESTROY_MODE_FREE_VALUES 21
#define DESTROY_MODE_IGNORE_VALUES 22
/*
* for convenience & backwards compatibility alias _string_map_ functions to
* _map_ functions since string map is used more often than long map
*/
#define initialize_map initialize_string_map
#define set_map_element set_string_map_element
#define get_map_element get_string_map_element
#define remove_map_element remove_string_map_element
#define get_map_keys get_string_map_keys
#define get_map_values get_string_map_values
#define destroy_map destroy_string_map
/* list structs / prototypes */
typedef struct list_node_struct
{
struct list_node_struct* next;
struct list_node_struct* previous;
void* value;
} list_node;
typedef struct list_struct
{
long length;
list_node* head;
list_node* tail;
} list;
extern list* initialize_list(void); /* O(1) */
extern void* shift_list(list* l); /* O(1) */
extern void unshift_list(list* l, void* value); /* O(1) */
extern void* pop_list(list* l); /* O(1) */
extern void push_list(list*l, void* value); /* O(1) */
extern void** destroy_list(list* l, int destruction_type, unsigned long* num_destroyed); /* O(n) */
extern void* list_element_at(list* l, unsigned long index); /* O(n) */
extern void** get_list_values(list* l, unsigned long* num_values_returned); /* O(n) */
/* The idea behind the remove_internal_node function and
* the other functions below that perform list operations on
* list_node pointers instead of values is as follows:
*
* It is O(n) to remove arbitrary node from list. BUT, if
* we have a pointer to that node already it is O(1). So,
* provide functions to manipulate list with list_node pointers
* instead of values & a function to remove an internal node
* given a pointer to that node. This means we can have
* access to internal nodes & use another
* data structure to store internal nodes and delete in O(1)
*/
extern void remove_internal_list_node(list*l, list_node* internal); /* O(1) */
extern list_node* create_list_node(void* value); /* O(1) */
extern void* free_list_node(list_node* delete_node); /* O(1) */
extern list_node* shift_list_node(list* l); /* O(1) */
extern void unshift_list_node(list* l, list_node* new_node); /* O(1) */
extern list_node* pop_list_node(list* l); /* O(1) */
extern void push_list_node(list*l, list_node* new_node); /* O(1) */
/* priority_queue structs / prototypes */
typedef struct priority_queue_node_struct
{
unsigned long priority;
char* id;
void* value;
} priority_queue_node;
typedef struct priority_queue_struct
{
long_map* priorities;
string_map* ids;
priority_queue_node* first;
long length;
} priority_queue;
extern priority_queue* initialize_priority_queue(void);
extern priority_queue_node* create_priority_node(unsigned long priority, char* id, void* value);
extern void* free_priority_queue_node(priority_queue_node* pn);
extern void push_priority_queue(priority_queue* pq, unsigned long priority, char* id, void * value);
extern void* shift_priority_queue(priority_queue* pq, unsigned long* priority, char** id);
extern void* peek_priority_queue(priority_queue* pq, unsigned long* priority, char** id, int dynamic_alloc_id);
extern void* get_priority_queue_element_with_id(priority_queue* pq, char* id, long* priority);
extern void* remove_priority_queue_element_with_id(priority_queue* pq, char* id, long* priority);
extern void push_priority_queue_node(priority_queue* pq, priority_queue_node* pn);
extern priority_queue_node* shift_priority_queue_node(priority_queue* pq);
extern priority_queue_node* get_priority_queue_node_with_id(priority_queue* pq, char* id);
extern priority_queue_node* remove_priority_queue_node_with_id(priority_queue* pq, char* id);
extern void set_priority_for_id_in_priority_queue(priority_queue* pq, char* id, unsigned long priority);
extern priority_queue_node* peek_priority_queue_node(priority_queue* pq);
extern void** destroy_priority_queue(priority_queue* pq, int destroy_mode, unsigned long* num_destroyed);
/* string_util structs / prototypes */
typedef struct
{
char* str;
int terminator;
} dyn_read_t;
/* non-dynamic functions */
extern char* replace_prefix(char* original, char* old_prefix, char* new_prefix);
extern char* trim_flanking_whitespace(char* str);
extern int safe_strcmp(const char* str1, const char* str2);
extern void to_lowercase(char* str);
extern void to_uppercase(char* str);
/* dynamic functions (e.g. new memory is allocated, return values must be freed) */
int free_null_terminated_string_array(char** strs);
char** copy_null_terminated_string_array(char** original);
extern char* dynamic_strcat(int num_strs, ...);
extern char* dcat_and_free(char** one, char** two, int free1, int free2);
extern char** split_on_separators(char* line, char* separators, int num_separators, int max_pieces, int include_remainder_at_max, unsigned long* num_pieces); /*if max_pieces < 0, it is ignored */
extern char* join_strs(char* separator, char** parts, int max_parts, int free_parts, int free_parts_array); /*if max_parts < 0, it is ignored*/
extern char* dynamic_replace(char* template_str, char* old_str, char* new_str);
int convert_to_regex(char* str, regex_t* p);
/* functions to dynamically read files */
extern dyn_read_t dynamic_read(FILE* open_file, char* terminators, int num_terminators, unsigned long* read_length);
extern int dyn_read_line(FILE* open_file, char** dest, unsigned long* read_len);
extern unsigned char* read_entire_file(FILE* in, unsigned long read_block_size, unsigned long* read_length);
/* run a command and get (dynamically allocated) output lines */
extern char** get_shell_command_output_lines(char* command, unsigned long* num_lines);
/* comparison functions for qsort */
extern int sort_string_cmp(const void *a, const void *b);
extern int sort_string_icmp(const void *a, const void *b);
/* wrappers for qsort calls */
extern void do_str_sort(char** string_arr, unsigned long string_arr_len);
extern void do_istr_sort(char** string_arr, unsigned long string_arr_len);
/* safe malloc & strdup functions used by all others (actually aliased to malloc / strdup and used) */
extern void* safe_malloc(size_t size);
extern char* safe_strdup(const char* str);
/* utility functions to free memory */
extern void free_if_not_null(void* p);
extern void free_and_set_null(void** p);
/* other file utils */
extern int mkdir_p(const char* path, mode_t mode); /* returns 0 on success, 1 on error */
extern void rm_r(const char* path);
extern int create_tmp_dir(const char* tmp_root, char** tmp_dir); /* returns 0 on success, 1 on error */
#define PATH_DOES_NOT_EXIST 0
#define PATH_IS_REGULAR_FILE 1
#define PATH_IS_DIRECTORY 2
#define PATH_IS_SYMLINK 3
#define PATH_IS_OTHER 4
/*
returns:
PATH_DOES_NOT_EXIST (0) if path doesn't exist
PATH_IS_REGULAR_FILE (1) if path is regular file
PATH_IS_DIRECTORY (2) if path is directory
PATH_IS_SYMLINK (3) if path is symbolic link
PATH_IS_OTHER (4) if path exists and is something else
*/
extern int path_exists(const char* path);
extern char** get_file_lines(char* file_path, unsigned long* lines_read);
#endif /* ERICS_TOOLS_H */

View File

@ -0,0 +1,189 @@
/*
* Copyright © 2008 by Eric Bishop <eric@gargoyle-router.com>
*
* This work as-is we provide.
* No warranty, express or implied.
* Weve done our best,
* to debug and test.
* Liability for damages denied.
*
* Permission is granted hereby,
* to copy, share, and modify.
* Use as is fit,
* free or for profit.
* On this notice these rights rely.
*
*
*
* Note that unlike other portions of Gargoyle this code
* does not fall under the GPL, but the rather whimsical
* 'Poetic License' above.
*
* Basically, this library contains a bunch of utilities
* that I find useful. I'm sure other libraries exist
* that are just as good or better, but I like these tools
* because I personally wrote them, so I know their quirks.
* (i.e. I know where the bodies are buried). I want to
* make sure that I can re-use these utilities for whatever
* code I may want to write in the future be it
* proprietary or open-source, so I've put them under
* a very, very permissive license.
*
* If you find this code useful, use it. If not, don't.
* I really don't care.
*
*/
#include "erics_tools.h"
#define malloc safe_malloc
#define strdup safe_strdup
static int __srand_called = 0;
int create_tmp_dir(const char* tmp_root, char** tmp_dir)
{
if(! __srand_called)
{
srand(time(NULL));
__srand_called = 1;
}
sprintf((*tmp_dir), "%s/tmp_%d", tmp_root, rand());
return (mkdir_p(*tmp_dir, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH ));
}
/*
returns:
PATH_DOES_NOT_EXIST (0) if path doesn't exist
PATH_IS_REGULAR_FILE (1) if path is regular file
PATH_IS_DIRECTORY (2) if path is directory
PATH_IS_SYMLINK (3) if path is symbolic link
PATH_IS_OTHER (4) if path exists and is something else
*/
int path_exists(const char* path)
{
struct stat fs;
int exists = lstat(path,&fs) >= 0 ? PATH_IS_OTHER : PATH_DOES_NOT_EXIST;
if(exists > 0)
{
exists = S_ISREG(fs.st_mode) ? PATH_IS_REGULAR_FILE : exists;
exists = S_ISDIR(fs.st_mode) ? PATH_IS_DIRECTORY : exists;
exists = S_ISLNK(fs.st_mode) ? PATH_IS_SYMLINK : exists;
}
return exists;
}
int mkdir_p(const char* path, mode_t mode)
{
int err=0;
struct stat fs;
char* dup_path = strdup(path);
char* sep = strchr(dup_path, '/');
sep = (sep == dup_path) ? strchr(sep+1, '/') : sep;
while(sep != NULL && err == 0)
{
sep[0] = '\0';
if(stat(dup_path,&fs) >= 0)
{
if(!S_ISDIR(fs.st_mode))
{
err = 1;
}
}
else
{
mkdir(dup_path, mode);
}
err =1;
if(stat(dup_path,&fs) >= 0)
{
err = S_ISDIR(fs.st_mode) ? 0 : 1;
}
sep[0] = '/';
sep = strchr(sep+1, '/');
}
if(err == 0)
{
if(stat(dup_path,&fs) >= 0)
{
if(!S_ISDIR(fs.st_mode))
{
err = 1;
}
}
else
{
mkdir(dup_path, mode);
}
err =1;
if(stat(dup_path,&fs) >= 0)
{
err = S_ISDIR(fs.st_mode) ? 0 : 1;
}
}
free(dup_path);
return err;
}
void rm_r(const char* path)
{
struct stat fs;
if(lstat(path,&fs) >= 0)
{
if(S_ISDIR (fs.st_mode))
{
/* remove directory recursively */
struct dirent **entries;
int num_entry_paths;
int entry_path_index;
num_entry_paths = scandir(path, &entries, 0, alphasort);
for(entry_path_index=0; entry_path_index < num_entry_paths; entry_path_index++)
{
struct dirent *dentry = entries[entry_path_index];
if(strcmp(dentry->d_name, "..") != 0 && strcmp(dentry->d_name, ".") != 0)
{
char* entry_path = (char*)malloc(strlen(path) + strlen(dentry->d_name) + 2);
sprintf(entry_path,"%s/%s", path, dentry->d_name);
/* recurse */
rm_r(entry_path);
free(entry_path);
}
}
remove(path);
}
else
{
/* remove regular file, no need to recurse */
remove(path);
}
}
}
char** get_file_lines(char* file_path, unsigned long* lines_read)
{
char** result = NULL;
int path_type = path_exists(file_path);
*lines_read = 0;
if(path_type != PATH_DOES_NOT_EXIST && path_type != PATH_IS_DIRECTORY) /* exists and is not directory */
{
FILE* read_file = fopen(file_path, "r");
unsigned char* file_data = NULL;
if(read_file != NULL)
{
unsigned long file_length;
file_data = read_entire_file(read_file, 1024, &file_length);
fclose(read_file);
}
if(file_data != NULL)
{
char line_seps[] = {'\r', '\n'};
result = split_on_separators((char*)file_data, line_seps , 2, -1, 0, lines_read);
free(file_data);
}
}
return result;
}

View File

@ -0,0 +1,277 @@
/*
* Copyright © 2008 by Eric Bishop <eric@gargoyle-router.com>
*
* This work as-is we provide.
* No warranty, express or implied.
* Weve done our best,
* to debug and test.
* Liability for damages denied.
*
* Permission is granted hereby,
* to copy, share, and modify.
* Use as is fit,
* free or for profit.
* On this notice these rights rely.
*
*
*
* Note that unlike other portions of Gargoyle this code
* does not fall under the GPL, but the rather whimsical
* 'Poetic License' above.
*
* Basically, this library contains a bunch of utilities
* that I find useful. I'm sure other libraries exist
* that are just as good or better, but I like these tools
* because I personally wrote them, so I know their quirks.
* (i.e. I know where the bodies are buried). I want to
* make sure that I can re-use these utilities for whatever
* code I may want to write in the future be it
* proprietary or open-source, so I've put them under
* a very, very permissive license.
*
* If you find this code useful, use it. If not, don't.
* I really don't care.
*
*/
#include "erics_tools.h"
#define malloc safe_malloc
#define strdup safe_strdup
list* initialize_list()
{
list* l = (list*)malloc(sizeof(list));
l->length = 0;
l->head = NULL;
l->tail = NULL;
return l;
}
list_node* create_list_node(void* value)
{
list_node* new_node = (list_node*)malloc(sizeof(list_node));
new_node->value = value;
new_node->previous = NULL;
new_node->next = NULL;
return new_node;
}
void* free_list_node(list_node* delete_node)
{
void* value = NULL;
if(delete_node != NULL)
{
value = delete_node->value;
free(delete_node);
}
return value;
}
list_node* shift_list_node(list* l)
{
list_node* return_node = NULL;
if(l != NULL)
{
if(l->head != NULL)
{
return_node = l->head;
l->head = l->head->next;
if(l->head != NULL) { l->head->previous = NULL; }
l->tail = l->tail == return_node ? NULL : l->tail;
l->length = l->length -1;
return_node->previous = NULL;
return_node->next = NULL;
}
}
return return_node;
}
void unshift_list_node(list* l, list_node* new_node)
{
if(l != NULL && new_node != NULL)
{
new_node->previous = NULL;
if(l->head == NULL) /* list is empty */
{
new_node->next = NULL;
l->tail = new_node;
}
else
{
new_node->next = l->head;
l->head->previous = new_node;
}
l->head = new_node;
l->length = l->length +1;
}
}
list_node* pop_list_node(list* l)
{
list_node* return_node = NULL;
if(l != NULL)
{
if(l->tail != NULL)
{
return_node = l->tail;
l->tail = l->tail->previous;
if(l->tail != NULL) { l->tail->next = NULL; }
l->head = l->head == return_node ? NULL : l->head;
l->length = l->length -1;
return_node->previous = NULL;
return_node->next = NULL;
}
}
return return_node;
}
void push_list_node(list* l, list_node* new_node)
{
if(l != NULL && new_node != NULL)
{
new_node->next = NULL;
if(l->tail == NULL) /* list is empty */
{
new_node->previous = NULL;
l->head = new_node;
}
else
{
new_node->previous = l->tail;
l->tail->next = new_node;
}
l->tail = new_node;
l->length = l->length +1;
}
}
void* shift_list(list* l)
{
return free_list_node ( shift_list_node(l) );
}
void unshift_list(list* l, void* value)
{
list_node* new_node = create_list_node(value);
unshift_list_node(l, new_node);
}
void* pop_list(list* l)
{
return free_list_node ( pop_list_node(l) );
}
void push_list(list*l, void* value)
{
list_node* new_node = create_list_node(value);
push_list_node(l, new_node);
}
void remove_internal_list_node(list* l, list_node* internal)
{
/* note we assume internal is in l, otherwise everything gets FUBAR! */
if(l != NULL && internal != NULL)
{
list_node* next = internal->next;
list_node* previous = internal->previous;
if(previous == NULL) /* internal is head */
{
l->head = next;
if(l->head != NULL) { l->head->previous = NULL; }
}
if(next == NULL) /* internal is tail */
{
l->tail = previous;
if(l->tail != NULL) { l->tail->next = NULL; }
}
if(previous != NULL && next != NULL)
{
previous->next = next;
next->previous = previous;
}
internal->next = NULL;
internal->previous = NULL;
l->length = l->length - 1;
}
}
void** destroy_list(list* l, int destruction_type, unsigned long* num_values)
{
void** values = NULL;
unsigned long value_index = 0;
if(l != NULL)
{
if(destruction_type == DESTROY_MODE_RETURN_VALUES)
{
values = (void**)malloc((1+l->length)*sizeof(void*));
}
for(value_index=0; l->length > 0; value_index++)
{
void* value = shift_list(l);
if(destruction_type == DESTROY_MODE_RETURN_VALUES)
{
values[value_index] = value;
}
else if(destruction_type == DESTROY_MODE_FREE_VALUES)
{
free(value);
}
}
if(destruction_type == DESTROY_MODE_RETURN_VALUES)
{
values[value_index] = NULL;
}
free(l);
}
*num_values = value_index;
return values;
}
void* list_element_at(list* l, unsigned long index)
{
void* return_value = NULL;
if(l != NULL)
{
unsigned long current_index = index - (unsigned long)((l->length)/2) > 0 ? l->length -1 : 0;
list_node* current_node = current_index == 0 ? l->head : l->tail;
while(current_index != index && current_node != NULL)
{
current_node = current_index > index ? current_node->previous : current_node->next;
current_index = current_index > index ? current_index -1 : current_index + 1;
}
if(current_node != NULL)
{
return_value = current_node->value;
}
}
return return_value;
}
void** get_list_values(list* l, unsigned long* num_values) /* returns null terminated array */
{
void** values = NULL;
unsigned long value_index = 0;
if(l != NULL)
{
list_node* current_node = l->head;
values = (void**)malloc((1+l->length)*sizeof(void*));
for(value_index = 0; value_index < l->length; value_index++)
{
values[value_index] = current_node->value;
current_node = current_node->next;
}
values[value_index] = NULL;
}
*num_values = value_index;
return values;
}

View File

@ -0,0 +1,350 @@
/*
* Copyright © 2008 by Eric Bishop <eric@gargoyle-router.com>
*
* This work as-is we provide.
* No warranty, express or implied.
* Weve done our best,
* to debug and test.
* Liability for damages denied.
*
* Permission is granted hereby,
* to copy, share, and modify.
* Use as is fit,
* free or for profit.
* On this notice these rights rely.
*
*
*
* Note that unlike other portions of Gargoyle this code
* does not fall under the GPL, but the rather whimsical
* 'Poetic License' above.
*
* Basically, this library contains a bunch of utilities
* that I find useful. I'm sure other libraries exist
* that are just as good or better, but I like these tools
* because I personally wrote them, so I know their quirks.
* (i.e. I know where the bodies are buried). I want to
* make sure that I can re-use these utilities for whatever
* code I may want to write in the future be it
* proprietary or open-source, so I've put them under
* a very, very permissive license.
*
* If you find this code useful, use it. If not, don't.
* I really don't care.
*
*/
#include "erics_tools.h"
#define malloc safe_malloc
#define strdup safe_strdup
typedef struct id_map_node_struct
{
list* id_list;
list_node* id_node;
} id_map_node;
priority_queue* initialize_priority_queue(void)
{
priority_queue* pq = (priority_queue*)malloc(sizeof(priority_queue));
pq->priorities = initialize_long_map();
pq->ids = initialize_string_map(0);
pq->first = NULL;
pq->length = 0;
return pq;
}
priority_queue_node* create_priority_node(unsigned long priority, char* id, void* value)
{
priority_queue_node* pn = (priority_queue_node*)malloc(sizeof(priority_queue_node));
pn->priority = priority;
pn->id = strdup(id);
pn->value = value;
return pn;
}
void* free_priority_queue_node(priority_queue_node* pn)
{
void *return_value = NULL;
if(pn != NULL)
{
return_value = pn->value;
free(pn->id);
free(pn);
}
return return_value;
}
void push_priority_queue(priority_queue* pq, unsigned long priority, char* id, void * value)
{
if(pq != NULL && id != NULL)
{
priority_queue_node* pn = create_priority_node(priority, id, value);
push_priority_queue_node(pq, pn);
}
}
/* note id is ALWAYS dynamically allocated, need to free */
void* shift_priority_queue(priority_queue* pq, unsigned long* priority, char** id)
{
void* return_value = NULL;
priority_queue_node* pn = shift_priority_queue_node(pq);
if(pn != NULL)
{
*priority = pn->priority;
*id = strdup(pn->id);
return_value = free_priority_queue_node(pn);
}
return return_value;
}
/* last param specified whether to dynamicall allocate id (otherwise pointer to id in priority_queue_node) */
void* peek_priority_queue(priority_queue* pq, unsigned long* priority, char** id, int dynamic_alloc_id)
{
void* return_value = NULL;
*priority = 0;
*id = NULL;
if(pq != NULL)
{
if(pq->first != NULL)
{
return_value = pq->first->value;
*priority = pq->first->priority;
if(dynamic_alloc_id)
{
*id = strdup(pq->first->id);
}
else
{
*id = pq->first->id;
}
}
}
return return_value;
}
void* get_priority_queue_element_with_id(priority_queue* pq, char* id, long* priority)
{
void* return_value = NULL;
priority_queue_node* pn = get_priority_queue_node_with_id(pq, id);
if(pn != NULL)
{
*priority = pn->priority;
return_value = free_priority_queue_node(pn);
}
else
{
*priority = 0;
}
return return_value;
}
void* remove_priority_queue_element_with_id(priority_queue* pq, char* id, long* priority)
{
void* return_value = NULL;
priority_queue_node* pn = remove_priority_queue_node_with_id(pq, id);
if(pn != NULL)
{
*priority = pn->priority;
return_value = free_priority_queue_node(pn);
}
else
{
*priority = 0;
}
return return_value;
}
void push_priority_queue_node(priority_queue* pq, priority_queue_node* pn)
{
if(pq != NULL && pn != NULL)
{
/* assume that most of the time we won't have collisions */
list_node* lpn = create_list_node(pn);
list* new_list = initialize_list();
list* old_list;
id_map_node* idn;
push_list_node(new_list, lpn);
old_list = (list*)set_long_map_element(pq->priorities, pn->priority, new_list);
if(old_list != NULL)
{
push_list_node(old_list, lpn);
set_long_map_element(pq->priorities, pn->priority, old_list);
free(new_list);
new_list = old_list;
}
/* update first */
if(pq->first == NULL)
{
pq->first = pn;
}
else if(pn->priority < pq->first->priority)
{
pq->first = pn;
}
/* save id */
idn = (id_map_node*)malloc(sizeof(id_map_node));
idn->id_list = new_list;
idn->id_node = lpn;
set_string_map_element(pq->ids, pn->id, idn);
pq->length = pq->length + 1;;
}
}
priority_queue_node* shift_priority_queue_node(priority_queue* pq)
{
priority_queue_node* return_node = NULL;
if(pq != NULL)
{
if(pq->first != NULL)
{
list* next_list = (list*)remove_long_map_element(pq->priorities, pq->first->priority);
list* next_first_list = NULL;
list_node* smallest_list_node = shift_list_node(next_list);
id_map_node* idn;
if(next_list->length == 0)
{
unsigned long tmp;
destroy_list(next_list, DESTROY_MODE_IGNORE_VALUES, &tmp);
next_first_list = (list*)get_smallest_long_map_element(pq->priorities, &tmp);
}
else
{
set_long_map_element(pq->priorities, pq->first->priority, next_list);
next_first_list = next_list;
}
return_node = free_list_node(smallest_list_node);
idn = (id_map_node*)remove_string_map_element(pq->ids, return_node->id);
free(idn);
if(next_first_list != NULL)
{
list_node* next_first_node = shift_list_node(next_first_list);
pq->first = (priority_queue_node*)next_first_node->value;
unshift_list_node(next_first_list, next_first_node);
}
else
{
pq->first = NULL;
}
pq->length = pq->length -1;
}
}
return return_node;
}
priority_queue_node* get_priority_queue_node_with_id(priority_queue* pq, char* id)
{
priority_queue_node* return_node = NULL;
if(pq != NULL && id != NULL)
{
id_map_node* idn = (id_map_node*)get_string_map_element(pq->ids, id);
if(idn != NULL)
{
return_node = (priority_queue_node*)idn->id_node->value;
}
}
return return_node;
}
priority_queue_node* remove_priority_queue_node_with_id(priority_queue* pq, char* id)
{
priority_queue_node* return_node = NULL;
if(pq != NULL && id != NULL)
{
id_map_node* idn = (id_map_node*)remove_string_map_element(pq->ids, id);
if(idn != NULL)
{
/* remove relevant node from list */
remove_internal_list_node(idn->id_list, idn->id_node);
return_node = free_list_node(idn->id_node);
/* if list is empty remove it from priority map */
if(idn->id_list->length == 0)
{
unsigned long tmp;
remove_long_map_element(pq->priorities, return_node->priority);
destroy_list(idn->id_list, DESTROY_MODE_IGNORE_VALUES, &tmp);
}
free(idn);
/* if we're removing first node, reset it */
if(return_node == pq->first)
{
unsigned long tmp;
list* next_first_list = (list*)get_smallest_long_map_element(pq->priorities, &tmp);
if(next_first_list != NULL)
{
list_node* next_first_node = shift_list_node(next_first_list);
pq->first = (priority_queue_node*)next_first_node->value;
unshift_list_node(next_first_list, next_first_node);
}
else
{
pq->first = NULL;
}
}
pq->length = pq->length -1;
}
}
return return_node;
}
void set_priority_for_id_in_priority_queue(priority_queue* pq, char* id, unsigned long priority)
{
if(pq != NULL && id != NULL)
{
priority_queue_node* id_pq_node = remove_priority_queue_node_with_id(pq, id);
id_pq_node->priority = priority;
push_priority_queue_node(pq, id_pq_node);
}
}
priority_queue_node* peek_priority_queue_node(priority_queue* pq)
{
return pq->first;
}
void** destroy_priority_queue(priority_queue* pq, int destroy_mode, unsigned long* num_elements_destroyed)
{
void** values = NULL;
unsigned long tmp;
*num_elements_destroyed = 0;
if(pq != NULL)
{
if(destroy_mode == DESTROY_MODE_RETURN_VALUES)
{
values = (void**)malloc((pq->length+1)*sizeof(void*));
}
while(pq->length > 0)
{
priority_queue_node* pqn = shift_priority_queue_node(pq);
void* next_value = free_priority_queue_node(pqn);
if(destroy_mode == DESTROY_MODE_RETURN_VALUES)
{
values[*num_elements_destroyed] = next_value;
}
else if(destroy_mode == DESTROY_MODE_FREE_VALUES)
{
free(next_value);
}
*num_elements_destroyed = *num_elements_destroyed + 1;
}
if(destroy_mode == DESTROY_MODE_RETURN_VALUES)
{
values[*num_elements_destroyed] = NULL;
}
destroy_long_map(pq->priorities, DESTROY_MODE_FREE_VALUES, &tmp);
destroy_string_map(pq->ids, DESTROY_MODE_FREE_VALUES, &tmp);
free(pq);
}
return values;
}

View File

@ -0,0 +1,80 @@
/*
* Copyright © 2008 by Eric Bishop <eric@gargoyle-router.com>
*
* This work as-is we provide.
* No warranty, express or implied.
* Weve done our best,
* to debug and test.
* Liability for damages denied.
*
* Permission is granted hereby,
* to copy, share, and modify.
* Use as is fit,
* free or for profit.
* On this notice these rights rely.
*
*
*
* Note that unlike other portions of Gargoyle this code
* does not fall under the GPL, but the rather whimsical
* 'Poetic License' above.
*
* Basically, this library contains a bunch of utilities
* that I find useful. I'm sure other libraries exist
* that are just as good or better, but I like these tools
* because I personally wrote them, so I know their quirks.
* (i.e. I know where the bodies are buried). I want to
* make sure that I can re-use these utilities for whatever
* code I may want to write in the future be it
* proprietary or open-source, so I've put them under
* a very, very permissive license.
*
* If you find this code useful, use it. If not, don't.
* I really don't care.
*
*/
#include "erics_tools.h"
void *safe_malloc(size_t size)
{
void* val = malloc(size);
if(val == NULL)
{
fprintf(stderr, "ERROR: MALLOC FAILURE!\n");
exit(1);
}
return val;
}
char* safe_strdup(const char* str)
{
char* new_str = NULL;
if(str != NULL)
{
new_str = strdup(str);
if(new_str == NULL)
{
fprintf(stderr, "ERROR: MALLOC FAILURE!\n");
exit(1);
}
}
return new_str;
}
void free_if_not_null(void* p)
{
if(p != NULL)
{
free(p);
}
}
void free_and_set_null(void** p)
{
if(*p != NULL)
{
free(*p);
*p = NULL;
}
}

View File

@ -0,0 +1,669 @@
/*
* Copyright © 2008 by Eric Bishop <eric@gargoyle-router.com>
*
* This work as-is we provide.
* No warranty, express or implied.
* Weve done our best,
* to debug and test.
* Liability for damages denied.
*
* Permission is granted hereby,
* to copy, share, and modify.
* Use as is fit,
* free or for profit.
* On this notice these rights rely.
*
*
*
* Note that unlike other portions of Gargoyle this code
* does not fall under the GPL, but the rather whimsical
* 'Poetic License' above.
*
* Basically, this library contains a bunch of utilities
* that I find useful. I'm sure other libraries exist
* that are just as good or better, but I like these tools
* because I personally wrote them, so I know their quirks.
* (i.e. I know where the bodies are buried). I want to
* make sure that I can re-use these utilities for whatever
* code I may want to write in the future be it
* proprietary or open-source, so I've put them under
* a very, very permissive license.
*
* If you find this code useful, use it. If not, don't.
* I really don't care.
*
*/
#include "erics_tools.h"
#define malloc safe_malloc
#define strdup safe_strdup
char* replace_prefix(char* original, char* old_prefix, char* new_prefix)
{
char* replaced = NULL;
if(original != NULL && old_prefix != NULL && new_prefix != NULL && strstr(original, old_prefix) == original)
{
int old_prefix_length = strlen(old_prefix);
int new_prefix_length = strlen(new_prefix);
int remainder_length = strlen(original) - old_prefix_length;
int new_length = new_prefix_length + remainder_length;
/* printf("%d %d %d %d\n", old_prefix_length, new_prefix_length, remainder_length, new_length); */
replaced = malloc(new_length+1);
memcpy(replaced, new_prefix, new_prefix_length);
memcpy(replaced+new_prefix_length, original+old_prefix_length, remainder_length);
replaced[new_prefix_length+remainder_length] = '\0';
}
return replaced;
}
char* trim_flanking_whitespace(char* str)
{
int new_start = 0;
int new_length = 0;
char whitespace[5] = { ' ', '\t', '\n', '\r', '\0' };
int num_whitespace_chars = 4;
int index = 0;
int is_whitespace = 1;
int test;
while( (test = str[index]) != '\0' && is_whitespace == 1)
{
int whitespace_index;
is_whitespace = 0;
for(whitespace_index = 0; whitespace_index < num_whitespace_chars && is_whitespace == 0; whitespace_index++)
{
is_whitespace = test == whitespace[whitespace_index] ? 1 : 0;
}
index = is_whitespace == 1 ? index+1 : index;
}
new_start = index;
index = strlen(str) - 1;
is_whitespace = 1;
while( index >= new_start && is_whitespace == 1)
{
int whitespace_index;
is_whitespace = 0;
for(whitespace_index = 0; whitespace_index < num_whitespace_chars && is_whitespace == 0; whitespace_index++)
{
is_whitespace = str[index] == whitespace[whitespace_index] ? 1 : 0;
}
index = is_whitespace == 1 ? index-1 : index;
}
new_length = str[new_start] == '\0' ? 0 : index + 1 - new_start;
if(new_start > 0)
{
for(index = 0; index < new_length; index++)
{
str[index] = str[index+new_start];
}
}
str[new_length] = 0;
return str;
}
int safe_strcmp(const char* str1, const char* str2)
{
if(str1 == NULL && str2 == NULL)
{
return 0;
}
else if(str1 == NULL && str2 != NULL)
{
return 1;
}
else if(str1 != NULL && str2 == NULL)
{
return -1;
}
return strcmp(str1, str2);
}
void to_lowercase(char* str)
{
int i;
for(i = 0; str[i] != '\0'; i++)
{
str[i] = tolower(str[i]);
}
}
void to_uppercase(char* str)
{
int i;
for(i = 0; str[i] != '\0'; i++)
{
str[i] = toupper(str[i]);
}
}
/* returns number freed */
int free_null_terminated_string_array(char** strs)
{
unsigned long str_index = 0;
if(strs != NULL)
{
for(str_index=0; strs[str_index] != NULL; str_index++)
{
free(strs[str_index]);
}
free(strs);
}
return str_index;
}
char** copy_null_terminated_string_array(char** original)
{
unsigned long size;
char** new;
for(size=0; original[size] != NULL ; size++) ;
new = (char**)malloc( (size+1)*sizeof(char*));
for(size=0; original[size] != NULL; size++)
{
new[size] = strdup(original[size]);
}
new[size] = NULL;
return new;
}
char* dynamic_strcat(int num_strs, ...)
{
va_list strs;
int new_length = 0;
int i;
int next_start;
char* new_str;
va_start(strs, num_strs);
for(i=0; i < num_strs; i++)
{
char* next_arg = va_arg(strs, char*);
if(next_arg != NULL)
{
new_length = new_length + strlen(next_arg);
}
}
va_end(strs);
new_str = malloc((1+new_length)*sizeof(char));
va_start(strs, num_strs);
next_start = 0;
for(i=0; i < num_strs; i++)
{
char* next_arg = va_arg(strs, char*);
if(next_arg != NULL)
{
int next_length = strlen(next_arg);
memcpy(new_str+next_start,next_arg, next_length);
next_start = next_start+next_length;
}
}
new_str[next_start] = '\0';
return new_str;
}
char* dcat_and_free(char** one, char** two, int free1, int free2)
{
char* s = NULL;
if(one != NULL && two != NULL) { s = dynamic_strcat(2, *one, *two); }
else if(one != NULL) { s = strdup(*one); }
else if(two != NULL) { s = strdup(*two); }
else { s= strdup(""); }
if(free1){ free(*one); *one=s; }
if(free2){ free(*two); *two=s; }
return s;
}
/*
* line is the line to be parsed -- it is not modified in any way
* max_pieces indicates number of pieces to return, if negative this is determined dynamically
* include_remainder_at_max indicates whether the last piece, when max pieces are reached,
* should be what it would normally be (0) or the entire remainder of the line (1)
* if max_pieces < 0 this parameter is ignored
*
*
* returns all non-separator pieces in a line
* result is dynamically allocated, MUST be freed after call-- even if
* line is empty (you still get a valid char** pointer to to a NULL char*)
*/
char** split_on_separators(char* line, char* separators, int num_separators, int max_pieces, int include_remainder_at_max, unsigned long *num_pieces)
{
char** split;
*num_pieces = 0;
if(line != NULL)
{
int split_index;
int non_separator_found;
char* dup_line;
char* start;
if(max_pieces < 0)
{
/* count number of separator characters in line -- this count + 1 is an upperbound on number of pieces */
int separator_count = 0;
int line_index;
for(line_index = 0; line[line_index] != '\0'; line_index++)
{
int sep_index;
int found = 0;
for(sep_index =0; found == 0 && sep_index < num_separators; sep_index++)
{
found = separators[sep_index] == line[line_index] ? 1 : 0;
}
separator_count = separator_count+ found;
}
max_pieces = separator_count + 1;
}
split = (char**)malloc((1+max_pieces)*sizeof(char*));
split_index = 0;
split[split_index] = NULL;
dup_line = strdup(line);
start = dup_line;
non_separator_found = 0;
while(non_separator_found == 0)
{
int matches = 0;
int sep_index;
for(sep_index =0; sep_index < num_separators; sep_index++)
{
matches = matches == 1 || separators[sep_index] == start[0] ? 1 : 0;
}
non_separator_found = matches==0 || start[0] == '\0' ? 1 : 0;
if(non_separator_found == 0)
{
start++;
}
}
while(start[0] != '\0' && split_index < max_pieces)
{
/* find first separator index */
int first_separator_index = 0;
int separator_found = 0;
while( separator_found == 0 )
{
int sep_index;
for(sep_index =0; separator_found == 0 && sep_index < num_separators; sep_index++)
{
separator_found = separators[sep_index] == start[first_separator_index] || start[first_separator_index] == '\0' ? 1 : 0;
}
if(separator_found == 0)
{
first_separator_index++;
}
}
/* copy next piece to split array */
if(first_separator_index > 0)
{
char* next_piece = NULL;
if(split_index +1 < max_pieces || include_remainder_at_max <= 0)
{
next_piece = (char*)malloc((first_separator_index+1)*sizeof(char));
memcpy(next_piece, start, first_separator_index);
next_piece[first_separator_index] = '\0';
}
else
{
next_piece = strdup(start);
}
split[split_index] = next_piece;
split[split_index+1] = NULL;
split_index++;
}
/* find next non-separator index, indicating start of next piece */
start = start+ first_separator_index;
non_separator_found = 0;
while(non_separator_found == 0)
{
int matches = 0;
int sep_index;
for(sep_index =0; sep_index < num_separators; sep_index++)
{
matches = matches == 1 || separators[sep_index] == start[0] ? 1 : 0;
}
non_separator_found = matches==0 || start[0] == '\0' ? 1 : 0;
if(non_separator_found == 0)
{
start++;
}
}
}
free(dup_line);
*num_pieces = split_index;
}
else
{
split = (char**)malloc((1)*sizeof(char*));
split[0] = NULL;
}
return split;
}
char* join_strs(char* separator, char** parts, int max_parts, int free_parts, int free_parts_array)
{
char* joined = NULL;
int num_parts = 0;
for(num_parts=0; parts[num_parts] != NULL && (max_parts < 0 || num_parts < max_parts); num_parts++){}
if(num_parts > 0)
{
num_parts--;
joined = strdup(parts[num_parts]);
if(free_parts){ free(parts[num_parts]); }
num_parts--;
while(num_parts >= 0)
{
char* tmp = joined;
joined = dynamic_strcat(3, parts[num_parts], separator, joined);
free(tmp);
if(free_parts){ free(parts[num_parts]); }
num_parts--;
}
}
if(free_parts_array)
{
free(parts);
}
return joined;
}
char* dynamic_replace(char* template_str, char* old, char* new)
{
char *ret;
int i, count = 0;
int newlen = strlen(new);
int oldlen = strlen(old);
char* dyn_template = strdup(template_str);
char* s = dyn_template;
for (i = 0; s[i] != '\0'; i++)
{
if (strstr(&s[i], old) == &s[i])
{
count++;
i += oldlen - 1;
}
}
ret = malloc(i + 1 + count * (newlen - oldlen));
i = 0;
while (*s)
{
if (strstr(s, old) == s)
{
strcpy(&ret[i], new);
i += newlen;
s += oldlen;
}
else
{
ret[i++] = *s++;
}
}
ret[i] = '\0';
free(dyn_template);
return ret;
}
/*
requires expression to be surrounded by '/' characters, and deals with escape
characters '\/', '\r', '\n', and '\t' when escapes haven't been interpreted
(e.g. after recieving regex string from user)
returns 1 on good regex, 0 on bad regex
*/
int convert_to_regex(char* str, regex_t* p)
{
char* trimmed = trim_flanking_whitespace(strdup(str));
int trimmed_length = strlen(trimmed);
char* new = NULL;
int valid = 1;
/* regex must be defined by surrounding '/' characters */
if(trimmed[0] != '/' || trimmed[trimmed_length-1] != '/')
{
valid = 0;
free(trimmed);
}
if(valid == 1)
{
char* internal = (char*)malloc(trimmed_length*sizeof(char));
int internal_length = trimmed_length-2;
int new_index = 0;
int internal_index = 0;
char previous = '\0';
memcpy(internal, trimmed+1, internal_length);
internal[internal_length] = '\0';
free(trimmed);
new = (char*)malloc(trimmed_length*sizeof(char));
while(internal[internal_index] != '\0' && valid == 1)
{
char next = internal[internal_index];
if(next == '/' && previous != '\\')
{
valid = 0;
}
else if((next == 'n' || next == 'r' || next == 't' || next == '/') && previous == '\\')
{
char previous2 = '\0';
if(internal_index >= 2)
{
previous2 = internal[internal_index-2];
}
new_index = previous2 == '\\' ? new_index : new_index-1;
switch(next)
{
case 'n':
new[new_index] = previous2 == '\\' ? next : '\n';
break;
case 'r':
new[new_index] = previous2 == '\\' ? next : '\r';
break;
case 't':
new[new_index] = previous2 == '\\' ? next : '\t';
break;
case '/':
new[new_index] = previous2 == '\\' ? next : '/';
break;
}
previous = '\0';
internal_index++;
new_index++;
}
else
{
new[new_index] = next;
previous = next;
internal_index++;
new_index++;
}
}
if(valid == 0 || previous == '\\')
{
valid = 0;
free(new);
new = NULL;
}
else
{
new[new_index] = '\0';
}
free(internal);
}
if(valid == 1)
{
valid = regcomp(p,new,REG_EXTENDED) == 0 ? 1 : 0;
if(valid == 0)
{
regfree(p);
}
free(new);
}
return valid;
}
/* note: str element in return value is dynamically allocated, need to free */
dyn_read_t dynamic_read(FILE* open_file, char* terminators, int num_terminators, unsigned long* read_length)
{
fpos_t start_pos;
unsigned long size_to_read = 0;
int terminator_found = 0;
int terminator;
char* str;
dyn_read_t ret_value;
fgetpos(open_file, &start_pos);
while(terminator_found == 0)
{
int nextch = fgetc(open_file);
int terminator_index = 0;
for(terminator_index = 0; terminator_index < num_terminators && terminator_found == 0; terminator_index++)
{
terminator_found = nextch == terminators[terminator_index] ? 1 : 0;
terminator = nextch;
}
terminator_found = nextch == EOF ? 1 : terminator_found;
terminator = nextch == EOF ? EOF : nextch;
if(terminator_found == 0)
{
size_to_read++;
}
}
str = (char*)malloc((size_to_read+1)*sizeof(char));
if(size_to_read > 0)
{
int i;
fsetpos(open_file, &start_pos);
for(i=0; i<size_to_read; i++)
{
str[i] = (char)fgetc(open_file);
}
fgetc(open_file); /* read the terminator */
}
str[size_to_read] = '\0';
*read_length = size_to_read;
ret_value.str = str;
ret_value.terminator = terminator;
return ret_value;
}
/* convenience method for calling dynamic_read with end-of-line separators */
int dyn_read_line(FILE* open_file, char** dest, unsigned long* read_len)
{
char line_seps[] = "\r\n";
dyn_read_t read = dynamic_read(open_file, line_seps, 2, read_len);
*dest = read.str;
return read.terminator;
}
unsigned char* read_entire_file(FILE* in, unsigned long read_block_size, unsigned long *length)
{
int max_read_size = read_block_size;
unsigned char* read_string = (unsigned char*)malloc(max_read_size+1);
unsigned long bytes_read = 0;
int end_found = 0;
while(end_found == 0)
{
int nextch = '?';
while(nextch != EOF && bytes_read < max_read_size)
{
nextch = fgetc(in);
if(nextch != EOF)
{
read_string[bytes_read] = (unsigned char)nextch;
bytes_read++;
}
}
read_string[bytes_read] = '\0';
end_found = (nextch == EOF) ? 1 : 0;
if(end_found == 0)
{
unsigned char *new_str;
max_read_size = max_read_size + read_block_size;
new_str = (unsigned char*)malloc(max_read_size+1);
memcpy(new_str, read_string, bytes_read);
free(read_string);
read_string = new_str;
}
}
*length = bytes_read;
return read_string;
}
char** get_shell_command_output_lines(char* command, unsigned long* num_lines)
{
char** ret = NULL;
if(command != NULL && num_lines != NULL)
{
FILE* shell_out = popen(command, "r");
*num_lines = 0;
if(shell_out != NULL)
{
char linebreaks[] = { '\n', '\r' };
unsigned long read_length;
char* all_data = (char*)read_entire_file(shell_out, 2048, &read_length);
ret = split_on_separators(all_data, linebreaks, 2, -1, 0, num_lines);
free(all_data);
pclose(shell_out);
}
}
return ret;
}
/* comparison functions for qsort */
int sort_string_cmp(const void *a, const void *b)
{
const char **a_ptr = (const char **)a;
const char **b_ptr = (const char **)b;
return strcmp(*a_ptr, *b_ptr);
}
int sort_string_icmp(const void *a, const void *b)
{
const char **a_ptr = (const char **)a;
const char **b_ptr = (const char **)b;
return stricmp(*a_ptr, *b_ptr);
}
/* wrappers for qsort calls */
void do_str_sort(char** string_arr, unsigned long string_arr_len)
{
qsort(string_arr, string_arr_len, sizeof(char*), sort_string_cmp);
}
void do_istr_sort(char** string_arr, unsigned long string_arr_len)
{
qsort(string_arr, string_arr_len, sizeof(char*), sort_string_icmp);
}

View File

@ -0,0 +1,85 @@
#include "erics_tools.h"
int main(void)
{
list* l = initialize_list();
list_node* c_node = create_list_node("c");
list_node* a_node = create_list_node("a");
list_node* z_node = create_list_node("z");
list_node* x_node = create_list_node("x");
push_list_node(l, a_node);
remove_internal_list_node(l, a_node);
push_list_node(l, a_node);
push_list(l, "b");
push_list_node(l, c_node);
unshift_list_node(l, z_node);
unshift_list(l, "y");
unshift_list_node(l, x_node);
remove_internal_list_node(l, z_node);
remove_internal_list_node(l, a_node);
remove_internal_list_node(l, x_node);
free_list_node(z_node);
free_list_node(a_node);
free_list_node(x_node);
while(l->length > 0)
{
printf("%s\n", (char*)pop_list(l));
}
unsigned long dl;
destroy_list(l, DESTROY_MODE_IGNORE_VALUES, &dl);
printf("-------queue test-------------\n");
unsigned long priority;
char* id;
priority_queue* pq = initialize_priority_queue();
push_priority_queue(pq, 30, "id_1", "value_1");
push_priority_queue(pq, 10, "id_2", "value_2");
push_priority_queue(pq, 10, "id_3", "value_3");
push_priority_queue(pq, 40, "id_4", "value_4");
push_priority_queue(pq, 5, "id_5", "value_5");
push_priority_queue(pq, 30, "id_6", "value_6");
push_priority_queue(pq, 30, "id_7", "value_7");
printf("queue length = %ld\n", pq->length);
unsigned long num_destroyed;
char* tmp = peek_priority_queue(pq, &priority, &id, 0);
printf("first is \"%s\"\n", tmp);
set_priority_for_id_in_priority_queue(pq, "id_5", 35);
tmp = peek_priority_queue(pq, &priority, &id, 0);
printf("first is \"%s\"\n", tmp);
set_priority_for_id_in_priority_queue(pq, "id_2", 36);
tmp = peek_priority_queue(pq, &priority, &id, 0);
printf("first is \"%s\"\n", tmp);
/*
char** values = (char**)destroy_priority_queue(pq, DESTROY_MODE_RETURN_VALUES, &num_destroyed);
int index = 0;
for(index = 0; values[index] != NULL; index++)
{
printf("%s\n", values[index]);
}
*/
while(pq->length > 0)
{
char* value = (char*)shift_priority_queue(pq, &priority, &id);
printf("%s\n", value);
free(id);
}
destroy_priority_queue(pq, DESTROY_MODE_FREE_VALUES, &dl);
return 0;
}

View File

@ -0,0 +1,322 @@
/*
* Copyright © 2008 by Eric Bishop <eric@gargoyle-router.com>
*
* This work 'as-is' we provide.
* No warranty, express or implied.
* We've done our best,
* to debug and test.
* Liability for damages denied.
*
* Permission is granted hereby,
* to copy, share, and modify.
* Use as is fit,
* free or for profit.
* On this notice these rights rely.
*
*
*
* Note that unlike other portions of Gargoyle this code
* does not fall under the GPL, but the rather whimsical
* 'Poetic License' above.
*
* Basically, this library contains a bunch of utilities
* that I find useful. I'm sure other libraries exist
* that are just as good or better, but I like these tools
* because I personally wrote them, so I know their quirks.
* (i.e. I know where the bodies are buried). I want to
* make sure that I can re-use these utilities for whatever
* code I may want to write in the future be it
* proprietary or open-source, so I've put them under
* a very, very permissive license.
*
* If you find this code useful, use it. If not, don't.
* I really don't care.
*
*/
#include "erics_tools.h"
#include <time.h>
void get_max_depth(long_map_node* n, unsigned long* max_depth, unsigned long current_depth);
void get_min_depth(long_map_node* n, unsigned long* min_depth, unsigned long current_depth);
void print_map(long_map_node* n, int depth);
int main (void)
{
unsigned long num_insertions = 2500;
unsigned long max_insertion = 5000;
int num_repeats = 3;
unsigned int seed = (unsigned int)time(NULL);
srand (seed);
printf("TESTING LONG MAP....\n\n");
printf("initializing map....\n");
long_map* lm = initialize_long_map();
printf("initialized!\n\n");
int repeat = 0;
for(repeat =0; repeat < num_repeats; repeat++)
{
printf("randomly inserting %ld integers randomly selected (not in order) between 0 and %ld\n", num_insertions, max_insertion);
unsigned long i;
unsigned long dupes = 0;
for(i=0; i<num_insertions; i++)
{
unsigned long r = (unsigned long)(max_insertion*((double)rand()/(double)RAND_MAX));
void* old;
if( (old = set_long_map_element(lm, r, "a")) != NULL)
{
dupes++;
}
}
unsigned long right_depth = 0;
unsigned long left_depth = 0;
get_max_depth(lm->root->left, &left_depth, 0);
get_max_depth(lm->root->right, &right_depth, 0);
unsigned long original_size = lm->num_elements;
printf("insertion complete\n");
printf("after insertion, tree size = %ld \n", original_size);
printf("(note this may be less than %ld because the same numbers can be selected for insertion more than once, replacing the original node)\n", num_insertions);
printf("dupes = %ld\n", dupes);
printf("depth of left branch = %ld, depth of right branch = %ld\n\n", left_depth, right_depth);
if(repeat+1 < num_repeats)
{
printf("randomly selecting %ld numbers between 0 and %ld to remove from tree.\n", num_insertions, max_insertion);
printf("note that these keys will not necessarily be present in tree -- the selection process is entirely random.\n");
int j;
int found = 0;
for(j=0; j < num_insertions; j++)
{
unsigned long r = (unsigned long)(max_insertion*((double)rand()/(double)RAND_MAX));
if( remove_long_map_element(lm, r) != NULL)
{
found++;
}
}
right_depth = 0;
left_depth = 0;
get_max_depth(lm->root->left, &left_depth, 0);
get_max_depth(lm->root->right, &right_depth, 0);
printf("removal complete\n");
printf("after removal, tree size = %ld \n", lm->num_elements);
printf("depth of left branch = %ld, depth of right branch = %ld\n", left_depth, right_depth);
if(original_size - found == lm->num_elements)
{
printf("size consistent with number of nodes successfully removed\n\n");
}
else
{
printf("SIZE IS BAD -- IS NOT CONSISTENT WITH NUMBER OF NODES REMOVED !!!!\n\n");
}
printf("removing remaining nodes in tree in random order\n");
unsigned long length;
unsigned long *keys = get_sorted_long_map_keys(lm, &length);
while(lm->root != NULL && lm->num_elements > 0)
{
unsigned long r = (unsigned long)(length*((double)rand()/(double)RAND_MAX));
if( remove_long_map_element(lm, keys[r]) != NULL)
{
found++;
if(original_size - found != lm->num_elements)
{
printf("SIZE IS BAD!!!!\n");
}
}
}
printf("done removing remaining nodes\n");
printf("tree size is now %ld, and root is %s\n\n", lm->num_elements, lm->root == NULL ? "null" : "not null");
free(keys);
printf("repeating insertion/deletion\n\n");
}
else
{
unsigned long num_destroyed;
printf("destroying map...\n");
void** values = destroy_long_map(lm, DESTROY_MODE_RETURN_VALUES, &num_destroyed);
printf("map destroyed.\n");
int v=0;
for(v=0; values[v] != NULL; v++){}
free(values);
printf("number of values returned after map destruction = %d\n", v);
}
}
printf("LONG MAP TESTING COMPLETE. TESTING STRING MAP (WITH KEY STORAGE) \n\n");
printf("initializing map....\n");
string_map* sm = initialize_string_map(1);
printf("initialized!\n\n");
repeat = 0;
for(repeat =0; repeat < num_repeats; repeat++)
{
printf("randomly inserting %ld integer strings randomly selected (not in order) between 0 and %ld\n", num_insertions, max_insertion);
unsigned long i;
unsigned long dupes = 0;
for(i=0; i<num_insertions; i++)
{
unsigned long r = (unsigned long)(max_insertion*((double)rand()/(double)RAND_MAX));
char* new_str = (char*)malloc(40*sizeof(char));
sprintf(new_str, "%ld", r);
void* old;
if( (old = set_string_map_element(sm, new_str, "a")) != NULL )
{
dupes++;
}
free(new_str);
}
unsigned long right_depth = 0;
unsigned long left_depth = 0;
get_max_depth(sm->lm.root->left, &left_depth, 0);
get_max_depth(sm->lm.root->right, &right_depth, 0);
unsigned long original_size = sm->num_elements;
printf("insertion complete\n");
printf("after insertion, tree size = %ld \n", original_size);
printf("(note this may be less than %ld because the same numbers can be selected for insertion more than once, replacing the original node)\n", num_insertions);
printf("dupes = %ld\n", dupes);
printf("depth of left branch = %ld, depth of right branch = %ld\n\n", left_depth, right_depth);
if(repeat+1 < num_repeats)
{
printf("randomly selecting %ld numbers between 0 and %ld to remove from tree.\n", num_insertions, max_insertion);
printf("note that these keys will not necessarily be present in tree -- the selection process is entirely random.\n");
int j;
int found = 0;
for(j=0; j < num_insertions; j++)
{
unsigned long r = (unsigned long)(max_insertion*((double)rand()/(double)RAND_MAX));
char* new_str = (char*)malloc(40*sizeof(char));
sprintf(new_str, "%ld", r);
void* old;
if( (old = remove_string_map_element(sm, new_str)) != NULL)
{
found++;
}
free(new_str);
}
right_depth = 0;
left_depth = 0;
get_max_depth(sm->lm.root->left, &left_depth, 0);
get_max_depth(sm->lm.root->right, &right_depth, 0);
printf("removal complete\n");
printf("after removal, tree size = %ld \n", sm->num_elements);
printf("depth of left branch = %ld, depth of right branch = %ld\n", left_depth, right_depth);
if(original_size - found == sm->num_elements)
{
printf("size consistent with number of nodes successfully removed\n\n");
}
else
{
printf("SIZE IS BAD -- IS NOT CONSISTENT WITH NUMBER OF NODES REMOVED !!!!\n\n");
}
printf("removing remaining nodes in tree in random order\n");
unsigned long length = sm->num_elements;
char** keys = get_string_map_keys(sm, &length);
while(sm->lm.root != NULL && sm->num_elements > 0)
{
unsigned long r = (unsigned long)(length*((double)rand()/(double)RAND_MAX));
if( remove_string_map_element(sm, keys[r]) != NULL)
{
found++;
if(original_size - found != sm->num_elements)
{
printf("SIZE IS BAD!!!!\n");
}
}
}
int k;
for(k=0; k<length; k++)
{
free(keys[k]);
}
free(keys);
printf("done removing remaining nodes\n");
printf("tree size is now %ld, and root is %s\n\n", sm->num_elements, sm->lm.root == NULL ? "null" : "not null");
printf("repeating insertion/deletion\n\n");
}
else
{
unsigned long num_destroyed;
printf("destroying map...\n");
void** values = destroy_string_map(sm, DESTROY_MODE_RETURN_VALUES, &num_destroyed);
printf("map destroyed.\n");
int v=0;
for(v=0; values[v] != NULL; v++){ }
free(values);
printf("number of values returned after map destruction = %d\n", v);
}
}
return(0);
}
void get_max_depth(long_map_node* n, unsigned long* max_depth, unsigned long current_depth)
{
if(n == NULL)
{
*max_depth = current_depth > *max_depth ? current_depth : *max_depth;
return;
}
else
{
*max_depth = current_depth+1 > *max_depth ? current_depth+1 : *max_depth;
get_max_depth(n->left, max_depth, current_depth+1);
get_max_depth(n->right, max_depth, current_depth+1);
}
}
void get_min_depth(long_map_node* n, unsigned long* min_depth, unsigned long current_depth)
{
if(n == NULL)
{
return;
}
else if(n->left == NULL && n->right == NULL)
{
*min_depth = current_depth < *min_depth ? current_depth : *min_depth;
}
else
{
get_min_depth(n->left, min_depth, current_depth+1);
get_min_depth(n->right, min_depth, current_depth+1);
}
}
void print_map(long_map_node* n, int depth)
{
if(n == NULL)
{
return;
}
int i;
for(i=0; i < depth; i++){ printf("\t");}
printf("%ld (%d)\n", n->key, n->balance);
for(i=0; i < depth; i++){ printf("\t");}
printf("left:\n");
print_map(n->left, depth+1);
for(i=0; i < depth; i++){ printf("\t");}
printf("right:\n");
print_map(n->right, depth+1);
}

View File

@ -0,0 +1,25 @@
#include "erics_tools.h"
int main(void)
{
FILE* f = fopen("tmp", "r");
char terminators[] = "\n\r";
unsigned long length;
char* file_data = (char*)read_entire_file(f, 100, &length);
printf("%s\n", file_data);
fclose(f);
f = fopen("tmp", "r");
dyn_read_t next;
next.terminator = '\n';
while(next.terminator != EOF)
{
next = dynamic_read(f, terminators, 2, &length);
printf("read \"%s\"\n", next.str);
free(next.str);
}
fclose(f);
return 0;
}

View File

@ -0,0 +1,884 @@
/*
* Copyright © 2008 by Eric Bishop <eric@gargoyle-router.com>
*
* This work 'as-is' we provide.
* No warranty, express or implied.
* We've done our best,
* to debug and test.
* Liability for damages denied.
*
* Permission is granted hereby,
* to copy, share, and modify.
* Use as is fit,
* free or for profit.
* On this notice these rights rely.
*
*
*
* Note that unlike other portions of Gargoyle this code
* does not fall under the GPL, but the rather whimsical
* 'Poetic License' above.
*
* Basically, this library contains a bunch of utilities
* that I find useful. I'm sure other libraries exist
* that are just as good or better, but I like these tools
* because I personally wrote them, so I know their quirks.
* (i.e. I know where the bodies are buried). I want to
* make sure that I can re-use these utilities for whatever
* code I may want to write in the future be it
* proprietary or open-source, so I've put them under
* a very, very permissive license.
*
* If you find this code useful, use it. If not, don't.
* I really don't care.
*
*/
#include "erics_tools.h"
#define malloc safe_malloc
#define strdup safe_strdup
/* internal utility structures/ functions */
typedef struct stack_node_struct
{
long_map_node** node_ptr;
signed char direction;
struct stack_node_struct* previous;
} stack_node;
static void** destroy_long_map_values(long_map* map, int destruction_type, unsigned long* num_destroyed);
static void apply_to_every_long_map_node(long_map_node* node, void (*apply_func)(unsigned long key, void* value));
static void apply_to_every_string_map_node(long_map_node* node, unsigned char has_key, void (*apply_func)(char* key, void* value));
static void get_sorted_node_keys(long_map_node* node, unsigned long* key_list, unsigned long* next_key_index, int depth);
static void get_sorted_node_values(long_map_node* node, void** value_list, unsigned long* next_value_index, int depth);
static signed char rebalance (long_map_node** n, signed char direction, signed char update_op);
static void rotate_right (long_map_node** parent);
static void rotate_left (long_map_node** parent);
/* internal for string map */
typedef struct
{
char* key;
void* value;
} string_map_key_value;
static unsigned long sdbm_string_hash(const char *key);
/***************************************************
* For testing only
***************************************************/
/*
void print_list(stack_node *l);
void print_list(stack_node *l)
{
if(l != NULL)
{
printf(" list key = %ld, dir=%d, \n", (*(l->node_ptr))->key, l->direction);
print_list(l->previous);
}
}
*/
/******************************************************
* End testing Code
*******************************************************/
/***************************************************
* string_map function definitions
***************************************************/
string_map* initialize_string_map(unsigned char store_keys)
{
string_map* map = (string_map*)malloc(sizeof(string_map));
map->store_keys = store_keys;
map->lm.root = NULL;
map->lm.num_elements = 0;
map->num_elements = map->lm.num_elements;
return map;
}
void* get_string_map_element(string_map* map, const char* key)
{
unsigned long hashed_key = sdbm_string_hash(key);
void* return_value = get_long_map_element( &(map->lm), hashed_key);
if(return_value != NULL && map->store_keys)
{
string_map_key_value* r = (string_map_key_value*)return_value;
return_value = r->value;
}
map->num_elements = map->lm.num_elements;
return return_value;
}
void* set_string_map_element(string_map* map, const char* key, void* value)
{
unsigned long hashed_key = sdbm_string_hash(key);
void* return_value = NULL;
if(map->store_keys)
{
string_map_key_value* kv = (string_map_key_value*)malloc(sizeof(string_map_key_value));
kv->key = strdup(key);
kv->value = value;
return_value = set_long_map_element( &(map->lm), hashed_key, kv);
if(return_value != NULL)
{
string_map_key_value* r = (string_map_key_value*)return_value;
return_value = r->value;
free(r->key);
free(r);
}
}
else
{
return_value = set_long_map_element( &(map->lm), hashed_key, value);
}
map->num_elements = map->lm.num_elements;
return return_value;
}
void* remove_string_map_element(string_map* map, const char* key)
{
unsigned long hashed_key = sdbm_string_hash(key);
void* return_value = remove_long_map_element( &(map->lm), hashed_key);
if(return_value != NULL && map->store_keys)
{
string_map_key_value* r = (string_map_key_value*)return_value;
return_value = r->value;
free(r->key);
free(r);
}
map->num_elements = map->lm.num_elements;
return return_value;
}
char** get_string_map_keys(string_map* map, unsigned long* num_keys_returned)
{
char** str_keys;
str_keys = (char**)malloc((map->num_elements+1)*sizeof(char*));
str_keys[0] = NULL;
*num_keys_returned = 0;
if(map->store_keys && map->num_elements > 0)
{
unsigned long list_length;
void** long_values = get_sorted_long_map_values( &(map->lm), &list_length);
unsigned long key_index;
for(key_index = 0; key_index < list_length; key_index++)
{
str_keys[key_index] = strdup( ((string_map_key_value*)(long_values[key_index]))->key);
*num_keys_returned = *num_keys_returned + 1;
}
str_keys[list_length] = NULL;
free(long_values);
}
return str_keys;
}
void** get_string_map_values(string_map* map, unsigned long* num_values_returned)
{
void** values = NULL;
if(map != NULL)
{
values = get_sorted_long_map_values ( &(map->lm), num_values_returned );
}
return values;
}
void** destroy_string_map(string_map* map, int destruction_type, unsigned long* num_destroyed)
{
void** return_values = NULL;
if(map != NULL)
{
if(map->store_keys)
{
void** kvs = destroy_long_map_values( &(map->lm), DESTROY_MODE_RETURN_VALUES, num_destroyed );
unsigned long kv_index = 0;
for(kv_index=0; kv_index < *num_destroyed; kv_index++)
{
string_map_key_value* kv = (string_map_key_value*)kvs[kv_index];
void* value = kv->value;
free(kv->key);
free(kv);
if(destruction_type == DESTROY_MODE_FREE_VALUES)
{
free(value);
}
if(destruction_type == DESTROY_MODE_RETURN_VALUES)
{
kvs[kv_index] = value;
}
}
if(destruction_type == DESTROY_MODE_RETURN_VALUES)
{
return_values = kvs;
}
else
{
free(kvs);
}
}
else
{
return_values = destroy_long_map_values( &(map->lm), destruction_type, num_destroyed );
}
free(map);
}
return return_values;
}
/***************************************************
* long_map function definitions
***************************************************/
long_map* initialize_long_map(void)
{
long_map* map = (long_map*)malloc(sizeof(long_map));
map->root = NULL;
map->num_elements = 0;
return map;
}
void* get_long_map_element(long_map* map, unsigned long key)
{
void* value = NULL;
if(map->root != NULL)
{
long_map_node* parent_node = map->root;
long_map_node* next_node;
while( key != parent_node->key && (next_node = (long_map_node *)(key < parent_node->key ? parent_node->left : parent_node->right)) != NULL)
{
parent_node = next_node;
}
if(parent_node->key == key)
{
value = parent_node->value;
}
}
return value;
}
void* get_smallest_long_map_element(long_map* map, unsigned long* smallest_key)
{
void* value = NULL;
if(map->root != NULL)
{
long_map_node* next_node = map->root;
while( next_node->left != NULL)
{
next_node = next_node->left;
}
value = next_node->value;
*smallest_key = next_node->key;
}
return value;
}
void* get_largest_long_map_element(long_map* map, unsigned long* largest_key)
{
void* value = NULL;
if(map->root != NULL)
{
long_map_node* next_node = map->root;
while( next_node->right != NULL)
{
next_node = next_node->right;
}
value = next_node->value;
*largest_key = next_node->key;
}
return value;
}
void* remove_smallest_long_map_element(long_map* map, unsigned long* smallest_key)
{
get_smallest_long_map_element(map, smallest_key);
return remove_long_map_element(map, *smallest_key);
}
void* remove_largest_long_map_element(long_map* map, unsigned long* largest_key)
{
get_largest_long_map_element(map, largest_key);
return remove_long_map_element(map, *largest_key);
}
/* if replacement performed, returns replaced value, otherwise null */
void* set_long_map_element(long_map* map, unsigned long key, void* value)
{
stack_node* parent_list = NULL;
void* old_value = NULL;
int old_value_found = 0;
long_map_node* parent_node;
long_map_node* next_node;
stack_node* next_parent;
stack_node* previous_parent;
signed char new_balance;
long_map_node* new_node = (long_map_node*)malloc(sizeof(long_map_node));
new_node->value = value;
new_node->key = key;
new_node->left = NULL;
new_node->right = NULL;
new_node->balance = 0;
if(map->root == NULL)
{
map->root = new_node;
}
else
{
parent_node = map->root;
next_parent = (stack_node*)malloc(sizeof(stack_node));
next_parent->node_ptr = &(map->root);
next_parent->previous = parent_list;
parent_list = next_parent;
while( key != parent_node->key && (next_node = (key < parent_node->key ? parent_node->left : parent_node->right) ) != NULL)
{
next_parent = (stack_node*)malloc(sizeof(stack_node));
next_parent->node_ptr = key < parent_node->key ? &(parent_node->left) : &(parent_node->right);
next_parent->previous = parent_list;
next_parent->previous->direction = key < parent_node->key ? -1 : 1;
parent_list = next_parent;
parent_node = next_node;
}
if(key == parent_node->key)
{
old_value = parent_node->value;
old_value_found = 1;
parent_node->value = value;
free(new_node);
/* we merely replaced a node, no need to rebalance */
}
else
{
if(key < parent_node->key)
{
parent_node->left = (void*)new_node;
parent_list->direction = -1;
}
else
{
parent_node->right = (void*)new_node;
parent_list->direction = 1;
}
/* we inserted a node, rebalance */
previous_parent = parent_list;
new_balance = 1; /* initial value is not used, but must not be 0 for initial loop condition */
while(previous_parent != NULL && new_balance != 0)
{
new_balance = rebalance(previous_parent->node_ptr, previous_parent->direction, 1);
previous_parent = previous_parent->previous;
}
}
}
while(parent_list != NULL)
{
previous_parent = parent_list;
parent_list = previous_parent->previous;
free(previous_parent);
}
if(old_value_found == 0)
{
map->num_elements = map->num_elements + 1;
}
return old_value;
}
void* remove_long_map_element(long_map* map, unsigned long key)
{
void* value = NULL;
long_map_node* root_node = map->root;
stack_node* parent_list = NULL;
long_map_node* remove_parent;
long_map_node* remove_node;
long_map_node* next_node;
long_map_node* replacement;
long_map_node* replacement_parent;
long_map_node* replacement_next;
stack_node* next_parent;
stack_node* previous_parent;
stack_node* replacement_stack_node;
signed char new_balance;
if(root_node != NULL)
{
remove_parent = root_node;
remove_node = key < remove_parent->key ? remove_parent->left : remove_parent->right;
if(remove_node != NULL && key != remove_parent->key)
{
next_parent = (stack_node*)malloc(sizeof(stack_node));
next_parent->node_ptr = &(map->root);
next_parent->previous = parent_list;
parent_list = next_parent;
while( key != remove_node->key && (next_node = (key < remove_node->key ? remove_node->left : remove_node->right)) != NULL)
{
next_parent = (stack_node*)malloc(sizeof(stack_node));
next_parent->node_ptr = key < remove_parent->key ? &(remove_parent->left) : &(remove_parent->right);
next_parent->previous = parent_list;
next_parent->previous->direction = key < remove_parent->key ? -1 : 1;
parent_list = next_parent;
remove_parent = remove_node;
remove_node = next_node;
}
parent_list->direction = key < remove_parent-> key ? -1 : 1;
}
else
{
remove_node = remove_parent;
}
if(key == remove_node->key)
{
/* find replacement for node we are deleting */
if( remove_node->right == NULL )
{
replacement = remove_node->left;
}
else if( remove_node->right->left == NULL)
{
replacement = remove_node->right;
replacement->left = remove_node->left;
replacement->balance = remove_node->balance;
/* put pointer to replacement node into list for balance update */
replacement_stack_node = (stack_node*)malloc(sizeof(stack_node));;
replacement_stack_node->previous = parent_list;
replacement_stack_node->direction = 1; /* replacement is from right */
if(remove_node == remove_parent) /* special case for root node */
{
replacement_stack_node->node_ptr = &(map->root);
}
else
{
replacement_stack_node->node_ptr = key < remove_parent-> key ? &(remove_parent->left) : &(remove_parent->right);
}
parent_list = replacement_stack_node;
}
else
{
/* put pointer to replacement node into list for balance update */
replacement_stack_node = (stack_node*)malloc(sizeof(stack_node));
replacement_stack_node->previous = parent_list;
replacement_stack_node->direction = 1; /* we always look for replacement on right */
if(remove_node == remove_parent) /* special case for root node */
{
replacement_stack_node->node_ptr = &(map->root);
}
else
{
replacement_stack_node->node_ptr = key < remove_parent-> key ? &(remove_parent->left) : &(remove_parent->right);
}
parent_list = replacement_stack_node;
/*
* put pointer to replacement node->right into list for balance update
* this node will have to be updated with the proper pointer
* after we have identified the replacement
*/
replacement_stack_node = (stack_node*)malloc(sizeof(stack_node));
replacement_stack_node->previous = parent_list;
replacement_stack_node->direction = -1; /* we always look for replacement to left of this node */
parent_list = replacement_stack_node;
/* find smallest node on right (large) side of tree */
replacement_parent = remove_node->right;
replacement = replacement_parent->left;
while((replacement_next = replacement->left) != NULL)
{
next_parent = (stack_node*)malloc(sizeof(stack_node));
next_parent->node_ptr = &(replacement_parent->left);
next_parent->previous = parent_list;
next_parent->direction = -1; /* we always go left */
parent_list = next_parent;
replacement_parent = replacement;
replacement = replacement_next;
}
replacement_parent->left = replacement->right;
replacement->left = remove_node->left;
replacement->right = remove_node->right;
replacement->balance = remove_node->balance;
replacement_stack_node->node_ptr = &(replacement->right);
}
/* insert replacement at proper location in tree */
if(remove_node == remove_parent)
{
map->root = replacement;
}
else
{
remove_parent->left = remove_node == remove_parent->left ? replacement : remove_parent->left;
remove_parent->right = remove_node == remove_parent->right ? replacement : remove_parent->right;
}
/* rebalance tree */
previous_parent = parent_list;
new_balance = 0;
while(previous_parent != NULL && new_balance == 0)
{
new_balance = rebalance(previous_parent->node_ptr, previous_parent->direction, -1);
previous_parent = previous_parent->previous;
}
/*
* since we found a value to remove, decrease number of elements in map
* set return value to the deleted node's value and free the node
*/
map->num_elements = map->num_elements - 1;
value = remove_node->value;
free(remove_node);
}
}
while(parent_list != NULL)
{
previous_parent = parent_list;
parent_list = previous_parent->previous;
free(previous_parent);
}
return value;
}
/* note: returned keys are dynamically allocated, you need to free them! */
unsigned long* get_sorted_long_map_keys(long_map* map, unsigned long* num_keys_returned)
{
unsigned long* key_list = (unsigned long*)malloc((map->num_elements)*sizeof(unsigned long));
unsigned long next_key_index = 0;
get_sorted_node_keys(map->root, key_list, &next_key_index, 0);
*num_keys_returned = map->num_elements;
return key_list;
}
void** get_sorted_long_map_values(long_map* map, unsigned long* num_values_returned)
{
void** value_list = (void**)malloc((map->num_elements+1)*sizeof(void*));
unsigned long next_value_index = 0;
get_sorted_node_values(map->root, value_list, &next_value_index, 0);
value_list[map->num_elements] = NULL; /* since we're dealing with pointers make list null terminated */
*num_values_returned = map->num_elements;
return value_list;
}
void** destroy_long_map(long_map* map, int destruction_type, unsigned long* num_destroyed)
{
void** return_values = NULL;
unsigned long return_index = 0;
*num_destroyed = 0;
if(destruction_type == DESTROY_MODE_RETURN_VALUES)
{
return_values = (void**)malloc((map->num_elements+1)*sizeof(void*));
return_values[map->num_elements] = NULL;
}
while(map->num_elements > 0)
{
unsigned long smallest_key = 0;
void* removed_value = remove_smallest_long_map_element(map, &smallest_key);
if(destruction_type == DESTROY_MODE_RETURN_VALUES)
{
return_values[return_index] = removed_value;
}
if(destruction_type == DESTROY_MODE_FREE_VALUES)
{
free(removed_value);
}
return_index++;
*num_destroyed = *num_destroyed + 1;
}
free(map);
return return_values;
}
void apply_to_every_long_map_value(long_map* map, void (*apply_func)(unsigned long key, void* value))
{
apply_to_every_long_map_node(map->root, apply_func);
}
void apply_to_every_string_map_value(string_map* map, void (*apply_func)(char* key, void* value))
{
apply_to_every_string_map_node( (map->lm).root, map->store_keys, apply_func);
}
/***************************************************
* internal utility function definitions
***************************************************/
static void** destroy_long_map_values(long_map* map, int destruction_type, unsigned long* num_destroyed)
{
void** return_values = NULL;
unsigned long return_index = 0;
*num_destroyed = 0;
if(destruction_type == DESTROY_MODE_RETURN_VALUES)
{
return_values = (void**)malloc((map->num_elements+1)*sizeof(void*));
return_values[map->num_elements] = NULL;
}
while(map->num_elements > 0)
{
unsigned long smallest_key = 0;
void* removed_value = remove_smallest_long_map_element(map, &smallest_key);
if(destruction_type == DESTROY_MODE_RETURN_VALUES)
{
return_values[return_index] = removed_value;
}
if(destruction_type == DESTROY_MODE_FREE_VALUES)
{
free(removed_value);
}
return_index++;
*num_destroyed = *num_destroyed + 1;
}
return return_values;
}
static void apply_to_every_long_map_node(long_map_node* node, void (*apply_func)(unsigned long key, void* value))
{
if(node != NULL)
{
apply_to_every_long_map_node(node->left, apply_func);
apply_func(node->key, node->value);
apply_to_every_long_map_node(node->right, apply_func);
}
}
static void apply_to_every_string_map_node(long_map_node* node, unsigned char has_key, void (*apply_func)(char* key, void* value))
{
if(node != NULL)
{
apply_to_every_string_map_node(node->left, has_key, apply_func);
if(has_key)
{
string_map_key_value* kv = (string_map_key_value*)(node->value);
apply_func(kv->key, kv->value);
}
else
{
apply_func(NULL, node->value);
}
apply_to_every_string_map_node(node->right, has_key, apply_func);
}
}
static void get_sorted_node_keys(long_map_node* node, unsigned long* key_list, unsigned long* next_key_index, int depth)
{
if(node != NULL)
{
get_sorted_node_keys(node->left, key_list, next_key_index, depth+1);
key_list[ *next_key_index ] = node->key;
(*next_key_index)++;
get_sorted_node_keys(node->right, key_list, next_key_index, depth+1);
}
}
static void get_sorted_node_values(long_map_node* node, void** value_list, unsigned long* next_value_index, int depth)
{
if(node != NULL)
{
get_sorted_node_values(node->left, value_list, next_value_index, depth+1);
value_list[ *next_value_index ] = node->value;
(*next_value_index)++;
get_sorted_node_values(node->right, value_list, next_value_index, depth+1);
}
}
/*
* direction = -1 indicates left subtree updated, direction = 1 for right subtree
* update_op = -1 indicates delete node, update_op = 1 for insert node
*/
static signed char rebalance (long_map_node** n, signed char direction, signed char update_op)
{
/*
printf( "original: key = %ld, balance = %d, update_op=%d, direction=%d\n", (*n)->key, (*n)->balance, update_op, direction);
*/
(*n)->balance = (*n)->balance + (update_op*direction);
if( (*n)->balance < -1)
{
if((*n)->left->balance < 0)
{
rotate_right(n);
(*n)->right->balance = 0;
(*n)->balance = 0;
}
else if((*n)->left->balance == 0)
{
rotate_right(n);
(*n)->right->balance = -1;
(*n)->balance = 1;
}
else if((*n)->left->balance > 0)
{
rotate_left( &((*n)->left) );
rotate_right(n);
/*
if( (*n)->balance < 0 )
{
(*n)->left->balance = 0;
(*n)->right->balance = 1;
}
else if( (*n)->balance == 0 )
{
(*n)->left->balance = 0;
(*n)->right->balance = 0;
}
else if( (*n)->balance > 0 )
{
(*n)->left->balance = -1;
(*n)->right->balance = 0;
}
*/
(*n)->left->balance = (*n)->balance > 0 ? -1 : 0;
(*n)->right->balance = (*n)->balance < 0 ? 1 : 0;
(*n)->balance = 0;
}
}
if( (*n)->balance > 1)
{
if((*n)->right->balance > 0)
{
rotate_left(n);
(*n)->left->balance = 0;
(*n)->balance = 0;
}
else if ((*n)->right->balance == 0)
{
rotate_left(n);
(*n)->left->balance = 1;
(*n)->balance = -1;
}
else if((*n)->right->balance < 0)
{
rotate_right( &((*n)->right) );
rotate_left(n);
/*
if( (*n)->balance < 0 )
{
(*n)->left->balance = 0;
(*n)->right->balance = 1;
}
else if( (*n)->balance == 0 )
{
(*n)->left->balance = 0;
(*n)->right->balance = 0;
}
else if( (*n)->balance > 0 )
{
(*n)->left->balance = -1;
(*n)->right->balance = 0;
}
*/
(*n)->left->balance = (*n)->balance > 0 ? -1 : 0;
(*n)->right->balance = (*n)->balance < 0 ? 1 : 0;
(*n)->balance = 0;
}
}
/*
printf( "key = %ld, balance = %d\n", (*n)->key, (*n)->balance);
*/
return (*n)->balance;
}
static void rotate_right (long_map_node** parent)
{
long_map_node* old_parent = *parent;
long_map_node* pivot = old_parent->left;
old_parent->left = pivot->right;
pivot->right = old_parent;
*parent = pivot;
}
static void rotate_left (long_map_node** parent)
{
long_map_node* old_parent = *parent;
long_map_node* pivot = old_parent->right;
old_parent->right = pivot->left;
pivot->left = old_parent;
*parent = pivot;
}
/***************************************************************************
* This algorithm was created for the sdbm database library (a public-domain
* reimplementation of ndbm) and seems to work relatively well in
* scrambling bits
*
*
* This code was derived from code found at:
* http://www.cse.yorku.ca/~oz/hash.html
***************************************************************************/
static unsigned long sdbm_string_hash(const char *key)
{
unsigned long hashed_key = 0;
int index = 0;
unsigned int nextch;
while(key[index] != '\0')
{
nextch = key[index];
hashed_key = nextch + (hashed_key << 6) + (hashed_key << 16) - hashed_key;
index++;
}
return hashed_key;
}

View File

@ -0,0 +1,74 @@
#
# Copyright (C) 2006 OpenWrt.org
# Copyright (C) 2009 Eric Bishop <eric@gargoyle-router.com>
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=libiptbwctl
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_BUILD_DIR:=$(BUILD_DIR)/libiptbwctl
include $(INCLUDE_DIR)/package.mk
define Package/libiptbwctl
SECTION:=net
CATEGORY:=Gargoyle
SUBMENU:=Network
DEPENDS:=+iptables-mod-bandwidth
TITLE:=IPT bandwidth control library
URL:=http://www.gargoyle-router.com
MAINTAINER:=Eric Bishop <eric@gargoyle-router.com>
endef
define Package/libiptbwctl/description
IPT bandwidth control library
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Build/Configure
endef
define Build/Compile
-$(MAKE) -C $(PKG_BUILD_DIR) clean
$(MAKE) -C $(PKG_BUILD_DIR) \
$(TARGET_CONFIGURE_OPTS) \
STAGING_DIR="$(STAGING_DIR)" \
CFLAGS="$(TARGET_CFLAGS) -I $(STAGING_DIR)/usr/include" \
LDFLAGS="$(TARGET_LDFLAGS) -L $(STAGING_DIR)/usr/lib" \
all
mkdir -p $(STAGING_DIR)/usr/include/
$(CP) $(PKG_BUILD_DIR)/*.h $(STAGING_DIR)/usr/include/
mkdir -p $(STAGING_DIR)/usr/lib
$(CP) $(PKG_BUILD_DIR)/*.so* $(STAGING_DIR)/usr/lib/
$(MAKE) -C $(PKG_BUILD_DIR)/utils \
$(TARGET_CONFIGURE_OPTS) \
STAGING_DIR="$(STAGING_DIR)" \
CFLAGS="$(TARGET_CFLAGS) -I $(STAGING_DIR)/usr/include" \
LDFLAGS="$(TARGET_LDFLAGS) -L $(STAGING_DIR)/usr/lib" \
all
endef
define Package/libiptbwctl/install
$(INSTALL_DIR) $(1)/usr/lib
$(INSTALL_DIR) $(1)/usr/bin
$(CP) $(PKG_BUILD_DIR)/*.so* $(1)/usr/lib/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/utils/bw_get $(1)/usr/bin/bw_get
$(INSTALL_BIN) $(PKG_BUILD_DIR)/utils/bw_set $(1)/usr/bin/bw_set
$(INSTALL_BIN) $(PKG_BUILD_DIR)/utils/bw_print_history_file $(1)/usr/bin/bw_print_history_file
$(INSTALL_BIN) $(PKG_BUILD_DIR)/utils/set_kernel_timezone $(1)/usr/bin/set_kernel_timezone
endef
$(eval $(call BuildPackage,libiptbwctl))

View File

@ -0,0 +1,60 @@
VERSION=1
ifeq ($(CC),)
CC=gcc
endif
ifeq ($(LD),)
LD=ld
endif
ifeq ($(AR),)
AR=ar
endif
ifeq ($(RANLIB),)
RANLIB=ranlib
endif
OS=$(shell uname)
ifeq ($(OS),Darwin)
LINK=$(LD)
SHLIB_EXT=dylib
SHLIB_FLAGS=-dylib
SHLIB_FILE=libiptbwctl.$(SHLIB_EXT).$(VERSION)
else
LINK=$(CC)
SHLIB_EXT=so
SHLIB_FILE=libiptbwctl.$(SHLIB_EXT).$(VERSION)
SHLIB_FLAGS=-shared -Wl,-soname,$(SHLIB_FILE)
endif
CFLAGS:=$(CFLAGS) -Os
WARNING_FLAGS=-Wall -Wstrict-prototypes
all: libiptbwctl
libiptbwctl: libiptbwctl.$(SHLIB_EXT) libiptbwctl.a
libiptbwctl.a: ipt_bwctl_static.o ipt_bwctl_safe_malloc_static.o
if [ -e $@ ] ; then rm $@ ; fi
$(AR) rc $@ $^
$(RANLIB) $@
libiptbwctl.$(SHLIB_EXT) : ipt_bwctl_dyn.o ipt_bwctl_safe_malloc_dyn.o
if [ -e libiptbwctl.$(SHLIB_EXT) ] ; then rm libiptbwctl.$(SHLIB_EXT)* ; fi
$(LINK) $(LDFLAGS) $(SHLIB_FLAGS) -o $(SHLIB_FILE) $^ -lc
ln -s $(SHLIB_FILE) libiptbwctl.$(SHLIB_EXT)
%_dyn.o: %.c
$(CC) $(CFLAGS) -fPIC $(WARNING_FLAGS) -o $@ -c $^
%_static.o: %.c
$(CC) $(CFLAGS) $(WARNING_FLAGS) -o $@ -c $^
clean:
cd utils
rm -rf bw_get bw_set *.a *.o *~ .*sw*
cd ..
if [ -n "$(SHLIB_EXT)" ] ; then rm -rf *.$(SHLIB_EXT)* ; fi
rm -rf *.a *.o *~ .*sw*

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,147 @@
/* libiptbwctl -- A userspace library for querying the bandwidth iptables module
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2009 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/ipc.h>
#include <errno.h>
#include <sys/sem.h>
#include <sys/time.h>
#define BANDWIDTH_QUERY_LENGTH 16384
/* socket id parameters (for userspace i/o) */
#define BANDWIDTH_SET 2048
#define BANDWIDTH_GET 2049
/* max id length */
#define BANDWIDTH_MAX_ID_LENGTH 50
/* pick something rather random... let's make it end in 666 to
* freak out the crazy fundies out there ;-) */
#define BANDWIDTH_SEMAPHORE_KEY 12699666
/* possible reset intervals */
#define BANDWIDTH_MINUTE 80
#define BANDWIDTH_HOUR 81
#define BANDWIDTH_DAY 82
#define BANDWIDTH_WEEK 83
#define BANDWIDTH_MONTH 84
#define BANDWIDTH_NEVER 85
#pragma pack(push, 1)
typedef struct ip_bw_struct
{
uint32_t ip;
uint64_t bw;
} ip_bw;
/*
* format of response:
* byte 1 : error code (0 for ok)
* bytes 2-5 : total_num_ips found in query (further gets may be necessary to retrieve them)
* bytes 6-9 : start_index, index (in a list of total_num_ips) of first ip in response
* bytes 10-13 : num_ips_in_response, number of ips in this response
* bytes 14-21 : reset_interval (helps deal with DST shifts in userspace)
* bytes 22-29 : reset_time (helps deal with DST shifts in userspace)
* byte 30 : reset_is_constant_interval (helps deal with DST shifts in userspace)
* remaining bytes contain blocks of ip data
* format is dependent on whether history was queried
*/
typedef struct ip_bw_kernel_data_item_struct
{
uint32_t ip;
uint32_t num_nodes;
uint64_t first_start;
uint64_t first_end;
uint64_t last_end;
uint64_t ipbw_data[0];
}ip_bw_kernel_data_item;
typedef struct
{
uint8_t error;
uint32_t ip_total;
uint32_t index_start;
uint32_t ip_num;
uint64_t reset_interval;
uint64_t reset_time;
uint8_t reset_is_constant_interval;
/*payload for history ip bw data*/
ip_bw_kernel_data_item data_item[0];
} ip_bw_kernel_data;
typedef struct history_struct
{
uint32_t ip;
uint32_t num_nodes;
time_t reset_interval;
time_t reset_time;
unsigned char is_constant_interval;
time_t first_start;
time_t first_end;
time_t last_end;
uint64_t* history_bws;
} ip_bw_history;
#pragma pack(pop)
time_t* get_interval_starts_for_history(ip_bw_history history);
extern void free_ip_bw_histories(ip_bw_history* histories, int num_histories);
extern int get_all_bandwidth_history_for_rule_id(char* id, unsigned long* num_ips, ip_bw_history** data, unsigned long max_wait_milliseconds);
extern int get_ip_bandwidth_history_for_rule_id(char* id, char* ip, ip_bw_history** data, unsigned long max_wait_milliseconds);
extern int get_all_bandwidth_usage_for_rule_id(char* id, unsigned long* num_ips, ip_bw** data, unsigned long max_wait_milliseconds);
extern int get_ip_bandwidth_usage_for_rule_id(char* id, char* ip, ip_bw** data, unsigned long max_wait_milliseconds);
extern int set_bandwidth_history_for_rule_id(char* id, unsigned char zero_unset, unsigned long num_ips, ip_bw_history* data, unsigned long max_wait_milliseconds);
extern int set_bandwidth_usage_for_rule_id(char* id, unsigned char zero_unset, unsigned long num_ips, time_t last_backup, ip_bw* data, unsigned long max_wait_milliseconds);
extern int save_usage_to_file(ip_bw* data, unsigned long num_ips, char* out_file_path);
extern int save_history_to_file(ip_bw_history* data, unsigned long num_ips, char* out_file_path);
extern ip_bw* load_usage_from_file(char* in_file_path, unsigned long* num_ips, time_t* last_backup);
extern ip_bw_history* load_history_from_file(char* in_file_path, unsigned long* num_ips);
extern void print_usage(FILE* out, ip_bw* usage, unsigned long num_ips);
extern void print_histories(FILE* out, char* id, ip_bw_history* histories, unsigned long num_histories, char output_type);
extern void unlock_bandwidth_semaphore(void);
extern void unlock_bandwidth_semaphore_on_exit(void);
/* sets kernel timezone minuteswest to match user timezone */
extern int get_minutes_west(time_t now);
extern void set_kernel_timezone(void);
/* safe malloc & strdup functions used to handle malloc errors cleanly */
extern void* ipt_bwctl_safe_malloc(size_t size);
extern char* ipt_bwctl_safe_strdup(const char* str);

View File

@ -0,0 +1,23 @@
#include "ipt_bwctl.h"
void *ipt_bwctl_safe_malloc(size_t size)
{
void* val = malloc(size);
if(val == NULL)
{
fprintf(stderr, "ERROR: MALLOC FAILURE!\n");
exit(1);
}
return val;
}
char* ipt_bwctl_safe_strdup(const char* str)
{
char* new_str = strdup(str);
if(new_str == NULL)
{
fprintf(stderr, "ERROR: MALLOC FAILURE!\n");
exit(1);
}
return new_str;
}

View File

@ -0,0 +1,12 @@
all: bw_set bw_get bw_print_history_file set_kernel_timezone
bw_set: bw_set.c
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ -liptbwctl
bw_get: bw_get.c
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ -liptbwctl
bw_print_history_file: bw_print_history_file.c
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ -liptbwctl
set_kernel_timezone: set_kernel_timezone.c
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ -liptbwctl
clean:
rm -rf bw_set bw_get print_history_file set_kernel_timezone *.o *~ .*sw*

View File

@ -0,0 +1,175 @@
/* libiptbwctl -- A userspace library for querying the bandwidth iptables module
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2009 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#include <ipt_bwctl.h>
#define malloc ipt_bwctl_safe_malloc
#define strdup ipt_bwctl_safe_strdup
int main(int argc, char **argv)
{
char *id = NULL;
char* out_file_path = NULL;;
char *address = NULL;
unsigned long num_ips;
void *ip_buf;
unsigned long out_index;
int query_succeeded;
int get_history = 0;
char output_type = 'h';
int c;
struct in_addr read_addr;
while((c = getopt(argc, argv, "i:I:a:A:f:F:tThHmMuU")) != -1)
{
switch(c)
{
case 'i':
case 'I':
if(strlen(optarg) < BANDWIDTH_MAX_ID_LENGTH && strlen(optarg) > 0)
{
id = strdup(optarg);
}
else
{
fprintf(stderr, "ERROR: ID length is improper length.\n");
exit(0);
}
break;
case 'a':
case 'A':
if(strcmp(optarg, "combined") == 0 || strcmp(optarg, "COMBINED") == 0)
{
address = strdup("0.0.0.0");
}
else if( inet_aton(optarg, &read_addr) )
{
address = strdup(optarg);
}
else
{
fprintf(stderr, "ERROR: invalid IP address specified\n");
exit(0);
}
break;
case 'f':
case 'F':
out_file_path = strdup(optarg);
break;
case 'h':
case 'H':
get_history = 1;
break;
case 'm':
case 'M':
output_type = 'm';
break;
case 't':
case 'T':
output_type = 't';
break;
case 'u':
case 'U':
default:
fprintf(stderr, "USAGE:\n\t%s -i [ID] -a [IP ADDRESS] -f [OUT_FILE_NAME]\n", argv[0]);
exit(0);
}
}
if(id == NULL)
{
fprintf(stderr, "ERROR: you must specify an id to query\n\n");
exit(0);
}
set_kernel_timezone();
unlock_bandwidth_semaphore_on_exit();
if(get_history == 0)
{
if(address == NULL)
{
query_succeeded = get_all_bandwidth_usage_for_rule_id(id, &num_ips, (ip_bw**)&ip_buf, 1000);
}
else
{
num_ips = 1;
query_succeeded = get_ip_bandwidth_usage_for_rule_id(id, address, (ip_bw**)&ip_buf, 1000);
}
}
else
{
if(address == NULL)
{
query_succeeded = get_all_bandwidth_history_for_rule_id(id, &num_ips, (ip_bw_history**)&ip_buf, 1000);
}
else
{
num_ips = 1;
query_succeeded = get_ip_bandwidth_history_for_rule_id(id, address, (ip_bw_history**)&ip_buf, 1000);
}
}
if(!query_succeeded)
{
fprintf(stderr, "ERROR: Bandwidth query failed, make sure rule with specified id exists, and that you are performing only one query at a time.\n\n");
exit(0);
}
if(out_file_path != NULL)
{
if(get_history == 0)
{
save_usage_to_file( (ip_bw*)ip_buf, num_ips, out_file_path);
}
else
{
save_history_to_file( (ip_bw_history*)ip_buf, num_ips, out_file_path);
}
}
else
{
if(get_history == 0)
{
print_usage(stdout, (ip_bw*)ip_buf, num_ips);
}
else
{
print_histories(stdout, id, (ip_bw_history*)ip_buf, num_ips, output_type );
}
}
if(num_ips == 0)
{
if(output_type != 't' && output_type != 'm')
{
fprintf(stderr, "No data available for id \"%s\"\n", id);
}
}
printf("\n");
if(out_file_path != NULL)
{
free(out_file_path);
}
return 0;
}

View File

@ -0,0 +1,37 @@
/* libiptbwctl -- A userspace library for querying the bandwidth iptables module
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2009 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#include <ipt_bwctl.h>
#define malloc ipt_bwctl_safe_malloc
#define strdup ipt_bwctl_safe_strdup
int main(int argc, char **argv)
{
if(argc > 1)
{
unsigned long num_ips;
ip_bw_history* histories = load_history_from_file(argv[1], &num_ips);
if(histories != NULL)
{
print_histories(stdout, argv[1], histories, num_ips, 'h');
}
}
return 0;
}

View File

@ -0,0 +1,184 @@
/* libiptbwctl -- A userspace library for querying the bandwidth iptables module
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2009 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#include <ipt_bwctl.h>
#define malloc ipt_bwctl_safe_malloc
#define strdup ipt_bwctl_safe_strdup
static char* read_entire_file(FILE* in, int read_block_size);
static char** split_on_separators(char* line, char* separators, int num_separators, int max_pieces, int include_remainder_at_max, unsigned long *pieces_read);
int main(int argc, char **argv)
{
char *id = NULL;
char* in_file_path = NULL;
FILE* in_file = NULL;
time_t last_backup = 0;
int last_backup_from_cl = 0;
int is_history_file = 0;
int c;
while((c = getopt(argc, argv, "i:I:b:B:f:F:UuHh")) != -1)
{
switch(c)
{
case 'i':
case 'I':
if(strlen(optarg) < BANDWIDTH_MAX_ID_LENGTH)
{
id = strdup(optarg);
}
else
{
fprintf(stderr, "ERROR: ID length is improper length.\n");
exit(0);
}
break;
case 'b':
case 'B':
if(sscanf(optarg, "%ld", &last_backup) == 0)
{
fprintf(stderr, "ERROR: invalid backup time specified. Should be unix epoch seconds -- number of seconds since 1970 (UTC)\n");
exit(0);
}
last_backup_from_cl = 1;
break;
case 'f':
case 'F':
in_file_path = strdup(optarg);
in_file = fopen(optarg, "rb");
if(in_file == NULL)
{
fprintf(stderr, "ERROR: cannot open specified file for reading\n");
exit(0);
}
fclose(in_file);
break;
case 'h':
case 'H':
is_history_file = 1;
break;
case 'u':
case 'U':
default:
fprintf(stderr, "USAGE:\n\t%s -i [ID] -b [LAST_BACKUP_TIME] -f [IN_FILE_NAME] [ IP BANDWIDTH PAIRS, IF -f NOT SPECIFIED ]\n", argv[0]);
exit(0);
}
}
if(id == NULL)
{
fprintf(stderr, "ERROR: you must specify an id for which to set data\n\n");
exit(0);
}
if(in_file_path == NULL && is_history_file)
{
fprintf(stderr, "ERROR: you need to specify file to load history from\n\t\t(history format is too complex to load from command line)\n");
}
set_kernel_timezone();
unlock_bandwidth_semaphore_on_exit();
int query_succeeded = 0;
if(in_file_path != NULL)
{
if(is_history_file)
{
unsigned long num_ips;
ip_bw_history* history_data = load_history_from_file(in_file_path, &num_ips);
if(history_data != NULL)
{
query_succeeded = set_bandwidth_history_for_rule_id(id, 1, num_ips, history_data, 1000);
}
}
else
{
unsigned long num_ips;
time_t last_backup;
ip_bw* usage_data = load_usage_from_file(in_file_path, &num_ips, &last_backup);
if(usage_data != NULL)
{
query_succeeded = set_bandwidth_usage_for_rule_id(id, 1, num_ips, last_backup, usage_data, 1000);
}
}
}
else
{
char** data_parts;
unsigned long num_data_parts;
data_parts = argv+optind;
num_data_parts = argc - optind;
unsigned long num_ips = num_data_parts/2;
ip_bw* buffer = (ip_bw*)malloc(num_ips*sizeof(ip_bw));
unsigned long data_index = 0;
unsigned long buffer_index = 0;
while(data_index < num_data_parts)
{
ip_bw next;
struct in_addr ipaddr;
int valid = inet_aton(data_parts[data_index], &ipaddr);
if((!valid) && (!last_backup_from_cl))
{
sscanf(data_parts[data_index], "%ld", &last_backup);
}
data_index++;
if(valid && data_index < num_data_parts)
{
next.ip = ipaddr.s_addr;
valid = sscanf(data_parts[data_index], "%lld", (long long int*)&(next.bw) );
data_index++;
}
else
{
valid = 0;
}
if(valid)
{
/* printf("ip=%d, bw=%lld\n", next.ip, (long long int)next.bw); */
buffer[buffer_index] = next;
buffer_index++;
}
}
num_ips = buffer_index; /* number that were successfully read */
query_succeeded = set_bandwidth_usage_for_rule_id(id, 1, num_ips, last_backup, buffer, 1000);
}
if(!query_succeeded)
{
fprintf(stderr, "ERROR: Could not set data. Please try again.\n\n");
}
else
{
fprintf(stderr, "Data set successfully\n\n");
}
if(in_file_path != NULL)
{
free(in_file_path);
}
return 0;
}

View File

@ -0,0 +1,27 @@
/* libiptbwctl -- A userspace library for querying the bandwidth iptables module
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2009 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#include <ipt_bwctl.h>
int main(void)
{
set_kernel_timezone();
return 0;
}

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,31 @@
#
# Copyright (C) 2017 Xingwang Liao <kuoruan@gmail.com>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-qos-gargoyle
PKG_VERSION:=1.3.8
PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=Xingwang Liao <kuoruan@gmail.com>
LUCI_TITLE:=LuCI Support for Gargoyle QoS
LUCI_DEPENDS:=+qos-gargoyle
LUCI_PKGARCH:=all
include $(TOPDIR)/feeds/luci/luci.mk
define Package/$(PKG_NAME)/config
# shown in make menuconfig <Help>
help
$(LUCI_TITLE)
.
Version: $(PKG_VERSION)-$(PKG_RELEASE)
$(PKG_MAINTAINER)
endef
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,5 @@
# luci-app-qos-gargoyle
This is my own fork of QoS Gargoyle
QoS Gargoyle: https://github.com/kuoruan/qos-gargoyle

View File

@ -0,0 +1,100 @@
-- Copyright 2017 Xingwang Liao <kuoruan@gmail.com>
-- Licensed to the public under the Apache License 2.0.
module("luci.controller.qos_gargoyle", package.seeall)
local util = require "luci.util"
local http = require "luci.http"
function index()
if not nixio.fs.access("/etc/config/qos_gargoyle") then
return
end
entry({"admin", "network", "qos_gargoyle"},
firstchild(), _("Gargoyle QoS"), 60)
entry({"admin", "network", "qos_gargoyle", "global"},
cbi("qos_gargoyle/global"), _("Global Settings"), 10)
entry({"admin", "network", "qos_gargoyle", "upload"},
cbi("qos_gargoyle/upload"), _("Upload Settings"), 20)
entry({"admin", "network", "qos_gargoyle", "upload", "class"},
cbi("qos_gargoyle/upload_class")).leaf = true
entry({"admin", "network", "qos_gargoyle", "upload", "rule"},
cbi("qos_gargoyle/upload_rule")).leaf = true
entry({"admin", "network", "qos_gargoyle", "download"},
cbi("qos_gargoyle/download"), _("Download Settings"), 30)
entry({"admin", "network", "qos_gargoyle", "download", "class"},
cbi("qos_gargoyle/download_class")).leaf = true
entry({"admin", "network", "qos_gargoyle", "download", "rule"},
cbi("qos_gargoyle/download_rule")).leaf = true
entry({"admin", "network", "qos_gargoyle", "troubleshooting"},
template("qos_gargoyle/troubleshooting"), _("Troubleshooting"), 40)
entry({"admin", "network", "qos_gargoyle", "troubleshooting", "data"},
call("action_troubleshooting_data"))
entry({"admin", "network", "qos_gargoyle", "load_data"},
call("action_load_data")).leaf = true
end
function action_troubleshooting_data()
local uci = require "luci.model.uci".cursor()
local i18n = require "luci.i18n"
local data = {}
local monenabled = uci:get("qos_gargoyle", "download", "qos_monenabled") or "false"
local show_data = util.trim(util.exec("/etc/init.d/qos_gargoyle show 2>/dev/null"))
if show_data == "" then
show_data = i18n.translate("No data found")
end
data.show = show_data
local mon_data
if monenabled == "true" then
mon_data = util.trim(util.exec("cat /tmp/qosmon.status 2>/dev/null"))
if mon_data == "" then
mon_data = i18n.translate("No data found")
end
else
mon_data = i18n.translate("\"Active Congestion Control\" not enabled")
end
data.mon = mon_data
http.prepare_content("application/json")
http.write_json(data)
end
function action_load_data(type)
local device
if type == "download" then
device = "imq0"
elseif type == "upload" then
local qos = require "luci.model.qos_gargoyle"
local wan = qos.get_wan()
device = wan and wan:ifname() or ""
end
if device then
local data
if device ~= "" then
data = util.exec("tc -s class show dev %s 2>/dev/null" % device)
end
http.prepare_content("text/plain")
http.write(data or "")
else
http.status(500, "Bad address")
end
end

View File

@ -0,0 +1,166 @@
-- Copyright 2017 Xingwang Liao <kuoruan@gmail.com>
-- Licensed to the public under the Apache License 2.0.
local wa = require "luci.tools.webadmin"
local uci = require "luci.model.uci".cursor()
local dsp = require "luci.dispatcher"
local http = require "luci.http"
local qos = require "luci.model.qos_gargoyle"
local m, class_s, rule_s, o
local download_classes = {}
local qos_gargoyle = "qos_gargoyle"
uci:foreach(qos_gargoyle, "download_class", function(s)
local class_alias = s.name
if class_alias then
download_classes[#download_classes + 1] = {name = s[".name"], alias = class_alias}
end
end)
m = Map(qos_gargoyle, translate("Download Settings"))
m.template = "qos_gargoyle/list_view"
class_s = m:section(TypedSection, "download_class", translate("Service Classes"),
translate("Each service class is specified by four parameters: percent bandwidth at capacity, "
.. "realtime bandwidth and maximum bandwidth and the minimimze round trip time flag."))
class_s.anonymous = true
class_s.addremove = true
class_s.template = "cbi/tblsection"
class_s.extedit = dsp.build_url("admin/network/qos_gargoyle/download/class/%s")
class_s.create = function(...)
local sid = TypedSection.create(...)
if sid then
m.uci:save(qos_gargoyle)
http.redirect(class_s.extedit % sid)
return
end
end
o = class_s:option(DummyValue, "name", translate("Class Name"))
o.cfgvalue = function(...)
return Value.cfgvalue(...) or translate("None")
end
o = class_s:option(DummyValue, "percent_bandwidth", translate("Percent Bandwidth At Capacity"))
o.cfgvalue = function(...)
local v = tonumber(Value.cfgvalue(...))
if v and v > 0 then
return "%d %%" % v
end
return translate("Not set")
end
o = class_s:option(DummyValue, "min_bandwidth", "%s (kbps)" % translate("Minimum Bandwidth"))
o.cfgvalue = function(...)
local v = tonumber(Value.cfgvalue(...))
return v or translate("Zero")
end
o = class_s:option(DummyValue, "max_bandwidth", "%s (kbps)" % translate("Maximum Bandwidth"))
o.cfgvalue = function(...)
local v = tonumber(Value.cfgvalue(...))
return v or translate("Unlimited")
end
o = class_s:option(DummyValue, "minRTT", translate("Minimize RTT"))
o.cfgvalue = function(...)
local v = Value.cfgvalue(...)
return v and translate(v) or translate("No")
end
o = class_s:option(DummyValue, "_ld", "%s (kbps)" % translate("Load"))
o.rawhtml = true
o.value = "<em class=\"ld-download\">*</em>"
rule_s = m:section(TypedSection, "download_rule", translate("Classification Rules"),
translate("Packets are tested against the rules in the order specified -- rules toward the top "
.. "have priority. As soon as a packet matches a rule it is classified, and the rest of the rules "
.. "are ignored. The order of the rules can be altered using the arrow controls.")
)
rule_s.addremove = true
rule_s.sortable = true
rule_s.anonymous = true
rule_s.template = "cbi/tblsection"
rule_s.extedit = dsp.build_url("admin/network/qos_gargoyle/download/rule/%s")
rule_s.create = function(...)
local sid = TypedSection.create(...)
if sid then
m.uci:save(qos_gargoyle)
http.redirect(rule_s.extedit % sid)
return
end
end
o = rule_s:option(ListValue, "class", translate("Service Class"))
for _, s in ipairs(download_classes) do o:value(s.name, s.alias) end
o = rule_s:option(Value, "proto", translate("Transport Protocol"))
o:value("", translate("All"))
o:value("tcp", "TCP")
o:value("udp", "UDP")
o:value("icmp", "ICMP")
o:value("gre", "GRE")
o.size = "10"
o.cfgvalue = function(...)
local v = Value.cfgvalue(...)
return v and v:upper() or ""
end
o.write = function(self, section, value)
Value.write(self, section, value:lower())
end
o = rule_s:option(Value, "source", translate("Source IP(s)"))
o:value("", translate("All"))
wa.cbi_add_knownips(o)
o.datatype = "ipmask4"
o = rule_s:option(Value, "srcport", translate("Source Port(s)"))
o:value("", translate("All"))
o.datatype = "or(port, portrange)"
o = rule_s:option(Value, "destination", translate("Destination IP(s)"))
o:value("", translate("All"))
wa.cbi_add_knownips(o)
o.datatype = "ipmask4"
o = rule_s:option(Value, "dstport", translate("Destination Port(s)"))
o:value("", translate("All"))
o.datatype = "or(port, portrange)"
o = rule_s:option(DummyValue, "min_pkt_size", translate("Minimum Packet Length"))
o.cfgvalue = function(...)
local v = tonumber(Value.cfgvalue(...))
if v and v > 0 then
return wa.byte_format(v)
end
return translate("Not set")
end
o = rule_s:option(DummyValue, "max_pkt_size", translate("Maximum Packet Length"))
o.cfgvalue = function(...)
local v = tonumber(Value.cfgvalue(...))
if v and v > 0 then
return wa.byte_format(v)
end
return translate("Not set")
end
o = rule_s:option(DummyValue, "connbytes_kb", translate("Connection Bytes Reach"))
o.cfgvalue = function(...)
local v = tonumber(Value.cfgvalue(...))
if v and v > 0 then
return wa.byte_format(v * 1024)
end
return translate("Not set")
end
if qos.has_ndpi() then
o = rule_s:option(DummyValue, "ndpi", translate("DPI Protocol"))
o.cfgvalue = function(...)
local v = Value.cfgvalue(...)
return v and v:upper() or translate("All")
end
end
return m

View File

@ -0,0 +1,61 @@
-- Copyright 2017 Xingwang Liao <kuoruan@gmail.com>
-- Licensed to the public under the Apache License 2.0.
local m, s, o
local sid = arg[1]
local qos_gargoyle = "qos_gargoyle"
m = Map(qos_gargoyle, translate("Edit Download Service Class"))
m.redirect = luci.dispatcher.build_url("admin/network/qos_gargoyle/download")
if m.uci:get(qos_gargoyle, sid) ~= "download_class" then
luci.http.redirect(m.redirect)
return
end
s = m:section(NamedSection, sid, "download_class")
s.anonymous = true
s.addremove = false
o = s:option(Value, "name", translate("Service Class Name"))
o.rmempty = false
o = s:option(Value, "percent_bandwidth", translate("Percent Bandwidth At Capacity"),
translate("The percentage of the total available bandwidth that should be allocated to this class "
.. "when all available bandwidth is being used. If unused bandwidth is available, more can (and "
.. "will) be allocated. The percentages can be configured to equal more (or less) than 100, but "
.. "when the settings are applied the percentages will be adjusted proportionally so that they "
.. "add to 100. This setting only comes into effect when the WAN link is saturated."))
o.datatype = "range(1, 100)"
o.rmempty = false
o = s:option(Value, "min_bandwidth", translate("Minimum Bandwidth"),
translate("The minimum service this class will be allocated when the link is at capacity. Classes "
.. "which specify minimum service are known as realtime classes by the active congestion "
.. "controller. Streaming video, VoIP and interactive online gaming are all examples of "
.. "applications that must have a minimum bandwith to function. To determine what to enter use "
.. "the application on an unloaded LAN and observe how much bandwidth it uses. Then enter a "
.. "number only slightly higher than this into this field. QoS will satisfiy the minimum service "
.. "of all classes first before allocating to other waiting classes so be careful to use minimum "
.. "bandwidths sparingly."))
o:value("0", translate("Zero"))
o.datatype = "uinteger"
o.default = "0"
o = s:option(Value, "max_bandwidth", translate("Maximum Bandwidth"),
translate("The maximum amount of bandwidth this class will be allocated in kbit/s. Even if unused "
.. "bandwidth is available, this service class will never be permitted to use more than this "
.. "amount of bandwidth."))
o:value("", translate("Unlimited"))
o.datatype = "uinteger"
o = s:option(Flag, "minRTT", translate("Minimize RTT"),
translate("Indicates to the active congestion controller that you wish to minimize round trip "
.. "times (RTT) when this class is active. Use this setting for online gaming or VoIP "
.. "applications that need low round trip times (ping times). Minimizing RTT comes at the expense "
.. "of efficient WAN throughput so while these class are active your WAN throughput will decline "
.. "(usually around 20%)."))
o.enabled = "Yes"
o.disabled = "No"
return m

View File

@ -0,0 +1,87 @@
-- Copyright 2017 Xingwang Liao <kuoruan@gmail.com>
-- Licensed to the public under the Apache License 2.0.
local wa = require "luci.tools.webadmin"
local uci = require "luci.model.uci".cursor()
local qos = require "luci.model.qos_gargoyle"
local m, s, o
local sid = arg[1]
local download_classes = {}
local qos_gargoyle = "qos_gargoyle"
uci:foreach(qos_gargoyle, "download_class", function(s)
local class_alias = s.name
if class_alias then
download_classes[#download_classes + 1] = {name = s[".name"], alias = class_alias}
end
end)
m = Map(qos_gargoyle, translate("Edit Download Classification Rule"))
m.redirect = luci.dispatcher.build_url("admin/network/qos_gargoyle/download")
if m.uci:get(qos_gargoyle, sid) ~= "download_rule" then
luci.http.redirect(m.redirect)
return
end
s = m:section(NamedSection, sid, "download_rule")
s.anonymous = true
s.addremove = false
o = s:option(ListValue, "class", translate("Service Class"))
for _, s in ipairs(download_classes) do o:value(s.name, s.alias) end
o = s:option(Value, "proto", translate("Transport Protocol"))
o:value("", translate("All"))
o:value("tcp", "TCP")
o:value("udp", "UDP")
o:value("icmp", "ICMP")
o:value("gre", "GRE")
o.write = function(self, section, value)
Value.write(self, section, value:lower())
end
o = s:option(Value, "source", translate("Source IP(s)"),
translate("Packet's source ip, can optionally have /[mask] after it (see -s option in iptables "
.. "man page)."))
o:value("", translate("All"))
wa.cbi_add_knownips(o)
o.datatype = "ipmask4"
o = s:option(Value, "srcport", translate("Source Port(s)"),
translate("Packet's source port, can be a range (eg. 80-90)."))
o:value("", translate("All"))
o.datatype = "or(port, portrange)"
o = s:option(Value, "destination", translate("Destination IP(s)"),
translate("Packet's destination ip, can optionally have /[mask] after it (see -d option in "
.. "iptables man page)."))
o:value("", translate("All"))
wa.cbi_add_knownips(o)
o.datatype = "ipmask4"
o = s:option(Value, "dstport", translate("Destination Port(s)"),
translate("Packet's destination port, can be a range (eg. 80-90)."))
o:value("", translate("All"))
o.datatype = "or(port, portrange)"
o = s:option(Value, "min_pkt_size", translate("Minimum Packet Length"),
translate("Packet's minimum size (in bytes)."))
o.datatype = "range(1, 1500)"
o = s:option(Value, "max_pkt_size", translate("Maximum Packet Length"),
translate("Packet's maximum size (in bytes)."))
o.datatype = "range(1, 1500)"
o = s:option(Value, "connbytes_kb", translate("Connection Bytes Reach"),
translate("The total size of data transmitted since the establishment of the link (in kBytes)."))
o.datatype = "range(0, 4194303)"
if qos.has_ndpi() then
o = s:option(ListValue, "ndpi", translate("DPI Protocol"))
o:value("", translate("All"))
qos.cbi_add_dpi_protocols(o)
end
return m

View File

@ -0,0 +1,123 @@
-- Copyright 2017 Xingwang Liao <kuoruan@gmail.com>
-- Licensed to the public under the Apache License 2.0.
local sys = require "luci.sys"
local uci = require "luci.model.uci".cursor()
local net = require "luci.model.network".init()
local qos = require "luci.model.qos_gargoyle"
local m, s, o
local upload_classes = {}
local download_classes = {}
local qos_gargoyle = "qos_gargoyle"
local function qos_enabled()
return sys.init.enabled(qos_gargoyle)
end
uci:foreach(qos_gargoyle, "upload_class", function(s)
local class_alias = s.name
if class_alias then
upload_classes[#upload_classes + 1] = {name = s[".name"], alias = class_alias}
end
end)
uci:foreach(qos_gargoyle, "download_class", function(s)
local class_alias = s.name
if class_alias then
download_classes[#download_classes + 1] = {name = s[".name"], alias = class_alias}
end
end)
m = Map(qos_gargoyle, translate("Gargoyle QoS"),
translate("Quality of Service (QoS) provides a way to control how available bandwidth is "
.. "allocated."))
s = m:section(NamedSection, "global", "global", translate("Global Settings"))
s.anonymous = true
o = s:option(Button, "_switch", nil, translate("QoS Switch"))
o.render = function(self, section, scope)
if qos_enabled() then
self.title = translate("Disable QoS")
self.inputstyle = "reset"
else
self.title = translate("Enable QoS")
self.inputstyle = "apply"
end
Button.render(self, section, scope)
end
o.write = function(...)
if qos_enabled() then
sys.init.stop(qos_gargoyle)
sys.init.disable(qos_gargoyle)
else
sys.init.enable(qos_gargoyle)
sys.init.start(qos_gargoyle)
end
end
s = m:section(NamedSection, "upload", "upload", translate("Upload Settings"))
s.anonymous = true
o = s:option(ListValue, "default_class", translate("Default Service Class"),
translate("Specifie how packets that do not match any rule should be classified."))
for _, s in ipairs(upload_classes) do o:value(s.name, s.alias) end
o = s:option(Value, "total_bandwidth", translate("Total Upload Bandwidth"),
translate("Should be set to around 98% of your available upload bandwidth. Entering a number "
.. "which is too high will result in QoS not meeting its class requirements. Entering a number "
.. "which is too low will needlessly penalize your upload speed. You should use a speed test "
.. "program (with QoS off) to determine available upload bandwidth. Note that bandwidth is "
.. "specified in kbps, leave blank to disable update QoS. There are 8 kilobits per kilobyte."))
o.datatype = "uinteger"
s = m:section(NamedSection, "download", "download", translate("Download Settings"))
s.anonymous = true
o = s:option(ListValue, "default_class", translate("Default Service Class"),
translate("Specifie how packets that do not match any rule should be classified."))
for _, s in ipairs(download_classes) do o:value(s.name, s.alias) end
o = s:option(Value, "total_bandwidth", translate("Total Download Bandwidth"),
translate("Specifying correctly is crucial to making QoS work. Note that bandwidth is specified "
.. "in kbps, leave blank to disable download QoS. There are 8 kilobits per kilobyte."))
o.datatype = "uinteger"
o = s:option(Flag, "qos_monenabled", translate("Enable Active Congestion Control"),
translate("<p>The active congestion control (ACC) observes your download activity and "
.. "automatically adjusts your download link limit to maintain proper QoS performance. ACC "
.. "automatically compensates for changes in your ISP's download speed and the demand from your "
.. "network adjusting the link speed to the highest speed possible which will maintain proper QoS "
.. "function. The effective range of this control is between 15% and 100% of the total download "
.. "bandwidth you entered above.</p>") ..
translate("<p>While ACC does not adjust your upload link speed you must enable and properly "
.. "configure your upload QoS for it to function properly.</p>")
)
o.enabled = "true"
o.disabled = "false"
o = s:option(Value, "ptarget_ip", translate("Use Non-standard Ping Target"),
translate("The segment of network between your router and the ping target is where congestion is "
.. "controlled. By monitoring the round trip ping times to the target congestion is detected. By "
.. "default ACC uses your WAN gateway as the ping target. If you know that congestion on your "
.. "link will occur in a different segment then you can enter an alternate ping target. Leave "
.. "empty to use the default settings."))
o:depends("qos_monenabled", "true")
local wan = qos.get_wan()
if wan then o:value(wan:gwaddr()) end
o.datatype = "ipaddr"
o = s:option(Value, "pinglimit", translate("Manual Ping Limit"),
translate("Round trip ping times are compared against the ping limits. ACC controls the link "
.. "limit to maintain ping times under the appropriate limit. By default ACC attempts to "
.. "automatically select appropriate target ping limits for you based on the link speeds you "
.. "entered and the performance of your link it measures during initialization. You cannot change "
.. "the target ping time for the minRTT mode but by entering a manual time you can control the "
.. "target ping time of the active mode. The time you enter becomes the increase in the target "
.. "ping time between minRTT and active mode. Leave empty to use the default settings."))
o:depends("qos_monenabled", "true")
o:value("Auto", translate("Auto"))
o.datatype = "or('Auto', range(10, 250))"
return m

View File

@ -0,0 +1,160 @@
-- Copyright 2017 Xingwang Liao <kuoruan@gmail.com>
-- Licensed to the public under the Apache License 2.0.
local wa = require "luci.tools.webadmin"
local uci = require "luci.model.uci".cursor()
local dsp = require "luci.dispatcher"
local http = require "luci.http"
local qos = require "luci.model.qos_gargoyle"
local m, class_s, rule_s, o
local upload_classes = {}
local qos_gargoyle = "qos_gargoyle"
uci:foreach(qos_gargoyle, "upload_class", function(s)
local class_alias = s.name
if class_alias then
upload_classes[#upload_classes + 1] = {name = s[".name"], alias = class_alias}
end
end)
m = Map(qos_gargoyle, translate("Upload Settings"))
m.template = "qos_gargoyle/list_view"
class_s = m:section(TypedSection, "upload_class", translate("Service Classes"),
translate("Each upload service class is specified by three parameters: percent bandwidth at "
.. "capacity, minimum bandwidth and maximum bandwidth."))
class_s.anonymous = true
class_s.addremove = true
class_s.template = "cbi/tblsection"
class_s.extedit = dsp.build_url("admin/network/qos_gargoyle/upload/class/%s")
class_s.create = function(...)
local sid = TypedSection.create(...)
if sid then
m.uci:save(qos_gargoyle)
http.redirect(class_s.extedit % sid)
return
end
end
o = class_s:option(DummyValue, "name", translate("Class Name"))
o.cfgvalue = function(...)
return Value.cfgvalue(...) or translate("None")
end
o = class_s:option(DummyValue, "percent_bandwidth", translate("Percent Bandwidth At Capacity"))
o.cfgvalue = function(...)
local v = tonumber(Value.cfgvalue(...))
if v and v > 0 then
return "%d %%" % v
end
return translate("Not set")
end
o = class_s:option(DummyValue, "min_bandwidth", "%s (kbps)" % translate("Minimum Bandwidth"))
o.cfgvalue = function(...)
local v = tonumber(Value.cfgvalue(...))
return v or translate("Zero")
end
o = class_s:option(DummyValue, "max_bandwidth", "%s (kbps)" % translate("Maximum Bandwidth"))
o.cfgvalue = function(...)
local v = tonumber(Value.cfgvalue(...))
return v or translate("Unlimited")
end
o = class_s:option(DummyValue, "_ld", "%s (kbps)" % translate("Load"))
o.rawhtml = true
o.value = "<em class=\"ld-upload\">*</em>"
rule_s = m:section(TypedSection, "upload_rule",translate("Classification Rules"),
translate("Packets are tested against the rules in the order specified -- rules toward the top "
.. "have priority. As soon as a packet matches a rule it is classified, and the rest of the rules "
.. "are ignored. The order of the rules can be altered using the arrow controls.")
)
rule_s.addremove = true
rule_s.sortable = true
rule_s.anonymous = true
rule_s.template = "cbi/tblsection"
rule_s.extedit = dsp.build_url("admin/network/qos_gargoyle/upload/rule/%s")
rule_s.create = function(...)
local sid = TypedSection.create(...)
if sid then
m.uci:save(qos_gargoyle)
http.redirect(rule_s.extedit % sid)
return
end
end
o = rule_s:option(ListValue, "class", translate("Service Class"))
for _, s in ipairs(upload_classes) do o:value(s.name, s.alias) end
o = rule_s:option(Value, "proto", translate("Transport Protocol"))
o:value("", translate("All"))
o:value("tcp", "TCP")
o:value("udp", "UDP")
o:value("icmp", "ICMP")
o:value("gre", "GRE")
o.size = "10"
o.cfgvalue = function(...)
local v = Value.cfgvalue(...)
return v and v:upper() or ""
end
o.write = function(self, section, value)
Value.write(self, section, value:lower())
end
o = rule_s:option(Value, "source", translate("Source IP(s)"))
o:value("", translate("All"))
wa.cbi_add_knownips(o)
o.datatype = "ipmask4"
o = rule_s:option(Value, "srcport", translate("Source Port(s)"))
o:value("", translate("All"))
o.datatype = "or(port, portrange)"
o = rule_s:option(Value, "destination", translate("Destination IP(s)"))
o:value("", translate("All"))
wa.cbi_add_knownips(o)
o.datatype = "ipmask4"
o = rule_s:option(Value, "dstport", translate("Destination Port(s)"))
o:value("", translate("All"))
o.datatype = "or(port, portrange)"
o = rule_s:option(DummyValue, "min_pkt_size", translate("Minimum Packet Length"))
o.cfgvalue = function(...)
local v = tonumber(Value.cfgvalue(...))
if v and v > 0 then
return wa.byte_format(v)
end
return translate("Not set")
end
o = rule_s:option(DummyValue, "max_pkt_size", translate("Maximum Packet Length"))
o.cfgvalue = function(...)
local v = tonumber(Value.cfgvalue(...))
if v and v > 0 then
return wa.byte_format(v)
end
return translate("Not set")
end
o = rule_s:option(DummyValue, "connbytes_kb", translate("Connection Bytes Reach"))
o.cfgvalue = function(...)
local v = tonumber(Value.cfgvalue(...))
if v and v > 0 then
return wa.byte_format(v * 1024)
end
return translate("Not set")
end
if qos.has_ndpi() then
o = rule_s:option(DummyValue, "ndpi", translate("DPI Protocol"))
o.cfgvalue = function(...)
local v = Value.cfgvalue(...)
return v and v:upper() or translate("All")
end
end
return m

View File

@ -0,0 +1,52 @@
-- Copyright 2017 Xingwang Liao <kuoruan@gmail.com>
-- Licensed to the public under the Apache License 2.0.
local m, s, o
local sid = arg[1]
local qos_gargoyle = "qos_gargoyle"
m = Map(qos_gargoyle, translate("Edit Upload Service Class"))
m.redirect = luci.dispatcher.build_url("admin/network/qos_gargoyle/upload")
if m.uci:get(qos_gargoyle, sid) ~= "upload_class" then
luci.http.redirect(m.redirect)
return
end
s = m:section(NamedSection, sid, "upload_class")
s.anonymous = true
s.addremove = false
o = s:option(Value, "name", translate("Service Class Name"))
o.rmempty = false
o = s:option(Value, "percent_bandwidth", translate("Percent Bandwidth At Capacity"),
translate("The percentage of the total available bandwidth that should be allocated to this class "
.. "when all available bandwidth is being used. If unused bandwidth is available, more can (and "
.. "will) be allocated. The percentages can be configured to equal more (or less) than 100, but "
.. "when the settings are applied the percentages will be adjusted proportionally so that they "
.. "add to 100. This setting only comes into effect when the WAN link is saturated."))
o.datatype = "range(1, 100)"
o.rmempty = false
o = s:option(Value, "min_bandwidth", translate("Minimum Bandwidth"),
translate("The minimum service this class will be allocated when the link is at capacity. Classes "
.. "which specify minimum service are known as realtime classes by the active congestion "
.. "controller. Streaming video, VoIP and interactive online gaming are all examples of "
.. "applications that must have a minimum bandwith to function. To determine what to enter use "
.. "the application on an unloaded LAN and observe how much bandwidth it uses. Then enter a "
.. "number only slightly higher than this into this field. QoS will satisfiy the minimum service "
.. "of all classes first before allocating to other waiting classes so be careful to use minimum "
.. "bandwidths sparingly."))
o:value("0", translate("Zero"))
o.datatype = "uinteger"
o.default = "0"
o = s:option(Value, "max_bandwidth", translate("Maximum Bandwidth"),
translate("The maximum amount of bandwidth this class will be allocated in kbit/s. Even if unused "
.. "bandwidth is available, this service class will never be permitted to use more than this "
.. "amount of bandwidth."))
o:value("", translate("Unlimited"))
o.datatype = "uinteger"
return m

View File

@ -0,0 +1,87 @@
-- Copyright 2017 Xingwang Liao <kuoruan@gmail.com>
-- Licensed to the public under the Apache License 2.0.
local wa = require "luci.tools.webadmin"
local uci = require "luci.model.uci".cursor()
local qos = require "luci.model.qos_gargoyle"
local m, s, o
local sid = arg[1]
local upload_classes = {}
local qos_gargoyle = "qos_gargoyle"
uci:foreach(qos_gargoyle, "upload_class", function(s)
local class_alias = s.name
if class_alias then
upload_classes[#upload_classes + 1] = {name = s[".name"], alias = class_alias}
end
end)
m = Map(qos_gargoyle, translate("Edit Upload Classification Rule"))
m.redirect = luci.dispatcher.build_url("admin/network/qos_gargoyle/upload")
if m.uci:get(qos_gargoyle, sid) ~= "upload_rule" then
luci.http.redirect(m.redirect)
return
end
s = m:section(NamedSection, sid, "upload_rule")
s.anonymous = true
s.addremove = false
o = s:option(ListValue, "class", translate("Service Class"))
for _, s in ipairs(upload_classes) do o:value(s.name, s.alias) end
o = s:option(Value, "proto", translate("Transport Protocol"))
o:value("", translate("All"))
o:value("tcp", "TCP")
o:value("udp", "UDP")
o:value("icmp", "ICMP")
o:value("gre", "GRE")
o.write = function(self, section, value)
Value.write(self, section, value:lower())
end
o = s:option(Value, "source", translate("Source IP(s)"),
translate("Packet's source ip, can optionally have /[mask] after it (see -s option in iptables "
.. "man page)."))
o:value("", translate("All"))
wa.cbi_add_knownips(o)
o.datatype = "ipmask4"
o = s:option(Value, "srcport", translate("Source Port(s)"),
translate("Packet's source port, can be a range (eg. 80-90)."))
o:value("", translate("All"))
o.datatype = "or(port, portrange)"
o = s:option(Value, "destination", translate("Destination IP(s)"),
translate("Packet's destination ip, can optionally have /[mask] after it (see -d option in "
.. "iptables man page)."))
o:value("", translate("All"))
wa.cbi_add_knownips(o)
o.datatype = "ipmask4"
o = s:option(Value, "dstport", translate("Destination Port(s)"),
translate("Packet's destination port, can be a range (eg. 80-90)."))
o:value("", translate("All"))
o.datatype = "or(port, portrange)"
o = s:option(Value, "min_pkt_size", translate("Minimum Packet Length"),
translate("Packet's minimum size (in bytes)."))
o.datatype = "range(1, 1500)"
o = s:option(Value, "max_pkt_size", translate("Maximum Packet Length"),
translate("Packet's maximum size (in bytes)."))
o.datatype = "range(1, 1500)"
o = s:option(Value, "connbytes_kb", translate("Connection Bytes Reach"),
translate("The total size of data transmitted since the establishment of the link (in kBytes)."))
o.datatype = "range(0, 4194303)"
if qos.has_ndpi() then
o = s:option(ListValue, "ndpi", translate("DPI Protocol"))
o:value("", translate("All"))
qos.cbi_add_dpi_protocols(o)
end
return m

View File

@ -0,0 +1,31 @@
-- Copyright 2017 Xingwang Liao <kuoruan@gmail.com>
-- Licensed to the public under the Apache License 2.0.
module("luci.model.qos_gargoyle", package.seeall)
function has_ndpi()
return luci.sys.call("lsmod | cut -d ' ' -f1 | grep -q 'xt_ndpi'") == 0
end
function cbi_add_dpi_protocols(field)
local util = require "luci.util"
local dpi_protocols = {}
for line in util.execi("iptables -m ndpi --help 2>/dev/null | grep '^--'") do
local _, _, protocol, name = line:find("%-%-([^%s]+) Match for ([^%s]+)")
if protocol and name then
dpi_protocols[protocol] = name
end
end
for p, n in util.kspairs(dpi_protocols) do
field:value(p, n)
end
end
function get_wan()
local net = require "luci.model.network".init()
return net:get_wannet()
end

View File

@ -0,0 +1,97 @@
<%#
Copyright 2017 Xingwang Liao <kuoruan@gmail.com>
Licensed to the public under the Apache License 2.0.
-%>
<%
local dsp = require "luci.dispatcher"
local request = dsp.context.path
local leaf = request[#request]
-%>
<style title="text/css">
/* container for entire page. fixes bootstrap theme's ridiculously small page width */
.container {
max-width: none;
margin: 0 30px;
width: auto;
}
/* column for configuration section name */
table th {
padding: 5px 0;
text-align: center;
vertical-align: middle;
}
/* cells showing the configuration values */
table td {
padding: 5px 0;
text-align: center;
vertical-align: middle;
}
/* sort buttons column */
table.cbi-section-table td.cbi-section-table-cell {
text-align: center;
}
</style>
<%+cbi/map%>
<script type="text/javascript">//<![CDATA[
function bpsToKbpsString(bps) {
var kbps = '*';
var bpsn = parseInt(bps) / 1000;
if (isNaN(bpsn)) {
kbps = '*';
} else if (bpsn < 1) {
kbps = bpsn.toFixed(1) + '';
} else {
kbps = bpsn.toFixed(0) + '';
}
return kbps;
}
(function(doc){
var tbs = doc.getElementsByClassName('ld-<%=leaf%>');
var lastTime = 0;
var dataTable = [];
var updateInProgress = false;
XHR.poll(3, '<%=dsp.build_url("admin", "network", "qos_gargoyle", "load_data", leaf)%>',
null, function(res) {
if (res.readyState == 4 && tbs) {
var timestamp = new Date().getTime();
var timeDiff = timestamp - lastTime;
lastTime = timestamp;
if (updateInProgress) return;
updateInProgress = true;
var lines = res.responseText.match(/hfsc\s1:[0-9]{1,2}\s.+leaf.+\n.+Sent\s[0-9]+/g);
if (lines) {
for (var i = 0, len = lines.length; i < len; i++) {
var line = lines[i];
var idx = parseInt(line.match(/hfsc\s1:([0-9]+)/)[1]) - 2;
var lastBytes;
if (idx < tbs.length) {
lastBytes = dataTable[idx];
var newBytes = line.match(/Sent\s([0-9]+)/)[1];
dataTable[idx] = newBytes;
var bps = NaN;
if (lastBytes) {
var diffBytes = parseInt(newBytes) - parseInt(lastBytes);
if (diffBytes <= 0) {
bps = 0;
} else {
bps = diffBytes * 8000 / timeDiff;
}
}
tbs[idx].innerText = bpsToKbpsString(bps);
}
}
}
updateInProgress = false;
}
}
);
}(document));
//]]></script>

View File

@ -0,0 +1,52 @@
<%#
Copyright 2017 Xingwang Liao <kuoruan@gmail.com>
Licensed to the public under the Apache License 2.0.
-%>
<% css = [[
#troubleshoot_text {
padding: 20px;
text-align: left;
}
#troubleshoot_text pre {
word-break: break-all;
margin: 0;
}
.description {
background-color: #33CCFF;
}
]]
-%>
<%+header%>
<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
<script type="text/javascript">//<![CDATA[
XHR.poll(15, '<%=url("admin/network/qos_gargoyle/troubleshooting/data")%>', null,
function(x, data) {
var tshoot = document.getElementById('troubleshoot_text');
if (data.hasOwnProperty("show")) {
tshoot.innerHTML = String.format(
'<pre>%s%s%s%s</pre>',
'<span class="description">Output of &#34;/etc/init.d/qos_gargoyle show&#34; : </span><br /><br />',
data.show,
'<br /><br /><span class="description">Output of &#34;cat /tmp/qosmon.status&#34; : </span><br /><br />',
data.mon
);
} else {
tshoot.innerHTML = '<strong><%:Error collecting troubleshooting information%></strong>';
}
}
);
//]]></script>
<div id="troubleshoot">
<fieldset class="cbi-section">
<legend><%:Troubleshooting Data%></legend>
<div id="troubleshoot_text"><img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /><%:Collecting data...%></div>
</fieldset>
</div>
<%+footer%>

View File

@ -0,0 +1,292 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "\"Active Congestion Control\" not enabled"
msgstr ""
msgid ""
"<p>The active congestion control (ACC) observes your download activity and "
"automatically adjusts your download link limit to maintain proper QoS "
"performance. ACC automatically compensates for changes in your ISP's "
"download speed and the demand from your network adjusting the link speed to "
"the highest speed possible which will maintain proper QoS function. The "
"effective range of this control is between 15% and 100% of the total "
"download bandwidth you entered above.</p>"
msgstr ""
msgid ""
"<p>While ACC does not adjust your upload link speed you must enable and "
"properly configure your upload QoS for it to function properly.</p>"
msgstr ""
msgid "All"
msgstr ""
msgid "Auto"
msgstr ""
msgid "Class Name"
msgstr ""
msgid "Classification Rules"
msgstr ""
msgid "Collecting data..."
msgstr ""
msgid "Connection Bytes Reach"
msgstr ""
msgid "DPI Protocol"
msgstr ""
msgid "Default Service Class"
msgstr ""
msgid "Destination IP(s)"
msgstr ""
msgid "Destination Port(s)"
msgstr ""
msgid "Disable QoS"
msgstr ""
msgid "Download Settings"
msgstr ""
msgid ""
"Each service class is specified by four parameters: percent bandwidth at "
"capacity, realtime bandwidth and maximum bandwidth and the minimimze round "
"trip time flag."
msgstr ""
msgid ""
"Each upload service class is specified by three parameters: percent "
"bandwidth at capacity, minimum bandwidth and maximum bandwidth."
msgstr ""
msgid "Edit Download Classification Rule"
msgstr ""
msgid "Edit Download Service Class"
msgstr ""
msgid "Edit Upload Classification Rule"
msgstr ""
msgid "Edit Upload Service Class"
msgstr ""
msgid "Enable Active Congestion Control"
msgstr ""
msgid "Enable QoS"
msgstr ""
msgid "Error collecting troubleshooting information"
msgstr ""
msgid "Gargoyle QoS"
msgstr ""
msgid "Global Settings"
msgstr ""
msgid ""
"Indicates to the active congestion controller that you wish to minimize "
"round trip times (RTT) when this class is active. Use this setting for "
"online gaming or VoIP applications that need low round trip times (ping "
"times). Minimizing RTT comes at the expense of efficient WAN throughput so "
"while these class are active your WAN throughput will decline (usually "
"around 20%)."
msgstr ""
msgid "Load"
msgstr ""
msgid "Loading"
msgstr ""
msgid "Manual Ping Limit"
msgstr ""
msgid "Maximum Bandwidth"
msgstr ""
msgid "Maximum Packet Length"
msgstr ""
msgid "Minimize RTT"
msgstr ""
msgid "Minimum Bandwidth"
msgstr ""
msgid "Minimum Packet Length"
msgstr ""
msgid "No"
msgstr ""
msgid "No data found"
msgstr ""
msgid "None"
msgstr ""
msgid "Not set"
msgstr ""
msgid ""
"Packet's destination ip, can optionally have /[mask] after it (see -d option "
"in iptables man page)."
msgstr ""
msgid "Packet's destination port, can be a range (eg. 80-90)."
msgstr ""
msgid "Packet's maximum size (in bytes)."
msgstr ""
msgid "Packet's minimum size (in bytes)."
msgstr ""
msgid ""
"Packet's source ip, can optionally have /[mask] after it (see -s option in "
"iptables man page)."
msgstr ""
msgid "Packet's source port, can be a range (eg. 80-90)."
msgstr ""
msgid ""
"Packets are tested against the rules in the order specified -- rules toward "
"the top have priority. As soon as a packet matches a rule it is classified, "
"and the rest of the rules are ignored. The order of the rules can be altered "
"using the arrow controls."
msgstr ""
msgid "Percent Bandwidth At Capacity"
msgstr ""
msgid "QoS Switch"
msgstr ""
msgid ""
"Quality of Service (QoS) provides a way to control how available bandwidth "
"is allocated."
msgstr ""
msgid ""
"Round trip ping times are compared against the ping limits. ACC controls the "
"link limit to maintain ping times under the appropriate limit. By default "
"ACC attempts to automatically select appropriate target ping limits for you "
"based on the link speeds you entered and the performance of your link it "
"measures during initialization. You cannot change the target ping time for "
"the minRTT mode but by entering a manual time you can control the target "
"ping time of the active mode. The time you enter becomes the increase in the "
"target ping time between minRTT and active mode. Leave empty to use the "
"default settings."
msgstr ""
msgid "Service Class"
msgstr ""
msgid "Service Class Name"
msgstr ""
msgid "Service Classes"
msgstr ""
msgid ""
"Should be set to around 98% of your available upload bandwidth. Entering a "
"number which is too high will result in QoS not meeting its class "
"requirements. Entering a number which is too low will needlessly penalize "
"your upload speed. You should use a speed test program (with QoS off) to "
"determine available upload bandwidth. Note that bandwidth is specified in "
"kbps, leave blank to disable update QoS. There are 8 kilobits per kilobyte."
msgstr ""
msgid "Source IP(s)"
msgstr ""
msgid "Source Port(s)"
msgstr ""
msgid "Specifie how packets that do not match any rule should be classified."
msgstr ""
msgid ""
"Specifying correctly is crucial to making QoS work. Note that bandwidth is "
"specified in kbps, leave blank to disable download QoS. There are 8 kilobits "
"per kilobyte."
msgstr ""
msgid ""
"The maximum amount of bandwidth this class will be allocated in kbit/s. Even "
"if unused bandwidth is available, this service class will never be permitted "
"to use more than this amount of bandwidth."
msgstr ""
msgid ""
"The minimum service this class will be allocated when the link is at "
"capacity. Classes which specify minimum service are known as realtime "
"classes by the active congestion controller. Streaming video, VoIP and "
"interactive online gaming are all examples of applications that must have a "
"minimum bandwith to function. To determine what to enter use the application "
"on an unloaded LAN and observe how much bandwidth it uses. Then enter a "
"number only slightly higher than this into this field. QoS will satisfiy the "
"minimum service of all classes first before allocating to other waiting "
"classes so be careful to use minimum bandwidths sparingly."
msgstr ""
msgid ""
"The percentage of the total available bandwidth that should be allocated to "
"this class when all available bandwidth is being used. If unused bandwidth "
"is available, more can (and will) be allocated. The percentages can be "
"configured to equal more (or less) than 100, but when the settings are "
"applied the percentages will be adjusted proportionally so that they add to "
"100. This setting only comes into effect when the WAN link is saturated."
msgstr ""
msgid ""
"The segment of network between your router and the ping target is where "
"congestion is controlled. By monitoring the round trip ping times to the "
"target congestion is detected. By default ACC uses your WAN gateway as the "
"ping target. If you know that congestion on your link will occur in a "
"different segment then you can enter an alternate ping target. Leave empty "
"to use the default settings."
msgstr ""
msgid ""
"The total size of data transmitted since the establishment of the link (in "
"kBytes)."
msgstr ""
msgid "Total Download Bandwidth"
msgstr ""
msgid "Total Upload Bandwidth"
msgstr ""
msgid "Transport Protocol"
msgstr ""
msgid "Troubleshooting"
msgstr ""
msgid "Troubleshooting Data"
msgstr ""
msgid "Unlimited"
msgstr ""
msgid "Upload Settings"
msgstr ""
msgid "Use Non-standard Ping Target"
msgstr ""
msgid "Zero"
msgstr ""

View File

@ -0,0 +1,334 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "\"Active Congestion Control\" not enabled"
msgstr "“主动拥塞控制”未启用"
msgid ""
"<p>The active congestion control (ACC) observes your download activity and "
"automatically adjusts your download link limit to maintain proper QoS "
"performance. ACC automatically compensates for changes in your ISP's "
"download speed and the demand from your network adjusting the link speed to "
"the highest speed possible which will maintain proper QoS function. The "
"effective range of this control is between 15% and 100% of the total "
"download bandwidth you entered above.</p>"
msgstr ""
"<p>主动拥塞控制系统ACC观察你的下载活动并自动调整你的下载链接限制以保持适"
"当的 QoS 性能。ACC 自动调整 QoS 功能以补偿来自你 ISP 的下载速度变化及来自你网"
"络链接速度的调整需求,使速度最大化。这个控制的有效范围在你上面输入的下载总带"
"宽的 15% 至 100% 之间。</p>"
msgid ""
"<p>While ACC does not adjust your upload link speed you must enable and "
"properly configure your upload QoS for it to function properly.</p>"
msgstr ""
"<p>虽然 ACC 不调整你的上传链路速度,但你必须启用并正确配置你的上传 QoS 带宽以"
"使该功能正常工作。</p>"
msgid "All"
msgstr "全部"
msgid "Auto"
msgstr "自动"
msgid "Class Name"
msgstr "类型名称"
msgid "Classification Rules"
msgstr "分类规则"
msgid "Collecting data..."
msgstr "正在收集数据..."
msgid "Connection Bytes Reach"
msgstr "连接流量达到"
msgid "DPI Protocol"
msgstr "DPI 协议"
msgid "Default Service Class"
msgstr "默认服务类型"
msgid "Destination IP(s)"
msgstr "目标 IP"
msgid "Destination Port(s)"
msgstr "目标端口"
msgid "Disable QoS"
msgstr "禁用 QoS"
msgid "Download Settings"
msgstr "下载设置"
msgid ""
"Each service class is specified by four parameters: percent bandwidth at "
"capacity, realtime bandwidth and maximum bandwidth and the minimimze round "
"trip time flag."
msgstr ""
"每个下载服务类型由四个参数指定:带宽占用百分比、最小保证带宽、最大带宽和最小"
"往返延时标志。"
msgid ""
"Each upload service class is specified by three parameters: percent "
"bandwidth at capacity, minimum bandwidth and maximum bandwidth."
msgstr "每个上传服务类型由三个参数指定:带宽占用百分比、最小带宽和最大带宽。"
msgid "Edit Download Classification Rule"
msgstr "编辑下载分类规则"
msgid "Edit Download Service Class"
msgstr "编辑下载服务类型"
msgid "Edit Upload Classification Rule"
msgstr "编辑上传分类规则"
msgid "Edit Upload Service Class"
msgstr "编辑上传服务类型"
msgid "Enable Active Congestion Control"
msgstr "启用主动拥塞控制"
msgid "Enable QoS"
msgstr "启用 QoS"
msgid "Error collecting troubleshooting information"
msgstr "收集故障排查信息失败"
msgid "Gargoyle QoS"
msgstr "石像鬼 QoS"
msgid "Global Settings"
msgstr "全局设置"
msgid ""
"Indicates to the active congestion controller that you wish to minimize "
"round trip times (RTT) when this class is active. Use this setting for "
"online gaming or VoIP applications that need low round trip times (ping "
"times). Minimizing RTT comes at the expense of efficient WAN throughput so "
"while these class are active your WAN throughput will decline (usually "
"around 20%)."
msgstr ""
"告诉主动拥塞控制器你希望该服务类型启用时尽量减少往返延时RTT。该设置一般用"
"在 VoIP 或在线游戏这类需要低延时Ping 值的应用上。减小往返延时RTT会带"
"来WAN有效吞吐量的额外花销所以当这些服务类型启用时你的 WAN 吞吐量将下降(通"
"常在20%左右)"
msgid "Load"
msgstr "负载"
msgid "Loading"
msgstr "正在加载"
msgid "Manual Ping Limit"
msgstr "手动 Ping 限制"
msgid "Maximum Bandwidth"
msgstr "最大带宽"
msgid "Maximum Packet Length"
msgstr "最大数据包长度"
msgid "Minimize RTT"
msgstr "最小往返延时"
msgid "Minimum Bandwidth"
msgstr "最小带宽"
msgid "Minimum Packet Length"
msgstr "最小数据包长度"
msgid "No"
msgstr "否"
msgid "No data found"
msgstr "无数据"
msgid "None"
msgstr "无"
msgid "Not set"
msgstr "未设置"
msgid ""
"Packet's destination ip, can optionally have /[mask] after it (see -d option "
"in iptables man page)."
msgstr ""
"数据包的目标 IP可以在后面加子网掩码/[mask],请看 iptables 的 -d 参数说"
"明)"
msgid "Packet's destination port, can be a range (eg. 80-90)."
msgstr "数据包的目标端口可以是一个范围例如80-90"
msgid "Packet's maximum size (in bytes)."
msgstr "数据包的最大大小单位bytes"
msgid "Packet's minimum size (in bytes)."
msgstr "数据包的最小大小单位bytes"
msgid ""
"Packet's source ip, can optionally have /[mask] after it (see -s option in "
"iptables man page)."
msgstr ""
"数据包的源 IP可以在后面加子网掩码/[mask],请看 iptables 的 -s 参数说明)"
msgid "Packet's source port, can be a range (eg. 80-90)."
msgstr "数据包的源端口可以是一个范围例如80-90"
msgid ""
"Packets are tested against the rules in the order specified -- rules toward "
"the top have priority. As soon as a packet matches a rule it is classified, "
"and the rest of the rules are ignored. The order of the rules can be altered "
"using the arrow controls."
msgstr ""
"数据包将按规则中指定的顺序进行匹配 —— 靠上的规则优先进行匹配。一旦数据包匹配"
"一条规则那它将被归类,并且其余的规则将被忽略。使用上下箭头可调整规则的顺序。"
msgid "Percent Bandwidth At Capacity"
msgstr "带宽占用百分比"
msgid "QoS Switch"
msgstr "QoS 开关"
msgid ""
"Quality of Service (QoS) provides a way to control how available bandwidth "
"is allocated."
msgstr "QoS 可以用来分配和控制可用带宽。"
msgid ""
"Round trip ping times are compared against the ping limits. ACC controls the "
"link limit to maintain ping times under the appropriate limit. By default "
"ACC attempts to automatically select appropriate target ping limits for you "
"based on the link speeds you entered and the performance of your link it "
"measures during initialization. You cannot change the target ping time for "
"the minRTT mode but by entering a manual time you can control the target "
"ping time of the active mode. The time you enter becomes the increase in the "
"target ping time between minRTT and active mode. Leave empty to use the "
"default settings."
msgstr ""
"Ping 延时会与 Ping 限制进行比较。ACC 控制链路限制以保持 Ping 延时在适当范围。"
"默认情况下ACC 会自动根据你输入的链接适当为你选择适当的 Ping 限制。如果你想"
"尝试不同的 Ping 限制,你可以在这里输入一个时间值。输入高的时间值将导致更高的 "
"Ping 限制,低的时间值会有更低的限制。留空则将由 ACC 自动控制。"
msgid "Service Class"
msgstr "服务类型"
msgid "Service Class Name"
msgstr "服务类型名称"
msgid "Service Classes"
msgstr "服务类型"
msgid ""
"Should be set to around 98% of your available upload bandwidth. Entering a "
"number which is too high will result in QoS not meeting its class "
"requirements. Entering a number which is too low will needlessly penalize "
"your upload speed. You should use a speed test program (with QoS off) to "
"determine available upload bandwidth. Note that bandwidth is specified in "
"kbps, leave blank to disable update QoS. There are 8 kilobits per kilobyte."
msgstr ""
"应被设置为你可用上传带宽的 98% 左右。输入数值太高将导致 QoS 不能匹配服务类型"
"的要求。输入数值太低将造成不必要的上传速度限制。你应当在 QoS 关闭的情况下使用"
"测速程序以确定可用的上传带宽。带宽以 kbps 为单位,留空以禁用上传 QoS。"
"1KByte/s=8Kbps"
msgid "Source IP(s)"
msgstr "源 IP"
msgid "Source Port(s)"
msgstr "源端口"
msgid "Specifie how packets that do not match any rule should be classified."
msgstr "指定当数据包不匹配任何规则时将被如何归类。"
msgid ""
"Specifying correctly is crucial to making QoS work. Note that bandwidth is "
"specified in kbps, leave blank to disable download QoS. There are 8 kilobits "
"per kilobyte."
msgstr ""
"正确设置对于 QoS 的工作至关重要。带宽以 kbps 为单位,留空以禁用下载 QoS。"
"1KByte/s=8Kbps"
msgid ""
"The maximum amount of bandwidth this class will be allocated in kbit/s. Even "
"if unused bandwidth is available, this service class will never be permitted "
"to use more than this amount of bandwidth."
msgstr ""
"该服务类型可被分配的带宽最大值(以 kbit/s 为单位)。即使存在未使用带宽,该服"
"务类型也将永远不被允许使用超过此量的带宽。"
msgid ""
"The minimum service this class will be allocated when the link is at "
"capacity. Classes which specify minimum service are known as realtime "
"classes by the active congestion controller. Streaming video, VoIP and "
"interactive online gaming are all examples of applications that must have a "
"minimum bandwith to function. To determine what to enter use the application "
"on an unloaded LAN and observe how much bandwidth it uses. Then enter a "
"number only slightly higher than this into this field. QoS will satisfiy the "
"minimum service of all classes first before allocating to other waiting "
"classes so be careful to use minimum bandwidths sparingly."
msgstr ""
"将被分配用于该服务类型的最低链路带宽容量。指定了最小带宽的服务类型会被主动拥"
"塞控制器看作实时类应用。例如视频流、VoIP 和在线互动游戏都应该设置最小带宽。要"
"确定需要多少最小带宽,可以在一个没有负载的局域网中使用应用程序并观察它使用了"
"多少带宽。然后输入一个略高于这个值的数字到字段中。在分配剩余带宽到其它服务类"
"型前QoS 将优先满足所有服务类型的最小带宽要求,所以要谨慎地使用最小带宽。"
msgid ""
"The percentage of the total available bandwidth that should be allocated to "
"this class when all available bandwidth is being used. If unused bandwidth "
"is available, more can (and will) be allocated. The percentages can be "
"configured to equal more (or less) than 100, but when the settings are "
"applied the percentages will be adjusted proportionally so that they add to "
"100. This setting only comes into effect when the WAN link is saturated."
msgstr ""
"当所有可用带宽被占满后该服务类型占据总带宽的百分比。如果带宽未用完,该服务类"
"型会被分配更多带宽。该百分比值可被设置为等于、大于或小于 100但当设置被应用"
"时,百分比值将会被按比例调整以便使它们加起来等于 100。该设置只在 WAN 端链路饱"
"和时才生效。<em>PSWAN 端链路饱和即带宽被占用完)</em>"
msgid ""
"The segment of network between your router and the ping target is where "
"congestion is controlled. By monitoring the round trip ping times to the "
"target congestion is detected. By default ACC uses your WAN gateway as the "
"ping target. If you know that congestion on your link will occur in a "
"different segment then you can enter an alternate ping target. Leave empty "
"to use the default settings."
msgstr ""
"在路由器和 Ping 目标之间的网络部分是拥塞控制的地方。拥塞通过监视和目标间的 "
"Ping 延时来检测。默认情况下 ACC 使用你的 WAN 网关作为 Ping 的目标。假如你知道"
"拥塞会在你链路的不同段发生,你可用输入一个备用的 Ping 目标。留空则将由ACC 自"
"动控制。"
msgid ""
"The total size of data transmitted since the establishment of the link (in "
"kBytes)."
msgstr "自连接建立以来传输的数据总量单位kBytes"
msgid "Total Download Bandwidth"
msgstr "下载总带宽"
msgid "Total Upload Bandwidth"
msgstr "上传总带宽"
msgid "Transport Protocol"
msgstr "协议"
msgid "Troubleshooting"
msgstr "故障排除"
msgid "Troubleshooting Data"
msgstr "故障排除数据"
msgid "Unlimited"
msgstr "不限制"
msgid "Upload Settings"
msgstr "上传设置"
msgid "Use Non-standard Ping Target"
msgstr "使用自定义 Ping 目标"
msgid "Zero"
msgstr "零"

View File

@ -0,0 +1,334 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "\"Active Congestion Control\" not enabled"
msgstr "“主动拥塞控制”未启用"
msgid ""
"<p>The active congestion control (ACC) observes your download activity and "
"automatically adjusts your download link limit to maintain proper QoS "
"performance. ACC automatically compensates for changes in your ISP's "
"download speed and the demand from your network adjusting the link speed to "
"the highest speed possible which will maintain proper QoS function. The "
"effective range of this control is between 15% and 100% of the total "
"download bandwidth you entered above.</p>"
msgstr ""
"<p>主动拥塞控制系统ACC观察你的下载活动并自动调整你的下载链接限制以保持适"
"当的 QoS 性能。ACC 自动调整 QoS 功能以补偿来自你 ISP 的下载速度变化及来自你网"
"络链接速度的调整需求,使速度最大化。这个控制的有效范围在你上面输入的下载总带"
"宽的 15% 至 100% 之间。</p>"
msgid ""
"<p>While ACC does not adjust your upload link speed you must enable and "
"properly configure your upload QoS for it to function properly.</p>"
msgstr ""
"<p>虽然 ACC 不调整你的上传链路速度,但你必须启用并正确配置你的上传 QoS 带宽以"
"使该功能正常工作。</p>"
msgid "All"
msgstr "全部"
msgid "Auto"
msgstr "自动"
msgid "Class Name"
msgstr "类型名称"
msgid "Classification Rules"
msgstr "分类规则"
msgid "Collecting data..."
msgstr "正在收集数据..."
msgid "Connection Bytes Reach"
msgstr "连接流量达到"
msgid "DPI Protocol"
msgstr "DPI 协议"
msgid "Default Service Class"
msgstr "默认服务类型"
msgid "Destination IP(s)"
msgstr "目标 IP"
msgid "Destination Port(s)"
msgstr "目标端口"
msgid "Disable QoS"
msgstr "禁用 QoS"
msgid "Download Settings"
msgstr "下载设置"
msgid ""
"Each service class is specified by four parameters: percent bandwidth at "
"capacity, realtime bandwidth and maximum bandwidth and the minimimze round "
"trip time flag."
msgstr ""
"每个下载服务类型由四个参数指定:带宽占用百分比、最小保证带宽、最大带宽和最小"
"往返延时标志。"
msgid ""
"Each upload service class is specified by three parameters: percent "
"bandwidth at capacity, minimum bandwidth and maximum bandwidth."
msgstr "每个上传服务类型由三个参数指定:带宽占用百分比、最小带宽和最大带宽。"
msgid "Edit Download Classification Rule"
msgstr "编辑下载分类规则"
msgid "Edit Download Service Class"
msgstr "编辑下载服务类型"
msgid "Edit Upload Classification Rule"
msgstr "编辑上传分类规则"
msgid "Edit Upload Service Class"
msgstr "编辑上传服务类型"
msgid "Enable Active Congestion Control"
msgstr "启用主动拥塞控制"
msgid "Enable QoS"
msgstr "启用 QoS"
msgid "Error collecting troubleshooting information"
msgstr "收集故障排查信息失败"
msgid "Gargoyle QoS"
msgstr "石像鬼 QoS"
msgid "Global Settings"
msgstr "全局设置"
msgid ""
"Indicates to the active congestion controller that you wish to minimize "
"round trip times (RTT) when this class is active. Use this setting for "
"online gaming or VoIP applications that need low round trip times (ping "
"times). Minimizing RTT comes at the expense of efficient WAN throughput so "
"while these class are active your WAN throughput will decline (usually "
"around 20%)."
msgstr ""
"告诉主动拥塞控制器你希望该服务类型启用时尽量减少往返延时RTT。该设置一般用"
"在 VoIP 或在线游戏这类需要低延时Ping 值的应用上。减小往返延时RTT会带"
"来WAN有效吞吐量的额外花销所以当这些服务类型启用时你的 WAN 吞吐量将下降(通"
"常在20%左右)"
msgid "Load"
msgstr "负载"
msgid "Loading"
msgstr "正在加载"
msgid "Manual Ping Limit"
msgstr "手动 Ping 限制"
msgid "Maximum Bandwidth"
msgstr "最大带宽"
msgid "Maximum Packet Length"
msgstr "最大数据包长度"
msgid "Minimize RTT"
msgstr "最小往返延时"
msgid "Minimum Bandwidth"
msgstr "最小带宽"
msgid "Minimum Packet Length"
msgstr "最小数据包长度"
msgid "No"
msgstr "否"
msgid "No data found"
msgstr "无数据"
msgid "None"
msgstr "无"
msgid "Not set"
msgstr "未设置"
msgid ""
"Packet's destination ip, can optionally have /[mask] after it (see -d option "
"in iptables man page)."
msgstr ""
"数据包的目标 IP可以在后面加子网掩码/[mask],请看 iptables 的 -d 参数说"
"明)"
msgid "Packet's destination port, can be a range (eg. 80-90)."
msgstr "数据包的目标端口可以是一个范围例如80-90"
msgid "Packet's maximum size (in bytes)."
msgstr "数据包的最大大小单位bytes"
msgid "Packet's minimum size (in bytes)."
msgstr "数据包的最小大小单位bytes"
msgid ""
"Packet's source ip, can optionally have /[mask] after it (see -s option in "
"iptables man page)."
msgstr ""
"数据包的源 IP可以在后面加子网掩码/[mask],请看 iptables 的 -s 参数说明)"
msgid "Packet's source port, can be a range (eg. 80-90)."
msgstr "数据包的源端口可以是一个范围例如80-90"
msgid ""
"Packets are tested against the rules in the order specified -- rules toward "
"the top have priority. As soon as a packet matches a rule it is classified, "
"and the rest of the rules are ignored. The order of the rules can be altered "
"using the arrow controls."
msgstr ""
"数据包将按规则中指定的顺序进行匹配 —— 靠上的规则优先进行匹配。一旦数据包匹配"
"一条规则那它将被归类,并且其余的规则将被忽略。使用上下箭头可调整规则的顺序。"
msgid "Percent Bandwidth At Capacity"
msgstr "带宽占用百分比"
msgid "QoS Switch"
msgstr "QoS 开关"
msgid ""
"Quality of Service (QoS) provides a way to control how available bandwidth "
"is allocated."
msgstr "QoS 可以用来分配和控制可用带宽。"
msgid ""
"Round trip ping times are compared against the ping limits. ACC controls the "
"link limit to maintain ping times under the appropriate limit. By default "
"ACC attempts to automatically select appropriate target ping limits for you "
"based on the link speeds you entered and the performance of your link it "
"measures during initialization. You cannot change the target ping time for "
"the minRTT mode but by entering a manual time you can control the target "
"ping time of the active mode. The time you enter becomes the increase in the "
"target ping time between minRTT and active mode. Leave empty to use the "
"default settings."
msgstr ""
"Ping 延时会与 Ping 限制进行比较。ACC 控制链路限制以保持 Ping 延时在适当范围。"
"默认情况下ACC 会自动根据你输入的链接适当为你选择适当的 Ping 限制。如果你想"
"尝试不同的 Ping 限制,你可以在这里输入一个时间值。输入高的时间值将导致更高的 "
"Ping 限制,低的时间值会有更低的限制。留空则将由 ACC 自动控制。"
msgid "Service Class"
msgstr "服务类型"
msgid "Service Class Name"
msgstr "服务类型名称"
msgid "Service Classes"
msgstr "服务类型"
msgid ""
"Should be set to around 98% of your available upload bandwidth. Entering a "
"number which is too high will result in QoS not meeting its class "
"requirements. Entering a number which is too low will needlessly penalize "
"your upload speed. You should use a speed test program (with QoS off) to "
"determine available upload bandwidth. Note that bandwidth is specified in "
"kbps, leave blank to disable update QoS. There are 8 kilobits per kilobyte."
msgstr ""
"应被设置为你可用上传带宽的 98% 左右。输入数值太高将导致 QoS 不能匹配服务类型"
"的要求。输入数值太低将造成不必要的上传速度限制。你应当在 QoS 关闭的情况下使用"
"测速程序以确定可用的上传带宽。带宽以 kbps 为单位,留空以禁用上传 QoS。"
"1KByte/s=8Kbps"
msgid "Source IP(s)"
msgstr "源 IP"
msgid "Source Port(s)"
msgstr "源端口"
msgid "Specifie how packets that do not match any rule should be classified."
msgstr "指定当数据包不匹配任何规则时将被如何归类。"
msgid ""
"Specifying correctly is crucial to making QoS work. Note that bandwidth is "
"specified in kbps, leave blank to disable download QoS. There are 8 kilobits "
"per kilobyte."
msgstr ""
"正确设置对于 QoS 的工作至关重要。带宽以 kbps 为单位,留空以禁用下载 QoS。"
"1KByte/s=8Kbps"
msgid ""
"The maximum amount of bandwidth this class will be allocated in kbit/s. Even "
"if unused bandwidth is available, this service class will never be permitted "
"to use more than this amount of bandwidth."
msgstr ""
"该服务类型可被分配的带宽最大值(以 kbit/s 为单位)。即使存在未使用带宽,该服"
"务类型也将永远不被允许使用超过此量的带宽。"
msgid ""
"The minimum service this class will be allocated when the link is at "
"capacity. Classes which specify minimum service are known as realtime "
"classes by the active congestion controller. Streaming video, VoIP and "
"interactive online gaming are all examples of applications that must have a "
"minimum bandwith to function. To determine what to enter use the application "
"on an unloaded LAN and observe how much bandwidth it uses. Then enter a "
"number only slightly higher than this into this field. QoS will satisfiy the "
"minimum service of all classes first before allocating to other waiting "
"classes so be careful to use minimum bandwidths sparingly."
msgstr ""
"将被分配用于该服务类型的最低链路带宽容量。指定了最小带宽的服务类型会被主动拥"
"塞控制器看作实时类应用。例如视频流、VoIP 和在线互动游戏都应该设置最小带宽。要"
"确定需要多少最小带宽,可以在一个没有负载的局域网中使用应用程序并观察它使用了"
"多少带宽。然后输入一个略高于这个值的数字到字段中。在分配剩余带宽到其它服务类"
"型前QoS 将优先满足所有服务类型的最小带宽要求,所以要谨慎地使用最小带宽。"
msgid ""
"The percentage of the total available bandwidth that should be allocated to "
"this class when all available bandwidth is being used. If unused bandwidth "
"is available, more can (and will) be allocated. The percentages can be "
"configured to equal more (or less) than 100, but when the settings are "
"applied the percentages will be adjusted proportionally so that they add to "
"100. This setting only comes into effect when the WAN link is saturated."
msgstr ""
"当所有可用带宽被占满后该服务类型占据总带宽的百分比。如果带宽未用完,该服务类"
"型会被分配更多带宽。该百分比值可被设置为等于、大于或小于 100但当设置被应用"
"时,百分比值将会被按比例调整以便使它们加起来等于 100。该设置只在 WAN 端链路饱"
"和时才生效。<em>PSWAN 端链路饱和即带宽被占用完)</em>"
msgid ""
"The segment of network between your router and the ping target is where "
"congestion is controlled. By monitoring the round trip ping times to the "
"target congestion is detected. By default ACC uses your WAN gateway as the "
"ping target. If you know that congestion on your link will occur in a "
"different segment then you can enter an alternate ping target. Leave empty "
"to use the default settings."
msgstr ""
"在路由器和 Ping 目标之间的网络部分是拥塞控制的地方。拥塞通过监视和目标间的 "
"Ping 延时来检测。默认情况下 ACC 使用你的 WAN 网关作为 Ping 的目标。假如你知道"
"拥塞会在你链路的不同段发生,你可用输入一个备用的 Ping 目标。留空则将由ACC 自"
"动控制。"
msgid ""
"The total size of data transmitted since the establishment of the link (in "
"kBytes)."
msgstr "自连接建立以来传输的数据总量单位kBytes"
msgid "Total Download Bandwidth"
msgstr "下载总带宽"
msgid "Total Upload Bandwidth"
msgstr "上传总带宽"
msgid "Transport Protocol"
msgstr "协议"
msgid "Troubleshooting"
msgstr "故障排除"
msgid "Troubleshooting Data"
msgstr "故障排除数据"
msgid "Unlimited"
msgstr "不限制"
msgid "Upload Settings"
msgstr "上传设置"
msgid "Use Non-standard Ping Target"
msgstr "使用自定义 Ping 目标"
msgid "Zero"
msgstr "零"

View File

@ -0,0 +1,14 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@qos_gargoyle[-1]
add ucitrack qos_gargoyle
set ucitrack.@qos_gargoyle[-1].init=qos_gargoyle
commit ucitrack
EOF
/etc/init.d/qos_gargoyle stop
/etc/init.d/qos_gargoyle disable
rm -rf /tmp/luci-modulecache /tmp/luci-indexcache
exit 0

View File

@ -0,0 +1,49 @@
#
# Copyright (C) 2010-2011 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-qosv4
PKG_VERSION:=1.1f
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define Package/luci-app-qosv4
SECTION:=LuCI
CATEGORY:=LuCI
SUBMENU:=3. Applications
TITLE:=LuCI Support for QoSv4.
DEPENDS:=+tc +iptables-mod-conntrack-extra +iptables-mod-conntrack-extra +iptables-mod-filter +iptables-mod-imq +iptables-mod-ipopt +iptables-mod-nat-extra +iptables-mod-imq +kmod-sched
PKGARCH:=all
MAINTAINER:=qq 3341249
endef
define Package/luci-app-qosv4/description
An agent script that makes qosv4 configuration simple.
endef
define Build/Compile
endef
define Package/luci-app-qosv4/postinst
#!/bin/sh
[ -n "${IPKG_INSTROOT}" ] || {
( . /etc/uci-defaults/luci-qosv4 ) && rm -f /etc/uci-defaults/luci-qosv4
chmod 755 /etc/init.d/qosv4 >/dev/null 2>&1
/etc/init.d/qosv4 enable >/dev/null 2>&1
sed -i -e '/qos_scheduler/d' /etc/crontabs/root >/dev/null 2>&1
exit 0
}
endef
define Package/luci-app-qosv4/install
$(CP) ./files/* $(1)
endef
$(eval $(call BuildPackage,luci-app-qosv4))

View File

@ -0,0 +1,29 @@
config 'qos_settings'
option 'UP' '100'
option 'DOWN' '500'
option 'UPLOADR' '2'
option 'DOWNLOADR' '2'
option 'UPLOADR2' '1'
option 'UPLOADC2' '5'
option 'DOWNLOADR2' '1'
option 'DOWNLOADC2' '2'
option 'qos_scheduler' '1'
option 'enable' '0'
config 'qos_ip'
option 'limit_ip' '192.168.1.5'
option 'UPLOADC' '15'
option 'DOWNLOADC' '15'
option 'ip_prio' '5'
option 'enable' '0'
config 'qos_nolimit_ip'
option 'nolimit_ip' '192.168.1.1'
option 'nolimit_mac' '00:00:00:00:00'
option 'enable' '0'
config 'transmission_limit'
option 'downlimit' '100'
option 'uplimit' '100'
option 'enable' '0'

View File

@ -0,0 +1,16 @@
#!/bin/sh /etc/rc.common
START=60
start(){
enabled=$(uci get qosv4.@qos_settings[0].enable)
[ "$enabled" -eq 1 ] && /usr/bin/qosv4 start
}
stop(){
/usr/bin/qosv4 stop
}
restart(){
/etc/init.d/qosv4 stop
/etc/init.d/qosv4 start
}

View File

@ -0,0 +1,10 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@qosv4[-1]
add ucitrack qosv4
set ucitrack.@qosv4[-1].init=qosv4
commit ucitrack
EOF
rm -f /tmp/luci-indexcahe
exit 0

View File

@ -0,0 +1,432 @@
#!/bin/sh
#copyright by zhoutao0712 V4 moded zjhzzyf
#变量初始化(速率单位是KB/S)
#UIP="192.168.1."
#NET="192.168.100.0/24"
#IPS="2"
#IPE="20"
#UP=1135
#DOWN=1175
#UPLOADR=1
#UPLOADC=15
#DOWNLOADR=10
#DOWNLOADC=$((DOWN*9/10))
#UPLOADR2=1
#UPLOADC2=5
#DOWNLOADR2=2
#DOWNLOADC2=$((DOWN*6/10))
#DOWNLOADR 单IP下载保证带宽DOWNLOADR=10
#DOWNLOADc 单IP下载最大带宽DOWNLOADC=$((DOWN*9/10))
#IPS限速开始IP地址
#IPE限速结束IP地址
#IPS和IPE最好不要填写2---254不然脚本运行时间会比较长会多占用一些内存。
#效率上如果采用了u32 hashv4.0和智能QOS修改版本就没影响。
#UP=35总上传带宽。我的ADSL虽然是50KB/S但为了低延迟还是填35吧
#DOWN=175:总下载带宽。我的ADSL虽然是205KB/S但为了低延迟还是填175吧
#UPLOADR=2:单IP上传保证带宽2KB/S就很OK了。
#UPLOADC=15:单IP上传最大带宽ADSL也就填这么多了搞QQ视频应该没问题。
#UPLOADR2=1:被“惩罚”后的单IP上传保证带宽。
#UPLOADC2=5被“惩罚”后的单IP上传UDP最大带宽一般不要超过6KB/S
#DOWNLOADR2:被“惩罚”后的单IP下载保证带宽
#DOWNLOADC2:被“惩罚”后的单IP下载最大带宽
. /lib/functions.sh
load_modules(){
rmmod imq
insmod imq numdevs=1
insmod cls_fw
insmod sch_hfsc
insmod sch_sfq
insmod sch_red
insmod sch_htb
insmod sch_prio
insmod ipt_multiport
insmod ipt_CONNMARK
insmod ipt_length
insmod ipt_IMQ
insmod ipt_hashlimit
insmod cls_u32
insmod xt_connlimit
insmod xt_connbytes
}
lan_net(){
lan_ipaddr=$(uci get network.lan.ipaddr)
lan_netmask=$(uci get network.lan.netmask)
calc=$(ipcalc.sh $lan_ipaddr $lan_netmask)
prefix=${calc##*=}
lan_network=${calc##*NETWORK=}
lan_network=$(echo $lan_network | sed 's/.PRE.*//')
NET="$lan_network/$prefix"
UIP=$(uci get network.lan.ipaddr|awk -F "." '{print $1"."$2"."$3 }')
qos_interface=$(uci -P /var/state get network.wan.ifname 2>/dev/null)
if [ -z $qos_interface ] ; then
qos_interface=$(uci -P /var/state get network.wan.device 2>/dev/null)
fi
}
#装载核心模块,创建QOS专用链
qos_start(){
#ifconfig $qos_interface up
ifconfig imq0 up
iptables -t mangle -N QOSDOWN
iptables -t mangle -N QOSUP
iptables -t mangle -A PREROUTING ! -p icmp -s $NET ! -d $NET -j QOSUP
#iptables -t mangle -I PREROUTING -p udp --dport 53 -j ACCEPT
iptables -t mangle -I POSTROUTING ! -p icmp -d $NET ! -s $NET -j QOSDOWN
#iptables -t mangle -I POSTROUTING -p udp --dport 53 -j ACCEPT
iptables -t mangle -A OUTPUT -o br-lan -j ACCEPT
iptables -t mangle -A INPUT -i br-lan -j ACCEPT
#iptables -t mangle -I OUTPUT -p udp --dport 53 -j ACCEPT
#iptables -t mangle -I INPUT -p udp --dport 53 -j ACCEPT
#iptables -t mangle -A OUTPUT -d ! $NET -j QOSUP
iptables -t mangle -A INPUT ! -s $NET -j QOSDOWN
#iptables -t mangle -A OUTPUT -j QOSUP
#iptables -t mangle -A INPUT -j QOSDOWN
iptables -t mangle -A QOSDOWN -p udp -m multiport --ports 53,67,68 -j RETURN
iptables -t mangle -A QOSUP -p udp -m multiport --destination-port 53,67,68 -j RETURN
iptables -t mangle -N PUNISH0
iptables -t mangle -A QOSUP -p udp -j PUNISH0
iptables -t mangle -A PUNISH0 -m hashlimit --hashlimit 100/sec --hashlimit-mode srcip --hashlimit-name udplmt -j RETURN
iptables -t mangle -A PUNISH0 -m recent --rcheck --seconds 20 -j DROP
iptables -t mangle -A PUNISH0 -m recent --set
iptables -t mangle -N NEWCONN
iptables -t mangle -A QOSUP -m state --state NEW -j NEWCONN
echo "line 99"
iptables -t mangle -A NEWCONN ! -p tcp -m connlimit --connlimit-above 100 -j DROP
iptables -t mangle -A NEWCONN -p tcp -m connlimit --connlimit-above 200 -j DROP
echo "line 102"
iptables -t mangle -A QOSDOWN -p tcp ! --syn -m length --length :128 -j RETURN
iptables -t mangle -A QOSUP -p tcp ! --syn -m length --length :80 -j RETURN
iptables -t mangle -A QOSDOWN -d $NET -j IMQ --todev 0
#iptables -t mangle -A QOSUP -s $NET -j IMQ --todev 1
#小包web浏览和游戏爆发队列限速 #在5秒内平均下载速率小于10KB/S的IP进入高优先级队列253
iptables -t mangle -N BCOUNT
iptables -t mangle -A QOSDOWN -p tcp -m length --length :768 -j MARK --set-mark 255
iptables -t mangle -A QOSUP -p tcp -m length --length :512 -j MARK --set-mark 255
iptables -t mangle -A QOSDOWN -p tcp -m multiport --sports 80,443,25,110 -j BCOUNT
echo "line 117"
iptables -t mangle -A QOSUP -p tcp -m multiport --sports 80,443,25,110 -m connbytes --connbytes :51200 --connbytes-dir both --connbytes-mode bytes -j MARK --set-mark 254
iptables -t mangle -A QOSUP -p tcp -m multiport --dports 80,443,25,110 -j BCOUNT
iptables -t mangle -A QOSDOWN -p tcp -m multiport --sports 80,443,25,110 -m connbytes --connbytes :102400 --connbytes-dir both --connbytes-mode bytes -j MARK --set-mark 254
echo "line 121"
#iptables -t mangle -A QOSDOWN -p tcp -m multiport --sports 80,443,25,110 -m bcount --range :153600 -j MARK --set-mark 254
#iptables -t mangle -A QOSUP -p tcp -m multiport --dports 80,443,25,110 -m bcount --range :51200 -j MARK --set-mark 254
iptables -t mangle -A QOSDOWN -m recent --rdest --rcheck --seconds 120 -j MARK --set-mark 253
iptables -t mangle -A QOSUP -p udp -m recent --rcheck --seconds 120 -j MARK --set-mark 253
iptables -t mangle -A QOSDOWN -j MARK --set-mark 252
iptables -t mangle -A QOSUP -j MARK --set-mark 252
echo "line 129"
#根队列初始化
#tc qdisc del dev imq0 root
#tc qdisc del dev $qos_interface root
echo "line 133"
tc qdisc add dev imq0 root handle 1: htb default 999
tc qdisc add dev $qos_interface root handle 1: htb default 999
tc class add dev $qos_interface parent 1: classid 1:1 htb rate $((UP))kbps
tc class add dev imq0 parent 1: classid 1:1 htb rate $((DOWN))kbps
#小包web浏览和游戏爆发队列限速
tc class add dev imq0 parent 1:1 classid 1:5000 htb rate $((DOWN/5))kbps quantum 15000 prio 1
tc filter add dev imq0 parent 1:0 protocol ip prio 5 handle 255 fw flowid 1:5000
tc class add dev $qos_interface parent 1:1 classid 1:5000 htb rate $((UP))kbps quantum 15000 prio 1
tc filter add dev $qos_interface parent 1:0 protocol ip prio 5 handle 255 fw flowid 1:5000
tc class add dev imq0 parent 1:1 classid 1:4000 htb rate $((DOWN/10))kbps ceil $((DOWN*6/10))kbps quantum 8000 prio 3
tc filter add dev imq0 parent 1:0 protocol ip prio 10 handle 254 fw flowid 1:4000
tc class add dev $qos_interface parent 1:1 classid 1:4000 htb rate $((UP/10))kbps ceil $((UP/2))kbps quantum 1500 prio 3
tc filter add dev $qos_interface parent 1:0 protocol ip prio 10 handle 254 fw flowid 1:4000
tc class add dev $qos_interface parent 1:1 classid 1:3000 htb rate $((UP/3))kbps ceil $((UP))kbps
tc class add dev imq0 parent 1:1 classid 1:3000 htb rate $((DOWN/3))kbps ceil $((DOWN))kbps
tc filter add dev $qos_interface parent 1:0 protocol ip prio 20 handle 253 fw flowid 1:3000
tc filter add dev imq0 parent 1:0 protocol ip prio 20 handle 253 fw flowid 1:3000
tc class add dev $qos_interface parent 1:1 classid 1:2000 htb rate $((UP*2/3))kbps ceil $((UP))kbps
tc class add dev imq0 parent 1:1 classid 1:2000 htb rate $((DOWN*2/3))kbps ceil $((DOWN))kbps
tc filter add dev $qos_interface parent 1:0 protocol ip prio 15 handle 252 fw flowid 1:2000
tc filter add dev imq0 parent 1:0 protocol ip prio 15 handle 252 fw flowid 1:2000
tc filter add dev imq0 parent 1:3000 prio 200 handle f0: protocol ip u32 divisor 256
tc filter add dev imq0 protocol ip parent 1:3000 prio 200 u32 ht 800:: match ip dst $NET hashkey mask 0x000000ff at 16 link f0:
tc filter add dev $qos_interface parent 1:3000 prio 200 handle f0: protocol ip u32 divisor 256
tc filter add dev $qos_interface protocol ip parent 1:3000 prio 200 u32 ht 800:: match ip src $NET hashkey mask 0x000000ff at 12 link f0:
tc filter add dev imq0 parent 1:2000 prio 100 handle f1: protocol ip u32 divisor 256
tc filter add dev imq0 protocol ip parent 1:2000 prio 100 u32 ht 801:: match ip dst $NET hashkey mask 0x000000ff at 16 link f1:
tc filter add dev $qos_interface parent 1:2000 prio 100 handle f1: protocol ip u32 divisor 256
tc filter add dev $qos_interface protocol ip parent 1:2000 prio 100 u32 ht 801:: match ip src $NET hashkey mask 0x000000ff at 12 link f1:
}
#所有普通IP单独限速
qos_ip_limit()
{
n=$(echo $limit_ip |cut -d "." -f4)
m=$(printf "%x\n" $n)
echo "$limit_ip start limit"
tc class add dev $qos_interface parent 1:3000 classid 1:${n}f htb rate $((UPLOADR2))kbps ceil $((UPLOADC2))kbps quantum 1500 prio 7
tc class add dev imq0 parent 1:3000 classid 1:${n}f htb rate $((DOWNLOADR2))kbps ceil $((DOWNLOADC2))kbps quantum 1500 prio 7
tc qdisc add dev $qos_interface parent 1:${n}f handle ${n}f bfifo limit 8kb
tc qdisc add dev imq0 parent 1:${n}f handle ${n}f sfq perturb 15
tc filter add dev $qos_interface parent 1:3000 protocol ip prio 200 u32 ht f0:${m}: match ip src 0/0 flowid 1:${n}f
tc filter add dev imq0 parent 1:3000 protocol ip prio 200 u32 ht f0:${m}: match ip dst 0/0 flowid 1:${n}f
tc class add dev $qos_interface parent 1:2000 classid 1:${n}a htb rate $((UPLOADR))kbps ceil $((UPLOADC))kbps quantum 1500 prio $ip_prio
tc class add dev imq0 parent 1:2000 classid 1:${n}a htb rate $((DOWNLOADR))kbps ceil $((DOWNLOADC))kbps quantum 2000 prio $ip_prio
tc qdisc add dev $qos_interface parent 1:${n}a handle ${n}a bfifo limit 8kb
tc qdisc add dev imq0 parent 1:${n}a handle ${n}a sfq perturb 15
tc filter add dev $qos_interface parent 1:2000 protocol ip prio 100 u32 ht f1:${m}: match ip src 0/0 flowid 1:${n}a
tc filter add dev imq0 parent 1:2000 protocol ip prio 100 u32 ht f1:${m}: match ip dst 0/0 flowid 1:${n}a
echo "$limit_ip end limit"
}
qos_ip_limit_2(){
tc class add dev $qos_interface parent 1:1 classid 1:999 htb rate 1kbps ceil $((UP/5))kbps quantum 1500 prio 7
tc class add dev imq0 parent 1:1 classid 1:999 htb rate 2kbps ceil $((DOWN))kbps quantum 1500 prio 7
}
#192.168.1.8,192.168.1.80-192.168.1.90有zhoutao0712发放的免死金牌
qos_else(){
#iptables -t mangle -I PUNISH0 -m iprange --src-range 192.168.1.80-192.168.1.90 -j RETURN
iptables -t mangle -I PUNISH0 -s $nolimit_ip -j RETURN
}
qos_transmission_limit(){
config_get enable $1 enable
config_get downlimit $1 downlimit
config_get uplimit $1 uplimit
echo "transmission limit enable=$enable "
transmission_enable=$(uci get transmission.@transmission[0].enable)
transmission_enabled=$(uci get transmission.@transmission[0].enabled)
echo "transmission-daemon enable=$transmission_enable"
echo "transmission-daemon enabled=$transmission_enabled"
if [ "$transmission_enable" == "1" -o "$transmission_enabled" == "1" ];then
if [ "$enable" == "1" ];then
echo " transmission limit ........downlimit=$downlimit uplimit=$uplimit"
transmission-remote --downlimit $downlimit
transmission-remote --uplimit $uplimit
echo "1" > /tmp/transmission-flag
else
echo " transmission no limit oooooooooo"
transmission-remote --no-downlimit
transmission-remote --no-uplimit
echo "0" > /tmp/transmission-flag
fi
fi
}
#加入例行任务5分钟执行一次当有ip在线的时候按需开启transmission限速和qos限速。
#当只有1个IP在线时候关闭内网QOS限速 。
#当没有IP在线时候关闭QOS限速 关闭transmission限速。
qos_scheduler(){
echo "qos_scheduler start....."
wait_time="5"
[ -z "$(cat /etc/crontabs/root| grep qos_scheduler)" ]&&echo -e "*/${wait_time} * * * * sh /tmp/qos_scheduler #qos_scheduler#" >> /etc/crontabs/root
cat >/tmp/qos_scheduler <<"EOF"
[ -e /tmp/qosv4_nolimit_mac ]&&mac_list=$(cat /tmp/qosv4_nolimit_mac)
local RUN="ip neigh| grep : ${mac_list} |grep -c $UIP"
ip_num=`eval $RUN`
old_ip_num=$(cat /tmp/qosv4_old_ip_num)
echo "new_ip_num=$ip_num"
echo "old_ip_num=$old_ip_num"
#如果在线ip和上次相同 退出
if [ "$ip_num" -eq "$old_ip_num" ]
then
ip neigh flush dev br-lan
echo "在线ip和上次相同"
exit
fi
qosv4_transmission_enabl=$(uci get qosv4.@transmission_limit[0].enable)
qosv4_transmission_uplimit=$(uci get qosv4.@transmission_limit[0].uplimit)
qosv4_transmission_downlimit=$(uci get qosv4.@transmission_limit[0].downlimit)
transmission_enable=$(uci get transmission.@transmission[0].enable)
transmission_enabled=$(uci get transmission.@transmission[0].enabled)
#如果在线ip为1 关闭内网QOS限速 根据设置开启 transmission限速然后退出
if [ "$ip_num" -eq "1" ]
then
ifconfig imq0 down
ifconfig $qos_interface down
if [ "$transmission_enable" == "1" -o "$transmission_enabled" == "1" ];then
if [ "$qosv4_transmission_enabl" == "1" ];then
echo " transmission limit ........downlimit=$qosv4_transmission_downlimit uplimit=$qosv4_transmission_uplimit"
transmission-remote --downlimit $qosv4_transmission_downlimit
transmission-remote --uplimit $qosv4_transmission_uplimit
else
transmission-remote --no-downlimit
transmission-remote --no-uplimit
echo "0" > /tmp/transmission-flag
fi
fi
ip neigh flush dev br-lan
echo "$ip_num" >/tmp/qosv4_old_ip_num
exit
fi
# 当没有IP在线时候关闭QOS限速 关闭transmission限速。
if [ "$ip_num" -eq "0" ]
then
ifconfig imq0 down
ifconfig $qos_interface down
if [ "$transmission_enable" == "1" -o "$transmission_enabled" == "1" ];then
transmission-remote --no-downlimit
transmission-remote --no-uplimit
fi
fi
ip neigh flush dev br-lan
echo "$ip_num" >/tmp/qosv4_old_ip_num
exit
fi
#如果在线ip 大于1 开启内网QOS限速 开启transmission限速
if [ $(ifconfig |grep -c imq0) -eq 0 ]
then
ifconfig imq0 up
ifconfig $qos_interface up
echo "cass 3"
fi
if [ "$transmission_enable" == "1" -o "$transmission_enabled" == "1" ];then
if [ "$qosv4_transmission_enabl" == "1" ];then
echo " transmission limit ........downlimit=$qosv4_transmission_downlimit uplimit=$qosv4_transmission_uplimit"
transmission-remote --downlimit $qosv4_transmission_downlimit
transmission-remote --uplimit $qosv4_transmission_uplimit
else
transmission-remote --no-downlimit
transmission-remote --no-uplimit
echo "0" > /tmp/transmission-flag
fi
fi
ip neigh flush dev br-lan
echo "$ip_num" >/tmp/qosv4_old_ip_num
EOF
}
qos_stop(){
for iface in $(tc qdisc show | grep htb | awk '{print $5}'); do
tc qdisc del dev "$iface" root
done
iptables -t mangle -D PREROUTING ! -p icmp -s $NET ! -d $NET -j QOSUP
iptables -t mangle -D POSTROUTING ! -p icmp -d $NET ! -s $NET -j QOSDOWN
iptables -t mangle -D OUTPUT -o br-lan -j ACCEPT
iptables -t mangle -D INPUT -i br-lan -j ACCEPT
iptables -t mangle -D OUTPUT ! -d $NET -j QOSUP
iptables -t mangle -D INPUT ! -s $NET -j QOSDOWN
iptables -t mangle -D OUTPUT -j QOSUP
iptables -t mangle -D INPUT -j QOSDOWN
iptables -t mangle -D PREROUTING -p udp --dport 53 -j ACCEPT
iptables -t mangle -D POSTROUTING -p udp --dport 53 -j ACCEPT
iptables -t mangle -D OUTPUT -p udp --dport 53 -j ACCEPT
iptables -t mangle -D INPUT --dport 53 -j ACCEPT
iptables -t mangle -F QOSDOWN
iptables -t mangle -F QOSUP
iptables -t mangle -F PUNISH0
iptables -t mangle -F NEWCONN
iptables -t mangle -F BCOUNT
iptables -t mangle -X QOSDOWN
iptables -t mangle -X QOSUP
iptables -t mangle -X PUNISH0
iptables -t mangle -X NEWCONN
iptables -t mangle -X BCOUNT
}
qos_config_get(){
config_get enable $1 enable
config_get UP $1 UP
config_get DOWN $1 DOWN
config_get UPLOADR2 $1 UPLOADR2
config_get UPLOADC2 $1 UPLOADC2
config_get DOWNLOADR2 $1 DOWNLOADR2
config_get DOWNLOADC2 $1 DOWNLOADC2
config_get UPLOADR $1 UPLOADR
config_get DOWNLOADR $1 DOWNLOADR
config_get qos_scheduler $1 qos_scheduler
}
qos_limit_ip_get(){
config_get enable $1 enable
config_get limit_ip $1 limit_ip
config_get ip_prio $1 ip_prio
config_get UPLOADC $1 UPLOADC
config_get DOWNLOADC $1 DOWNLOADC
echo "enable=$enable limit_ip=$limit_ip ip_prio=$ip_prio UPLOADC=$UPLOADC DOWNLOADC=$DOWNLOADC"
[ "$enable" == "1" ]&&qos_ip_limit
}
qos_nolimit_ip_get(){
config_get enable $1 enable
config_get nolimit_ip $1 nolimit_ip
config_get nolimit_mac $1 nolimit_mac
[ "$enable" == "1" ]&&printf "|grep -v $nolimit_mac " >>/tmp/qosv4_nolimit_mac
}
lan_net
config_load qosv4
case $1 in
start)
qos_stop >/dev/null 2>&1
qos_stop >/dev/null 2>&1
echo "start qos v4........"
rm -rf /tmp/qosv4_nolimit_mac
config_foreach qos_config_get qos_settings
echo "qosv4 enable=$enable "
if [ "$enable" == "1" ];then
load_modules >/dev/null 2>&1
qos_start
config_foreach qos_limit_ip_get qos_ip
qos_ip_limit_2
config_foreach qos_nolimit_ip_get qos_nolimit_ip
config_foreach qos_transmission_limit transmission_limit
else
qos_stop >/dev/null 2>&1
qos_stop >/dev/null 2>&1
fi
echo "qos_scheduler = $qos_scheduler"
if [ "$qos_scheduler" == "1" ];then
qos_scheduler
else
[ -n "$(cat /etc/crontabs/root| grep qos_scheduler)" ]&& sed -i -e '/qos_scheduler/d' /etc/crontabs/root
fi
;;
stop)
qos_stop >/dev/null 2>&1
qos_stop >/dev/null 2>&1
;;
esac

View File

@ -0,0 +1,17 @@
module("luci.controller.qosv4", package.seeall)
function index()
require("luci.i18n")
luci.i18n.loadc("qosv4")
local fs = luci.fs or nixio.fs
if not fs.access("/etc/config/qosv4") then
return
end
local page = entry({"admin", "network", "qosv4"}, cbi("qosv4"), "QOSv4")
page.i18n = "qosv4"
page.dependent = true
end

View File

@ -0,0 +1,163 @@
require("luci.tools.webadmin")
--[[
config 'qos_settings'
option 'enable' '0'
option 'UP' '100'
option 'DOWN' '500'
option qos_scheduler 1
config 'qos_ip'
option 'enable' '0'
option 'limit_ip' '192.168.1.5'
option 'UPLOADR' '2'
option 'DOWNLOADR' '2'
option 'UPLOADC' '15'
option 'DOWNLOADC' '15'
option 'UPLOADR2' '1'
option 'UPLOADC2' '5'
option 'DOWNLOADR2' '1'
option 'DOWNLOADC2' '2'
config 'qos_nolimit_ip'
option 'enable' '0'
option 'limit_ip' '192.168.1.6'
]]--
local sys = require "luci.sys"
m = Map("qosv4", translate("qosv4 title","QOSv4"),
translate("qosv4 title desc","qosv4 title desc"))
s = m:section(TypedSection, "qos_settings", translate("qos goble setting","qos goble setting"))
s.anonymous = true
s.addremove = false
enable = s:option(Flag, "enable", translate("qos enable", "qos enable"))
enable.default = false
enable.optional = false
enable.rmempty = false
qos_scheduler = s:option(Flag, "qos_scheduler", translate("qos scheduler enable", "qos scheduler enable"),
translate("qos scheduler desc","qos scheduler desc"))
qos_scheduler.default = false
qos_scheduler.optional = false
qos_scheduler.rmempty = false
DOWN = s:option(Value, "DOWN", translate("DOWN speed","DOWN speed"),
translate("DOWN speed desc","DOWN speed desc"))
DOWN.optional = false
DOWN.rmempty = false
UP = s:option(Value, "UP", translate("UP speed","UP speed"),
translate("UP speed desc","UP speed desc"))
UP.optional = false
UP.rmempty = false
DOWNLOADR = s:option(Value, "DOWNLOADR", translate("DOWNLOADR speed","DOWNLOADR speed"))
DOWNLOADR.optional = false
DOWNLOADR.rmempty = false
UPLOADR = s:option(Value, "UPLOADR", translate("UPLOADR speed","UPLOADR speed"))
UPLOADR.optional = false
UPLOADR.rmempty = false
DOWNLOADR2 = s:option(Value, "DOWNLOADR2", translate("DOWNLOADR2 speed","DOWNLOADR2 speed"))
DOWNLOADR2.optional = false
DOWNLOADR2.rmempty = false
UPLOADR2 = s:option(Value, "UPLOADR2", translate("UPLOADR2 speed","UPLOADR2 speed"))
UPLOADR2.optional = false
UPLOADR2.rmempty = false
DOWNLOADC2 = s:option(Value, "DOWNLOADC2", translate("DOWNLOADC2 speed","DOWNLOADC2 speed"))
DOWNLOADC2.optional = false
DOWNLOADC2.rmempty = false
UPLOADC2 = s:option(Value, "UPLOADC2", translate("UPLOADC2 speed","UPLOADC2 speed"))
UPLOADC2.optional = false
UPLOADC2.rmempty = false
s = m:section(TypedSection, "qos_ip", translate("qos black ip","qos black ip"))
s.template = "cbi/tblsection"
s.anonymous = true
s.addremove = true
enable = s:option(Flag, "enable", translate("enable", "enable"))
enable.default = false
enable.optional = false
enable.rmempty = false
limit_ip = s:option(Value, "limit_ip", translate("limit_ip","limit_ip"))
limit_ip.rmempty = true
luci.tools.webadmin.cbi_add_knownips(limit_ip)
DOWNLOADC = s:option(Value, "DOWNLOADC", translate("DOWNLOADC speed","DOWNLOADC speed"))
DOWNLOADC.optional = false
DOWNLOADC.rmempty = false
UPLOADC = s:option(Value, "UPLOADC", translate("UPLOADC speed","UPLOADC speed"))
UPLOADC.optional = false
UPLOADC.rmempty = false
ip_prio = s:option(Value, "ip_prio", translate("ip prio","ip prio"),
translate("ip prio desc"," default 5 "))
ip_prio.optional = false
ip_prio.rmempty = false
s = m:section(TypedSection, "transmission_limit", translate("transmission limit","transmission limit"))
s.template = "cbi/tblsection"
s.anonymous = true
s.addremove = false
enable = s:option(Flag, "enable", translate("enable", "enable"))
enable.default = false
enable.optional = false
enable.rmempty = false
downlimit= s:option(Value, "downlimit", translate("downlimit speed","downlimit speed"))
downlimit.optional = false
downlimit.rmempty = false
uplimit= s:option(Value, "uplimit", translate("uplimit speed","uplimit speed"))
uplimit.optional = false
uplimit.rmempty = false
s = m:section(TypedSection, "qos_nolimit_ip", translate("qos white","qos white"))
s.template = "cbi/tblsection"
s.anonymous = true
s.addremove = true
enable = s:option(Flag, "enable", translate("enable", "enable"))
enable.default = false
enable.optional = false
enable.rmempty = false
nolimit_mac= s:option(Value, "nolimit_mac", translate("white mac","white mac"))
nolimit_mac.rmempty = true
nolimit_ip= s:option(Value, "nolimit_ip", translate("white ip","white ip"))
nolimit_ip.rmempty = true
luci.ip.neighbors(function(entry)
nolimit_ip:value(entry["IP address"])
nolimit_mac:value(
entry["HW address"],
entry["HW address"] .. " (" .. entry["IP address"] .. ")"
)
end)
return m

View File

@ -0,0 +1,66 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=qos-gargoyle
PKG_VERSION:=1.0.1
PKG_RELEASE:=1
PKG_LICENSE:=GPL-3.0+
PKG_MAINTAINER:=Xingwang Liao <kuoruan@gmail.com>
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/qos-gargoyle
SECTION:=net
CATEGORY:=Gargoyle
SUBMENU:=Network
TITLE:=A set of QoS scripts
DEPENDS:=+tc +ip +kmod-sched +iptables-mod-filter +iptables-mod-ipopt +iptables-mod-imq
MAINTAINER:=Xingwang Liao <kuoruan@gmail.com>
PKGARCH:=all
endef
define Package/qos-gargoyle/description
A set of QoS scripts from Gargoyle
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
$(call Build/Prepare/Default)
endef
define Build/Configure
endef
define Build/Compile
-$(MAKE) -C $(PKG_BUILD_DIR) clean
$(MAKE) -C $(PKG_BUILD_DIR) \
$(TARGET_CONFIGURE_OPTS) \
CFLAGS="$(TARGET_CFLAGS) -I $(STAGING_DIR)/usr/include" \
LDFLAGS="$(TARGET_LDFLAGS) -L $(STAGING_DIR)/usr/lib" \
BUILD_DIR="$(BUILD_DIR)" \
all
endef
define Package/qos-gargoyle/install
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_DIR) $(1)/etc/hotplug.d/iface
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_BIN) ./files/qos_gargoyle.init $(1)/etc/init.d/qos_gargoyle
$(INSTALL_BIN) ./files/qos_gargoyle.hotplug $(1)/etc/hotplug.d/iface/23-qos_gargoyle
$(INSTALL_DATA) ./files/qos_gargoyle.conf $(1)/etc/config/qos_gargoyle
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/qosmon $(1)/usr/sbin/qosmon
endef
define Package/qos-gargoyle/prerm
#!/bin/sh
$${IPKG_INSTROOT}/etc/init.d/qos_gargoyle stop 2>/dev/null
$${IPKG_INSTROOT}/etc/init.d/qos_gargoyle disable 2>/dev/null
exit 0
endef
$(eval $(call BuildPackage,qos-gargoyle))

View File

@ -0,0 +1,119 @@
config global global
option mtu "1500" # the maximum allowed packet size (in bytes) on the interface being shaped
option network "wan" # the name of the network whose interface should have its outgoing traffic shaped
#option interface "eth1" # the name of the network interface which should have its outgoing traffic shaped
config upload upload
option total_bandwidth "600" # 600kbit/s
option default_class "uclass_2" # default traffic class, must be a section of type upload_class
config download download
option total_bandwidth "4000" # 4000kbit/s (500Kbyte/s)
option default_class "dclass_1" # default traffic class, must be a section of type download_class
#upload classes
config upload_class uclass_1
option percent_bandwidth "40" # percent of total bandwidth to use
config upload_class uclass_2
option percent_bandwidth "20" # percent of total bandwidth to use
option max_bandwidth "30" # max bandwidth useage in absolute speed (kbit/s)
config upload_class uclass_3
option percent_bandwidth "30" # percent of total bandwidth to use
option min_bandwidth "160" # min bandwidth to allocate to this class
config upload_class uclass_4
option percent_bandwidth "10" # percent of total bandwidth to use
#download classes
config download_class dclass_1
option percent_bandwidth "30" # percent of total bandwidth to use
option min_bandwidth "80" # min bandwidth to allocate to this class
config download_class dclass_2
option percent_bandwidth "60" # percent of total bandwidth to use
config download_class dclass_3
option percent_bandwidth "10" # percent of total bandwidth to use
option min_bandwidth "80" # min bandwidth to allocate to this class
# classification rules
#
# POSSIBLE OPTIONS:
# class name of bandwidth class to use if rule matches, this is required in each rule section
# test_order an integer that specifies the order in which the rule should be checked for a match (lower numbers are checked first)
# proto check that packet has this protocol (tcp, udp, both), if port is specified default is both
# source check that packet has this source ip, can optionally have /[mask] after it (see -s option in iptables man page)
# destination check that packet has this destination ip, can optionally have /[mask] after it (see -d option in iptables man page)
# dport check that packet has this destination port
# sport check that packet has this source port
# min_pkt_size check that packet is at least this size (in bytes)
# max_pkt_size check that packet is no larger than this size (in bytes)
# layer7 check whether packet matches layer7 specification
# ipp2p check wither packet matches ipp2p specification (used to recognize p2p protocols)
# "ipp2p" or "all" will match any of the specified p2p protocols, you can
# also specifically match any protocol listed in the documentation here:
# http://ipp2p.org/docu_en.html
#
# sytnax for upload rules and download rules is identical
config upload_rule
option class "uclass_4"
option test_order "1"
option destination "195.56.146.238"
config upload_rule
option class "uclass_3"
option test_order "2"
option proto "both"
option dstport "80-90"
config upload_rule
option class "uclass_1"
option test_order "3"
option dstport "22"
config upload_rule
option class "uclass_3"
option test_order "44"
option proto "udp"
option max_pkt_size "250"
config upload_rule
option class "uclass_3"
option test_order "5"
option proto "udp"
option max_pkt_size "250"
config upload_rule
option class "uclass_4"
option test_order "6"
option ipp2p "all"
config upload_rule
option class "uclass_3"
option test_order "7"
option layer7 "pop3"
#download rules
config download_rule
option class "dclass_2"
option test_order "1"
option dstport "80-90"
config download_rule
option class "dclass_3"
option test_order "2"
option ipp2p "all"

View File

@ -0,0 +1,26 @@
config upload 'upload'
option default_class 'uclass_2'
option total_bandwidth '1000'
config download 'download'
option qos_monenabled 'true'
option total_bandwidth '10000'
option default_class 'dclass_1'
config download_class 'dclass_1'
option name 'Normal'
option percent_bandwidth '100'
config upload_class 'uclass_1'
option name 'Fast'
option percent_bandwidth '90'
config upload_class 'uclass_2'
option name 'Normal'
option percent_bandwidth '10'
config upload_rule 'upload_rule_100'
option class 'uclass_1'
option max_pkt_size '128'

View File

@ -0,0 +1,20 @@
#!/bin/sh
[ "$ACTION" = "ifup" ] || [ "$ACTION" = "ifdown" ] || exit 0
. /lib/functions/network.sh
wan_net_name=
network_find_wan wan_net_name
if [ "$INTERFACE" = "$wan_net_name" ]; then
if [ -h /etc/rc.d/S50qos_gargoyle ]; then
if [ "$ACTION" = "ifup" ]; then
/etc/init.d/qos_gargoyle start $DEVICE
fi
fi
if [ "$ACTION" = "ifdown" ]; then
/etc/init.d/qos_gargoyle stop
fi
fi

View File

@ -0,0 +1,771 @@
#!/bin/sh /etc/rc.common
#
# Copyright (c) 2008 Eric Bishop
# Copyright (c) 2016 GuoGuo<gch981213@gmail.com>
# Copyright (c) 2017 Xingwang Liao<kuoruan@gmail.com>
# This is free software licensed under the terms of the GNU GPL v2.0
#
START=50
EXTRA_COMMANDS=show
EXTRA_HELP=" show Show current Qos configuration (if active)"
config_file_name="qos_gargoyle"
upload_mask="0x007F"
download_mask="0x7F00"
qos_mark_file="/etc/qos_class_marks"
# created while qos is being initialized so hotplug and init script don't
# both try to initialize qos at the same time
lock_file="/var/run/qos_updating"
load_all_config_options()
{
local config_name="$1"
local section_id="$2"
ALL_OPTION_VARIABLES=""
# this callback loads all the variables
# in the section_id section when we do
# config_load. We need to redefine
# the option_cb for different sections
# so that the active one isn't still active
# after we're done with it. For reference
# the $1 variable is the name of the option
# and $2 is the name of the section
config_cb()
{
if [ ."$2" = ."$section_id" ]; then
option_cb()
{
ALL_OPTION_VARIABLES="$ALL_OPTION_VARIABLES $1"
}
else
option_cb() { return 0; }
fi
}
config_load "$config_name"
for var in $ALL_OPTION_VARIABLES
do
config_get "$var" "$section_id" "$var"
done
}
load_all_config_sections()
{
local config_name="$1"
local section_type="$2"
all_config_sections=""
get_section_names()
{
all_config_sections="$all_config_sections $1"
}
config_load "$config_name"
config_foreach get_section_names "$section_type"
echo "$all_config_sections"
}
get_classname_mark()
{
local class="$1"
local class_mark_list="$2"
echo "$class_mark_list" | awk -v class="$class" '{ for (i = 1; i <= NF; i++){ if($i~class":"){ gsub(class":",""); print $i } }}'
}
apply_all_rules()
{
local rule_type="$1"
local class_mark_list="$2"
local chain="$3"
local table="$4"
local need_proto
local tmp_proto
# add filter rules
rule_list=$(load_all_config_sections "$config_file_name" "$rule_type")
for rule in $rule_list ; do
class=""
proto=""
min_pkt_size=""
max_pkt_size=""
match_str=""
need_proto=""
load_all_config_options "$config_file_name" "$rule"
for option in $ALL_OPTION_VARIABLES ; do
option_value=$(eval echo \$$option)
case "$option" in
source)
if [ "$3" = "qos_egress" ] ; then
if [ "$option_value" = "$local_ip" ] || [ "$option_value" = "$wan_ip" ]; then
option_value="$wan_ip"
fi
fi
match_str="$match_str -s $option_value"
;;
destination)
if [ "$3" = "qos_ingress" ] ; then
if [ "$option_value" = "$local_ip" ] || [ "$option_value" = "$wan_ip" ]; then
option_value="$wan_ip"
fi
fi
match_str="$match_str -d $option_value"
;;
srcport)
if [ -n "$(echo $option_value | grep '-')" ] ; then option_value="$(echo "$option_value" | sed -e 's,-,:,g')" ; fi
match_str="$match_str --sport $option_value"
need_proto="1"
;;
dstport)
if [ -n "$(echo $option_value | grep '-')" ] ; then option_value="$(echo "$option_value" | sed -e 's,-,:,g')" ; fi
match_str="$match_str --dport $option_value"
need_proto="1"
;;
ndpi)
match_str="$match_str -m ndpi --$option_value"
;;
connbytes_kb)
match_str="$match_str -m connbytes --connbytes $(($option_value*1024)): --connbytes-dir both --connbytes-mode bytes"
;;
esac
done
if [ -n "$min_pkt_size" ] || [ -n "$max_pkt_size" ] ; then
if [ -z "$min_pkt_size" ] ; then min_pkt_size=0 ; fi
if [ -z "$max_pkt_size" ] ; then max_pkt_size=1500 ; fi
match_str="$match_str -m length --length $min_pkt_size:$max_pkt_size"
fi
if [ -n "$class" ] ; then
if [ -n "$proto" ] || [ -n "$match_str" ] ; then
next_mark=$(get_classname_mark "$class" "$class_mark_list" )
# We need to specify both udp and tcp if the user indicated a port
# and he did not indiate a protocal.
if [ -z "$proto" ] && [ -n "$need_proto" ] ; then
$echo_on
iptables -t $table -I $chain -p tcp $match_str -j MARK --set-mark $next_mark
iptables -t $table -I $chain -p udp $match_str -j MARK --set-mark $next_mark
$echo_off
else
# Otherwise just specify what the user requested (or nothing)
if [ -n "$proto" ] ; then
tmp_proto="-p $proto"
else
tmp_proto=""
fi
$echo_on
iptables -t $table -I $chain $tmp_proto $match_str -j MARK --set-mark $next_mark
$echo_off
fi
fi
fi
done
}
update_markfile()
{
# initialize mark file in /tmp first, and test md5sum
# this should speed things up and prevent writing to flash unnecessarily (since /tmp is ramdisk)
tmp_qos_mark_file="/tmp/qos_marks.tmp.tmp"
rm -rf "$tmp_qos_mark_file"
# re-populate per the QoS setup.
if [ $total_upload_bandwidth -ge 0 ] ; then
upload_class_list=$(load_all_config_sections "$config_file_name" "upload_class")
next_class_index=2
for uclass_name in $upload_class_list ; do
printf "upload $uclass_name %d $upload_mask\n" $next_class_index >> "$tmp_qos_mark_file"
next_class_index=$(($next_class_index+1))
done
fi
if [ $total_download_bandwidth -ge 0 ] ; then
download_class_list=$(load_all_config_sections "$config_file_name" "download_class")
next_class_index=2
for dclass_name in $download_class_list ; do
printf "download $dclass_name %d $download_mask\n" $(($next_class_index << 8)) >> "$tmp_qos_mark_file"
next_class_index=$(($next_class_index+1))
done
fi
mark_files_match="0"
if [ -e "$qos_mark_file" ] ; then
new_md5=$(md5sum "$tmp_qos_mark_file" | awk '{ print $1 ; } ')
old_md5=$(md5sum "$qos_mark_file" | awk '{ print $1 ; } ')
if [ "$new_md5" = "$old_md5" ] ; then
mark_files_match="1"
fi
fi
if [ "$mark_files_match" = "0" ] ; then
mv "$tmp_qos_mark_file" "$qos_mark_file"
else
rm -rf "$tmp_qos_mark_file"
fi
}
module_installed()
{
lsmod | grep -wq "$1"
}
install_modules()
{
for m in "$@"; do
module_installed $m || insmod $m >&- 2>&-
done
}
initialize_qos()
{
# Now, load/insert necessary kernel modules
# The following packages are required for the modules:
module_installed imq && rmmod imq >&- 2>&-
insmod imq numdevs=1 hook_chains="INPUT,FORWARD" hook_tables="mangle,mangle" >&- 2>&-
ip link set dev imq0 mtu 1500
install_modules cls_fw cls_flow sch_hfsc sch_sfq
# Set the atm parameters if pppoe is being used.
wan_proto=
network_get_protocol wan_proto $wan_net_name
if [ "$wan_proto" = "pppoe" ] ; then
# On my PPPoE WAN link I see the following overhead
# MAC Header=14 & PPPoE=8 plus I read that an addition ATM=8 is added by the modem"
overhead="stab linklayer atm overhead 30 mtu 1492 tsize 128 "
# else
# Even for the ethernet cases I do not think the qdisc sees the MAC Header
# but for now leave this out.
# overhead="stab linklayer ethernet overhead 14 mpu 64"
fi
# On low memory routers we need to take it easy on how big the queues can get.
# When depth is limited to 32 maximum bandwidth through any class will be around 11Mbps.
# Otherwise it will be around 350Mbps.
total_mem="$(sed -e '/^MemTotal: /!d; s#MemTotal: *##; s# kB##g' /proc/meminfo)"
if [ "$total_mem" -lt 16000 ] ; then
sfq_depth="depth 32"
else
sfq_depth=""
fi
# load upload variables
load_all_config_options "$config_file_name" "upload"
if [ -n "$total_bandwidth" ] ; then
total_upload_bandwidth="$total_bandwidth"
else
total_upload_bandwidth=-1
fi
upload_default_class="$default_class"
if [ $total_upload_bandwidth -ge 0 ] ; then
# load upload classes
upload_class_list=$(load_all_config_sections "$config_file_name" "upload_class")
for uclass_name in $upload_class_list ; do
percent_bandwidth=""
min_bandwidth=""
max_bandwidth=""
load_all_config_options "$config_file_name" "$uclass_name"
if [ -z "$percent_bandwidth" ] ; then
percent_bandwidth="0"
fi
if [ -z "$min_bandwidth" ] ; then
min_bandwidth="-1"
fi
if [ -z "$max_bandwidth" ] ; then
max_bandwidth="-1"
fi
classdef="$percent_bandwidth $max_bandwidth $min_bandwidth"
eval $uclass_name=\"\$classdef\" #"#comment quote here so formatting in editor isn't FUBAR
done
# Attach egress queuing discipline to QoS interface, now with temperary default
$echo_on
tc qdisc add dev $qos_interface root handle 1:0 hfsc default 1
# For the root qdisc, only ul is relevant, since there is no link sharing, and rt only applies to leaf qdiscs
#
# A detailed explanation of how/why ls is being set is warranted here...
# This is being set to max bandwidth possible on a connection (right now that's about 1Gbit, we can increase later if necessary)
# Only ratio of child ls rates is important -- bounding is done by the ul parameter, since hfsc takes min of ls and ul
# This means we can just multiply percents by 10 in child nodes to get what ls curves for each should be
# Again, for ls the ratios matter, but the absolute values do not, provided that they are all < than the ul for either the
# child or any of the parent nodes
#
tc class add dev $qos_interface parent 1:0 classid 1:1 hfsc ls rate 1000Mbit ul rate ${total_upload_bandwidth}kbit
$echo_off
class_mark_list=""
upload_shift=0
next_class_index=2
next_classid=$(printf "0x%X" $(($next_class_index << $upload_shift)) )
def_upload_idx=$next_class_index
def_upload_class=$next_classid
for uclass_name in $upload_class_list ; do
class_mark_list="$class_mark_list$uclass_name:$next_classid "
uclass_def=$(eval echo "\$$uclass_name")
# % bandwidth at capacity for this class
m2=$(( 10 * $(echo $uclass_def | awk ' {print $1}' ) ))
# is there a minimum bandwidth specified in kbps?
min_bandwidth=$( echo $uclass_def | awk ' {print $3}' )
ll_str=""
if [ "$min_bandwidth" -gt 0 ] ; then
ll_str=" rt m1 $((2*$min_bandwidth))kbit d 20ms m2 ${min_bandwidth}kbit"
fi
# is there an upper limit specified in kbps?
max_bandwidth=$( echo $uclass_def | awk ' {print $2}' )
ul_str=""
if [ "$max_bandwidth" -ge 0 ] ; then
ul_str=" ul m2 ${max_bandwidth}kbit"
else
max_bandwidth="$total_upload_bandwidth"
fi
# tbw is the Delay x Bandwidth product in bytes per second. We do not actually know the packet
# delay so we make an estimate of 150ms here and hope for the best. max_bandwidth is in kbps
# we multiply 150ms by 1000 below so the units work out.
tbw=$(($max_bandwidth*150/8));
if [ "$tbw" -lt 3000 ] ; then
tbw=3000
fi
# We will use the SFQ qdisc with flow classifier. The limit on the depth of our qdisc depends on the upper limit
# of the bandwidth allocated to this class. To impliment per IP sharing of the class we use the flow classifier
# and the 'nfct-src' on the upload side and 'dst' on the download side. I found a nice man page here
# https://arndtroide.homelinux.org/cgi-bin/man/man2html?tc-sfq+8
$echo_on
# Add the leaf class
tc class add dev $qos_interface parent 1:1 classid 1:$next_class_index hfsc ls m2 ${m2}Mbit $ll_str $ul_str
# Add the qdisc to the leaf class, assuming average packet at 500 bytes.
tc qdisc add dev $qos_interface parent 1:$next_class_index handle $next_class_index:1 sfq headdrop limit $(($tbw/500)) $sfq_depth divisor 256
# Add a filter to the root class to direct packets to this leaf class according to the conntrack mark
tc filter add dev $qos_interface parent 1:0 protocol ip handle $next_classid fw flowid 1:$next_class_index
# Add a filter to the leaf class to define flows as being the source IP address.
tc filter add dev $qos_interface parent $next_class_index: handle 1 flow divisor 256 map key nfct-src and 0xff
$echo_off
if [ "$upload_default_class" = "$uclass_name" ] ; then
def_upload_idx=$next_class_index
def_upload_class=$next_classid
fi
next_class_index=$(($next_class_index+1))
next_classid=$(printf "0x%X" $(($next_class_index << $upload_shift)) )
done
$echo_on
# Go back and touch up the root qdisc to have the proper default class
tc qdisc change dev $qos_interface $overhead root handle 1:0 hfsc default $def_upload_idx
# Set up egress chain
iptables -t mangle -N qos_egress
iptables -t mangle -A POSTROUTING -o $qos_interface -j qos_egress
# Next the user entered rules.
$echo_off
apply_all_rules "upload_rule" "$class_mark_list" "qos_egress" "mangle"
$echo_on
# set default class mark first in case we don't match anything
iptables -t mangle -I qos_egress -j MARK --set-mark $def_upload_class
# if we already set a mark in quota chain, we need to save that mark to the connmark, then return so it doesn't get over-written
iptables -t mangle -I qos_egress -m mark ! --mark 0x0 -j RETURN
iptables -t mangle -I qos_egress -m mark ! --mark 0x0 -j CONNMARK --save-mark --mask $upload_mask
# save current mark to connmark at end of chain
iptables -t mangle -A qos_egress -j CONNMARK --save-mark --mask $upload_mask
$echo_off
fi
# load download variables
total_bandwidth=""
default_class=""
load_all_config_options "$config_file_name" "download"
if [ -n "$total_bandwidth" ] ; then
total_download_bandwidth="$total_bandwidth"
else
total_download_bandwidth=-1
fi
download_default_class="$default_class"
# Only if both upload and download QoS are enabled can we enable Gargoyle active QoS monitor
if [ $total_download_bandwidth -eq 0 ] || [ $total_upload_bandwidth -eq 0 ] ; then
qos_monenabled="false" ;
fi
if [ $total_download_bandwidth -ge 0 ] ; then
# Set up the InterMediate Queuing device (IMQ)
ip link set imq0 up
# Attach ingress queuing discipline to IMQ interface
tc qdisc add dev imq0 root handle 1:0 hfsc default 1
tc class add dev imq0 parent 1:0 classid 1:1 hfsc ls rate 1000Mbit ul rate ${total_download_bandwidth}kbit
# load download classes
download_class_list=$(load_all_config_sections "$config_file_name" "download_class")
for dclass_name in $download_class_list ; do
percent_bandwidth=""
min_bandwidth=""
max_bandwidth=""
minRTT=""
load_all_config_options "$config_file_name" "$dclass_name"
if [ -z "$percent_bandwidth" ] ; then
percent_bandwidth="0"
fi
if [ -z "$min_bandwidth" ] ; then
min_bandwidth="-1"
fi
if [ -z "$max_bandwidth" ] ; then
max_bandwidth="-1"
fi
classdef="$percent_bandwidth $max_bandwidth $min_bandwidth $minRTT"
eval $dclass_name=\"\$classdef\" #"#comment quote here so formatting in editor isn't FUBAR
done
class_mark_list=""
download_shift=8
next_class_index=2
next_classid=$(printf "0x%X" $(($next_class_index << $download_shift)) )
def_download_idx=$next_class_index
def_download_class=$next_classid
for dclass_name in $download_class_list ; do
class_mark_list="$class_mark_list$dclass_name:$next_classid "
dclass_def=$(eval echo "\$$dclass_name")
# bandwidth for this class
m2=$(( 10 * $(echo $dclass_def | awk ' {print $1}' ) ))
# The Gargoyle ACC switches from optimum WAN utilization mode to minimum RTT mode
# when it detects a class has become active that includes a two part service curve.
# So to trigger this behaviour we create two parts curves when minRTT is set.
minRTT=$( echo $dclass_def | awk ' {print $4}' )
if [ "$minRTT" = "Yes" ] ; then
ll_str=" ls m1 ${m2}Mbit d 20ms m2 ${m2}Mbit"
else
ll_str=" ls m2 ${m2}Mbit"
fi
# is there a minimum bandwidth specified?
min_bandwidth=$( echo $dclass_def | awk ' {print $3}' )
rt_str=""
if [ "$min_bandwidth" -gt 0 ] ; then
if [ "$minRTT" = "Yes" ] ; then
rt_str=" rt m1 $((2*$min_bandwidth))kbit d 20ms m2 ${min_bandwidth}kbit"
else
rt_str=" rt m2 ${min_bandwidth}kbit"
fi
fi
# is there an upper limit specified?
max_bandwidth=$( echo $dclass_def | awk ' {print $2}' )
ul_str=""
if [ "$max_bandwidth" -ge 0 ] ; then
ul_str=" ul m2 ${max_bandwidth}kbit"
else
max_bandwidth="$total_download_bandwidth"
fi
tbw=$(($max_bandwidth*150/8));
if [ "$tbw" -lt 5000 ] ; then
tbw=5000
fi
$echo_on
tc class add dev imq0 parent 1:1 classid 1:$next_class_index hfsc $rt_str $ll_str $ul_str
# Assume average download packet size is 1000 bytes.
tc qdisc add dev imq0 parent 1:$next_class_index handle $next_class_index:1 sfq headdrop limit $(($tbw/1000)) $sfq_depth divisor 256
tc filter add dev imq0 parent 1:0 prio $next_class_index protocol ip handle $next_classid fw flowid 1:$next_class_index
tc filter add dev imq0 parent $next_class_index: handle 1 flow divisor 256 map key dst and 0xff
$echo_off
if [ "$download_default_class" = "$dclass_name" ] ; then
def_download_idx=$next_class_index
def_download_class=$next_classid
fi
next_class_index=$(($next_class_index+1))
next_classid=$(printf "0x%X" $(($next_class_index << $download_shift)) )
done
$echo_on
# Go back and touch up the root qdisc to have the proper default class
tc qdisc change dev imq0 $overhead root handle 1:0 hfsc default $def_download_idx
# Create ingress chain
iptables -t mangle -N qos_ingress
# Mark ingress in FORWARD and INPUT chains to make sure any DNAT (virt. server) is taken into account
iptables -t mangle -A FORWARD -i $qos_interface -j qos_ingress
iptables -t mangle -A INPUT -i $qos_interface -j qos_ingress
# Now the rest of the user entered rules.
$echo_off
apply_all_rules "download_rule" "$class_mark_list" "qos_ingress" "mangle" "$download_mask"
$echo_on
# set default class mark first in case we don't match anything
iptables -t mangle -I qos_ingress -j MARK --set-mark $def_download_class
# if we already set a mark in quota chain, we need to save that mark to the connmark, then return so it doesn't get over-written
iptables -t mangle -I qos_ingress -m mark ! --mark 0x0 -j RETURN
iptables -t mangle -I qos_ingress -m mark ! --mark 0x0 -j CONNMARK --save-mark --mask $download_mask
# make sure all packets get sent through IMQ
iptables -t mangle -I qos_ingress -j IMQ --todev 0
# save current mark to connmark at end of chain
iptables -t mangle -A qos_ingress -j CONNMARK --save-mark --mask $download_mask
$echo_off
fi
# Enable Gargoyle active QoS monitor
if [ $total_upload_bandwidth -ge 0 ] && [ $total_download_bandwidth -ge 0 ] && [ "$qos_monenabled" = "true" ] ; then
$echo_on
# if the user specified a ping target then use that otherwise use the gateway.
if [ -z "$ptarget_ip" ] ; then
old_ifs="$IFS"
IFS=$(printf "\n\r")
targets=$(traceroute -n -I -w 1 -q 2 -m6 100.100.100.100 | grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}.*ms' | grep -v '8.8.8.8' | sed 's/ms//g')
ptarget_ip=""
for t in $targets ; do
if [ -z "$ptarget_ip" ] ; then
# ip of potential gateway
target=$(echo "$t" | awk '{ print $1 ; }')
target_is_local=$(echo "$target" | grep -E '^(192\.168|10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|0\.0\.0\.0|127\.|255\.)')
# round or rather ceil() time up to nearest millisecond since bash doesn't like working with decimals
time=$(echo "$t" | awk ' { print $3 ; }' | sed 's/\..*$//g')
time=$(( $time + 1 ))
if [ -z "$target_is_local" ] || [ "$time" -gt 5 ] ; then
ptarget_ip="$target"
fi
fi
done
IFS="$old_ifs"
# in case ping target is still not defined use default gateway
if [ -z "$ptarget_ip" ] ; then
network_get_gateway ptarget_ip $wan_net_name
fi
fi
# Ping responses for the ping target never go to the IMQ device.
iptables -t mangle -I qos_ingress -p icmp --icmp-type 0 -d $wan_ip -s $ptarget_ip -j RETURN
# Make a class to handle the outgoing ping requests from the router.
# These pings 84 bytes each for ethernet plus 22 more for PPPoE connections, allowing a maximum rate of 200ms we get 2.6kbps
tc class add dev $qos_interface parent 1:1 classid 1:127 hfsc rt umax 106 dmax 10ms rate 4kbit
tc qdisc add dev $qos_interface parent 1:127 pfifo
tc filter add dev $qos_interface parent 1:0 prio 1 protocol ip handle 127 fw flowid 1:127
# Mark all ping requests from the router to the ptarget to the above special class overriding any other mark.
iptables -t mangle -I qos_egress -p icmp --icmp-type 8 -s $wan_ip -d $ptarget_ip -j MARK --set-mark 127
# Start the monitor
if [ -n "$pinglimit" ] ; then
# In manual mode the user selects the active mode pinglimit indirectly. qosmon always measures the RTT of a ping on an unloaded link.
# This is called the ping entitlement. With a manual entry the minRTT ping limit is 110% of this measured ping entitlement
# and the active mode ping limit is the minRTT limit plus the user entered value. See the qosmon source code for more details.
# In summary manaully entered ping times only affect the active mode, not the minRTT mode ping time limits.
qosmon -a -b 800 $ptarget_ip $total_download_bandwidth $pinglimit
else
# In auto mode we calculate transmission delay based on our bandwidth and then ask qosmon
# to add this value to its measured ping entitlement to form the final ping limit.
pinglimit=$((1500*10*2/3/$total_download_bandwidth+1500*10/$total_upload_bandwidth+2))
qosmon -a -b 800 $ptarget_ip $total_download_bandwidth $pinglimit
fi
$echo_off
fi
update_markfile
}
define_interface()
{
. /lib/functions/network.sh
# Wait for up to 15 seconds for the wan interface to indicate it is up.
wan_net_name=""
network_find_wan wan_net_name
wait_sec=15
while [ -z "$wan_net_name" ] && [ $wait_sec -gt 0 ] ; do
sleep 1
wait_sec=$(($wait_sec - 1))
network_find_wan wan_net_name
done
if [ -z "$wan_net_name" ] ; then
echo "Network is not available."
exit 1
fi
qos_interface=""
wan_ip=""
local_ip=""
network_get_device qos_interface $wan_net_name
network_get_ipaddr wan_ip $wan_net_name
network_get_ipaddr local_ip lan
}
ipt_delete_rule()
{
local table=$1
local delete_chain=$2
local lines
lines=$(iptables -t $table -nL $delete_chain --line-numbers | grep $3 | awk '{print $1}')
for n in $lines ; do
iptables -t $table -D $delete_chain $n 2>/dev/null
done
}
stop()
{
# if already in process of being initialized, do not continue
# until that is finished, and then exit cleanly, without
# doing anything further
if [ -e "$lock_file" ] ; then
while [ -e "$lock_file" ] ; do
sleep 1
done
exit
fi
# Kill the qos monitor in case it is running
killall qosmon 2>/dev/null
$echo_on
for iface in $(tc qdisc show | grep hfsc | awk '{print $5}'); do
tc qdisc del dev "$iface" root
done
# eliminate existing rules in mangle table
ipt_delete_rule "mangle" "POSTROUTING" "qos_egress"
ipt_delete_rule "mangle" "FORWARD" "qos_ingress"
ipt_delete_rule "mangle" "INPUT" "qos_ingress"
iptables -t mangle -F qos_ingress 2>/dev/null
iptables -t mangle -F qos_egress 2>/dev/null
iptables -t mangle -X qos_ingress 2>/dev/null
iptables -t mangle -X qos_egress 2>/dev/null
$echo_off
}
start()
{
test_total_up=$(uci -q get qos_gargoyle.upload.total_bandwidth)
test_total_down=$(uci -q get qos_gargoyle.download.total_bandwidth)
if [ -z "$test_total_up" ] && [ -z "$test_total_down" ] ;then
disable
exit 0
fi
# This script is called by a hotplug event. If the WAN comes
# up fast we could end up trying to run qos_gargoyle while the
# boot is still in progress which causes issues in low memory
# routers. To avoid this we check to see if rcS is still running
# or not. If it is we wait until it completes or 60 seconds.
cnt=0
while ps | grep '[//]rcS S boot' >/dev/null
do
sleep 4
cnt=`expr $cnt + 1`
if [ $cnt -ge 15 ] ; then
break;
fi
done
stop
touch "$lock_file"
# load qos_interface from global variables
define_interface
if [ -n "$qos_interface" ] ; then
load_all_config_options "$config_file_name" "global"
initialize_qos
fi
rm -rf "$lock_file"
}
restart()
{
echo_on="set -x"
echo_off="set +x"
start
}
show()
{
# load global variables
load_all_config_options "$config_file_name" "global"
# load qos_interface from global variables
define_interface
echo "Egress configuration on $qos_interface"
iptables -t mangle -vnL qos_egress 2>/dev/null
tc -s qdisc show dev $qos_interface
tc -s class show dev $qos_interface
tc -s filter show dev $qos_interface
echo "Ingress configuration in imq0"
iptables -t mangle -vnL qos_ingress 2>/dev/null
tc -s qdisc show dev imq0
tc -s class show dev imq0
tc -s filter show dev imq0
tc -s filter show dev imq0 parent 2:
}
boot()
{
# Do nothing during init. Start is called by hotplug.
return 0
}

View File

@ -0,0 +1,47 @@
--- a/Makefile
+++ b/Makefile
@@ -21,7 +21,7 @@ qosmon: qosmon.o
$(CC) $(LDFLAGS) $^ $(TCOBJS) -o $@ $(LDLIBS)
qosmon.o: qosmon.c
- $(CC) -D ONLYBG $(CFLAGS) -I $(TCDIR)/include -I $(TCDIR)/tc -c $^ -o $@
+ $(CC) -D ONLYBG -D_GNU_SOURCE $(CFLAGS) -I $(TCDIR)/include -I $(TCDIR)/tc -c $^ -o $@
install: all uninstall
-mkdir -p $(BINDIR)
--- a/qosmon.c
+++ b/qosmon.c
@@ -34,6 +34,8 @@
#include <string.h>
#include <errno.h>
#include <time.h>
+#include <limits.h>
+#include <sys/param.h>
#include "utils.h"
#include "tc_util.h"
@@ -62,12 +64,13 @@
//after then and define dump_filter and talk accordingly.
#ifdef RTNL_FAMILY_MAX
#define dump_filter(a,b,c) rtnl_dump_filter(a,b,c)
-#define talk(a,b,c,d,e) rtnl_talk(a,b,c,d,e)
+#define talk(a,b,c) rtnl_talk(a,b,c)
#else
#define dump_filter(a,b,c) rtnl_dump_filter(a,b,c,NULL,NULL)
-#define talk(a,b,c,d,e) rtnl_talk(a,b,c,d,e,NULL,NULL)
+#define talk(a,b,c) rtnl_talk(a,b,c,NULL,NULL)
#endif
+#define __sighandler_t sighandler_t
/* use_names is required when linking to tc_util.o */
bool use_names = false;
@@ -630,7 +633,7 @@ int tc_class_modify(__u32 rate)
}
- if (talk(&rth, &req.n, 0, 0, NULL) < 0)
+ if (talk(&rth, &req.n, NULL) < 0)
return 2;
return 0;

View File

@ -0,0 +1,35 @@
BINDIR = /usr/sbin
#TCDIR:=$(BUILD_DIR)/iproute2-full/ip*
TCDIR:=$(shell if [ -e "${BUILD_DIR}/iproute2-full" ] ; then echo "${BUILD_DIR}/iproute2-full"/ip* ; else echo "${BUILD_DIR}/iproute2-tiny"/ip* ; fi ; )
#The ncurses library only needed if we remove the ONLYBG switch
#below. Mostly for debug
#LDLIBS += -lncurses
LDLIBS += -lm
TCOBJS := $(TCDIR)/tc/tc_util.o
TCOBJS += $(TCDIR)/tc/tc_core.o
TCOBJS += $(TCDIR)/tc/q_hfsc.o
TCOBJS += $(TCDIR)/lib/libnetlink.a
TCOBJS += $(TCDIR)/lib/libutil.a
LDFLAGS += -Wl,-export-dynamic
all: qosmon
qosmon: qosmon.o
$(CC) $(LDFLAGS) $^ $(TCOBJS) -o $@ $(LDLIBS)
qosmon.o: qosmon.c
$(CC) -D ONLYBG $(CFLAGS) -I $(TCDIR)/include -I $(TCDIR)/tc -c $^ -o $@
install: all uninstall
-mkdir -p $(BINDIR)
cp qosmon $(BINDIR)
uninstall:
rm -f $(BINDIR)/qosmon
clean:
rm -rf *.o *~ .*sw* qosmon

File diff suppressed because it is too large Load Diff

View File

@ -468,6 +468,70 @@ endef
$(eval $(call KernelPackage,ipt-raw))
define KernelPackage/ipt-imq
TITLE:=Intermediate Queueing support
KCONFIG:= \
CONFIG_IMQ \
CONFIG_IMQ_NUM_DEVS=2 \
CONFIG_NETFILTER_XT_TARGET_IMQ
FILES:= \
$(LINUX_DIR)/drivers/net/imq.$(LINUX_KMOD_SUFFIX) \
$(foreach mod,$(IPT_IMQ-m),$(LINUX_DIR)/net/$(mod).$(LINUX_KMOD_SUFFIX))
AUTOLOAD:=$(call AutoProbe,$(notdir imq $(IPT_IMQ-m)))
$(call AddDepends/ipt)
endef
define KernelPackage/ipt-imq/description
Kernel support for Intermediate Queueing devices
endef
$(eval $(call KernelPackage,ipt-imq))
define KernelPackage/ipt-bandwidth
SUBMENU:=$(NF_MENU)
TITLE:=bandwidth
KCONFIG:=$(KCONFIG_IPT_BANDWIDTH)
FILES:=$(LINUX_DIR)/net/ipv4/netfilter/*bandwidth*.$(LINUX_KMOD_SUFFIX)
AUTOLOAD:=$(call AutoLoad,$(notdir $(IPT_BANDWIDTH-m)))
DEPENDS:= kmod-ipt-core
endef
$(eval $(call KernelPackage,ipt-bandwidth))
define KernelPackage/ipt-timerange
SUBMENU:=$(NF_MENU)
TITLE:=timerange
KCONFIG:=$(KCONFIG_IPT_TIMERANGE)
FILES:=$(LINUX_DIR)/net/ipv4/netfilter/*timerange*.$(LINUX_KMOD_SUFFIX)
AUTOLOAD:=$(call AutoLoad,$(notdir $(IPT_TIMERANGE-m)))
DEPENDS:= kmod-ipt-core
endef
$(eval $(call KernelPackage,ipt-timerange))
define KernelPackage/ipt-webmon
SUBMENU:=$(NF_MENU)
TITLE:=webmon
KCONFIG:=$(KCONFIG_IPT_WEBMON)
FILES:=$(LINUX_DIR)/net/ipv4/netfilter/*webmon*.$(LINUX_KMOD_SUFFIX)
AUTOLOAD:=$(call AutoLoad,$(notdir $(IPT_WEBMON-m)))
DEPENDS:= kmod-ipt-core
endef
$(eval $(call KernelPackage,ipt-webmon))
define KernelPackage/ipt-weburl
SUBMENU:=$(NF_MENU)
TITLE:=weburl
KCONFIG:=$(KCONFIG_IPT_WEBURL)
FILES:=$(LINUX_DIR)/net/ipv4/netfilter/*weburl*.$(LINUX_KMOD_SUFFIX)
AUTOLOAD:=$(call AutoLoad,$(notdir $(IPT_WEBURL-m)))
DEPENDS:= kmod-ipt-core
endef
$(eval $(call KernelPackage,ipt-weburl))
define KernelPackage/ipt-raw6
TITLE:=Netfilter IPv6 raw table support
KCONFIG:=CONFIG_IP6_NF_RAW

38
package/network/utils/iptables/Makefile Executable file → Normal file
View File

@ -170,6 +170,39 @@ Includes support for:
endef
define Package/iptables-mod-imq
$(call Package/iptables/Module, +kmod-ipt-imq)
TITLE:=IMQ support
endef
define Package/iptables-mod-imq/description
iptables extension for IMQ support.
Targets:
- IMQ
endef
define Package/iptables-mod-bandwidth
$(call Package/iptables/Module, +kmod-ipt-bandwidth)
TITLE:=bandwidth
endef
define Package/iptables-mod-timerange
$(call Package/iptables/Module, +kmod-ipt-timerange)
TITLE:=timerange
endef
define Package/iptables-mod-webmon
$(call Package/iptables/Module, +kmod-ipt-webmon)
TITLE:=webmon
endef
define Package/iptables-mod-weburl
$(call Package/iptables/Module, +kmod-ipt-weburl)
TITLE:=weburl
endef
define Package/iptables-mod-ipopt
$(call Package/iptables/Module, +kmod-ipt-ipopt)
TITLE:=IP/Packet option extensions
@ -679,6 +712,11 @@ $(eval $(call BuildPlugin,iptables-mod-conntrack-label,$(IPT_CONNTRACK_LABEL-m))
$(eval $(call BuildPlugin,iptables-mod-extra,$(IPT_EXTRA-m)))
$(eval $(call BuildPlugin,iptables-mod-physdev,$(IPT_PHYSDEV-m)))
$(eval $(call BuildPlugin,iptables-mod-filter,$(IPT_FILTER-m)))
$(eval $(call BuildPlugin,iptables-mod-imq,$(IPT_IMQ-m)))
$(eval $(call BuildPlugin,iptables-mod-bandwidth,$(IPT_BANDWIDTH-m)))
$(eval $(call BuildPlugin,iptables-mod-timerange,$(IPT_TIMERANGE-m)))
$(eval $(call BuildPlugin,iptables-mod-webmon,$(IPT_WEBMON-m)))
$(eval $(call BuildPlugin,iptables-mod-weburl,$(IPT_WEBURL-m)))
$(eval $(call BuildPlugin,iptables-mod-ipopt,$(IPT_IPOPT-m)))
$(eval $(call BuildPlugin,iptables-mod-ipsec,$(IPT_IPSEC-m)))
$(eval $(call BuildPlugin,iptables-mod-nat-extra,$(IPT_NAT_EXTRA-m)))

View File

@ -0,0 +1,657 @@
/* bandwidth -- An iptables extension for bandwidth monitoring/control
* Can be used to efficiently monitor bandwidth and/or implement bandwidth quotas
* Can be queried using the iptbwctl userspace library
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2009 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <limits.h>
/*
* in iptables 1.4.0 and higher, iptables.h includes xtables.h, which
* we can use to check whether we need to deal with the new requirements
* in pre-processor directives below
*/
#include <iptables.h>
#include <linux/netfilter_ipv4/ipt_bandwidth.h>
#ifdef _XTABLES_H
#define iptables_rule_match xtables_rule_match
#define iptables_match xtables_match
#define iptables_target xtables_target
#define ipt_tryload xt_tryload
#endif
/*
* XTABLES_VERSION_CODE is only defined in versions 1.4.1 and later, which
* also require the use of xtables_register_match
*
* Version 1.4.0 uses register_match like previous versions
*/
#ifdef XTABLES_VERSION_CODE
#define register_match xtables_register_match
#endif
int get_minutes_west(void);
void set_kernel_timezone(void);
int parse_sub(char* subnet_string, uint32_t* subnet, uint32_t* subnet_mask);
static unsigned long get_pow(unsigned long base, unsigned long pow);
static void param_problem_exit_error(char* msg);
/* Function which prints out usage message. */
static void help(void)
{
printf("bandwidth options:\n");
printf(" --id [unique identifier for querying bandwidth]\n");
printf(" --type [combined|individual_src|individual_dst|individual_local|individual_remote]\n");
printf(" --subnet [a.b.c.d/mask] (0 < mask < 32)\n");
printf(" --greater_than [BYTES]\n");
printf(" --less_than [BYTES]\n");
printf(" --current_bandwidth [BYTES]\n");
printf(" --reset_interval [minute|hour|day|week|month]\n");
printf(" --reset_time [OFFSET IN SECONDS]\n");
printf(" --intervals_to_save [NUMBER OF PREVIOS INTERVALS TO STORE IN MEMORY]\n");
printf(" --last_backup_time [UTC SECONDS SINCE 1970]\n");
printf(" --bcheck Check another bandwidth rule without incrementing it\n");
printf(" --bcheck_with_src_dst_swap Check another bandwidth rule without incrementing it, swapping src & dst ips for check\n");
}
static struct option opts[] =
{
{ .name = "id", .has_arg = 1, .flag = 0, .val = BANDWIDTH_ID },
{ .name = "type", .has_arg = 1, .flag = 0, .val = BANDWIDTH_TYPE },
{ .name = "subnet", .has_arg = 1, .flag = 0, .val = BANDWIDTH_SUBNET },
{ .name = "greater_than", .has_arg = 1, .flag = 0, .val = BANDWIDTH_GT },
{ .name = "less_than", .has_arg = 1, .flag = 0, .val = BANDWIDTH_LT },
{ .name = "current_bandwidth", .has_arg = 1, .flag = 0, .val = BANDWIDTH_CURRENT },
{ .name = "reset_interval", .has_arg = 1, .flag = 0, .val = BANDWIDTH_RESET_INTERVAL },
{ .name = "reset_time", .has_arg = 1, .flag = 0, .val = BANDWIDTH_RESET_TIME },
{ .name = "intervals_to_save", .has_arg = 1, .flag = 0, .val = BANDWIDTH_NUM_INTERVALS },
{ .name = "last_backup_time", .has_arg = 1, .flag = 0, .val = BANDWIDTH_LAST_BACKUP},
{ .name = "bcheck", .has_arg = 0, .flag = 0, .val = BANDWIDTH_CHECK_NOSWAP },
{ .name = "bcheck_with_src_dst_swap", .has_arg = 0, .flag = 0, .val = BANDWIDTH_CHECK_SWAP },
{ .name = 0 }
};
/* Function which parses command options; returns true if it
ate an option */
static int parse( int c,
char **argv,
int invert,
unsigned int *flags,
#ifdef _XTABLES_H
const void *entry,
#else
const struct ipt_entry *entry,
unsigned int *nfcache,
#endif
struct ipt_entry_match **match
)
{
struct ipt_bandwidth_info *info = (struct ipt_bandwidth_info *)(*match)->data;
int valid_arg = 0;
long int num_read;
uint64_t read_64;
time_t read_time;
/* set defaults first time we get here */
if(*flags == 0)
{
/* generate random id */
srand ( time(NULL) );
unsigned long id_num = rand();
sprintf(info->id, "%lu", id_num);
info->type = BANDWIDTH_COMBINED;
info->check_type = BANDWIDTH_CHECK_NOSWAP;
info->local_subnet = 0;
info->local_subnet_mask = 0;
info->cmp = BANDWIDTH_MONITOR; /* don't test greater/less than, just monitor bandwidth */
info->current_bandwidth = 0;
info->reset_is_constant_interval = 0;
info->reset_interval = BANDWIDTH_NEVER;
info->reset_time=0;
info->last_backup_time = 0;
info->next_reset = 0;
info->num_intervals_to_save=0;
info->non_const_self = NULL;
info->ref_count = NULL;
*flags = *flags + BANDWIDTH_INITIALIZED;
}
switch (c)
{
case BANDWIDTH_ID:
if(strlen(optarg) < BANDWIDTH_MAX_ID_LENGTH)
{
sprintf(info->id, "%s", optarg);
valid_arg = 1;
}
c=0;
break;
case BANDWIDTH_TYPE:
valid_arg = 1;
if(strcmp(optarg, "combined") == 0)
{
info->type = BANDWIDTH_COMBINED;
}
else if(strcmp(optarg, "individual_src") == 0)
{
info->type = BANDWIDTH_INDIVIDUAL_SRC;
}
else if(strcmp(optarg, "individual_dst") == 0)
{
info->type = BANDWIDTH_INDIVIDUAL_DST;
}
else if(strcmp(optarg, "individual_local") == 0)
{
info->type = BANDWIDTH_INDIVIDUAL_LOCAL;
*flags = *flags + BANDWIDTH_REQUIRES_SUBNET;
}
else if(strcmp(optarg, "individual_remote") == 0)
{
info->type = BANDWIDTH_INDIVIDUAL_REMOTE;
*flags = *flags + BANDWIDTH_REQUIRES_SUBNET;
}
else
{
valid_arg = 0;
}
c=0;
break;
case BANDWIDTH_SUBNET:
valid_arg = parse_sub(optarg, &(info->local_subnet), &(info->local_subnet_mask));
break;
case BANDWIDTH_LT:
num_read = sscanf(argv[optind-1], "%lld", &read_64);
if(num_read > 0 && (*flags & BANDWIDTH_CMP) == 0)
{
info->cmp = BANDWIDTH_LT;
info->bandwidth_cutoff = read_64;
valid_arg = 1;
}
c = BANDWIDTH_CMP; //only need one flag for less_than/greater_than
break;
case BANDWIDTH_GT:
num_read = sscanf(argv[optind-1], "%lld", &read_64);
if(num_read > 0 && (*flags & BANDWIDTH_CMP) == 0)
{
info->cmp = BANDWIDTH_GT;
info->bandwidth_cutoff = read_64;
valid_arg = 1;
}
c = BANDWIDTH_CMP; //only need one flag for less_than/greater_than
break;
case BANDWIDTH_CHECK_NOSWAP:
if( (*flags & BANDWIDTH_CMP) == 0 )
{
info->cmp = BANDWIDTH_CHECK;
info->check_type = BANDWIDTH_CHECK_NOSWAP;
valid_arg = 1;
}
c = BANDWIDTH_CMP;
break;
case BANDWIDTH_CHECK_SWAP:
if( (*flags & BANDWIDTH_CMP) == 0 )
{
info->cmp = BANDWIDTH_CHECK;
info->check_type = BANDWIDTH_CHECK_SWAP;
valid_arg = 1;
}
c = BANDWIDTH_CMP;
break;
case BANDWIDTH_CURRENT:
num_read = sscanf(argv[optind-1], "%lld", &read_64);
if(num_read > 0 )
{
info->current_bandwidth = read_64;
valid_arg = 1;
}
break;
case BANDWIDTH_RESET_INTERVAL:
valid_arg = 1;
if(strcmp(argv[optind-1],"minute") ==0)
{
info->reset_interval = BANDWIDTH_MINUTE;
info->reset_is_constant_interval = 0;
}
else if(strcmp(argv[optind-1],"hour") ==0)
{
info->reset_interval = BANDWIDTH_HOUR;
info->reset_is_constant_interval = 0;
}
else if(strcmp(argv[optind-1],"day") ==0)
{
info->reset_interval = BANDWIDTH_DAY;
info->reset_is_constant_interval = 0;
}
else if(strcmp(argv[optind-1],"week") ==0)
{
info->reset_interval = BANDWIDTH_WEEK;
info->reset_is_constant_interval = 0;
}
else if(strcmp(argv[optind-1],"month") ==0)
{
info->reset_interval = BANDWIDTH_MONTH;
info->reset_is_constant_interval = 0;
}
else if(strcmp(argv[optind-1],"never") ==0)
{
info->reset_interval = BANDWIDTH_NEVER;
}
else if(sscanf(argv[optind-1], "%ld", &read_time) > 0)
{
info->reset_interval = read_time;
info->reset_is_constant_interval = 1;
}
else
{
valid_arg = 0;
}
break;
case BANDWIDTH_NUM_INTERVALS:
if( sscanf(argv[optind-1], "%ld", &num_read) > 0)
{
info->num_intervals_to_save = num_read;
valid_arg=1;
}
c=0;
break;
case BANDWIDTH_RESET_TIME:
num_read = sscanf(argv[optind-1], "%ld", &read_time);
if(num_read > 0 )
{
info->reset_time = read_time;
valid_arg = 1;
}
break;
case BANDWIDTH_LAST_BACKUP:
num_read = sscanf(argv[optind-1], "%ld", &read_time);
if(num_read > 0 )
{
info->last_backup_time = read_time;
valid_arg = 1;
}
break;
}
*flags = *flags + (unsigned int)c;
//if we have both reset_interval & reset_time, check reset_time is in valid range
if((*flags & BANDWIDTH_RESET_TIME) == BANDWIDTH_RESET_TIME && (*flags & BANDWIDTH_RESET_INTERVAL) == BANDWIDTH_RESET_INTERVAL)
{
if( (info->reset_interval == BANDWIDTH_NEVER) ||
(info->reset_interval == BANDWIDTH_MONTH && info->reset_time >= 60*60*24*28) ||
(info->reset_interval == BANDWIDTH_WEEK && info->reset_time >= 60*60*24*7) ||
(info->reset_interval == BANDWIDTH_DAY && info->reset_time >= 60*60*24) ||
(info->reset_interval == BANDWIDTH_HOUR && info->reset_time >= 60*60) ||
(info->reset_interval == BANDWIDTH_MINUTE && info->reset_time >= 60)
)
{
valid_arg = 0;
param_problem_exit_error("Parameter for '--reset_time' is not in valid range");
}
}
if(info->type != BANDWIDTH_COMBINED && (*flags & BANDWIDTH_CURRENT) == BANDWIDTH_CURRENT)
{
valid_arg = 0;
param_problem_exit_error("You may only specify current bandwidth for combined type\n Use user-space library for setting bandwidth for individual types");
}
return valid_arg;
}
static void print_bandwidth_args( struct ipt_bandwidth_info* info )
{
if(info->cmp == BANDWIDTH_CHECK)
{
if(info->check_type == BANDWIDTH_CHECK_NOSWAP)
{
printf("--bcheck ");
}
else
{
printf("--bcheck_with_src_dst_swap ");
}
}
printf("--id %s ", info->id);
if(info->cmp != BANDWIDTH_CHECK)
{
/* determine current time in seconds since epoch, with offset for current timezone */
int minuteswest = get_minutes_west();
time_t now;
time(&now);
now = now - (minuteswest*60);
if(info->type == BANDWIDTH_COMBINED)
{
printf("--type combined ");
}
if(info->type == BANDWIDTH_INDIVIDUAL_SRC)
{
printf("--type individual_src ");
}
if(info->type == BANDWIDTH_INDIVIDUAL_DST)
{
printf("--type individual_dst ");
}
if(info->type == BANDWIDTH_INDIVIDUAL_LOCAL)
{
printf("--type individual_local ");
}
if(info->type == BANDWIDTH_INDIVIDUAL_REMOTE)
{
printf("--type individual_remote ");
}
if(info->local_subnet != 0)
{
unsigned char* sub = (unsigned char*)(&(info->local_subnet));
int msk_bits=0;
int pow=0;
for(pow=0; pow<32; pow++)
{
uint32_t test = get_pow(2, pow);
msk_bits = ( (info->local_subnet_mask & test) == test) ? msk_bits+1 : msk_bits;
}
printf("--subnet %u.%u.%u.%u/%u ", (unsigned char)sub[0], (unsigned char)sub[1], (unsigned char)sub[2], (unsigned char)sub[3], msk_bits);
}
if(info->cmp == BANDWIDTH_GT)
{
printf("--greater_than %lld ", info->bandwidth_cutoff);
}
if(info->cmp == BANDWIDTH_LT)
{
printf("--less_than %lld ", info->bandwidth_cutoff);
}
if (info->type == BANDWIDTH_COMBINED) /* too much data to print for multi types, have to use socket to get/set data */
{
if( info->reset_interval != BANDWIDTH_NEVER && info->next_reset != 0 && info->next_reset < now)
{
/*
* current bandwidth only gets reset when first packet after reset interval arrives, so output
* zero if we're already past interval, but no packets have arrived
*/
printf("--current_bandwidth 0 ");
}
else
{
printf("--current_bandwidth %lld ", info->current_bandwidth);
}
}
if(info->reset_is_constant_interval)
{
printf("--reset_interval %ld ", info->reset_interval);
}
else
{
if(info->reset_interval == BANDWIDTH_MINUTE)
{
printf("--reset_interval minute ");
}
else if(info->reset_interval == BANDWIDTH_HOUR)
{
printf("--reset_interval hour ");
}
else if(info->reset_interval == BANDWIDTH_DAY)
{
printf("--reset_interval day ");
}
else if(info->reset_interval == BANDWIDTH_WEEK)
{
printf("--reset_interval week ");
}
else if(info->reset_interval == BANDWIDTH_MONTH)
{
printf("--reset_interval month ");
}
}
if(info->reset_time > 0)
{
printf("--reset_time %ld ", info->reset_time);
}
if(info->num_intervals_to_save > 0)
{
printf("--intervals_to_save %d ", info->num_intervals_to_save);
}
}
}
/*
* Final check, we can't have reset_time without reset_interval
*/
static void final_check(unsigned int flags)
{
if (flags == 0)
{
param_problem_exit_error("You must specify at least one argument. ");
}
if( (flags & BANDWIDTH_RESET_INTERVAL) == 0 && (flags & BANDWIDTH_RESET_TIME) != 0)
{
param_problem_exit_error("You may not specify '--reset_time' without '--reset_interval' ");
}
if( (flags & BANDWIDTH_REQUIRES_SUBNET) == BANDWIDTH_REQUIRES_SUBNET && (flags & BANDWIDTH_SUBNET) == 0 )
{
param_problem_exit_error("You must specify a local subnet (--subnet a.b.c.d/mask) to match individual local/remote IPs ");
}
/* update timezone minutes_west in kernel to match userspace*/
set_kernel_timezone();
}
/* Prints out the matchinfo. */
#ifdef _XTABLES_H
static void print(const void *ip, const struct xt_entry_match *match, int numeric)
#else
static void print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric)
#endif
{
printf("bandwidth ");
struct ipt_bandwidth_info *info = (struct ipt_bandwidth_info *)match->data;
print_bandwidth_args(info);
}
/* Saves the union ipt_matchinfo in parsable form to stdout. */
#ifdef _XTABLES_H
static void save(const void *ip, const struct xt_entry_match *match)
#else
static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
#endif
{
struct ipt_bandwidth_info *info = (struct ipt_bandwidth_info *)match->data;
time_t now;
print_bandwidth_args(info);
time(&now);
printf("--last_backup-time %ld ", now);
}
static struct iptables_match bandwidth =
{
.next = NULL,
.name = "bandwidth",
#ifdef XTABLES_VERSION_CODE
.version = XTABLES_VERSION,
#else
.version = IPTABLES_VERSION,
#endif
.size = XT_ALIGN(sizeof(struct ipt_bandwidth_info)),
.userspacesize = XT_ALIGN(sizeof(struct ipt_bandwidth_info)),
.help = &help,
.parse = &parse,
.final_check = &final_check,
.print = &print,
.save = &save,
.extra_opts = opts
};
void _init(void)
{
register_match(&bandwidth);
}
static void param_problem_exit_error(char* msg)
{
#ifdef xtables_error
xtables_error(PARAMETER_PROBLEM, "%s", msg);
#else
exit_error(PARAMETER_PROBLEM, msg);
#endif
}
/*
* implement a simple function to get positive powers of positive integers so we don't have to mess with math.h
* all we really need are powers of 2 for calculating netmask
* This is only called a couple of times, so speed isn't an issue either
*/
static unsigned long get_pow(unsigned long base, unsigned long pow)
{
unsigned long ret = pow == 0 ? 1 : base*get_pow(base, pow-1);
return ret;
}
int parse_sub(char* subnet_string, uint32_t* subnet, uint32_t* subnet_mask)
{
int valid = 0;
unsigned int A,B,C,D,E,F,G,H;
int read_int = sscanf(subnet_string, "%u.%u.%u.%u/%u.%u.%u.%u", &A, &B, &C, &D, &E, &F, &G, &H);
if(read_int >= 5)
{
if( A <= 255 && B <= 255 && C <= 255 && D <= 255)
{
unsigned char* sub = (unsigned char*)(subnet);
unsigned char* msk = (unsigned char*)(subnet_mask);
*( sub ) = (unsigned char)A;
*( sub + 1 ) = (unsigned char)B;
*( sub + 2 ) = (unsigned char)C;
*( sub + 3 ) = (unsigned char)D;
if(read_int == 5)
{
unsigned int mask = E;
if(mask <= 32)
{
int msk_index;
for(msk_index=0; msk_index*8 < mask; msk_index++)
{
int bit_index;
msk[msk_index] = 0;
for(bit_index=0; msk_index*8 + bit_index < mask && bit_index < 8; bit_index++)
{
msk[msk_index] = msk[msk_index] + get_pow(2, 7-bit_index);
}
}
}
valid = 1;
}
if(read_int == 8)
{
if( E <= 255 && F <= 255 && G <= 255 && H <= 255)
*( msk ) = (unsigned char)E;
*( msk + 1 ) = (unsigned char)F;
*( msk + 2 ) = (unsigned char)G;
*( msk + 3 ) = (unsigned char)H;
valid = 1;
}
}
}
if(valid)
{
*subnet = (*subnet & *subnet_mask );
}
return valid;
}
int get_minutes_west(void)
{
time_t now;
struct tm* utc_info;
struct tm* tz_info;
int utc_day;
int utc_hour;
int utc_minute;
int tz_day;
int tz_hour;
int tz_minute;
int minuteswest;
time(&now);
utc_info = gmtime(&now);
utc_day = utc_info->tm_mday;
utc_hour = utc_info->tm_hour;
utc_minute = utc_info->tm_min;
tz_info = localtime(&now);
tz_day = tz_info->tm_mday;
tz_hour = tz_info->tm_hour;
tz_minute = tz_info->tm_min;
utc_day = utc_day < tz_day - 1 ? tz_day + 1 : utc_day;
tz_day = tz_day < utc_day - 1 ? utc_day + 1 : tz_day;
minuteswest = (24*60*utc_day + 60*utc_hour + utc_minute) - (24*60*tz_day + 60*tz_hour + tz_minute) ;
return minuteswest;
}
void set_kernel_timezone(void)
{
struct timeval tv;
struct timezone old_tz;
struct timezone new_tz;
new_tz.tz_minuteswest = get_minutes_west();;
new_tz.tz_dsttime = 0;
/* Get tv to pass to settimeofday(2) to be sure we avoid hour-sized warp */
/* (see gettimeofday(2) man page, or /usr/src/linux/kernel/time.c) */
gettimeofday(&tv, &old_tz);
/* set timezone */
settimeofday(&tv, &new_tz);
}

View File

@ -0,0 +1,876 @@
/* timerange -- An iptables extension to match multiple timeranges within a week
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2009-2010 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>
#include <time.h>
#include <sys/time.h>
/*
* in iptables 1.4.0 and higher, iptables.h includes xtables.h, which
* we can use to check whether we need to deal with the new requirements
* in pre-processor directives below
*/
#include <iptables.h>
#include <linux/netfilter_ipv4/ipt_timerange.h>
#ifdef _XTABLES_H
#define iptables_rule_match xtables_rule_match
#define iptables_match xtables_match
#define iptables_target xtables_target
#define ipt_tryload xt_tryload
#endif
/*
* XTABLES_VERSION_CODE is only defined in versions 1.4.1 and later, which
* also require the use of xtables_register_match
*
* Version 1.4.0 uses register_match like previous versions
*/
#ifdef XTABLES_VERSION_CODE
#define register_match xtables_register_match
#endif
/* utility functions necessary for module to work across multiple iptables versions */
static int my_check_inverse(const char option[], int* invert, int *my_optind, int argc);
static void param_problem_exit_error(char* msg);
long* parse_time_ranges(char* time_ranges, unsigned char is_weekly_range);
void merge_adjacent_time_ranges(long* time_ranges, unsigned char is_weekly_range);
unsigned long parse_time(char* time_str);
long* parse_weekdays(char* wd_str);
char** split_on_separators(char* line, char* separators, int num_separators, int max_pieces, int include_remainder_at_max);
void to_lowercase(char* str);
char* trim_flanking_whitespace(char* str);
void set_kernel_timezone(void);
/* Function which prints out usage message. */
static void help(void)
{
printf( "timerange options:\n --hours [HOURLY RANGES] --weekdays [WEEKDAYS ACTIVE] --weekly_ranges [WEEKLY RANGES]\n");
}
static struct option opts[] =
{
{ .name = "hours", .has_arg = 1, .flag = 0, .val = HOURS },
{ .name = "weekdays", .has_arg = 1, .flag = 0, .val = WEEKDAYS },
{ .name = "weekly_ranges", .has_arg = 1, .flag = 0, .val = WEEKLY_RANGE },
{ .name = 0 }
};
/* Function which parses command options; returns true if it
ate an option */
static int parse( int c,
char **argv,
int invert,
unsigned int *flags,
#ifdef _XTABLES_H
const void *entry,
#else
const struct ipt_entry *entry,
unsigned int *nfcache,
#endif
struct ipt_entry_match **match
)
{
struct ipt_timerange_info *info = (struct ipt_timerange_info *)(*match)->data;
int valid_arg = 0;
if(*flags == 0)
{
my_check_inverse(optarg, &invert, &optind, 0);
info->invert = invert ? 1 : 0;
}
long* parsed = NULL;
switch (c)
{
case HOURS:
parsed = parse_time_ranges(argv[optind-1], 0);
if(parsed != NULL && (*flags & HOURS) == 0 && (*flags & WEEKLY_RANGE) == 0)
{
int range_index = 0;
for(range_index = 0; parsed[range_index] != -1; range_index++)
{
if(range_index > 100)
{
return 0;
}
info->ranges[range_index] = parsed[range_index];
}
info->ranges[range_index] = -1;
free(parsed);
valid_arg = 1;
*flags = *flags+ c;
info->type = *flags;
}
break;
case WEEKDAYS:
parsed = parse_weekdays(argv[optind-1]);
if(parsed != NULL && (*flags & WEEKDAYS) == 0 && (*flags & WEEKLY_RANGE) == 0)
{
int day_index;
for(day_index=0; day_index < 7; day_index++)
{
info->days[day_index] = parsed[day_index];
}
free(parsed);
valid_arg = 1 ;
*flags = *flags + c;
info->type = *flags;
}
break;
case WEEKLY_RANGE:
parsed = parse_time_ranges(argv[optind-1], 1);
if(parsed != NULL && (*flags & HOURS) == 0 && (*flags & WEEKDAYS) == 0 && (*flags & WEEKLY_RANGE) == 0 )
{
int range_index = 0;
for(range_index = 0; parsed[range_index] != -1; range_index++)
{
if(range_index > 100)
{
return 0;
}
info->ranges[range_index] = parsed[range_index];
}
info->ranges[range_index] = -1;
free(parsed);
valid_arg = 1;
*flags = *flags+c;
info->type = *flags;
}
break;
}
return valid_arg;
}
static void print_timerange_args( struct ipt_timerange_info* info )
{
int i;
if(info->invert == 1)
{
printf(" ! ");
}
switch(info->type)
{
case DAYS_HOURS:
case HOURS:
printf(" --hours ");
for(i=0; info->ranges[i] != -1; i++)
{
printf("%ld", info->ranges[i]);
if(info->ranges[i+1] != -1)
{
if(i % 2 == 0){ printf("-"); }
else { printf(","); }
}
}
if(info->type == HOURS) { break; }
case WEEKDAYS:
printf(" --weekdays ");
for(i=0; i<7; i++)
{
printf("%d", info->days[i]);
if(i != 6){ printf(","); }
}
break;
case WEEKLY_RANGE:
printf(" --weekly_ranges ");
for(i=0; info->ranges[i] != -1; i++)
{
printf("%ld", info->ranges[i]);
if(info->ranges[i+1] != -1)
{
if(i % 2 == 0){ printf("-"); }
else { printf(","); }
}
}
break;
}
printf(" ");
}
/* Final check; must have specified a test string with either --contains or --contains_regex. */
static void final_check(unsigned int flags)
{
if(flags ==0)
{
param_problem_exit_error("Invalid arguments to time_range");
}
/* update timezone minutes_west in kernel to match userspace*/
set_kernel_timezone();
}
/* Prints out the matchinfo. */
#ifdef _XTABLES_H
static void print(const void *ip, const struct xt_entry_match *match, int numeric)
#else
static void print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric)
#endif
{
printf("timerange ");
struct ipt_timerange_info *info = (struct ipt_timerange_info *)match->data;
print_timerange_args(info);
}
/* Saves the union ipt_matchinfo in parsable form to stdout. */
#ifdef _XTABLES_H
static void save(const void *ip, const struct xt_entry_match *match)
#else
static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
#endif
{
struct ipt_timerange_info *info = (struct ipt_timerange_info *)match->data;
print_timerange_args(info);
}
static struct iptables_match timerange =
{
.next = NULL,
.name = "timerange",
#ifdef XTABLES_VERSION_CODE
.version = XTABLES_VERSION,
#else
.version = IPTABLES_VERSION,
#endif
.size = XT_ALIGN(sizeof(struct ipt_timerange_info)),
.userspacesize = XT_ALIGN(sizeof(struct ipt_timerange_info)),
.help = &help,
.parse = &parse,
.final_check = &final_check,
.print = &print,
.save = &save,
.extra_opts = opts
};
void _init(void)
{
register_match(&timerange);
}
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
static int my_check_inverse(const char option[], int* invert, int *my_optind, int argc)
{
if (option && strcmp(option, "!") == 0)
{
if (*invert)
{
param_problem_exit_error("Multiple `!' flags not allowed");
}
*invert = TRUE;
if (my_optind != NULL)
{
++*my_optind;
if (argc && *my_optind > argc)
{
param_problem_exit_error("no argument following `!'");
}
}
return TRUE;
}
return FALSE;
}
static void param_problem_exit_error(char* msg)
{
#ifdef xtables_error
xtables_error(PARAMETER_PROBLEM, "%s", msg);
#else
exit_error(PARAMETER_PROBLEM, msg);
#endif
}
/* takes a string of days e.g. "Monday, Tuesday, Friday", and turns into an array of 7 longs
* each 0 or 1, one for each weekday starting with sunday, e.g. [0,1,1,0,0,1,0] for our example
*/
long* parse_weekdays(char* wd_str)
{
long* weekdays = (long*)malloc(7*sizeof(long));
weekdays[0] = weekdays[1] = weekdays[2] = weekdays[3] = weekdays[4] = weekdays[5] = weekdays[6] = 0;
char** days = split_on_separators(wd_str, ",", 1, -1, 0);
int day_index;
int found = 0;
for(day_index=0; days[day_index] != NULL; day_index++)
{
char day[4];
trim_flanking_whitespace(days[day_index]);
memcpy(day, days[day_index], 3);
free(days[day_index]);
day[3] = '\0';
to_lowercase(day);
if(strcmp(day, "sun") == 0)
{
weekdays[0] = 1;
found = 1;
}
else if(strcmp(day, "mon") ==0)
{
weekdays[1] = 1;
found = 1;
}
else if(strcmp(day, "tue") ==0)
{
weekdays[2] = 1;
found = 1;
}
else if(strcmp(day, "wed") ==0)
{
weekdays[3] = 1;
found = 1;
}
else if(strcmp(day, "thu") ==0)
{
weekdays[4] = 1;
found = 1;
}
else if(strcmp(day, "fri") ==0)
{
weekdays[5] = 1;
found = 1;
}
else if(strcmp(day, "sat") ==0)
{
weekdays[6] = 1;
found = 1;
}
else if(strcmp(day, "all") ==0)
{
weekdays[0] = weekdays[1] = weekdays[2] = weekdays[3] = weekdays[4] = weekdays[5] = weekdays[6] = 1;
found = 1;
}
}
free(days);
if(found == 0)
{
free(weekdays);
weekdays = NULL;
}
return weekdays;
}
/* is_weekly_range indicates whether we're parsing hours within a single day or a range over a whole week */
long* parse_time_ranges(char* time_ranges, unsigned char is_weekly_range)
{
char** pieces = split_on_separators(time_ranges, ",", 1, -1, 0);
int num_pieces = 0;
for(num_pieces = 0; pieces[num_pieces] != NULL; num_pieces++) {};
long *parsed = (long*)malloc( (1+(num_pieces*2)) * sizeof(long));
int piece_index = 0;
for(piece_index = 0; pieces[piece_index] != NULL; piece_index++)
{
trim_flanking_whitespace(pieces[piece_index]);
char** times=split_on_separators(pieces[piece_index], "-", 1, 2, 0);
int time_count = 0;
for(time_count = 0; times[time_count] != 0 ; time_count++){}
if( time_count == 2 )
{
unsigned long start = parse_time(trim_flanking_whitespace(times[0]));
unsigned long end = parse_time(trim_flanking_whitespace(times[1]));
parsed[ piece_index*2 ] = (long)start;
parsed[ (piece_index*2)+1 ] = (long)end;
free( times[1] );
}
if( time_count > 0) { free(times[0]); }
free(times);
free(pieces[piece_index]);
}
free(pieces);
parsed[ (num_pieces*2) ] = -1; // terminated with -1
// make sure there is no overlap -- this will invalidate ranges
int range_index = 0;
char overlap_found = 0;
for(range_index = 0; range_index < num_pieces; range_index++)
{
// now test for overlap
long start1 = parsed[ (range_index*2) ];
long end1 = parsed[ (range_index*2)+1 ];
end1= end1 < start1 ? end1 + (is_weekly_range ? 7*24*60*60 : 24*60*60) : end1;
int range_index2 = 0;
for(range_index2 = 0; range_index2 < num_pieces; range_index2++)
{
if(range_index2 != range_index)
{
long start2 = parsed[ (range_index2*2) ];
long end2 = parsed[ (range_index2*2)+1 ];
end2= end2 < start2 ? end2 + (is_weekly_range ? 7*24*60*60 : 24*60*60) : end2;
overlap_found = overlap_found || (start1 < end2 && end1 > start2 );
}
}
}
if(!overlap_found)
{
// sort ranges
int sorted_index = 0;
while(parsed[sorted_index] != -1)
{
int next_start=-1;
int next_start_index=-1;
int test_index;
long tmp1;
long tmp2;
for(test_index=sorted_index; parsed[test_index] != -1; test_index=test_index+2)
{
next_start_index = next_start < 0 || next_start > parsed[test_index] ? test_index : next_start_index;
next_start = next_start < 0 || next_start > parsed[test_index] ? parsed[test_index] : next_start;
}
tmp1 = parsed[next_start_index];
tmp2 = parsed[next_start_index+1];
parsed[next_start_index] = parsed[sorted_index];
parsed[next_start_index+1] = parsed[sorted_index+1];
parsed[sorted_index] = tmp1;
parsed[sorted_index+1] = tmp2;
sorted_index = sorted_index + 2;
}
}
else
{
// de-allocate parsed, set to NULL
free(parsed);
parsed = NULL;
}
// merge time ranges where end of first = start of second
merge_adjacent_time_ranges(parsed, is_weekly_range);
// if always active, free & return NULL
int max_multiple = is_weekly_range ? 7 : 1;
if(parsed[0] == 0 && parsed[1] == max_multiple*24*60*60)
{
free(parsed);
parsed = NULL;
}
//adjust so any range that crosses end of range is split in two
int num_range_indices=0;
for(num_range_indices=0; parsed[num_range_indices] != -1; num_range_indices++){}
long* adjusted_range = (long*)malloc((3+num_range_indices)*sizeof(long));
int ar_index = 0;
int old_index = 0;
if(parsed[num_range_indices-1] < parsed[0])
{
adjusted_range[0] = 0;
adjusted_range[1] = parsed[num_range_indices-1];
ar_index = ar_index + 2;
parsed[num_range_indices-1] = -1;
}
for(old_index=0; parsed[old_index] != -1; old_index++)
{
adjusted_range[ar_index] = parsed[old_index];
ar_index++;
}
if(ar_index % 2 == 1 )
{
adjusted_range[ar_index] = is_weekly_range ? 7*24*60*60 : 24*60*60;
ar_index++;
}
adjusted_range[ar_index] = -1;
free(parsed);
return adjusted_range;
}
void merge_adjacent_time_ranges(long* time_ranges, unsigned char is_weekly_range)
{
int range_length = 0;
while(time_ranges[range_length] != -1){ range_length++; }
int* merged_indices = (int*)malloc((range_length+1)*sizeof(int));
int merged_index=0;
int next_index;
for(next_index=0; time_ranges[next_index] != -1; next_index++)
{
if(next_index == 0)
{
merged_indices[merged_index] = next_index;
merged_index++;
}
else if( time_ranges[next_index+1] == -1 )
{
merged_indices[merged_index] = next_index;
merged_index++;
}
else if( time_ranges[next_index] != time_ranges[next_index-1] && time_ranges[next_index] != time_ranges[next_index+1] )
{
merged_indices[merged_index] = next_index;
merged_index++;
}
}
merged_indices[merged_index] = -1;
for(next_index=0; merged_indices[next_index] != -1; next_index++)
{
time_ranges[next_index] = time_ranges[ merged_indices[next_index] ];
}
time_ranges[next_index] = -1;
free(merged_indices);
}
/*
* assumes 24hr time, not am/pm, in format:
* (Day of week) hours:minutes:seconds
* if day of week is present, returns seconds since midnight on Sunday
* otherwise, seconds since midnight
*/
unsigned long parse_time(char* time_str)
{
while((*time_str == ' ' || *time_str == '\t') && *time_str != '\0') { time_str++; }
int weekday = -1;
if(strlen(time_str) > 3)
{
char wday_test[4];
memcpy(wday_test, time_str, 3);
wday_test[3] = '\0';
to_lowercase(wday_test);
if(strcmp(wday_test, "sun") == 0)
{
weekday = 0;
}
else if(strcmp(wday_test, "mon") == 0)
{
weekday = 1;
}
else if(strcmp(wday_test, "tue") == 0)
{
weekday = 2;
}
else if(strcmp(wday_test, "wed") == 0)
{
weekday = 3;
}
else if(strcmp(wday_test, "thu") == 0)
{
weekday = 4;
}
else if(strcmp(wday_test, "fri") == 0)
{
weekday = 5;
}
else if(strcmp(wday_test, "sat") == 0)
{
weekday = 6;
}
}
if(weekday >= 0)
{
time_str = time_str + 3;
while( (*time_str < 48 || *time_str > 57) && *time_str != '\0') { time_str++; }
}
char** time_parts=split_on_separators(time_str, ":", 1, -1, 0);
unsigned long seconds = weekday < 0 ? 0 : ( ((unsigned long)(weekday))*60*60*24 );
unsigned long tmp;
unsigned long multiple = 60*60;
int tp_index = 0;
for(tp_index=0; time_parts[tp_index] != NULL; tp_index++)
{
sscanf(time_parts[tp_index], "%ld", &tmp);
seconds = seconds + (tmp*multiple);
multiple = (unsigned long)(multiple/60);
free(time_parts[tp_index]);
}
free(time_parts);
return seconds;
}
void to_lowercase(char* str)
{
int i;
for(i = 0; str[i] != '\0'; i++)
{
str[i] = tolower(str[i]);
}
}
/*
* line_str is the line to be parsed -- it is not modified in any way
* max_pieces indicates number of pieces to return, if negative this is determined dynamically
* include_remainder_at_max indicates whether the last piece, when max pieces are reached,
* should be what it would normally be (0) or the entire remainder of the line (1)
* if max_pieces < 0 this parameter is ignored
*
*
* returns all non-separator pieces in a line
* result is dynamically allocated, MUST be freed after call-- even if
* line is empty (you still get a valid char** pointer to to a NULL char*)
*/
char** split_on_separators(char* line_str, char* separators, int num_separators, int max_pieces, int include_remainder_at_max)
{
char** split;
if(line_str != NULL)
{
int split_index;
int non_separator_found;
char* dup_line;
char* start;
if(max_pieces < 0)
{
/* count number of separator characters in line -- this count + 1 is an upperbound on number of pieces */
int separator_count = 0;
int line_index;
for(line_index = 0; line_str[line_index] != '\0'; line_index++)
{
int sep_index;
int found = 0;
for(sep_index =0; found == 0 && sep_index < num_separators; sep_index++)
{
found = separators[sep_index] == line_str[line_index] ? 1 : 0;
}
separator_count = separator_count+ found;
}
max_pieces = separator_count + 1;
}
split = (char**)malloc((1+max_pieces)*sizeof(char*));
split_index = 0;
split[split_index] = NULL;
dup_line = strdup(line_str);
start = dup_line;
non_separator_found = 0;
while(non_separator_found == 0)
{
int matches = 0;
int sep_index;
for(sep_index =0; sep_index < num_separators; sep_index++)
{
matches = matches == 1 || separators[sep_index] == start[0] ? 1 : 0;
}
non_separator_found = matches==0 || start[0] == '\0' ? 1 : 0;
if(non_separator_found == 0)
{
start++;
}
}
while(start[0] != '\0' && split_index < max_pieces)
{
/* find first separator index */
int first_separator_index = 0;
int separator_found = 0;
while( separator_found == 0 )
{
int sep_index;
for(sep_index =0; separator_found == 0 && sep_index < num_separators; sep_index++)
{
separator_found = separators[sep_index] == start[first_separator_index] || start[first_separator_index] == '\0' ? 1 : 0;
}
if(separator_found == 0)
{
first_separator_index++;
}
}
/* copy next piece to split array */
if(first_separator_index > 0)
{
char* next_piece = NULL;
if(split_index +1 < max_pieces || include_remainder_at_max <= 0)
{
next_piece = (char*)malloc((first_separator_index+1)*sizeof(char));
memcpy(next_piece, start, first_separator_index);
next_piece[first_separator_index] = '\0';
}
else
{
next_piece = strdup(start);
}
split[split_index] = next_piece;
split[split_index+1] = NULL;
split_index++;
}
/* find next non-separator index, indicating start of next piece */
start = start+ first_separator_index;
non_separator_found = 0;
while(non_separator_found == 0)
{
int matches = 0;
int sep_index;
for(sep_index =0; sep_index < num_separators; sep_index++)
{
matches = matches == 1 || separators[sep_index] == start[0] ? 1 : 0;
}
non_separator_found = matches==0 || start[0] == '\0' ? 1 : 0;
if(non_separator_found == 0)
{
start++;
}
}
}
free(dup_line);
}
else
{
split = (char**)malloc((1)*sizeof(char*));
split[0] = NULL;
}
return split;
}
char* trim_flanking_whitespace(char* str)
{
int new_start = 0;
int new_length = 0;
char whitespace[5] = { ' ', '\t', '\n', '\r', '\0' };
int num_whitespace_chars = 4;
int str_index = 0;
int is_whitespace = 1;
int test;
while( (test = str[str_index]) != '\0' && is_whitespace == 1)
{
int whitespace_index;
is_whitespace = 0;
for(whitespace_index = 0; whitespace_index < num_whitespace_chars && is_whitespace == 0; whitespace_index++)
{
is_whitespace = test == whitespace[whitespace_index] ? 1 : 0;
}
str_index = is_whitespace == 1 ? str_index+1 : str_index;
}
new_start = str_index;
str_index = strlen(str) - 1;
is_whitespace = 1;
while( str_index >= new_start && is_whitespace == 1)
{
int whitespace_index;
is_whitespace = 0;
for(whitespace_index = 0; whitespace_index < num_whitespace_chars && is_whitespace == 0; whitespace_index++)
{
is_whitespace = str[str_index] == whitespace[whitespace_index] ? 1 : 0;
}
str_index = is_whitespace == 1 ? str_index-1 : str_index;
}
new_length = str[new_start] == '\0' ? 0 : str_index + 1 - new_start;
if(new_start > 0)
{
for(str_index = 0; str_index < new_length; str_index++)
{
str[str_index] = str[str_index+new_start];
}
}
str[new_length] = 0;
return str;
}
void set_kernel_timezone(void)
{
time_t now;
struct tm* utc_info;
struct tm* tz_info;
int utc_day;
int utc_hour;
int utc_minute;
int tz_day;
int tz_hour;
int tz_minute;
int minuteswest;
struct timeval tv;
struct timezone old_tz;
struct timezone new_tz;
time(&now);
utc_info = gmtime(&now);
utc_day = utc_info->tm_mday;
utc_hour = utc_info->tm_hour;
utc_minute = utc_info->tm_min;
tz_info = localtime(&now);
tz_day = tz_info->tm_mday;
tz_hour = tz_info->tm_hour;
tz_minute = tz_info->tm_min;
utc_day = utc_day < tz_day - 1 ? tz_day + 1 : utc_day;
tz_day = tz_day < utc_day - 1 ? utc_day + 1 : tz_day;
minuteswest = (24*60*utc_day + 60*utc_hour + utc_minute) - (24*60*tz_day + 60*tz_hour + tz_minute) ;
new_tz.tz_minuteswest = minuteswest;
new_tz.tz_dsttime = 0;
/* Get tv to pass to settimeofday(2) to be sure we avoid hour-sized warp */
/* (see gettimeofday(2) man page, or /usr/src/linux/kernel/time.c) */
gettimeofday(&tv, &old_tz);
/* set timezone */
settimeofday(&tv, &new_tz);
}

View File

@ -0,0 +1,700 @@
/* webmon -- An iptables extension to match URLs in HTTP requests
* This module can match using string match or regular expressions
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2008-2011 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <arpa/inet.h>
/*
* in iptables 1.4.0 and higher, iptables.h includes xtables.h, which
* we can use to check whether we need to deal with the new requirements
* in pre-processor directives below
*/
#include <iptables.h>
#include <linux/netfilter_ipv4/ipt_webmon.h>
#ifdef _XTABLES_H
#define iptables_rule_match xtables_rule_match
#define iptables_match xtables_match
#define iptables_target xtables_target
#define ipt_tryload xt_tryload
#endif
/*
* XTABLES_VERSION_CODE is only defined in versions 1.4.1 and later, which
* also require the use of xtables_register_match
*
* Version 1.4.0 uses register_match like previous versions
*/
#ifdef XTABLES_VERSION_CODE
#define register_match xtables_register_match
#endif
#define STRIP "%d.%d.%d.%d"
#define NIPQUAD(addr) \
((unsigned char *)&addr)[0], \
((unsigned char *)&addr)[1], \
((unsigned char *)&addr)[2], \
((unsigned char *)&addr)[3]
/* utility functions necessary for module to work across multiple iptables versions */
static void param_problem_exit_error(char* msg);
void parse_ips_and_ranges(char* addr_str, struct ipt_webmon_info *info);
char** split_on_separators(char* line, char* separators, int num_separators, int max_pieces, int include_remainder_at_max);
char* trim_flanking_whitespace(char* str);
unsigned char* read_entire_file(FILE* in, unsigned long read_block_size, unsigned long *length);
#define DEFAULT_MAX 300
#define SEARCH_LOAD_FILE 100
#define DOMAIN_LOAD_FILE 101
#define CLEAR_SEARCH 102
#define CLEAR_DOMAIN 103
static char* domain_load_file = NULL;
static char* search_load_file = NULL;
static uint32_t global_max_domains = DEFAULT_MAX;
static uint32_t global_max_searches = DEFAULT_MAX;
/* Function which prints out usage message. */
static void help(void)
{
printf( "webmon options:\n");
}
static struct option opts[] =
{
{ .name = "exclude_ips", .has_arg = 1, .flag = 0, .val = WEBMON_EXCLUDE },
{ .name = "include_ips", .has_arg = 1, .flag = 0, .val = WEBMON_INCLUDE },
{ .name = "max_domains", .has_arg = 1, .flag = 0, .val = WEBMON_MAXDOMAIN },
{ .name = "max_searches", .has_arg = 1, .flag = 0, .val = WEBMON_MAXSEARCH },
{ .name = "search_load_file", .has_arg = 1, .flag = 0, .val = SEARCH_LOAD_FILE },
{ .name = "domain_load_file", .has_arg = 1, .flag = 0, .val = DOMAIN_LOAD_FILE },
{ .name = "clear_search", .has_arg = 0, .flag = 0, .val = CLEAR_SEARCH },
{ .name = "clear_domain", .has_arg = 0, .flag = 0, .val = CLEAR_DOMAIN },
{ .name = 0 }
};
static void webmon_init(
#ifdef _XTABLES_H
struct xt_entry_match *match
#else
struct ipt_entry_match *match, unsigned int *nfcache
#endif
)
{
struct ipt_webmon_info *info = (struct ipt_webmon_info *)match->data;
info->max_domains=DEFAULT_MAX;
info->max_searches=DEFAULT_MAX;
info->num_exclude_ips=0;
info->num_exclude_ranges=0;
info->exclude_type = WEBMON_EXCLUDE;
info->ref_count = NULL;
}
/* Function which parses command options; returns true if it ate an option */
static int parse( int c,
char **argv,
int invert,
unsigned int *flags,
#ifdef _XTABLES_H
const void *entry,
#else
const struct ipt_entry *entry,
unsigned int *nfcache,
#endif
struct ipt_entry_match **match
)
{
struct ipt_webmon_info *info = (struct ipt_webmon_info *)(*match)->data;
int valid_arg = 1;
long max;
switch (c)
{
case WEBMON_EXCLUDE:
parse_ips_and_ranges(optarg, info);
info->exclude_type = WEBMON_EXCLUDE;
break;
case WEBMON_INCLUDE:
parse_ips_and_ranges(optarg, info);
info->exclude_type = WEBMON_INCLUDE;
break;
case WEBMON_MAXSEARCH:
if( sscanf(argv[optind-1], "%ld", &max) == 0)
{
info->max_searches = DEFAULT_MAX ;
valid_arg = 0;
}
else
{
info->max_searches = (uint32_t)max;
global_max_searches = info->max_searches;
}
break;
case WEBMON_MAXDOMAIN:
if( sscanf(argv[optind-1], "%ld", &max) == 0)
{
info->max_domains = DEFAULT_MAX ;
valid_arg = 0;
}
else
{
info->max_domains = (uint32_t)max;
global_max_domains = info->max_domains;
}
break;
case SEARCH_LOAD_FILE:
search_load_file = strdup(optarg);
break;
case DOMAIN_LOAD_FILE:
domain_load_file = strdup(optarg);
break;
case CLEAR_SEARCH:
search_load_file = strdup("/dev/null");
break;
case CLEAR_DOMAIN:
domain_load_file = strdup("/dev/null");
break;
default:
valid_arg = 0;
}
return valid_arg;
}
static void print_webmon_args( struct ipt_webmon_info* info )
{
printf("--max_domains %ld ", (unsigned long int)info->max_domains);
printf("--max_searches %ld ", (unsigned long int)info->max_searches);
if(info->num_exclude_ips > 0 || info->num_exclude_ranges > 0)
{
int ip_index = 0;
char comma[3] = "";
printf("--%s ", (info->exclude_type == WEBMON_EXCLUDE ? "exclude_ips" : "include_ips"));
for(ip_index=0; ip_index < info->num_exclude_ips; ip_index++)
{
printf("%s"STRIP, comma, NIPQUAD((info->exclude_ips)[ip_index]) );
sprintf(comma, ",");
}
for(ip_index=0; ip_index < info->num_exclude_ranges; ip_index++)
{
struct ipt_webmon_ip_range r = (info->exclude_ranges)[ip_index];
printf("%s"STRIP"-"STRIP, comma, NIPQUAD(r.start), NIPQUAD(r.end) );
sprintf(comma, ",");
}
printf(" ");
}
}
static void do_load(char* file, uint32_t max, unsigned char type)
{
if(file != NULL)
{
unsigned char* data = NULL;
unsigned long data_length = 0;
char* file_data = NULL;
if(strcmp(file, "/dev/null") != 0)
{
FILE* in = fopen(file, "r");
if(in != NULL)
{
file_data = (char*)read_entire_file(in, 4096, &data_length);
fclose(in);
}
}
if(file_data == NULL)
{
file_data=strdup("");
}
if(file_data != NULL)
{
data_length = strlen(file_data) + sizeof(uint32_t)+2;
data = (unsigned char*)malloc(data_length);
if(data != NULL)
{
int sockfd = -1;
uint32_t* maxp = (uint32_t*)(data+1);
data[0] = type;
*maxp = max;
sprintf( (data+1+sizeof(uint32_t)), "%s", file_data);
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if(sockfd >= 0)
{
setsockopt(sockfd, IPPROTO_IP, WEBMON_SET, data, data_length);
close(sockfd);
}
free(data);
}
free(file_data);
}
}
}
static void final_check(unsigned int flags)
{
do_load(domain_load_file, global_max_domains, WEBMON_DOMAIN);
do_load(search_load_file, global_max_searches, WEBMON_SEARCH);
}
/* Prints out the matchinfo. */
#ifdef _XTABLES_H
static void print(const void *ip, const struct xt_entry_match *match, int numeric)
#else
static void print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric)
#endif
{
printf("WEBMON ");
struct ipt_webmon_info *info = (struct ipt_webmon_info *)match->data;
print_webmon_args(info);
}
/* Saves the union ipt_matchinfo in parsable form to stdout. */
#ifdef _XTABLES_H
static void save(const void *ip, const struct xt_entry_match *match)
#else
static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
#endif
{
struct ipt_webmon_info *info = (struct ipt_webmon_info *)match->data;
print_webmon_args(info);
}
static struct iptables_match webmon =
{
.next = NULL,
.name = "webmon",
#ifdef XTABLES_VERSION_CODE
.version = XTABLES_VERSION,
#else
.version = IPTABLES_VERSION,
#endif
.size = XT_ALIGN(sizeof(struct ipt_webmon_info)),
.userspacesize = XT_ALIGN(sizeof(struct ipt_webmon_info)),
.help = &help,
.init = &webmon_init,
.parse = &parse,
.final_check = &final_check,
.print = &print,
.save = &save,
.extra_opts = opts
};
void _init(void)
{
register_match(&webmon);
}
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
static void param_problem_exit_error(char* msg)
{
#ifdef xtables_error
xtables_error(PARAMETER_PROBLEM, "%s", msg);
#else
exit_error(PARAMETER_PROBLEM, msg);
#endif
}
void parse_ips_and_ranges(char* addr_str, struct ipt_webmon_info *info)
{
char** addr_parts = split_on_separators(addr_str, ",", 1, -1, 0);
info->num_exclude_ips=0;
info->num_exclude_ranges = 0;
int ip_part_index;
for(ip_part_index=0; addr_parts[ip_part_index] != NULL; ip_part_index++)
{
char* next_str = addr_parts[ip_part_index];
if(strchr(next_str, '-') != NULL)
{
char** range_parts = split_on_separators(next_str, "-", 1, 2, 1);
char* start = trim_flanking_whitespace(range_parts[0]);
char* end = trim_flanking_whitespace(range_parts[1]);
int start_ip[4];
int end_ip[4];
int start_valid = sscanf(start, "%d.%d.%d.%d", start_ip, start_ip+1, start_ip+2, start_ip+3);
int end_valid = sscanf(end, "%d.%d.%d.%d", end_ip, end_ip+1, end_ip+2, end_ip+3);
if(start_valid == 4 && end_valid == 4)
{
struct ipt_webmon_ip_range r;
struct in_addr sip, eip;
inet_pton(AF_INET, start, &sip);
inet_pton(AF_INET, end, &eip);
r.start = (uint32_t)sip.s_addr;
r.end = (uint32_t)eip.s_addr;
if(info->num_exclude_ranges < WEBMON_MAX_IP_RANGES && (unsigned long)ntohl(r.start) < (unsigned long)ntohl(r.end) )
{
(info->exclude_ranges)[ info->num_exclude_ranges ] = r;
info->num_exclude_ranges = info->num_exclude_ranges + 1;
}
}
free(start);
free(end);
free(range_parts);
}
else if(strchr(next_str, '/') != NULL)
{
char** range_parts = split_on_separators(next_str, "/", 1, 2, 1);
char* start = trim_flanking_whitespace(range_parts[0]);
char* end = trim_flanking_whitespace(range_parts[1]);
int base_ip[4];
int base_valid = sscanf(start, "%d.%d.%d.%d", base_ip, base_ip+1, base_ip+2, base_ip+3);
if(base_valid == 4)
{
int mask_valid = 0;
uint32_t mask;
if(strchr(end, '.') != NULL)
{
uint32_t mask_ip[4];
int mask_test = sscanf(end, "%d.%d.%d.%d", mask_ip, mask_ip+1, mask_ip+2, mask_ip+3);
if(mask_test == 4)
{
struct in_addr mask_add;
inet_pton(AF_INET, end, &mask_add);
mask = (uint32_t)mask_add.s_addr;
mask_valid = 1;
}
}
else
{
int mask_bits;
if( sscanf(end, "%d", &mask_bits) > 0)
{
if(mask_bits >=0 && mask_bits <= 32)
{
uint32_t byte = 0;
mask = 0;
for(byte=0; byte < 4; byte++)
{
unsigned char byte_bits = mask_bits > 8 ? 8 : mask_bits;
uint32_t byte_mask = 0;
mask_bits = mask_bits - byte_bits;
while(byte_bits > 0)
{
byte_mask = byte_mask | (256 >> byte_bits);
byte_bits--;
}
mask = mask | ((uint32_t)byte_mask << (byte*8));
printf("mask = "STRIP"\n", NIPQUAD(mask));
}
mask_valid = 1;
}
}
}
if(mask_valid)
{
struct ipt_webmon_ip_range r;
struct in_addr bip;
inet_pton(AF_INET, start, &bip);
r.start = ( ((uint32_t)bip.s_addr) & mask );
r.end = ( ((uint32_t)bip.s_addr) | (~mask) );
if(info->num_exclude_ranges < WEBMON_MAX_IP_RANGES && ntohl(r.start) <= ntohl(r.end) )
{
(info->exclude_ranges)[ info->num_exclude_ranges ] = r;
info->num_exclude_ranges = info->num_exclude_ranges + 1;
}
}
}
free(start);
free(end);
free(range_parts);
}
else
{
int parsed_ip[4];
int valid = sscanf(next_str, "%d.%d.%d.%d", parsed_ip, parsed_ip+1, parsed_ip+2, parsed_ip+3);
if(valid == 4)
{
struct in_addr ip;
trim_flanking_whitespace(next_str);
inet_pton(AF_INET, next_str, &ip);
if(info->num_exclude_ranges < WEBMON_MAX_IPS)
{
(info->exclude_ips)[ info->num_exclude_ips ] = (uint32_t)ip.s_addr;
info->num_exclude_ips = info->num_exclude_ips + 1;
}
}
}
free(next_str);
}
free(addr_parts);
}
/*
* line_str is the line to be parsed -- it is not modified in any way
* max_pieces indicates number of pieces to return, if negative this is determined dynamically
* include_remainder_at_max indicates whether the last piece, when max pieces are reached,
* should be what it would normally be (0) or the entire remainder of the line (1)
* if max_pieces < 0 this parameter is ignored
*
*
* returns all non-separator pieces in a line
* result is dynamically allocated, MUST be freed after call-- even if
* line is empty (you still get a valid char** pointer to to a NULL char*)
*/
char** split_on_separators(char* line_str, char* separators, int num_separators, int max_pieces, int include_remainder_at_max)
{
char** split;
if(line_str != NULL)
{
int split_index;
int non_separator_found;
char* dup_line;
char* start;
if(max_pieces < 0)
{
/* count number of separator characters in line -- this count + 1 is an upperbound on number of pieces */
int separator_count = 0;
int line_index;
for(line_index = 0; line_str[line_index] != '\0'; line_index++)
{
int sep_index;
int found = 0;
for(sep_index =0; found == 0 && sep_index < num_separators; sep_index++)
{
found = separators[sep_index] == line_str[line_index] ? 1 : 0;
}
separator_count = separator_count+ found;
}
max_pieces = separator_count + 1;
}
split = (char**)malloc((1+max_pieces)*sizeof(char*));
split_index = 0;
split[split_index] = NULL;
dup_line = strdup(line_str);
start = dup_line;
non_separator_found = 0;
while(non_separator_found == 0)
{
int matches = 0;
int sep_index;
for(sep_index =0; sep_index < num_separators; sep_index++)
{
matches = matches == 1 || separators[sep_index] == start[0] ? 1 : 0;
}
non_separator_found = matches==0 || start[0] == '\0' ? 1 : 0;
if(non_separator_found == 0)
{
start++;
}
}
while(start[0] != '\0' && split_index < max_pieces)
{
/* find first separator index */
int first_separator_index = 0;
int separator_found = 0;
while( separator_found == 0 )
{
int sep_index;
for(sep_index =0; separator_found == 0 && sep_index < num_separators; sep_index++)
{
separator_found = separators[sep_index] == start[first_separator_index] || start[first_separator_index] == '\0' ? 1 : 0;
}
if(separator_found == 0)
{
first_separator_index++;
}
}
/* copy next piece to split array */
if(first_separator_index > 0)
{
char* next_piece = NULL;
if(split_index +1 < max_pieces || include_remainder_at_max <= 0)
{
next_piece = (char*)malloc((first_separator_index+1)*sizeof(char));
memcpy(next_piece, start, first_separator_index);
next_piece[first_separator_index] = '\0';
}
else
{
next_piece = strdup(start);
}
split[split_index] = next_piece;
split[split_index+1] = NULL;
split_index++;
}
/* find next non-separator index, indicating start of next piece */
start = start+ first_separator_index;
non_separator_found = 0;
while(non_separator_found == 0)
{
int matches = 0;
int sep_index;
for(sep_index =0; sep_index < num_separators; sep_index++)
{
matches = matches == 1 || separators[sep_index] == start[0] ? 1 : 0;
}
non_separator_found = matches==0 || start[0] == '\0' ? 1 : 0;
if(non_separator_found == 0)
{
start++;
}
}
}
free(dup_line);
}
else
{
split = (char**)malloc((1)*sizeof(char*));
split[0] = NULL;
}
return split;
}
char* trim_flanking_whitespace(char* str)
{
int new_start = 0;
int new_length = 0;
char whitespace[5] = { ' ', '\t', '\n', '\r', '\0' };
int num_whitespace_chars = 4;
int str_index = 0;
int is_whitespace = 1;
int test;
while( (test = str[str_index]) != '\0' && is_whitespace == 1)
{
int whitespace_index;
is_whitespace = 0;
for(whitespace_index = 0; whitespace_index < num_whitespace_chars && is_whitespace == 0; whitespace_index++)
{
is_whitespace = test == whitespace[whitespace_index] ? 1 : 0;
}
str_index = is_whitespace == 1 ? str_index+1 : str_index;
}
new_start = str_index;
str_index = strlen(str) - 1;
is_whitespace = 1;
while( str_index >= new_start && is_whitespace == 1)
{
int whitespace_index;
is_whitespace = 0;
for(whitespace_index = 0; whitespace_index < num_whitespace_chars && is_whitespace == 0; whitespace_index++)
{
is_whitespace = str[str_index] == whitespace[whitespace_index] ? 1 : 0;
}
str_index = is_whitespace == 1 ? str_index-1 : str_index;
}
new_length = str[new_start] == '\0' ? 0 : str_index + 1 - new_start;
if(new_start > 0)
{
for(str_index = 0; str_index < new_length; str_index++)
{
str[str_index] = str[str_index+new_start];
}
}
str[new_length] = 0;
return str;
}
unsigned char* read_entire_file(FILE* in, unsigned long read_block_size, unsigned long *length)
{
int max_read_size = read_block_size;
unsigned char* read_string = (unsigned char*)malloc(max_read_size+1);
unsigned long bytes_read = 0;
int end_found = 0;
while(end_found == 0)
{
int nextch = '?';
while(nextch != EOF && bytes_read < max_read_size)
{
nextch = fgetc(in);
if(nextch != EOF)
{
read_string[bytes_read] = (unsigned char)nextch;
bytes_read++;
}
}
read_string[bytes_read] = '\0';
end_found = (nextch == EOF) ? 1 : 0;
if(end_found == 0)
{
unsigned char *new_str;
max_read_size = max_read_size + read_block_size;
new_str = (unsigned char*)malloc(max_read_size+1);
memcpy(new_str, read_string, bytes_read);
free(read_string);
read_string = new_str;
}
}
*length = bytes_read;
return read_string;
}

View File

@ -0,0 +1,290 @@
/* weburl -- An iptables extension to match URLs in HTTP requests
* This module can match using string match or regular expressions
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2008-2010 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
/*
* in iptables 1.4.0 and higher, iptables.h includes xtables.h, which
* we can use to check whether we need to deal with the new requirements
* in pre-processor directives below
*/
#include <iptables.h>
#include <linux/netfilter_ipv4/ipt_weburl.h>
#ifdef _XTABLES_H
#define iptables_rule_match xtables_rule_match
#define iptables_match xtables_match
#define iptables_target xtables_target
#define ipt_tryload xt_tryload
#endif
/*
* XTABLES_VERSION_CODE is only defined in versions 1.4.1 and later, which
* also require the use of xtables_register_match
*
* Version 1.4.0 uses register_match like previous versions
*/
#ifdef XTABLES_VERSION_CODE
#define register_match xtables_register_match
#endif
/* utility functions necessary for module to work across multiple iptables versions */
static int my_check_inverse(const char option[], int* invert, int *my_optind, int argc);
static void param_problem_exit_error(char* msg);
/* Function which prints out usage message. */
static void help(void)
{
printf( "weburl options:\n --contains [!] [STRING]\n --contains_regex [!] [REGEX]\n --matches_exactly [!] [STRING]\n --domain_only\n --path_only\n");
}
static struct option opts[] =
{
{ .name = "contains", .has_arg = 1, .flag = 0, .val = WEBURL_CONTAINS_TYPE }, //string
{ .name = "contains_regex", .has_arg = 1, .flag = 0, .val = WEBURL_REGEX_TYPE }, //regex
{ .name = "matches_exactly", .has_arg = 1, .flag = 0, .val = WEBURL_EXACT_TYPE }, //exact string match
{ .name = "domain_only", .has_arg = 0, .flag = 0, .val = WEBURL_DOMAIN_PART }, //only match domain portion of url
{ .name = "path_only", .has_arg = 0, .flag = 0, .val = WEBURL_PATH_PART }, //only match path portion of url
{ .name = 0 }
};
/* Function which parses command options; returns true if it
ate an option */
static int parse( int c,
char **argv,
int invert,
unsigned int *flags,
#ifdef _XTABLES_H
const void *entry,
#else
const struct ipt_entry *entry,
unsigned int *nfcache,
#endif
struct ipt_entry_match **match
)
{
struct ipt_weburl_info *info = (struct ipt_weburl_info *)(*match)->data;
int valid_arg = 0;
if(*flags < 10)
{
info->match_part = WEBURL_ALL_PART;
}
switch (c)
{
case WEBURL_CONTAINS_TYPE:
case WEBURL_REGEX_TYPE:
case WEBURL_EXACT_TYPE:
info->match_type = c;
//test whether to invert rule
my_check_inverse(optarg, &invert, &optind, 0);
info->invert = invert ? 1 : 0;
//test that test string is reasonable length, then to info
int testlen = strlen(argv[optind-1]);
if(testlen > 0 && testlen < MAX_TEST_STR)
{
strcpy(info->test_str, argv[optind-1]);
}
else if(testlen >= MAX_TEST_STR)
{
char err[100];
sprintf(err, "Parameter definition is too long, must be less than %d characters", MAX_TEST_STR);
param_problem_exit_error(err);
}
else
{
param_problem_exit_error("Parameter definition is incomplete");
}
if(*flags % 10 == 1)
{
param_problem_exit_error("You may only specify one string/pattern to match");
}
*flags = *flags + 1;
valid_arg = 1;
break;
case WEBURL_DOMAIN_PART:
case WEBURL_PATH_PART:
info->match_part = c;
if(*flags >= 10)
{
param_problem_exit_error("You may specify at most one part of the url to match:\n\t--domain_only, --path_only or neither (to match full url)\n");
}
*flags = *flags+10;
valid_arg = 1;
break;
}
return valid_arg;
}
static void print_weburl_args( struct ipt_weburl_info* info )
{
//invert
if(info->invert > 0)
{
printf("! ");
}
//match type
switch (info->match_type)
{
case WEBURL_CONTAINS_TYPE:
printf("--contains ");
break;
case WEBURL_REGEX_TYPE:
printf("--contains_regex ");
break;
case WEBURL_EXACT_TYPE:
printf("--matches_exactly ");
break;
}
//test string
printf("%s ", info->test_str);
//match part
switch(info->match_part)
{
case WEBURL_DOMAIN_PART:
printf("--domain_only ");
break;
case WEBURL_PATH_PART:
printf("--path_only ");
break;
case WEBURL_ALL_PART:
//print nothing
break;
}
}
/* Final check; must have specified a test string with either --contains or --contains_regex. */
static void final_check(unsigned int flags)
{
if (flags %10 == 0)
{
param_problem_exit_error("You must specify '--contains' or '--contains_regex' or '--matches_exactly'");
}
}
/* Prints out the matchinfo. */
#ifdef _XTABLES_H
static void print(const void *ip, const struct xt_entry_match *match, int numeric)
#else
static void print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric)
#endif
{
printf("WEBURL ");
struct ipt_weburl_info *info = (struct ipt_weburl_info *)match->data;
print_weburl_args(info);
}
/* Saves the union ipt_matchinfo in parsable form to stdout. */
#ifdef _XTABLES_H
static void save(const void *ip, const struct xt_entry_match *match)
#else
static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
#endif
{
struct ipt_weburl_info *info = (struct ipt_weburl_info *)match->data;
print_weburl_args(info);
}
static struct iptables_match weburl =
{
.next = NULL,
.name = "weburl",
#ifdef XTABLES_VERSION_CODE
.version = XTABLES_VERSION,
#else
.version = IPTABLES_VERSION,
#endif
.size = XT_ALIGN(sizeof(struct ipt_weburl_info)),
.userspacesize = XT_ALIGN(sizeof(struct ipt_weburl_info)),
.help = &help,
.parse = &parse,
.final_check = &final_check,
.print = &print,
.save = &save,
.extra_opts = opts
};
void _init(void)
{
register_match(&weburl);
}
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
static int my_check_inverse(const char option[], int* invert, int *my_optind, int argc)
{
if (option && strcmp(option, "!") == 0)
{
if (*invert)
{
param_problem_exit_error("Multiple `!' flags not allowed");
}
*invert = TRUE;
if (my_optind != NULL)
{
++*my_optind;
if (argc && *my_optind > argc)
{
param_problem_exit_error("no argument following `!'");
}
}
return TRUE;
}
return FALSE;
}
static void param_problem_exit_error(char* msg)
{
#ifdef xtables_error
xtables_error(PARAMETER_PROBLEM, "%s", msg);
#else
exit_error(PARAMETER_PROBLEM, msg);
#endif
}

View File

@ -0,0 +1,105 @@
/* Shared library add-on to iptables to add IMQ target support. */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <xtables.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter/xt_IMQ.h>
/* Function which prints out usage message. */
static void IMQ_help(void)
{
printf(
"IMQ target options:\n"
" --todev <N> enqueue to imq<N>, defaults to 0\n");
}
static struct option IMQ_opts[] = {
{ "todev", 1, 0, '1' },
{ 0 }
};
/* Initialize the target. */
static void IMQ_init(struct xt_entry_target *t)
{
struct xt_imq_info *mr = (struct xt_imq_info*)t->data;
mr->todev = 0;
}
/* Function which parses command options; returns true if it
ate an option */
static int IMQ_parse(int c, char **argv, int invert, unsigned int *flags,
const void *entry, struct xt_entry_target **target)
{
struct xt_imq_info *mr = (struct xt_imq_info*)(*target)->data;
switch(c) {
case '1':
/* if (xtables_check_inverse(optarg, &invert, NULL, 0, argv))
xtables_error(PARAMETER_PROBLEM,
"Unexpected `!' after --todev");
*/
mr->todev=atoi(optarg);
break;
default:
return 0;
}
return 1;
}
/* Prints out the targinfo. */
static void IMQ_print(const void *ip,
const struct xt_entry_target *target,
int numeric)
{
struct xt_imq_info *mr = (struct xt_imq_info*)target->data;
printf("IMQ: todev %u ", mr->todev);
}
/* Saves the union ipt_targinfo in parsable form to stdout. */
static void IMQ_save(const void *ip, const struct xt_entry_target *target)
{
struct xt_imq_info *mr = (struct xt_imq_info*)target->data;
printf(" --todev %u", mr->todev);
}
static struct xtables_target imq_target = {
.name = "IMQ",
.version = XTABLES_VERSION,
.family = NFPROTO_IPV4,
.size = XT_ALIGN(sizeof(struct xt_imq_info)),
.userspacesize = XT_ALIGN(sizeof(struct xt_imq_info)),
.help = IMQ_help,
.init = IMQ_init,
.parse = IMQ_parse,
.print = IMQ_print,
.save = IMQ_save,
.extra_opts = IMQ_opts,
};
static struct xtables_target imq_target6 = {
.name = "IMQ",
.version = XTABLES_VERSION,
.family = NFPROTO_IPV6,
.size = XT_ALIGN(sizeof(struct xt_imq_info)),
.userspacesize = XT_ALIGN(sizeof(struct xt_imq_info)),
.help = IMQ_help,
.init = IMQ_init,
.parse = IMQ_parse,
.print = IMQ_print,
.save = IMQ_save,
.extra_opts = IMQ_opts,
};
// void __attribute((constructor)) nf_ext_init(void){
void _init(void){
xtables_register_target(&imq_target);
xtables_register_target(&imq_target6);
}

View File

@ -0,0 +1,15 @@
This target is used to redirect the traffic to the IMQ driver and you can apply
QoS rules like HTB or CBQ.
For example you can select only traffic comming from a specific interface or
is going out on a specific interface.
Also it permits to capture the traffic BEFORE NAT in the case of outgoing traffic
or AFTER NAT in the case of incomming traffic.
.TP
\fB\-\-to\-dev\fP \fIvalue\fP
Set the IMQ interface where to send this traffic
.TP
Example:
.TP
Redirect incomming traffic from interface eth0 to imq0 and outgoing traffic to imq1:
iptables \-t mangle \-A FORWARD \-i eth0 \-j IMQ \-\-to\-dev 0
iptables \-t mangle \-A FORWARD \-o eth0 \-j IMQ \-\-to\-dev 1

View File

@ -0,0 +1,9 @@
#ifndef _XT_IMQ_H
#define _XT_IMQ_H
struct xt_imq_info {
unsigned int todev; /* target imq device */
};
#endif /* _XT_IMQ_H */

View File

@ -0,0 +1,106 @@
/* bandwidth -- An iptables extension for bandwidth monitoring/control
* Can be used to efficiently monitor bandwidth and/or implement bandwidth quotas
* Can be queried using the iptbwctl userspace library
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2009 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#ifndef _IPT_BANDWIDTH_H
#define _IPT_BANDWIDTH_H
/*flags -- first three don't map to parameters the rest do */
#define BANDWIDTH_INITIALIZED 1
#define BANDWIDTH_REQUIRES_SUBNET 2
#define BANDWIDTH_SUBNET 4
#define BANDWIDTH_CMP 8
#define BANDWIDTH_CURRENT 16
#define BANDWIDTH_RESET_INTERVAL 32
#define BANDWIDTH_RESET_TIME 64
#define BANDWIDTH_LAST_BACKUP 128
/* parameter defs that don't map to flag bits */
#define BANDWIDTH_TYPE 70
#define BANDWIDTH_ID 71
#define BANDWIDTH_GT 72
#define BANDWIDTH_LT 73
#define BANDWIDTH_MONITOR 74
#define BANDWIDTH_CHECK 75
#define BANDWIDTH_CHECK_NOSWAP 76
#define BANDWIDTH_CHECK_SWAP 77
#define BANDWIDTH_NUM_INTERVALS 78
/* possible reset intervals */
#define BANDWIDTH_MINUTE 80
#define BANDWIDTH_HOUR 81
#define BANDWIDTH_DAY 82
#define BANDWIDTH_WEEK 83
#define BANDWIDTH_MONTH 84
#define BANDWIDTH_NEVER 85
/* possible monitoring types */
#define BANDWIDTH_COMBINED 90
#define BANDWIDTH_INDIVIDUAL_SRC 91
#define BANDWIDTH_INDIVIDUAL_DST 92
#define BANDWIDTH_INDIVIDUAL_LOCAL 93
#define BANDWIDTH_INDIVIDUAL_REMOTE 94
/* socket id parameters (for userspace i/o) */
#define BANDWIDTH_SET 2048
#define BANDWIDTH_GET 2049
/* max id length */
#define BANDWIDTH_MAX_ID_LENGTH 50
/* 4 bytes for total number of entries, 100 entries of 12 bytes each, + 1 byte indicating whether all have been dumped */
#define BANDWIDTH_QUERY_LENGTH 1205
#define BANDWIDTH_ENTRY_LENGTH 12
struct ipt_bandwidth_info
{
char id[BANDWIDTH_MAX_ID_LENGTH];
unsigned char type;
unsigned char check_type;
uint32_t local_subnet;
uint32_t local_subnet_mask;
unsigned char cmp;
unsigned char reset_is_constant_interval;
time_t reset_interval; //specific fixed type (see above) or interval length in seconds
time_t reset_time; //seconds from start of month/week/day/hour/minute to do reset, or start point of interval if it is a constant interval
uint64_t bandwidth_cutoff;
uint64_t current_bandwidth;
time_t next_reset;
time_t previous_reset;
time_t last_backup_time;
uint32_t num_intervals_to_save;
unsigned long hashed_id;
void* iam;
uint64_t* combined_bw;
struct ipt_bandwidth_info* non_const_self;
unsigned long* ref_count;
};
#endif /*_IPT_BANDWIDTH_H*/

View File

@ -0,0 +1,43 @@
/* timerange -- An iptables extension to match multiple timeranges within a week
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2009 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#ifndef _IPT_TIMERANGE_H
#define _IPT_TIMERANGE_H
#define RANGE_LENGTH 51
#define HOURS 1
#define WEEKDAYS 2
#define DAYS_HOURS (HOURS+WEEKDAYS)
#define WEEKLY_RANGE 4
struct ipt_timerange_info
{
long ranges[RANGE_LENGTH];
char days[7];
char type;
unsigned char invert;
};
#endif /*_IPT_TIMERANGE_H*/

View File

@ -0,0 +1,63 @@
/* webmon -- A netfilter module to match URLs in HTTP requests
* This module can match using string match or regular expressions
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2008-2010 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#ifndef _IPT_WEBMON_H
#define _IPT_WEBMON_H
#define WEBMON_MAX_IPS 256
#define WEBMON_MAX_IP_RANGES 16
#define WEBMON_EXCLUDE 1
#define WEBMON_INCLUDE 2
#define WEBMON_MAXDOMAIN 4
#define WEBMON_MAXSEARCH 8
#define WEBMON_DOMAIN 16
#define WEBMON_SEARCH 32
#define WEBMON_SET 3064
struct ipt_webmon_ip_range
{
uint32_t start;
uint32_t end;
};
struct ipt_webmon_info
{
uint32_t max_domains;
uint32_t max_searches;
uint32_t exclude_ips[WEBMON_MAX_IPS];
struct ipt_webmon_ip_range exclude_ranges[WEBMON_MAX_IP_RANGES];
uint32_t num_exclude_ips;
uint32_t num_exclude_ranges;
unsigned char exclude_type;
uint32_t* ref_count;
};
#endif /*_IPT_WEBMON_H*/

View File

@ -0,0 +1,45 @@
/* weburl -- A netfilter module to match URLs in HTTP requests
* This module can match using string match or regular expressions
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright © 2008 by Eric Bishop <eric@gargoyle-router.com>
*
* This file is free software: you may copy, redistribute 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 file 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/>.
*/
#ifndef _IPT_WEBURL_H
#define _IPT_WEBURL_H
#define MAX_TEST_STR 1024
#define WEBURL_CONTAINS_TYPE 1
#define WEBURL_REGEX_TYPE 2
#define WEBURL_EXACT_TYPE 3
#define WEBURL_ALL_PART 4
#define WEBURL_DOMAIN_PART 5
#define WEBURL_PATH_PART 6
struct ipt_weburl_info
{
char test_str[MAX_TEST_STR];
unsigned char match_type;
unsigned char match_part;
unsigned char invert;
};
#endif /*_IPT_WEBURL_H*/