diff --git a/package/jsda/gargoyle-firewall-util/Makefile b/package/jsda/gargoyle-firewall-util/Makefile new file mode 100644 index 0000000000..648a3f75b2 --- /dev/null +++ b/package/jsda/gargoyle-firewall-util/Makefile @@ -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 +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)) diff --git a/package/jsda/gargoyle-firewall-util/files/gargoyle_additions.firewall b/package/jsda/gargoyle-firewall-util/files/gargoyle_additions.firewall new file mode 100755 index 0000000000..86df4d28cb --- /dev/null +++ b/package/jsda/gargoyle-firewall-util/files/gargoyle_additions.firewall @@ -0,0 +1,2 @@ +. /usr/lib/gargoyle_firewall_util/gargoyle_firewall_util.sh +initialize_firewall diff --git a/package/jsda/gargoyle-firewall-util/files/gargoyle_firewall.hotplug b/package/jsda/gargoyle-firewall-util/files/gargoyle_firewall.hotplug new file mode 100644 index 0000000000..125d4b0c91 --- /dev/null +++ b/package/jsda/gargoyle-firewall-util/files/gargoyle_firewall.hotplug @@ -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 diff --git a/package/jsda/gargoyle-firewall-util/files/gargoyle_firewall_util.sh b/package/jsda/gargoyle-firewall-util/files/gargoyle_firewall_util.sh new file mode 100644 index 0000000000..89a904954e --- /dev/null +++ b/package/jsda/gargoyle-firewall-util/files/gargoyle_firewall_util.sh @@ -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 +} diff --git a/package/jsda/gargoyle-firewall-util/files/modemaccess.pppoe b/package/jsda/gargoyle-firewall-util/files/modemaccess.pppoe new file mode 100644 index 0000000000..f28a9547c2 --- /dev/null +++ b/package/jsda/gargoyle-firewall-util/files/modemaccess.pppoe @@ -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 diff --git a/package/jsda/gargoyle-firewall-util/files/set_kernel_timezone.init b/package/jsda/gargoyle-firewall-util/files/set_kernel_timezone.init new file mode 100755 index 0000000000..05241ddd94 --- /dev/null +++ b/package/jsda/gargoyle-firewall-util/files/set_kernel_timezone.init @@ -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 +} diff --git a/package/jsda/gargoyle-firewall-util/src/Makefile b/package/jsda/gargoyle-firewall-util/src/Makefile new file mode 100644 index 0000000000..fac998e0f3 --- /dev/null +++ b/package/jsda/gargoyle-firewall-util/src/Makefile @@ -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* diff --git a/package/jsda/gargoyle-firewall-util/src/backup_quotas.c b/package/jsda/gargoyle-firewall-util/src/backup_quotas.c new file mode 100644 index 0000000000..ad27a36945 --- /dev/null +++ b/package/jsda/gargoyle-firewall-util/src/backup_quotas.c @@ -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 + * + * 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 . + */ + +#include +#include +#include +#include + +#include +#include +#include +#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; +} diff --git a/package/jsda/gargoyle-firewall-util/src/delete_chain_from_table.c b/package/jsda/gargoyle-firewall-util/src/delete_chain_from_table.c new file mode 100644 index 0000000000..25ead9f4b2 --- /dev/null +++ b/package/jsda/gargoyle-firewall-util/src/delete_chain_from_table.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include + +#include +#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; +} diff --git a/package/jsda/gargoyle-firewall-util/src/make_iptables_rules.c b/package/jsda/gargoyle-firewall-util/src/make_iptables_rules.c new file mode 100644 index 0000000000..326ebafdb2 --- /dev/null +++ b/package/jsda/gargoyle-firewall-util/src/make_iptables_rules.c @@ -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 + * + * 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 . + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#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 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; +} diff --git a/package/jsda/gargoyle-firewall-util/src/print_quotas.c b/package/jsda/gargoyle-firewall-util/src/print_quotas.c new file mode 100644 index 0000000000..0190a9fd27 --- /dev/null +++ b/package/jsda/gargoyle-firewall-util/src/print_quotas.c @@ -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 + * + * 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 . + */ + +#include +#include +#include +#include + +#include +#include +#include +#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; +} diff --git a/package/jsda/gargoyle-firewall-util/src/restore_quotas.c b/package/jsda/gargoyle-firewall-util/src/restore_quotas.c new file mode 100644 index 0000000000..dac4c63f4d --- /dev/null +++ b/package/jsda/gargoyle-firewall-util/src/restore_quotas.c @@ -0,0 +1,1071 @@ +/* restore_quotas -- Used to initialize and restore bandwidth quotas based on UCI config files + * and any previously saved quota data in /usr/data/quotas + * Originally designed for use with Gargoyle router firmware (gargoyle-router.com) + * + * + * Copyright © 2009-2010 by Eric Bishop + * + * 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 . + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#define malloc safe_malloc +#define strdup safe_strdup + +#define INGRESS_INDEX 0 +#define EGRESS_INDEX 1 +#define COMBINED_INDEX 2 + +void restore_backup_for_id(char* id, char* quota_backup_dir, unsigned char is_individual_other, list* defined_ip_groups); +uint32_t ip_to_host_int(char* ip_str); +uint32_t* ip_range_to_host_ints(char* ip_str); +list* filter_group_from_list(list** orig_ip_list, char* ip_group_str); + +void delete_chain_from_table(char* table, char* delete_chain); +void run_shell_command(char* command, int free_command_str); +void free_split_pieces(char** split_pieces); + +list* get_all_sections_of_type(struct uci_context *ctx, char* package, char* section_type); +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(int argc, char** argv) +{ + + char* wan_if = NULL; + char* local_subnet = NULL; + char* death_mark = NULL; + char* death_mask = NULL; + char* crontab_line = NULL; + int ret; + + unsigned char full_qos_active = 0; + + char c; + while((ret = getopt(argc, argv, "W:w:s:S:d:D:m:M:c:C:qQ")) != -1) //section, page, css includes, javascript includes, title, output interface variables + { + c = ret; + switch(c) + { + case 'W': + case 'w': + wan_if = strdup(optarg); + break; + case 'S': + case 's': + local_subnet = strdup(optarg); + break; + case 'D': + case 'd': + death_mark = strdup(optarg); + break; + case 'M': + case 'm': + death_mask = strdup(optarg); + break; + case 'C': + case 'c': + crontab_line = strdup(optarg); + break; + case 'Q': + case 'q': + full_qos_active = 1; + break; + } + } + + /* even if parameters are wrong, whack old rules */ + char quota_table[] = "mangle"; + char crontab_dir[] = "/etc/crontabs/"; + char crontab_file_path[] = "/etc/crontabs/root"; + delete_chain_from_table(quota_table, "egress_quotas"); + delete_chain_from_table(quota_table, "ingress_quotas"); + delete_chain_from_table(quota_table, "combined_quotas"); + delete_chain_from_table(quota_table, "forward_quotas"); + delete_chain_from_table("nat", "quota_redirects"); + + if(wan_if == NULL) + { + fprintf(stderr, "ERRROR: No wan interface specified\n"); + return 0; + } + if(local_subnet == NULL) + { + fprintf(stderr, "ERRROR: No local subnet specified\n"); + return 0; + } + if(death_mark == NULL) + { + fprintf(stderr, "ERRROR: No death mark specified\n"); + return 0; + } + if(death_mask == NULL) + { + fprintf(stderr, "ERRROR: No death mask specified\n"); + return 0; + } + + struct uci_context *ctx = uci_alloc_context(); + struct uci_ptr ptr; + + list* quota_sections = get_all_sections_of_type(ctx, "firewall", "quota"); + if(quota_sections->length > 0) + { + /* load defined base ids */ + string_map* defined_base_ids = initialize_string_map(0); + string_map* upload_qos_marks = initialize_string_map(0); + string_map* download_qos_marks = initialize_string_map(0); + + long_map* up_speeds = initialize_long_map(); + long_map* down_speeds = initialize_long_map(); + list* quota_section_buf = initialize_list(); + while(quota_sections->length > 0) + { + char* next_quota = shift_list(quota_sections); + char* base_id = get_uci_option(ctx, "firewall", next_quota, "id"); + char* exceeded_up_speed_str = get_uci_option(ctx, "firewall", next_quota, "exceeded_up_speed"); + char* exceeded_down_speed_str = get_uci_option(ctx, "firewall", next_quota, "exceeded_down_speed"); + + if(base_id != NULL) + { + //D, for dummy place holder + char* oldval = set_string_map_element(defined_base_ids, base_id, strdup("D") ); + if(oldval != NULL) { free(oldval); } + } + push_list(quota_section_buf, next_quota); + + if(exceeded_up_speed_str != NULL && exceeded_down_speed_str != NULL) + { + long up; + long down; + if(sscanf(exceeded_up_speed_str, "%ld", &up) > 0 && sscanf(exceeded_down_speed_str, "%ld", &down) > 0 ) + { + if(up > 0 && down > 0) + { + char* oldval = set_long_map_element(up_speeds, up, strdup(exceeded_up_speed_str) ); + if(oldval != NULL) { free(oldval); } + oldval = set_long_map_element(down_speeds, down, strdup(exceeded_down_speed_str) ); + if(oldval != NULL) { free(oldval); } + } + } + } + free(base_id); + free(exceeded_up_speed_str); + free(exceeded_down_speed_str); + } + unsigned long num_destroyed; + destroy_list(quota_sections, DESTROY_MODE_FREE_VALUES, &num_destroyed); + quota_sections = quota_section_buf; + + /* initialize qos mark maps */ + unsigned long mark_band = 2; + int upload_shift = 0; + int download_shift = 8; + while(up_speeds->num_elements > 0) + { + unsigned long mark = mark_band << upload_shift; + char mark_str[10]; + unsigned long smallest_speed; + char* next_up_speed = remove_smallest_long_map_element(up_speeds, &smallest_speed); + sprintf(mark_str, "%ld", mark); + set_string_map_element(upload_qos_marks, next_up_speed, strdup(mark_str)); + free(next_up_speed); + mark_band++; + } + mark_band = 2; + while(down_speeds->num_elements > 0) + { + unsigned long mark = mark_band << download_shift; + char mark_str[10]; + unsigned long smallest_speed; + char* next_down_speed = remove_smallest_long_map_element(down_speeds, &smallest_speed); + sprintf(mark_str, "%ld", mark); + set_string_map_element(download_qos_marks, next_down_speed, strdup(mark_str)); + free(next_down_speed); + mark_band++; + } + + /* initialize chains */ + run_shell_command(dynamic_strcat(3, "iptables -t ", quota_table, " -N forward_quotas 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(3, "iptables -t ", quota_table, " -N egress_quotas 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(3, "iptables -t ", quota_table, " -N ingress_quotas 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(3, "iptables -t ", quota_table, " -N combined_quotas 2>/dev/null"), 1); + + run_shell_command("iptables -t nat -N quota_redirects 2>/dev/null", 0); + run_shell_command("iptables -t nat -A quota_redirects -j CONNMARK --set-mark 0x0/0xFF000000 2>/dev/null", 0); + run_shell_command("iptables -t nat -I zone_lan_prerouting -j quota_redirects 2>/dev/null", 0); + + char* no_death_mark_test = dynamic_strcat(3, " -m connmark --mark 0x0/", death_mask, " "); + run_shell_command(dynamic_strcat(6, "iptables -t ", quota_table, " -I INPUT 1 -i ", wan_if, no_death_mark_test, " -j ingress_quotas 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(6, "iptables -t ", quota_table, " -I INPUT 2 -i ", wan_if, no_death_mark_test, " -j combined_quotas 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(6, "iptables -t ", quota_table, " -I OUTPUT 1 -o ", wan_if, no_death_mark_test, " -j egress_quotas 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(6, "iptables -t ", quota_table, " -I OUTPUT 2 -o ", wan_if, no_death_mark_test, " -j combined_quotas 2>/dev/null"), 1); + + run_shell_command(dynamic_strcat(3, "iptables -t ", quota_table, " -I FORWARD -j forward_quotas 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(6, "iptables -t ", quota_table, " -A forward_quotas -o ", wan_if, no_death_mark_test, " -j egress_quotas 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(6, "iptables -t ", quota_table, " -A forward_quotas -i ", wan_if, no_death_mark_test, " -j ingress_quotas 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(6, "iptables -t ", quota_table, " -A forward_quotas -i ", wan_if, no_death_mark_test, " -j CONNMARK --set-mark 0x0F000000/0x0F000000 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(6, "iptables -t ", quota_table, " -A forward_quotas -o ", wan_if, no_death_mark_test, " -j CONNMARK --set-mark 0x0F000000/0x0F000000 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(3, "iptables -t ", quota_table, " -A forward_quotas -m connmark --mark 0x0F000000/0x0F000000 -j combined_quotas 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(3, "iptables -t ", quota_table, " -A forward_quotas -j CONNMARK --set-mark 0x0/0x0F000000 2>/dev/null"), 1); + free(no_death_mark_test); + + run_shell_command(dynamic_strcat(5, "iptables -t ", quota_table, " -A egress_quotas -j CONNMARK --set-mark 0x0/", death_mask, " 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(5, "iptables -t ", quota_table, " -A ingress_quotas -j CONNMARK --set-mark 0x0/", death_mask, " 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(5, "iptables -t ", quota_table, " -A combined_quotas -j CONNMARK --set-mark 0x0/", death_mask, " 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(3, "iptables -t ", quota_table, " -A egress_quotas -j CONNMARK --set-mark 0x0/0xFF000000 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(3, "iptables -t ", quota_table, " -A ingress_quotas -j CONNMARK --set-mark 0x0/0xFF000000 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(3, "iptables -t ", quota_table, " -A combined_quotas -j CONNMARK --set-mark 0x0/0xFF000000 2>/dev/null"), 1); + + /* add rules */ + char* set_death_mark = dynamic_strcat(5, " -j CONNMARK --set-mark ", death_mark, "/", death_mask, " "); + list* other_quota_section_names = initialize_list(); + list* defined_ip_groups = initialize_list(); + + unlock_bandwidth_semaphore_on_exit(); + while(quota_sections->length > 0 || other_quota_section_names->length > 0) + { + char* next_quota = NULL; + int process_other_quota = 0; + if(quota_sections->length > 0) + { + next_quota = shift_list(quota_sections); + } + else + { + process_other_quota = 1; + next_quota = shift_list(other_quota_section_names); + } + + char* quota_enabled_var = get_uci_option(ctx, "firewall", next_quota, "enabled"); + int enabled = 1; + if(quota_enabled_var != NULL) + { + if(strcmp(quota_enabled_var, "0") == 0) + { + enabled = 0; + } + } + free(quota_enabled_var); + + if(enabled) + { + char* ip = get_uci_option(ctx, "firewall", next_quota, "ip"); + char* exceeded_up_speed_str = NULL; + char* exceeded_down_speed_str = NULL; + if(!full_qos_active) /* without defined up/down speeds we always set hard cutoff, which is what we want when full qos is active */ + { + exceeded_up_speed_str = get_uci_option(ctx, "firewall", next_quota, "exceeded_up_speed"); + exceeded_down_speed_str = get_uci_option(ctx, "firewall", next_quota, "exceeded_down_speed"); + } + if(exceeded_up_speed_str == NULL) { exceeded_up_speed_str = strdup(" "); } + if(exceeded_down_speed_str == NULL) { exceeded_down_speed_str = strdup(" "); } + + + if(ip == NULL) { ip = strdup("ALL"); } + if(strlen(ip) == 0) { ip = strdup("ALL"); } + + /* remove spaces in ip range definitions */ + while(strstr(ip, " -") != NULL) + { + char* tmp_ip = ip; + ip = dynamic_replace(ip, " -", "-"); + free(tmp_ip); + } + while(strstr(ip, "- ") != NULL) + { + char* tmp_ip = ip; + ip = dynamic_replace(ip, "- ", "-"); + free(tmp_ip); + } + + if( (strcmp(ip, "ALL_OTHERS_COMBINED") == 0 || strcmp(ip, "ALL_OTHERS_INDIVIDUAL") == 0) && (!process_other_quota) ) + { + push_list(other_quota_section_names, strdup(next_quota)); + } + else + { + unsigned char is_individual_other = strcmp(ip, "ALL_OTHERS_INDIVIDUAL") == 0 ? 1 : 0; + if( strcmp(ip, "ALL_OTHERS_COMBINED") != 0 && strcmp(ip, "ALL_OTHERS_INDIVIDUAL") != 0 && strcmp(ip, "ALL") != 0 ) + { + /* this is an explicitly defined ip or ip range, so save it for later, to deal with individual other overlap problem */ + push_list(defined_ip_groups, strdup(ip)); + } + + /* compute proper base id for rule, adding variable to uci if necessary */ + char* quota_base_id = get_uci_option(ctx, "firewall", next_quota, "id"); + if(quota_base_id == NULL) + { + char id_breaks[] = { ',', ' ', '\t'}; + unsigned long num_pieces; + char** split_ip = split_on_separators(ip, id_breaks, 3, -1, 0, &num_pieces); + char* first_ip = dynamic_replace(split_ip[0], "/", "_"); + free_null_terminated_string_array(split_ip); + + quota_base_id = strdup(first_ip); + unsigned long next_postfix_count = 0; + while( get_string_map_element(defined_base_ids, quota_base_id) != NULL) + { + char next_postfix[20]; + if(next_postfix_count > 25) + { + sprintf(next_postfix, "_%c", ('A' + next_postfix_count)); + } + else + { + sprintf(next_postfix, "_Z%ld", (next_postfix_count - 25)); + } + free(quota_base_id); + quota_base_id = dynamic_strcat(2, first_ip, next_postfix); + } + free(first_ip); + + /* D for dummy place holder */ + set_string_map_element(defined_base_ids, quota_base_id, strdup("D")); + + /* add id we've decided on to UCI */ + char* var_set = dynamic_strcat(4, "firewall.", next_quota, ".id=", quota_base_id); + if (uci_lookup_ptr(ctx, &ptr, var_set, true) == UCI_OK) + { + uci_set(ctx, &ptr); + } + } + + char* ignore_backup = get_uci_option(ctx, "firewall", next_quota, "ignore_backup_at_next_restore"); + int do_restore = 1; + if(ignore_backup != NULL) + { + do_restore = strcmp(ignore_backup, "1") == 0 ? 0 : 1; + if(!do_restore) + { + //remove variable from uci + char* var_name = dynamic_strcat(3, "firewall.", next_quota, ".ignore_backup_at_next_restore"); + if (uci_lookup_ptr(ctx, &ptr, var_name, true) == UCI_OK) + { + uci_delete(ctx, &ptr); + } + free(var_name); + } + } + free(ignore_backup); + + char* reset_interval = get_uci_option(ctx, "firewall", next_quota, "reset_interval"); + char* reset = strdup(""); + if(reset_interval != NULL) + { + char* reset_time = get_uci_option(ctx, "firewall", next_quota, "reset_time"); + + char* interval_option = strdup(" --reset_interval "); + reset = dcat_and_free(&reset, &interval_option, 1, 1); + reset = dcat_and_free(&reset, &reset_interval, 1, 1); + if(reset_time != NULL) + { + char* reset_option = strdup(" --reset_time "); + reset = dcat_and_free(&reset, &reset_option, 1, 1); + reset = dcat_and_free(&reset, &reset_time, 1, 1); + } + } + + char* time_match_str = strdup(""); + + 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 *timerange_match = is_off_peak ? strdup(" -m timerange ! ") : strdup(" -m timerange "); + char *hour_match = strdup(" --hours \""); + char *weekday_match = strdup(" --weekdays \""); + char *weekly_match = strdup(" --weekly_ranges \""); + char *quote_end = strdup("\" "); + + time_match_str = dcat_and_free(&time_match_str, &timerange_match, 1,1); + if(hours_var != NULL && weekly_ranges_var == NULL) + { + time_match_str = dcat_and_free(&time_match_str, &hour_match, 1, 1); + time_match_str = dcat_and_free(&time_match_str, &hours_var, 1, 1); + time_match_str = dcat_and_free(&time_match_str, "e_end, 1, 0); + } + if(weekdays_var != NULL && weekly_ranges_var == NULL) + { + time_match_str = dcat_and_free(&time_match_str, &weekday_match, 1, 1); + time_match_str = dcat_and_free(&time_match_str, &weekdays_var, 1, 1); + time_match_str = dcat_and_free(&time_match_str, "e_end, 1, 0); + } + if(weekly_ranges_var != NULL) + { + time_match_str = dcat_and_free(&time_match_str, &weekly_match, 1, 1); + time_match_str = dcat_and_free(&time_match_str, &weekly_ranges_var, 1, 1); + time_match_str = dcat_and_free(&time_match_str, "e_end, 1, 0); + } + free(quote_end); + } + + char* types[] = { "ingress_limit", "egress_limit", "combined_limit" }; + char* postfixes[] = { "_ingress", "_egress", "_combined" }; + char* chains[] = { "ingress_quotas", "egress_quotas", "combined_quotas" }; + + int type_index; + for(type_index=0; type_index < 3; type_index++) + { + char** ip_egress_tests = NULL; + char* applies_to = strdup("combined"); + char* subnet_definition = strdup(""); + + char* limit = get_uci_option(ctx, "firewall", next_quota, types[type_index]); + + char* type_id = dynamic_strcat(2, quota_base_id, postfixes[type_index] ); + + char* up_qos_mark = get_string_map_element(upload_qos_marks, exceeded_up_speed_str); + char* down_qos_mark = get_string_map_element(download_qos_marks, exceeded_down_speed_str); + if(full_qos_active) + { + up_qos_mark = get_uci_option(ctx, "firewall", next_quota, "exceeded_up_class_mark"); + down_qos_mark = get_uci_option(ctx, "firewall", next_quota, "exceeded_down_class_mark"); + } + + /* + * need to do ip test even if limit is null, because ALL_OTHERS quotas should not apply when any of the three types of explicit limit is defined + * and we therefore need to use this test to set mark indicating an explicit quota has been checked + */ + char* ip_test = strdup(""); + if( strcmp(ip, "ALL_OTHERS_COMBINED") != 0 && strcmp(ip, "ALL_OTHERS_INDIVIDUAL") != 0 && strcmp(ip, "ALL") != 0 ) + { + + char* src_test = strstr(ip, "-") == NULL ? dynamic_strcat(3, " --src ", ip, " ") : dynamic_strcat(3, " -m iprange --src-range ", ip, " "); + char* dst_test = strstr(ip, "-") == NULL ? dynamic_strcat(3, " --dst ", ip, " ") : dynamic_strcat(3, " -m iprange --dst-range ", ip, " "); + + if(strstr(ip, ",") != NULL || strstr(ip, " ") != NULL || strstr(ip, "\t") != NULL ) + { + char ip_breaks[] = { ',', ' ', '\t' }; + unsigned long num_ips = 0; + char** ip_list = split_on_separators(ip, ip_breaks, 3, -1, 0, &num_ips); + unsigned long ip_index; + for(ip_index=0; ip_index < num_ips; ip_index++) + { + char *next_ip = ip_list[ip_index]; + char* egress_test = strstr(next_ip, "-") == NULL ? dynamic_strcat(3, " --src ", next_ip, " ") : dynamic_strcat(3, " -m iprange --src-range ", next_ip, " "); + char* ingress_test = strstr(next_ip, "-") == NULL ? dynamic_strcat(3, " --dst ", next_ip, " ") : dynamic_strcat(3, " -m iprange --dst-range ", next_ip, " "); + + if(strcmp(types[type_index], "egress_limit") == 0) + { + run_shell_command(dynamic_strcat(6, "iptables -t ", quota_table, " -A ", chains[type_index], egress_test, " -j CONNMARK --set-mark 0x0F000000/0x0F000000 2>/dev/null"), 1); + } + else if(strcmp(types[type_index], "ingress_limit") == 0) + { + run_shell_command(dynamic_strcat(6, "iptables -t ", quota_table, " -A ", chains[type_index], ingress_test, " -j CONNMARK --set-mark 0x0F000000/0x0F000000 2>/dev/null"), 1); + } + else if(strcmp(types[type_index], "combined_limit") == 0) + { + run_shell_command(dynamic_strcat(8, "iptables -t ", quota_table, " -A ", chains[type_index], " -i ", wan_if, ingress_test, " -j CONNMARK --set-mark 0x0F000000/0x0F000000 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(8, "iptables -t ", quota_table, " -A ", chains[type_index], " -o ", wan_if, egress_test, " -j CONNMARK --set-mark 0x0F000000/0x0F000000 2>/dev/null"), 1); + } + ip_list[ip_index] = egress_test; + free(next_ip); + free(ingress_test); + } + ip_egress_tests = ip_list; + char* rule_end = strdup(" -m connmark --mark 0x0F000000/0x0F000000 "); + ip_test = dcat_and_free(&ip_test, &rule_end, 1, 1); + } + else if(strcmp(types[type_index], "egress_limit") == 0) + { + ip_test=dcat_and_free(&ip_test, &src_test, 1, 0); + } + else if(strcmp(types[type_index], "ingress_limit") == 0) + { + ip_test=dcat_and_free(&ip_test, &dst_test, 1, 0); + } + else if(strcmp(types[type_index], "combined_limit") == 0) + { + run_shell_command(dynamic_strcat(8, "iptables -t ", quota_table, " -A ", chains[type_index], " -i ", wan_if, dst_test, " -j CONNMARK --set-mark 0x0F000000/0x0F000000 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(8, "iptables -t ", quota_table, " -A ", chains[type_index], " -o ", wan_if, src_test, " -j CONNMARK --set-mark 0x0F000000/0x0F000000 2>/dev/null"), 1); + char* rule_end = strdup(" -m connmark --mark 0x0F000000/0x0F000000 "); + ip_test = dcat_and_free(&ip_test, &rule_end, 1, 1); + } + run_shell_command(dynamic_strcat(6, "iptables -t ", quota_table, " -A ", chains[type_index], ip_test, " -j CONNMARK --set-mark 0xF0000000/0xF0000000 2>/dev/null"), 1); + free(dst_test); + free(src_test); + } + else if( strcmp(ip, "ALL_OTHERS_COMBINED") == 0 || strcmp(ip, "ALL_OTHERS_INDIVIDUAL") == 0 ) + { + char* rule_end = strdup(" -m connmark --mark 0x0/0xF0000000 "); + ip_test=dcat_and_free(&ip_test, &rule_end, 1, 1); + if(strcmp(ip, "ALL_OTHERS_INDIVIDUAL") == 0) + { + free(applies_to); + if(strcmp(types[type_index], "egress_limit") == 0) + { + char* subnet_test = dynamic_strcat(3, " -s ", local_subnet, " "); + ip_test = dcat_and_free(&subnet_test, &ip_test, 1, 1); + applies_to = strdup("individual_src"); + } + else if(strcmp(types[type_index], "ingress_limit") == 0) + { + char* subnet_test = dynamic_strcat(3, " -d ", local_subnet, " "); + ip_test = dcat_and_free(&subnet_test, &ip_test, 1, 1); + applies_to = strdup("individual_dst"); + } + else if(strcmp(types[type_index], "combined_limit") == 0) + { + applies_to = strdup("individual_local"); + } + + char *subnet_option = strdup(" --subnet "); + subnet_definition = dcat_and_free(&subnet_definition, &subnet_option, 1, 1); + subnet_definition = dcat_and_free(&subnet_definition, &local_subnet, 1, 0); + } + } + + if(up_qos_mark != NULL && down_qos_mark != NULL) + { + char* set_egress_mark = dynamic_strcat(2, " -j MARK --set-mark ", up_qos_mark); + char* set_ingress_mark = dynamic_strcat(2, " -j MARK --set-mark ", down_qos_mark); + if(type_index == EGRESS_INDEX || type_index == INGRESS_INDEX) + { + int other_type_index= type_index == EGRESS_INDEX ? INGRESS_INDEX : EGRESS_INDEX; + char* other_limit = get_uci_option(ctx, "firewall", next_quota, types[other_type_index]); + if(other_limit != NULL) + { + char* other_type_id = dynamic_strcat(2, quota_base_id, postfixes[other_type_index] ); + if(type_index == EGRESS_INDEX) + { + run_shell_command(dynamic_strcat(10, "iptables -t ", quota_table, " -A ", chains[type_index], ip_test, time_match_str, " -m bandwidth --id \"", other_type_id, "\" --bcheck_with_src_dst_swap ", set_egress_mark), 1); + } + else + { + run_shell_command(dynamic_strcat(10, "iptables -t ", quota_table, " -A ", chains[type_index], ip_test, time_match_str, " -m bandwidth --id \"", other_type_id, "\" --bcheck_with_src_dst_swap ", set_ingress_mark), 1); + } + free(other_type_id); + } + free(other_limit); + } + free(set_egress_mark); + free(set_ingress_mark); + } + + if(limit != NULL) + { + if(up_qos_mark != NULL && down_qos_mark != NULL) + { + char* set_egress_mark = dynamic_strcat(2, " -j MARK --set-mark ", up_qos_mark); + char* set_ingress_mark = dynamic_strcat(2, " -j MARK --set-mark ", down_qos_mark); + if(strcmp(types[type_index], "egress_limit") == 0) + { + run_shell_command(dynamic_strcat(15, "iptables -t ", quota_table, " -A ", chains[type_index], ip_test, time_match_str, " -m bandwidth --id \"", type_id, "\" --type ", applies_to, subnet_definition, " --greater_than ", limit, reset, set_egress_mark), 1); + } + else if(strcmp(types[type_index], "ingress_limit") == 0) + { + run_shell_command(dynamic_strcat(15, "iptables -t ", quota_table, " -A ", chains[type_index], ip_test, time_match_str, " -m bandwidth --id \"", type_id, "\" --type ", applies_to, subnet_definition, " --greater_than ", limit, reset, set_ingress_mark), 1); + } + else //combined + { + run_shell_command(dynamic_strcat(14, "iptables -t ", quota_table, " -A ", chains[type_index], ip_test, time_match_str, " -m bandwidth --id \"", type_id, "\" --type ", applies_to, subnet_definition, " --greater_than ", limit, reset), 1); + run_shell_command(dynamic_strcat(13, "iptables -t ", quota_table, " -A ", chains[type_index], " -o ", wan_if, " ", ip_test, time_match_str, " -m bandwidth --id \"", type_id, "\" --bcheck ", set_egress_mark), 1); //egress + run_shell_command(dynamic_strcat(13, "iptables -t ", quota_table, " -A ", chains[type_index], " -i ", wan_if, " ", ip_test, time_match_str, " -m bandwidth --id \"", type_id, "\" --bcheck_with_src_dst_swap ", set_ingress_mark), 1); //ingress + } + free(set_egress_mark); + free(set_ingress_mark); + } + else + { + //insert quota block rule + run_shell_command(dynamic_strcat(15, "iptables -t ", quota_table, " -A ", chains[type_index], ip_test, time_match_str, " -m bandwidth --id \"", type_id, "\" --type ", applies_to, subnet_definition, " --greater_than ", limit, reset, set_death_mark), 1); + + //insert redirect rule + if(strcmp(ip, "ALL") == 0 || strcmp(ip, "ALL_OTHERS_INDIVIDUAL") == 0) + { + char* check_str = (strcmp(types[type_index], "ingress_limit") == 0) ? strdup(" --bcheck_with_src_dst_swap ") : strdup(" --bcheck "); + run_shell_command(dynamic_strcat(7, "iptables -t nat -A quota_redirects -p tcp ", time_match_str, " -m multiport --destination-port 80,443 -m bandwidth ", check_str, " --id \"", type_id, "\" -j REDIRECT "), 1); + free(check_str); + } + else if(strcmp(ip, "ALL_OTHERS_COMBINED") == 0) + { + run_shell_command(dynamic_strcat(5, "iptables -t nat -A quota_redirects -p tcp ", time_match_str, " -m connmark --mark 0x0/0xF0000000 -m multiport --destination-port 80,443 -m bandwidth --bcheck --id \"", type_id, "\" -j REDIRECT "), 1); + } + else + { + if(ip_egress_tests != NULL) + { + unsigned long egress_test_index; + for(egress_test_index=0; ip_egress_tests[egress_test_index] != NULL; egress_test_index++ ) + { + run_shell_command(dynamic_strcat(3, "iptables -t nat -A quota_redirects ", ip_egress_tests[egress_test_index], " -j CONNMARK --set-mark 0x0F000000/0x0F000000 2>/dev/null"), 1); + } + } + else + { + run_shell_command(dynamic_strcat(4, "iptables -t nat -A quota_redirects ", ( strstr(ip, "-") == NULL ? " --src " : " -m iprange --src-range "), ip, " -j CONNMARK --set-mark 0x0F000000/0x0F000000 2>/dev/null"), 1); + } + run_shell_command(dynamic_strcat(5, "iptables -t nat -A quota_redirects -p tcp ", time_match_str, " -m connmark --mark 0x0F000000/0x0F000000 -m multiport --destination-port 80,443 -m bandwidth --bcheck --id \"", type_id, "\" -j REDIRECT "), 1); + run_shell_command("iptables -t nat -A quota_redirects -m connmark --mark 0x0F000000/0x0F000000 -j CONNMARK --set-mark 0xF0000000/0xF0000000 2>/dev/null", 0); + run_shell_command("iptables -t nat -A quota_redirects -j CONNMARK --set-mark 0x0/0x0F000000 2>/dev/null", 0); + } + } + + //restore from backup + if(do_restore) + { + restore_backup_for_id(type_id, "/usr/data/quotas", is_individual_other, defined_ip_groups); + } + free(limit); + + } + if(strstr(ip_test, "connmark") != NULL) + { + run_shell_command(dynamic_strcat(5, "iptables -t ", quota_table, " -A ", chains[type_index], " -j CONNMARK --set-mark 0x0/0x0F000000 2>/dev/null"), 1); + } + + free(ip_test); + free(applies_to); + free(subnet_definition); + free(type_id); + if(full_qos_active) + { + free(up_qos_mark); + free(down_qos_mark); + } + if(ip_egress_tests != NULL) + { + free_null_terminated_string_array(ip_egress_tests); + } + } + free(time_match_str); + free(quota_base_id); + } + free(ip); + free(exceeded_up_speed_str); + free(exceeded_down_speed_str); + } + free(next_quota); + + } + + run_shell_command("iptables -t nat -A quota_redirects -j CONNMARK --set-mark 0x0/0xFF000000 2>/dev/null", 0); + run_shell_command(dynamic_strcat(3,"iptables -t ", quota_table, " -A egress_quotas -j CONNMARK --set-mark 0x0/0xFF000000 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(3,"iptables -t ", quota_table, " -A ingress_quotas -j CONNMARK --set-mark 0x0/0xFF000000 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(3,"iptables -t ", quota_table, " -A combined_quotas -j CONNMARK --set-mark 0x0/0xFF000000 2>/dev/null"), 1); + run_shell_command(dynamic_strcat(5,"iptables -t filter -I FORWARD -m connmark --mark ", death_mark, "/", death_mask, " -j REJECT 2>/dev/null"), 1); + + //make sure crontab is up to date + if(crontab_line != NULL) + { + FILE* crontab_file = fopen(crontab_file_path,"r"); + int cron_line_found = 0; + if(crontab_file == NULL) + { + run_shell_command(dynamic_strcat(2, "mkdir -p ", crontab_dir), 1); + } + else + { + unsigned long read_length; + char* all_cron_data = (char*)read_entire_file(crontab_file, 2048, &read_length); + fclose(crontab_file); + + unsigned long num_lines; + char linebreaks[] = { '\n', '\r' }; + char** cron_lines = split_on_separators(all_cron_data, linebreaks, 2, -1, 0, &num_lines); + int line_index = 0; + for(line_index=0; line_index < num_lines && (!cron_line_found); line_index++) + { + if(strcmp(cron_lines[line_index], crontab_line) == 0) + { + cron_line_found = 1; + } + } + free_null_terminated_string_array(cron_lines); + } + if(!cron_line_found) + { + crontab_file = fopen(crontab_file_path, "a"); + fprintf(crontab_file, "%s\n", crontab_line); + fclose(crontab_file); + } + } + } + else + { + //remove crontab line if it exists + FILE* crontab_file = fopen(crontab_file_path,"r"); + if(crontab_file != NULL) + { + unsigned long cron_line_found = 0; + unsigned long read_length; + unsigned long num_lines; + char linebreaks[] = { '\n', '\r' }; + char* all_cron_data = (char*)read_entire_file(crontab_file, 2048, &read_length); + fclose(crontab_file); + char** cron_lines = split_on_separators(all_cron_data, linebreaks, 2, -1, 0, &num_lines); + int line_index = 0; + for(line_index=0; line_index < num_lines && (!cron_line_found); line_index++) + { + if(strcmp(cron_lines[line_index], crontab_line) == 0) + { + cron_line_found = 1; + } + } + if(cron_line_found) + { + fopen(crontab_file_path, "w"); + for(line_index=0; line_index < num_lines; line_index++) + { + if(strcmp(cron_lines[line_index], crontab_line) != 0) + { + fprintf(crontab_file, "%s\n", cron_lines[line_index]); + } + } + fclose(crontab_file); + } + free_null_terminated_string_array(cron_lines); + } + } + + /* commit changes to uci, to remove ignore_backup_at_next_restore variables permanently */ + if (uci_lookup_ptr(ctx, &ptr, "firewall", true) == UCI_OK) + { + uci_commit(ctx, &ptr.p, false); + } + uci_free_context(ctx); + + return 0; +} + +void restore_backup_for_id(char* id, char* quota_backup_dir, unsigned char is_individual_other, list* defined_ip_groups) +{ + char* quota_file_path = dynamic_strcat(3, quota_backup_dir, "/quota_", id); + unsigned long num_ips = 0; + time_t last_backup = 0; + ip_bw* loaded_backup_data = load_usage_from_file(quota_file_path, &num_ips, &last_backup); + if(loaded_backup_data != NULL) + { + //printf("restoring quota... id=%s, is_individual_other=%d, num_defined_ip_groups=%d\n", id, is_individual_other, defined_ip_groups->length); + if(is_individual_other) + { + //filter out any ips in the "other" data, that have now been assigned quotas of their own. + list* ip_bw_list = initialize_list(); + int ip_index; + for(ip_index=0; ip_index < num_ips; ip_index++) + { + ip_bw* ptr = loaded_backup_data + ip_index; + push_list(ip_bw_list, ptr); + } + + unsigned long num_groups = 0; + char** group_strs = (char**)get_list_values(defined_ip_groups, &num_groups); + unsigned long group_index; + + for(group_index = 0; group_index < num_groups; group_index++) + { + filter_group_from_list(&ip_bw_list, group_strs[group_index]); + } + + + //rebuild the backup data array from the filtered list + if(num_ips != ip_bw_list->length) + { + num_ips = ip_bw_list->length; + ip_bw* adj_backup = (ip_bw*)malloc( (1+ip_bw_list->length)*sizeof(ip_bw) ); + while(ip_bw_list->length > 0) + { + ip_bw* next = pop_list(ip_bw_list); + adj_backup[ ip_bw_list->length ] = *next; + } + free(loaded_backup_data); + loaded_backup_data = adj_backup; + } + + destroy_list(ip_bw_list, DESTROY_MODE_IGNORE_VALUES, &num_groups); + free(group_strs); //don't want to destroy values, they're still contained in list, so just destroy container array + } + set_bandwidth_usage_for_rule_id(id, 1, num_ips, last_backup, loaded_backup_data, 5000); + } + free(quota_file_path); +} + +list* filter_group_from_list(list** orig_ip_bw_list, char* ip_group_str) +{ + char* dyn_group_str = strdup(ip_group_str); + + /* remove spaces in ip range definitions */ + while(strstr(dyn_group_str, " -") != NULL) + { + char* tmp_group_str = dyn_group_str; + dyn_group_str = dynamic_replace(dyn_group_str, " -", "-"); + free(tmp_group_str); + } + while(strstr(dyn_group_str, "- ") != NULL) + { + char* tmp_group_str = dyn_group_str; + dyn_group_str = dynamic_replace(dyn_group_str, "- ", "-"); + free(tmp_group_str); + } + while(strstr(dyn_group_str, " -") != NULL) + { + char* tmp_group_str = dyn_group_str; + dyn_group_str = dynamic_replace(dyn_group_str, " /", "/"); + free(tmp_group_str); + } + while(strstr(dyn_group_str, "- ") != NULL) + { + char* tmp_group_str = dyn_group_str; + dyn_group_str = dynamic_replace(dyn_group_str, "/ ", "/"); + free(tmp_group_str); + } + + char group_breaks[]= ",\t "; + unsigned long num_groups = 0; + char** split_group = split_on_separators(dyn_group_str, group_breaks, 3, -1, 0, &num_groups); + unsigned long group_index; + + for(group_index = 0; group_index < num_groups; group_index++) + { + uint32_t* range = ip_range_to_host_ints( split_group[group_index] ); + list* new_ip_bw_list = initialize_list(); + while((*orig_ip_bw_list)->length > 0) + { + ip_bw* next_ip_bw = shift_list(*orig_ip_bw_list); + uint32_t test_ip = ntohl(next_ip_bw->ip); + if(test_ip >= range[0] && test_ip <= range[1]) + { + //overlap found! filter the ip by not adding it back! + } + else + { + push_list(new_ip_bw_list, next_ip_bw); + } + } + free(range); + unsigned long num_destroyed; + destroy_list(*orig_ip_bw_list, DESTROY_MODE_IGNORE_VALUES, &num_destroyed); + *orig_ip_bw_list = new_ip_bw_list; + } + free_null_terminated_string_array(split_group); + free(dyn_group_str); + + return *orig_ip_bw_list; + +} + +uint32_t* ip_range_to_host_ints(char* ip_str) +{ + uint32_t* ret_val = (uint32_t*)malloc(2*sizeof(uint32_t)); + uint32_t start = 0; + uint32_t end = 0; + + unsigned long num_pieces = 0; + char ip_breaks[] = "/-"; + char** split_ip = split_on_separators(ip_str, ip_breaks, 2, -1, 0, &num_pieces); + start = ip_to_host_int(split_ip[0]); + if(strstr(ip_str, "/") != NULL) + { + uint32_t mask = -1; + if(strstr(split_ip[1], ".") != NULL) + { + mask = ip_to_host_int(split_ip[1]); + } + else + { + uint32_t mask_size; + sscanf(split_ip[1], "%d", &mask_size); + mask = mask << (32-mask_size); + } + start = start & mask; + end = start | ( ~mask ); + } + else if(strstr(ip_str, "-") != NULL) + { + end = ip_to_host_int(split_ip[1]); + } + else + { + end = start; + } + free_null_terminated_string_array(split_ip); + + ret_val[0] = start; + ret_val[1] = end; + return ret_val; +} + +uint32_t ip_to_host_int(char* ip_str) +{ + struct in_addr inp; + inet_aton(ip_str, &inp); + return (uint32_t)ntohl(inp.s_addr); +} + +void delete_chain_from_table(char* table, char* delete_chain) +{ + 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); + run_shell_command(next_command, 1); + } +} + +void run_shell_command(char* command, int free_command_str) +{ + //printf("%s\n", command); + system(command); + if(free_command_str) + { + free(command); + } +} + +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; +} diff --git a/package/jsda/libericstools/Makefile b/package/jsda/libericstools/Makefile new file mode 100644 index 0000000000..e8ae3c3ff9 --- /dev/null +++ b/package/jsda/libericstools/Makefile @@ -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 +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)) diff --git a/package/jsda/libericstools/src/Makefile b/package/jsda/libericstools/src/Makefile new file mode 100644 index 0000000000..ed93ebc501 --- /dev/null +++ b/package/jsda/libericstools/src/Makefile @@ -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 diff --git a/package/jsda/libericstools/src/erics_tools.h b/package/jsda/libericstools/src/erics_tools.h new file mode 100644 index 0000000000..b6866b9773 --- /dev/null +++ b/package/jsda/libericstools/src/erics_tools.h @@ -0,0 +1,276 @@ +/* + * Copyright © 2008 by Eric Bishop + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 */ diff --git a/package/jsda/libericstools/src/file_util.c b/package/jsda/libericstools/src/file_util.c new file mode 100644 index 0000000000..9859cee675 --- /dev/null +++ b/package/jsda/libericstools/src/file_util.c @@ -0,0 +1,189 @@ +/* + * Copyright © 2008 by Eric Bishop + * + * 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 + +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; +} diff --git a/package/jsda/libericstools/src/list.c b/package/jsda/libericstools/src/list.c new file mode 100644 index 0000000000..92d6283c3a --- /dev/null +++ b/package/jsda/libericstools/src/list.c @@ -0,0 +1,277 @@ +/* + * Copyright © 2008 by Eric Bishop + * + * 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 + +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; +} diff --git a/package/jsda/libericstools/src/priority_queue.c b/package/jsda/libericstools/src/priority_queue.c new file mode 100644 index 0000000000..3f1875df85 --- /dev/null +++ b/package/jsda/libericstools/src/priority_queue.c @@ -0,0 +1,350 @@ +/* + * Copyright © 2008 by Eric Bishop + * + * 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 + +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; +} diff --git a/package/jsda/libericstools/src/safe_malloc.c b/package/jsda/libericstools/src/safe_malloc.c new file mode 100644 index 0000000000..4ab204a217 --- /dev/null +++ b/package/jsda/libericstools/src/safe_malloc.c @@ -0,0 +1,80 @@ +/* + * Copyright © 2008 by Eric Bishop + * + * 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" + +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; + } +} diff --git a/package/jsda/libericstools/src/string_util.c b/package/jsda/libericstools/src/string_util.c new file mode 100644 index 0000000000..8fc2c7d7c2 --- /dev/null +++ b/package/jsda/libericstools/src/string_util.c @@ -0,0 +1,669 @@ +/* + * Copyright © 2008 by Eric Bishop + * + * 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 + +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; ilength > 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; +} diff --git a/package/jsda/libericstools/src/test_map.c b/package/jsda/libericstools/src/test_map.c new file mode 100644 index 0000000000..ef5552fc8d --- /dev/null +++ b/package/jsda/libericstools/src/test_map.c @@ -0,0 +1,322 @@ +/* + * Copyright © 2008 by Eric Bishop + * + * 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 + +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; iroot->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; ilm.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; knum_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); +} diff --git a/package/jsda/libericstools/src/test_string.c b/package/jsda/libericstools/src/test_string.c new file mode 100644 index 0000000000..b752dccefd --- /dev/null +++ b/package/jsda/libericstools/src/test_string.c @@ -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; +} diff --git a/package/jsda/libericstools/src/tree_map.c b/package/jsda/libericstools/src/tree_map.c new file mode 100644 index 0000000000..9dd14b5627 --- /dev/null +++ b/package/jsda/libericstools/src/tree_map.c @@ -0,0 +1,884 @@ +/* + * Copyright © 2008 by Eric Bishop + * + * 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; +} diff --git a/package/jsda/libiptbwctl/Makefile b/package/jsda/libiptbwctl/Makefile new file mode 100644 index 0000000000..74c80fdf73 --- /dev/null +++ b/package/jsda/libiptbwctl/Makefile @@ -0,0 +1,74 @@ +# +# Copyright (C) 2006 OpenWrt.org +# Copyright (C) 2009 Eric Bishop +# +# 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 +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)) diff --git a/package/jsda/libiptbwctl/src/Makefile b/package/jsda/libiptbwctl/src/Makefile new file mode 100644 index 0000000000..42c308fc73 --- /dev/null +++ b/package/jsda/libiptbwctl/src/Makefile @@ -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* diff --git a/package/jsda/libiptbwctl/src/ipt_bwctl.c b/package/jsda/libiptbwctl/src/ipt_bwctl.c new file mode 100644 index 0000000000..c5d669ca72 --- /dev/null +++ b/package/jsda/libiptbwctl/src/ipt_bwctl.c @@ -0,0 +1,1279 @@ +/* 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 + * + * 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 . + */ + +#include "ipt_bwctl.h" +#define malloc ipt_bwctl_safe_malloc +#define strdup ipt_bwctl_safe_strdup + +static int bandwidth_semaphore = -1; + +union semun +{ + int val; // Value for SETVAL + struct semid_ds *buf; // Buffer for IPC_STAT, IPC_SET + unsigned short *array; // Array for GETALL, SETALL + struct seminfo *__buf; // Buffer for IPC_INFO (Linux specific) +}; + +/* semaphore functions */ +static int get_sem_val(int sid, int member); +static int get_sem(int *sid, key_t key); +static int lock_sem(int sid); +static int unlock_sem(int sid); +static int lock(unsigned long max_wait_milliseconds); +static int unlock(void); + +/* needed to calculate history time intervals */ +static time_t get_next_node_start_time(time_t current_start_time, + time_t reset_interval, + time_t reset_time, + unsigned char is_constant_interval + ); + +/* functions used to get data from kernel module */ +static void parse_returned_ip_data(void *out_data, + uint32_t* out_index, + unsigned char* in_buffer, + uint32_t* in_index, + unsigned char get_history, + time_t reset_interval, + time_t reset_time, + unsigned char is_constant_interval + ); + +static int get_bandwidth_data(char* id, + unsigned char get_history, + char* ip, + unsigned long* num_ips, + void** data, + unsigned long max_wait_milliseconds + ); + +/* functions used to send/restore data to kernel module */ +static int set_ip_block(void* ip_block_data, + unsigned char is_history, + unsigned char* output_buffer, + uint32_t* current_output_index, + uint32_t output_buffer_length + ); + +static int set_bandwidth_data(char* id, + unsigned char zero_unset, + unsigned char set_history, + unsigned long num_ips, + time_t last_backup, + void* data, + unsigned long max_wait_milliseconds + ); + +/* utility i/o functions when saving/restoring data to/from file */ +static unsigned char* read_entire_file(FILE* in, + unsigned long read_block_size, + unsigned long *length + ); + +static char** split_on_separators(char* line, + char* separators, + int num_separators, + int max_pieces, + int include_remainder_at_max, + unsigned long *pieces_read + ); + +static int get_sem_val(int sid, int member) +{ + int semval; + semval = semctl(sid, member, GETVAL, 0); + return(semval); +} + +static int get_sem(int *sid, key_t key) +{ + int cntr; + union semun semopts; + int members = 1; + + int success = ((*sid = semget(key, members, IPC_CREAT|IPC_EXCL|0777))== -1) ? 0 : 1; + if(success) + { + semopts.val = 1; + /* Initialize all members (could be done with SETALL) */ + for(cntr=0; cntr 0) + { + sem_lock.sem_num = member; + if((semop(sid, &sem_lock, 1)) != -1) + { + success = 1; + } + } + + return success; +} + +static int unlock_sem(int sid) +{ + int member = 0; + struct sembuf sem_unlock={ member, 1, IPC_NOWAIT}; + + /* will fail if we can't can't unlock semaphore for some reason, + * will NOT fail if semaphore is already unlocked + */ + int success = 1; + + /* Is the semaphore set locked? */ + int semval = get_sem_val(sid, member); + if(semval == 0) + { + /* it's locked, unlock it */ + sem_unlock.sem_num = member; + success = ((semop(sid, &sem_unlock, 1)) == -1) ? 0 : 1; + } + return success; +} + + + +static int lock(unsigned long max_wait_milliseconds) +{ + int locked = 0; + if(bandwidth_semaphore == -1) + { + get_sem(&bandwidth_semaphore, (key_t)(BANDWIDTH_SEMAPHORE_KEY) ); + } + if(bandwidth_semaphore != -1) + { + do + { + locked = lock_sem(bandwidth_semaphore); + if(locked == 0 && max_wait_milliseconds > 25) + { + usleep(1000*25); + } + max_wait_milliseconds = max_wait_milliseconds > 25 ? max_wait_milliseconds - 25 : 0; + } while(locked == 0 && max_wait_milliseconds > 0); + } + return locked; +} + +static int unlock(void) +{ + int unlocked = 0; + if(bandwidth_semaphore == -1) + { + get_sem(&bandwidth_semaphore, (key_t)(BANDWIDTH_SEMAPHORE_KEY) ); + } + if(bandwidth_semaphore != -1) + { + unlocked = unlock_sem(bandwidth_semaphore); + } + return unlocked; +} + +static time_t get_next_node_start_time(time_t current_start_time, + time_t reset_interval, + time_t reset_time, + unsigned char is_constant_interval + ) +{ + time_t next = current_start_time; + if(is_constant_interval) + { + next = current_start_time + reset_interval; + } + else + { + while(next + reset_time <= current_start_time) + { + struct tm* curr = localtime(¤t_start_time); + curr->tm_isdst = -1; + if(reset_interval == BANDWIDTH_MINUTE) + { + curr->tm_sec = 0; + curr->tm_min = curr->tm_min+1; + next = mktime(curr); + } + else if(reset_interval == BANDWIDTH_HOUR) + { + curr->tm_sec = 0; + curr->tm_min = 0; + curr->tm_hour = curr->tm_hour+1; + next = mktime(curr); + } + else if(reset_interval == BANDWIDTH_DAY) + { + curr->tm_sec = 0; + curr->tm_min = 0; + curr->tm_hour = 0; + curr->tm_mday = curr->tm_mday+1; + next = mktime(curr); + } + else if(reset_interval == BANDWIDTH_WEEK) + { + curr->tm_sec = 0; + curr->tm_min = 0; + curr->tm_hour = 0; + curr->tm_mday = curr->tm_mday+1; + time_t tmp = mktime(curr); + curr = localtime(&tmp); + while(curr->tm_wday != 0) + { + curr->tm_mday=curr->tm_mday+1; + tmp = mktime(curr); + curr = localtime(&tmp); + } + next = mktime(curr); + } + else if(reset_interval == BANDWIDTH_MONTH) + { + curr->tm_sec = 0; + curr->tm_min = 0; + curr->tm_hour = 0; + curr->tm_mday = 1; + curr->tm_mon = curr->tm_mon+1; + next = mktime(curr); + } + } + next = next + reset_time; + } + return next; +} + +static void parse_returned_ip_data(void *out_data, + uint32_t* out_index, + unsigned char* in_buffer, + uint32_t* in_index, + unsigned char get_history, + time_t reset_interval, + time_t reset_time, + unsigned char is_constant_interval + ) +{ + uint32_t ip = *( (uint32_t*)(in_buffer + *in_index) ); + ip_bw_kernel_data_item* ip_bw_data = (ip_bw_kernel_data*)(in_buffer + *in_index); + if(get_history == 0) + { + (((ip_bw*)out_data)[*out_index]).ip = ip; + *in_index = *in_index + 4; + (((ip_bw*)out_data)[*out_index]).bw = *( (uint64_t*)(in_buffer + *in_index) ); + *in_index = *in_index + 8; + } + else + { + ip_bw_history *history = ((ip_bw_history*)out_data) + *out_index; + history->reset_interval = reset_interval; + history->reset_time = reset_time; + history->is_constant_interval = is_constant_interval; + + history->ip = ip_bw_data->ip; + history->num_nodes = ip_bw_data->num_nodes; + history->first_start = ip_bw_data->first_start; + history->first_end = ip_bw_data->first_end; + history->last_end = ip_bw_data->last_end; + + history->history_bws = (uint64_t*)malloc( (history->num_nodes+1)*sizeof(uint64_t) ); + + /* read bws */ + int node_index = 0; + *in_index += 32; + for (node_index = 0; node_index < history->num_nodes; node_index++) + { + *in_index += 8; + (history->history_bws)[node_index] = ip_bw_data->ipbw_data[node_index]; + } + + /* + * We now need to deal with DST + * + * The problem is that the kernel can't tell the difference + * between timezones being switched and entering daylight savings + * time. Whenever the time offset from UTC shifts, the kernel module + * shifts values in the bandwidth history to reflect the time + * as it would be if the current offset from UTC had always been + * in effect. So, we need to go backwards through the history and + * anytime we go from DST to non-DST (or visa-versa) implement a + * shift so that returned times reflect reality. + */ + time_t now; + time(&now); + int current_minutes_west = get_minutes_west(now); + history->first_start = history->first_start + (60*(get_minutes_west(history->first_start)-current_minutes_west)); + history->first_end = history->first_end + (60*(get_minutes_west(history->first_end)-current_minutes_west)); + history->last_end = history->last_end + (60*(get_minutes_west(history->last_end)-current_minutes_west)); + } + *out_index = *out_index + 1; +} + +static int get_bandwidth_data(char* id, unsigned char get_history, char* ip, unsigned long* num_ips, void** data, unsigned long max_wait_milliseconds) +{ + unsigned char buf[BANDWIDTH_QUERY_LENGTH]; + memset(buf, '\0', BANDWIDTH_QUERY_LENGTH); + int done = 0; + ip_bw_kernel_data* ip_bw_data = (ip_bw_kernel_data*)(buf); + + *data = NULL; + *num_ips = 0; + + int got_lock = lock(max_wait_milliseconds); + int sockfd = -1; + if(got_lock) + { + sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + } + + uint32_t* request_ip = (uint32_t*)buf; + uint32_t* request_index = (uint32_t*)(buf + 4); + unsigned char* request_history =(unsigned char*)(buf + 8); + char* request_id = (char*)(buf+9); + + if(strcmp(ip, "ALL") == 0) + { + *request_ip = 0; + } + else + { + struct in_addr addr; + inet_aton(ip, &addr); + *request_ip = (uint32_t)addr.s_addr; + } + *request_index = 0; + *request_history = get_history; + sprintf(request_id, "%s", id); + + unsigned char error = 0; + unsigned char data_initialized = 0; + uint32_t data_index = 0; + while(!done && sockfd >= 0 && got_lock) + { + uint32_t size = BANDWIDTH_QUERY_LENGTH; + + getsockopt(sockfd, IPPROTO_IP, BANDWIDTH_GET, buf, &size); + + error = (unsigned char)buf[0]; + if(error != 0) + { + done = 1; + } + else + { + uint32_t total_ips = ip_bw_data->ip_total; + /*uint32_t next_ip_index = *( (uint32_t*)(buf+5) ); //unused */ + uint32_t response_ips = ip_bw_data->ip_num; + time_t reset_interval = ip_bw_data->reset_interval; + time_t reset_time = ip_bw_data->reset_time; + unsigned char is_constant_interval = ip_bw_data->reset_is_constant_interval; + + if(!data_initialized) + { + *num_ips = total_ips; + if(get_history) + { + *data = (void*)malloc(sizeof(ip_bw_history)*(total_ips+1)); + memset(*data, 0, sizeof(ip_bw_history)*(total_ips+1)); + } + else + { + *data = (void*)malloc(sizeof(ip_bw)*(total_ips+1)); + memset(*data, 0, sizeof(ip_bw)*(total_ips+1)); + } + } + + int response_index=0; + uint32_t buffer_index = 30; + for(response_index=0; response_index < response_ips; response_index++) + { + parse_returned_ip_data(*data, &data_index, buf, &buffer_index, get_history, reset_interval, reset_time, is_constant_interval); + } + *request_index= *request_index + response_ips; + done = *request_index < total_ips ? 0 : 1; + } + } + if( (error != 0) && data_initialized) + { + if(get_history) + { + free_ip_bw_histories( (ip_bw_history*)(*data), *num_ips ); + } + else + { + free(*data); + } + *data = NULL; + *num_ips = 0; + } + + if(sockfd >= 0) + { + close(sockfd); + } + if(got_lock) + { + unlock(); + } + return got_lock && (error == 0); +} + +static int set_ip_block(void* ip_block_data, unsigned char is_history, unsigned char* output_buffer, uint32_t* current_output_index, uint32_t output_buffer_length) +{ + if(is_history) + { + ip_bw_history* history = (ip_bw_history*)ip_block_data; + uint32_t block_length = (2*4) + (3*8) + (8*history->num_nodes); + if(*current_output_index + block_length > output_buffer_length) + { + return 1; + } + + *( (uint32_t*)(output_buffer + *current_output_index) ) = history->ip; + *current_output_index = *current_output_index + 4; + + *( (uint32_t*)(output_buffer + *current_output_index) ) = history->num_nodes; + *current_output_index = *current_output_index + 4; + + /* + * We now need to deal with DST + * + * The problem is that the kernel module can't tell the difference + * between timezones being switched and entering daylight savings + * time. Whenever the time offset from UTC shifts, the kernel module + * shifts values in the bandwidth history to reflect the time + * as it would be if the current offset from UTC had always been + * in effect. In order to keep all data in the kernel module + * consistent we need to make sure values we send kernel module + * are adjusted for current offset from UTC, not the real one, + * where the offset may be inconsistent because of DST. + * + * Also note we ignore all but the first, second and last values in the + * history time list. These are the only values the kernel needs/uses. + * We have the whole list in the structure so that we don't have to make + * programs that deal with the library worry about the conversion when + * values are returned. However, when setting data, it's perfectly ok + * to just have the three necessary values and set everything else to zero + */ + time_t first_start = history->first_start; + time_t first_end = history->first_end; + time_t last_end = history->last_end; + + time_t now; + time(&now); + int current_minutes_west = get_minutes_west(now); + first_start = first_start + (get_minutes_west(first_start)-current_minutes_west); + first_end = first_end + (get_minutes_west(first_end)-current_minutes_west); + last_end = last_end + (get_minutes_west(last_end)-current_minutes_west); + + *( (uint64_t*)(output_buffer + *current_output_index) ) = (uint64_t)first_start; + *current_output_index = *current_output_index + 8; + + *( (uint64_t*)(output_buffer + *current_output_index) ) = (uint64_t)first_end; + *current_output_index = *current_output_index + 8; + + *( (uint64_t*)(output_buffer + *current_output_index) ) = (uint64_t)last_end; + *current_output_index = *current_output_index + 8; + + uint32_t node_num = 0; + for(node_num=0; node_num < history->num_nodes; node_num++) + { + *( (uint64_t*)(output_buffer + *current_output_index) ) = (history->history_bws)[ node_num ]; + *current_output_index = *current_output_index + 8; + } + } + else + { + if(*current_output_index + 12 > output_buffer_length) + { + return 1; + } + + ip_bw* ib = (ip_bw*)ip_block_data; + *( (uint32_t*)(output_buffer + *current_output_index) ) = ib->ip; + *current_output_index = *current_output_index + 4; + *( (uint64_t*)(output_buffer + *current_output_index) ) = ib->bw; + + /* + struct in_addr addr; + addr.s_addr = ib->ip; + printf("setting ip = %s, ip index = %ld\n", inet_ntoa(addr), (*current_output_index)-4); + printf("setting bw = %lld, bw_index = %ld\n", ib->bw, *current_output_index); + */ + *current_output_index = *current_output_index + 8; + } + return 0; +} + +static int set_bandwidth_data(char* id, unsigned char zero_unset, unsigned char set_history, unsigned long num_ips, time_t last_backup, void* data, unsigned long max_wait_milliseconds) +{ + unsigned char buf[BANDWIDTH_QUERY_LENGTH]; + memset(buf, 0, BANDWIDTH_QUERY_LENGTH); + int done = 0; + + int got_lock = lock(max_wait_milliseconds); + int sockfd = -1; + if(got_lock) + { + sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + } + + uint32_t* total_ips = (uint32_t*)(buf+0); + uint32_t* next_ip_index = (uint32_t*)(buf+4); + uint32_t* num_ips_in_buffer = (uint32_t*)(buf+8); + unsigned char* history_included = (unsigned char*)(buf+12); + unsigned char* zero_unset_ips = (unsigned char*)(buf+13); + uint64_t* last_backup_time = (uint64_t*)(buf+14); + unsigned char* set_id = (unsigned char*)(buf+22); + + *total_ips = num_ips; + *next_ip_index = 0; + *num_ips_in_buffer = 0; + *history_included = set_history; + *zero_unset_ips = zero_unset; + *last_backup_time = (uint64_t)last_backup; + memcpy(set_id, id, BANDWIDTH_MAX_ID_LENGTH); + set_id[BANDWIDTH_MAX_ID_LENGTH-1] = '\0'; + + while(!done && sockfd >= 0 && got_lock) + { + uint32_t buf_index = (3*4) + (2*1) + 8 + BANDWIDTH_MAX_ID_LENGTH; + uint32_t ip_index = *next_ip_index; + unsigned char buffer_full = 0; + memset( (buf + buf_index), 0, (BANDWIDTH_QUERY_LENGTH-buf_index) ); + *num_ips_in_buffer = 0; + done = (ip_index >= *total_ips); + + while( (!buffer_full) && (!done) ) + { + void *next_data = set_history ? (void*)(((ip_bw_history*)data) + ip_index) : (void*)(((ip_bw*)data) + ip_index); + buffer_full = set_ip_block(next_data , set_history, buf, &buf_index, BANDWIDTH_QUERY_LENGTH); + ip_index = buffer_full ? ip_index : ip_index+1; + *num_ips_in_buffer = buffer_full ? *num_ips_in_buffer : *num_ips_in_buffer + 1; + done = (ip_index >= *total_ips); + } + setsockopt(sockfd, IPPROTO_IP, BANDWIDTH_SET, buf, BANDWIDTH_QUERY_LENGTH); + + *next_ip_index = ip_index; + } + if(sockfd >= 0) + { + close(sockfd); + } + if(got_lock) + { + unlock(); + } + return got_lock; +} + +static 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; +} + +/* + * 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*) + */ +static char** split_on_separators(char* line, char* separators, int num_separators, int max_pieces, int include_remainder_at_max, unsigned long *pieces_read) +{ + char** split; + *pieces_read = 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++; + *pieces_read = 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; +} + +time_t* get_interval_starts_for_history(ip_bw_history history) +{ + time_t *start_times = NULL; + if(history.num_nodes > 0) + { + start_times = (time_t*)malloc(history.num_nodes*sizeof(time_t)); + int node_index =0; + time_t next_start = history.first_start; + time_t next_end = get_next_node_start_time(next_start, history.reset_interval, history.reset_time, history.is_constant_interval); + for(node_index=0; node_index < history.num_nodes; node_index++) + { + start_times[node_index] = next_start; + next_start = next_end; + next_end = get_next_node_start_time(next_start, history.reset_interval, history.reset_time, history.is_constant_interval); + } + } + return start_times; +} + +void free_ip_bw_histories(ip_bw_history* histories, int num_histories) +{ + if(histories == NULL) + { + return; + } + int history_index = 0; + for(history_index=0; history_index < num_histories; history_index++) + { + if((histories[history_index]).history_bws != NULL) + { + free( (histories[history_index]).history_bws ); + } + } + free(histories); +} + +int get_all_bandwidth_history_for_rule_id(char* id, unsigned long* num_ips, ip_bw_history** data, unsigned long max_wait_milliseconds) +{ + return get_bandwidth_data(id, 1, "ALL", num_ips, (void*)data, max_wait_milliseconds); +} + +int get_ip_bandwidth_history_for_rule_id(char* id, char* ip, ip_bw_history** data, unsigned long max_wait_milliseconds) +{ + unsigned long num_ips; + return get_bandwidth_data(id, 1, ip, &num_ips, (void*)data, max_wait_milliseconds); +} + +int get_all_bandwidth_usage_for_rule_id(char* id, unsigned long* num_ips, ip_bw** data, unsigned long max_wait_milliseconds) +{ + return get_bandwidth_data(id, 0, "ALL", num_ips, (void*)data, max_wait_milliseconds); +} + +int get_ip_bandwidth_usage_for_rule_id(char* id, char* ip, ip_bw** data, unsigned long max_wait_milliseconds) +{ + unsigned long num_ips; + return get_bandwidth_data(id, 0, ip, &num_ips, (void*)data, max_wait_milliseconds); +} + +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) +{ + return set_bandwidth_data(id, zero_unset, 1, num_ips, 0, data, max_wait_milliseconds); +} + +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) +{ + return set_bandwidth_data(id, zero_unset, 0, num_ips, last_backup, data, max_wait_milliseconds); +} + +/* save single id in ascii */ +int save_usage_to_file(ip_bw* data, unsigned long num_ips, char* out_file_path) +{ + + int success = 0; + FILE* out_file = fopen(out_file_path, "w"); + if(out_file != NULL) + { + //dump backup time + time_t now; + time(&now); + fprintf(out_file, "%-15ld\n", now); + + //dump ips + int out_index=0; + for(out_index=0; out_index < num_ips; out_index++) + { + struct in_addr ipaddr; + ip_bw next = data[out_index]; + ipaddr.s_addr = next.ip; + fprintf(out_file, "%-15s\t%lld\n", inet_ntoa(ipaddr), (long long int)next.bw); + } + fclose(out_file); + success = 1; + } + return success; +} + +/* save history (must be for one id only) in binary so it takes up less space */ +int save_history_to_file(ip_bw_history* data, unsigned long num_ips, char* out_file_path) +{ + int success = 0; + FILE* out_file = fopen(out_file_path, "wb"); + if(out_file != NULL) + { + //dump number of ips & history interval parameter + //note that we assume interval is same for all histories + //(which will be the case if they all come from the same rule id) + fwrite((uint32_t*)(&num_ips), 4, 1, out_file); + if(num_ips > 0) + { + ip_bw_history first = data[0]; + uint64_t interval = (uint64_t)(first.reset_interval); + uint64_t time = (uint64_t)(first.reset_time); + unsigned char is_constant = first.is_constant_interval; + + fwrite( &interval, 8, 1, out_file); + fwrite( &time, 8, 1, out_file); + fwrite( &is_constant, 1, 1, out_file); + } + + unsigned char bw_fits_in_32bits = 1; + uint32_t out_index=0; + for(out_index=0; out_index < num_ips && bw_fits_in_32bits; out_index++) + { + uint32_t node_index = 0; + ip_bw_history next = data[out_index]; + for(node_index=0; node_index < next.num_nodes && bw_fits_in_32bits; node_index++) + { + uint64_t bw = (next.history_bws)[node_index]; + bw_fits_in_32bits = bw_fits_in_32bits && (bw < INT32_MAX); + } + } + + //dump data for each ip + for(out_index=0; out_index < num_ips; out_index++) + { + ip_bw_history next = data[out_index]; + + fwrite( &(next.ip), 4, 1, out_file); + fwrite( &(next.num_nodes), 4, 1, out_file); + if(next.num_nodes == 0) + { + uint64_t dummy = 0; + unsigned char bw_bits = 32; + fwrite( &dummy, 8, 1, out_file); + fwrite( &dummy, 8, 1, out_file); + fwrite( &dummy, 8, 1, out_file); + fwrite( &bw_bits, 1, 1, out_file); + } + else + { + uint32_t node_index = 0; + uint64_t first_start = (uint64_t)next.first_start; + uint64_t first_end = (uint64_t)next.first_end; + uint64_t last_end = (uint64_t)next.last_end; + unsigned char bw_bits = 32; + for(node_index=0; node_index < next.num_nodes && bw_bits == 32; node_index++) + { + uint64_t bw = (next.history_bws)[node_index]; + bw_bits = bw_bits == 32 && (bw < INT32_MAX) ? 32 : 64; + } + + fwrite( &first_start, 8, 1, out_file); + fwrite( &first_end, 8, 1, out_file); + fwrite( &last_end, 8, 1, out_file); + fwrite( &bw_bits, 1, 1, out_file); + for(node_index=0; node_index < next.num_nodes; node_index++) + { + if(bw_bits == 32) + { + uint32_t bw = (uint32_t)(next.history_bws)[node_index]; + fwrite( &bw, 4, 1, out_file); + + } + else + { + uint64_t bw = (next.history_bws)[node_index]; + fwrite( &bw, 8, 1, out_file); + } + } + } + } + fclose(out_file); + success = 1; + } + return success; +} + + +ip_bw* load_usage_from_file(char* in_file_path, unsigned long* num_ips, time_t* last_backup) +{ + ip_bw* data = NULL; + *num_ips = 0; + *last_backup = 0; + FILE* in_file = fopen(in_file_path, "r"); + if(in_file != NULL) + { + unsigned long num_data_parts = 0; + char* file_data = read_entire_file(in_file, 4086, &num_data_parts); + fclose(in_file); + char whitespace[] = {'\n', '\r', '\t', ' '}; + char** data_parts = split_on_separators(file_data, whitespace, 4, -1, 0, &num_data_parts); + free(file_data); + + *num_ips = (num_data_parts/2) + 1; + data = (ip_bw*)malloc( (*num_ips) * sizeof(ip_bw) ); + *num_ips = 0; + unsigned long data_index = 0; + unsigned long data_part_index = 0; + while(data_part_index < num_data_parts) + { + ip_bw next; + struct in_addr ipaddr; + int valid = inet_aton(data_parts[data_part_index], &ipaddr); + if(!valid) + { + sscanf(data_parts[data_part_index], "%ld", last_backup); + //printf("last_backup = %ld\n", *last_backup); + } + data_part_index++; + + if(valid && data_index < num_data_parts) + { + next.ip = ipaddr.s_addr; + valid = sscanf(data_parts[data_part_index], "%lld", (long long int*)&(next.bw) ); + data_part_index++; + } + else + { + valid = 0; + } + + if(valid) + { + //printf("next.bw = %lld\n", next.bw); + //printf("next.ip = %d\n", next.ip); + data[data_index] = next; + data_index++; + *num_ips = *num_ips + 1; + } + } + + /* cleanup by freeing data_parts */ + for(data_part_index = 0; data_part_index < num_data_parts; data_part_index++) + { + free(data_parts[data_part_index]); + } + + free(data_parts); + } + return data; +} + +ip_bw_history* load_history_from_file(char* in_file_path, unsigned long* num_ips) +{ + ip_bw_history* data = NULL; + *num_ips = 0; + FILE* in_file = fopen(in_file_path, "rb"); + if(in_file != NULL) + { + uint64_t reset_interval; + uint64_t reset_time; + unsigned char is_constant_interval; + + uint32_t nips = 0; + fread(&nips, 4, 1, in_file); + *num_ips = (unsigned long)nips; + + if(*num_ips > 0) + { + fread(&reset_interval, 8, 1, in_file); + fread(&reset_time, 8, 1, in_file); + fread(&is_constant_interval, 1, 1, in_file); + data = (ip_bw_history*)malloc( (*num_ips) * sizeof(ip_bw_history)); + } + + uint32_t ip_index; + for(ip_index=0; ip_index < *num_ips; ip_index++) + { + + uint32_t ip; + uint32_t num_nodes; + uint64_t first_start; + uint64_t first_end; + uint64_t last_end; + unsigned char bw_bits; + + fread(&ip, 4, 1, in_file); + fread(&num_nodes, 4, 1, in_file); + fread(&first_start, 8, 1, in_file); + fread(&first_end, 8, 1, in_file); + fread(&last_end, 8, 1, in_file); + fread(&bw_bits, 1, 1, in_file); + + ip_bw_history next; + next.reset_interval = (time_t)reset_interval; + next.reset_time = (time_t)reset_time; + next.is_constant_interval = is_constant_interval; + next.ip = ip; + next.num_nodes = num_nodes; + next.first_start = (time_t)first_start; + next.first_end = (time_t)first_end; + next.last_end = (time_t)last_end; + next.history_bws = NULL; + if(next.num_nodes > 0) + { + next.history_bws = malloc( next.num_nodes * sizeof(uint64_t) ); + uint32_t node_index = 0; + for(node_index=0; node_index < next.num_nodes; node_index++) + { + if(bw_bits == 32) + { + uint32_t nextbw = 0; + fread(&nextbw, 4, 1, in_file); + (next.history_bws)[node_index] = (uint64_t)nextbw; + + } + else + { + uint64_t nextbw = 0; + fread(&nextbw, 8, 1, in_file); + (next.history_bws)[node_index] = nextbw; + } + } + } + data[ip_index] = next; + } + fclose(in_file); + } + return data; +} + + +void print_usage(FILE* out, ip_bw* usage, unsigned long num_ips) +{ + unsigned long usage_index; + for(usage_index =0; usage_index < num_ips; usage_index++) + { + ip_bw next = usage[usage_index]; + if(next.ip != 0) + { + struct in_addr ipaddr; + ipaddr.s_addr = next.ip; + fprintf(out, "%-15s\t%lld\n", inet_ntoa(ipaddr), (long long int)next.bw); + } + else + { + fprintf(out, "%-15s\t%lld\n", "COMBINED", (long long int)next.bw); + } + } + fprintf(out, "\n"); +} + +void print_histories(FILE* out, char* id, ip_bw_history* histories, unsigned long num_histories, char output_type) +{ + unsigned long history_index = 0; + for(history_index=0; history_index < num_histories; history_index++) + { + ip_bw_history history = histories[history_index]; + + int history_initialized = 1; + if( history.first_start == 0 && history.first_end == 0 && history.last_end == 0) + { + history_initialized = 0; + } + + if(history_initialized) + { + char *ip_str = NULL; + time_t *times = NULL; + + if(history.ip != 0) + { + struct in_addr ipaddr; + ipaddr.s_addr = history.ip; + ip_str = strdup(inet_ntoa(ipaddr)); + } + else + { + ip_str = strdup("COMBINED"); + } + + + if(output_type == 'm' || output_type == 'h') + { + fprintf(out, "%s %-15s\n", id, ip_str); + } + + if(output_type == 'm') + { + printf("%ld\n", history.first_start); + printf("%ld\n", history.first_end); + printf("%ld\n", history.last_end); + } + else + { + times = get_interval_starts_for_history(history); + } + + int hindex = 0; + for(hindex=0; hindex < history.num_nodes; hindex++) + { + uint64_t bw = (history.history_bws)[hindex]; + if(output_type == 'm') + { + if(hindex != 0) { printf(","); }; + printf("%lld", (unsigned long long int)bw); + } + else if(times != NULL) + { + time_t start = times[hindex]; + time_t end = hindex+1 < history.num_nodes ? times[hindex+1] : 0 ; + + char* start_str = strdup(asctime(localtime(&start))); + char* end_str = end == 0 ? strdup("(Now)") : strdup(asctime(localtime(&end))); + char* nl = strchr(start_str, '\n'); + if(nl != NULL) + { + *nl = '\0'; + } + nl = strchr(end_str, '\n'); + if(nl != NULL) + { + *nl = '\0'; + } + + if(output_type == 'h') + { + fprintf(out, "%lld\t%s\t%s\n", (unsigned long long int)bw, start_str, end_str); + } + else + { + fprintf(out, "%s,%s,%ld,%ld,%lld\n", id, ip_str, start, end, (unsigned long long int)bw ); + } + free(start_str); + free(end_str); + } + } + fprintf(out, "\n"); + if(times != NULL) { free(times); }; + if(ip_str != NULL) { free(ip_str); }; + } + } +} + +void unlock_bandwidth_semaphore(void) +{ + unlock(); +} + +void signal_handler(int sig) +{ + if(sig == SIGTERM || sig == SIGINT ) + { + unlock_bandwidth_semaphore(); + exit(0); + } +} + +void unlock_bandwidth_semaphore_on_exit(void) +{ + signal(SIGTERM,signal_handler); + signal(SIGINT, signal_handler); +} + +int get_minutes_west(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; + + 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) +{ + time_t now; + struct timeval tv; + struct timezone old_tz; + struct timezone new_tz; + + time(&now); + new_tz.tz_minuteswest = get_minutes_west(now); + 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); +} diff --git a/package/jsda/libiptbwctl/src/ipt_bwctl.h b/package/jsda/libiptbwctl/src/ipt_bwctl.h new file mode 100644 index 0000000000..1ceaf703c0 --- /dev/null +++ b/package/jsda/libiptbwctl/src/ipt_bwctl.h @@ -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 + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); diff --git a/package/jsda/libiptbwctl/src/ipt_bwctl_safe_malloc.c b/package/jsda/libiptbwctl/src/ipt_bwctl_safe_malloc.c new file mode 100644 index 0000000000..463379acfd --- /dev/null +++ b/package/jsda/libiptbwctl/src/ipt_bwctl_safe_malloc.c @@ -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; +} diff --git a/package/jsda/libiptbwctl/src/utils/Makefile b/package/jsda/libiptbwctl/src/utils/Makefile new file mode 100644 index 0000000000..c2bc9f3d2d --- /dev/null +++ b/package/jsda/libiptbwctl/src/utils/Makefile @@ -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* diff --git a/package/jsda/libiptbwctl/src/utils/bw_get.c b/package/jsda/libiptbwctl/src/utils/bw_get.c new file mode 100644 index 0000000000..5bf3f90b02 --- /dev/null +++ b/package/jsda/libiptbwctl/src/utils/bw_get.c @@ -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 + * + * 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 . + */ + +#include +#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; +} diff --git a/package/jsda/libiptbwctl/src/utils/bw_print_history_file.c b/package/jsda/libiptbwctl/src/utils/bw_print_history_file.c new file mode 100644 index 0000000000..00f6e26436 --- /dev/null +++ b/package/jsda/libiptbwctl/src/utils/bw_print_history_file.c @@ -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 + * + * 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 . + */ + +#include +#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; +} diff --git a/package/jsda/libiptbwctl/src/utils/bw_set.c b/package/jsda/libiptbwctl/src/utils/bw_set.c new file mode 100644 index 0000000000..28cbee0e20 --- /dev/null +++ b/package/jsda/libiptbwctl/src/utils/bw_set.c @@ -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 + * + * 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 . + */ + +#include +#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; +} diff --git a/package/jsda/libiptbwctl/src/utils/set_kernel_timezone.c b/package/jsda/libiptbwctl/src/utils/set_kernel_timezone.c new file mode 100644 index 0000000000..5fd958b3df --- /dev/null +++ b/package/jsda/libiptbwctl/src/utils/set_kernel_timezone.c @@ -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 + * + * 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 . + */ + +#include + +int main(void) +{ + set_kernel_timezone(); + return 0; +} diff --git a/package/jsda/luci-app-qos-gargoyle/LICENSE b/package/jsda/luci-app-qos-gargoyle/LICENSE new file mode 100755 index 0000000000..8dada3edaf --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/LICENSE @@ -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. diff --git a/package/jsda/luci-app-qos-gargoyle/Makefile b/package/jsda/luci-app-qos-gargoyle/Makefile new file mode 100755 index 0000000000..78900c782e --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/Makefile @@ -0,0 +1,31 @@ +# +# Copyright (C) 2017 Xingwang Liao +# +# 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 + +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 + $(LUCI_TITLE) + . + Version: $(PKG_VERSION)-$(PKG_RELEASE) + $(PKG_MAINTAINER) +endef + +# call BuildPackage - OpenWrt buildroot signature diff --git a/package/jsda/luci-app-qos-gargoyle/README.md b/package/jsda/luci-app-qos-gargoyle/README.md new file mode 100755 index 0000000000..ef52b75360 --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/README.md @@ -0,0 +1,5 @@ +# luci-app-qos-gargoyle + +This is my own fork of QoS Gargoyle + +QoS Gargoyle: https://github.com/kuoruan/qos-gargoyle diff --git a/package/jsda/luci-app-qos-gargoyle/luasrc/controller/qos_gargoyle.lua b/package/jsda/luci-app-qos-gargoyle/luasrc/controller/qos_gargoyle.lua new file mode 100755 index 0000000000..5b9ca56c5f --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/luasrc/controller/qos_gargoyle.lua @@ -0,0 +1,100 @@ +-- Copyright 2017 Xingwang Liao +-- 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 diff --git a/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/download.lua b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/download.lua new file mode 100755 index 0000000000..a165178874 --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/download.lua @@ -0,0 +1,166 @@ +-- Copyright 2017 Xingwang Liao +-- 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 = "*" + +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 diff --git a/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/download_class.lua b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/download_class.lua new file mode 100755 index 0000000000..c26cb78301 --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/download_class.lua @@ -0,0 +1,61 @@ +-- Copyright 2017 Xingwang Liao +-- 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 diff --git a/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/download_rule.lua b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/download_rule.lua new file mode 100755 index 0000000000..cbc970e2f0 --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/download_rule.lua @@ -0,0 +1,87 @@ +-- Copyright 2017 Xingwang Liao +-- 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 diff --git a/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/global.lua b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/global.lua new file mode 100755 index 0000000000..dcc8f2e4a0 --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/global.lua @@ -0,0 +1,123 @@ +-- Copyright 2017 Xingwang Liao +-- 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("

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.

") .. + translate("

While ACC does not adjust your upload link speed you must enable and properly " + .. "configure your upload QoS for it to function properly.

") + ) +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 diff --git a/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/upload.lua b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/upload.lua new file mode 100755 index 0000000000..a5f86927ef --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/upload.lua @@ -0,0 +1,160 @@ +-- Copyright 2017 Xingwang Liao +-- 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 = "*" + +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 diff --git a/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/upload_class.lua b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/upload_class.lua new file mode 100755 index 0000000000..9739b9794b --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/upload_class.lua @@ -0,0 +1,52 @@ +-- Copyright 2017 Xingwang Liao +-- 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 diff --git a/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/upload_rule.lua b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/upload_rule.lua new file mode 100755 index 0000000000..8abcac35d9 --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/luasrc/model/cbi/qos_gargoyle/upload_rule.lua @@ -0,0 +1,87 @@ +-- Copyright 2017 Xingwang Liao +-- 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 diff --git a/package/jsda/luci-app-qos-gargoyle/luasrc/model/qos_gargoyle.lua b/package/jsda/luci-app-qos-gargoyle/luasrc/model/qos_gargoyle.lua new file mode 100755 index 0000000000..b0113f1872 --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/luasrc/model/qos_gargoyle.lua @@ -0,0 +1,31 @@ +-- Copyright 2017 Xingwang Liao +-- 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 diff --git a/package/jsda/luci-app-qos-gargoyle/luasrc/view/qos_gargoyle/list_view.htm b/package/jsda/luci-app-qos-gargoyle/luasrc/view/qos_gargoyle/list_view.htm new file mode 100755 index 0000000000..87d3994de2 --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/luasrc/view/qos_gargoyle/list_view.htm @@ -0,0 +1,97 @@ +<%# + Copyright 2017 Xingwang Liao + Licensed to the public under the Apache License 2.0. +-%> + +<% + local dsp = require "luci.dispatcher" + local request = dsp.context.path + local leaf = request[#request] +-%> + + + +<%+cbi/map%> + + diff --git a/package/jsda/luci-app-qos-gargoyle/luasrc/view/qos_gargoyle/troubleshooting.htm b/package/jsda/luci-app-qos-gargoyle/luasrc/view/qos_gargoyle/troubleshooting.htm new file mode 100755 index 0000000000..17197850e8 --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/luasrc/view/qos_gargoyle/troubleshooting.htm @@ -0,0 +1,52 @@ +<%# + Copyright 2017 Xingwang Liao + 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%> + + + + +
+
+ <%:Troubleshooting Data%> +
<%:Loading%><%:Collecting data...%>
+
+
+ +<%+footer%> diff --git a/package/jsda/luci-app-qos-gargoyle/po/templates/qos-gargoyle.pot b/package/jsda/luci-app-qos-gargoyle/po/templates/qos-gargoyle.pot new file mode 100755 index 0000000000..9e194d7bc2 --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/po/templates/qos-gargoyle.pot @@ -0,0 +1,292 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +msgid "\"Active Congestion Control\" not enabled" +msgstr "" + +msgid "" +"

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.

" +msgstr "" + +msgid "" +"

While ACC does not adjust your upload link speed you must enable and " +"properly configure your upload QoS for it to function properly.

" +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 "" diff --git a/package/jsda/luci-app-qos-gargoyle/po/zh-cn/qos-gargoyle.po b/package/jsda/luci-app-qos-gargoyle/po/zh-cn/qos-gargoyle.po new file mode 100755 index 0000000000..4b1d10624a --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/po/zh-cn/qos-gargoyle.po @@ -0,0 +1,334 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8\n" + +msgid "\"Active Congestion Control\" not enabled" +msgstr "“主动拥塞控制”未启用" + +msgid "" +"

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.

" +msgstr "" +"

主动拥塞控制系统(ACC)观察你的下载活动并自动调整你的下载链接限制以保持适" +"当的 QoS 性能。ACC 自动调整 QoS 功能以补偿来自你 ISP 的下载速度变化及来自你网" +"络链接速度的调整需求,使速度最大化。这个控制的有效范围在你上面输入的下载总带" +"宽的 15% 至 100% 之间。

" + +msgid "" +"

While ACC does not adjust your upload link speed you must enable and " +"properly configure your upload QoS for it to function properly.

" +msgstr "" +"

虽然 ACC 不调整你的上传链路速度,但你必须启用并正确配置你的上传 QoS 带宽以" +"使该功能正常工作。

" + +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 端链路饱" +"和时才生效。(PS:WAN 端链路饱和即带宽被占用完)" + +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 "零" diff --git a/package/jsda/luci-app-qos-gargoyle/po/zh-cn/qos_gargoyle.po b/package/jsda/luci-app-qos-gargoyle/po/zh-cn/qos_gargoyle.po new file mode 100755 index 0000000000..4b1d10624a --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/po/zh-cn/qos_gargoyle.po @@ -0,0 +1,334 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8\n" + +msgid "\"Active Congestion Control\" not enabled" +msgstr "“主动拥塞控制”未启用" + +msgid "" +"

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.

" +msgstr "" +"

主动拥塞控制系统(ACC)观察你的下载活动并自动调整你的下载链接限制以保持适" +"当的 QoS 性能。ACC 自动调整 QoS 功能以补偿来自你 ISP 的下载速度变化及来自你网" +"络链接速度的调整需求,使速度最大化。这个控制的有效范围在你上面输入的下载总带" +"宽的 15% 至 100% 之间。

" + +msgid "" +"

While ACC does not adjust your upload link speed you must enable and " +"properly configure your upload QoS for it to function properly.

" +msgstr "" +"

虽然 ACC 不调整你的上传链路速度,但你必须启用并正确配置你的上传 QoS 带宽以" +"使该功能正常工作。

" + +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 端链路饱" +"和时才生效。(PS:WAN 端链路饱和即带宽被占用完)" + +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 "零" diff --git a/package/jsda/luci-app-qos-gargoyle/root/etc/uci-defaults/40_luci-qos-gargoyle b/package/jsda/luci-app-qos-gargoyle/root/etc/uci-defaults/40_luci-qos-gargoyle new file mode 100755 index 0000000000..4f6cc1b41c --- /dev/null +++ b/package/jsda/luci-app-qos-gargoyle/root/etc/uci-defaults/40_luci-qos-gargoyle @@ -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 diff --git a/package/jsda/luci-app-qosv4/Makefile b/package/jsda/luci-app-qosv4/Makefile new file mode 100755 index 0000000000..95e95a09b8 --- /dev/null +++ b/package/jsda/luci-app-qosv4/Makefile @@ -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)) diff --git a/package/jsda/luci-app-qosv4/files/etc/config/qosv4 b/package/jsda/luci-app-qosv4/files/etc/config/qosv4 new file mode 100755 index 0000000000..dcb8800b14 --- /dev/null +++ b/package/jsda/luci-app-qosv4/files/etc/config/qosv4 @@ -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' diff --git a/package/jsda/luci-app-qosv4/files/etc/init.d/qosv4 b/package/jsda/luci-app-qosv4/files/etc/init.d/qosv4 new file mode 100755 index 0000000000..d915be007b --- /dev/null +++ b/package/jsda/luci-app-qosv4/files/etc/init.d/qosv4 @@ -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 +} diff --git a/package/jsda/luci-app-qosv4/files/etc/uci-defaults/luci-qosv4 b/package/jsda/luci-app-qosv4/files/etc/uci-defaults/luci-qosv4 new file mode 100755 index 0000000000..f67dafd868 --- /dev/null +++ b/package/jsda/luci-app-qosv4/files/etc/uci-defaults/luci-qosv4 @@ -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 diff --git a/package/jsda/luci-app-qosv4/files/usr/bin/qosv4 b/package/jsda/luci-app-qosv4/files/usr/bin/qosv4 new file mode 100755 index 0000000000..de7de10bda --- /dev/null +++ b/package/jsda/luci-app-qosv4/files/usr/bin/qosv4 @@ -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 IPDOWNLOADC=$((DOWN*9/10)) +#IPSٿʼIPַ +#IPEٽIPַ +#IPSIPEòҪд2---254ȻűʱȽϳռһЩڴ档 +#Чϣu32 hashv4.0QOS޸İ汾ûӰ졣 +#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/SIPȼ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.90zhoutao0712ŵ +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١ +#ֻ1IPʱ򣬹ر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 + + + + diff --git a/package/jsda/luci-app-qosv4/files/usr/lib/lua/luci/controller/qosv4.lua b/package/jsda/luci-app-qosv4/files/usr/lib/lua/luci/controller/qosv4.lua new file mode 100755 index 0000000000..1294b3d570 --- /dev/null +++ b/package/jsda/luci-app-qosv4/files/usr/lib/lua/luci/controller/qosv4.lua @@ -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 diff --git a/package/jsda/luci-app-qosv4/files/usr/lib/lua/luci/i18n/qosv4.zh-cn.lmo b/package/jsda/luci-app-qosv4/files/usr/lib/lua/luci/i18n/qosv4.zh-cn.lmo new file mode 100755 index 0000000000..9eafe77e93 Binary files /dev/null and b/package/jsda/luci-app-qosv4/files/usr/lib/lua/luci/i18n/qosv4.zh-cn.lmo differ diff --git a/package/jsda/luci-app-qosv4/files/usr/lib/lua/luci/model/cbi/qosv4.lua b/package/jsda/luci-app-qosv4/files/usr/lib/lua/luci/model/cbi/qosv4.lua new file mode 100755 index 0000000000..1ddf1c8102 --- /dev/null +++ b/package/jsda/luci-app-qosv4/files/usr/lib/lua/luci/model/cbi/qosv4.lua @@ -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 + diff --git a/package/jsda/qos-gargoyle/Makefile b/package/jsda/qos-gargoyle/Makefile new file mode 100644 index 0000000000..5ac73beb96 --- /dev/null +++ b/package/jsda/qos-gargoyle/Makefile @@ -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 + +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 + 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)) diff --git a/package/jsda/qos-gargoyle/files/example-qos-config2.conf b/package/jsda/qos-gargoyle/files/example-qos-config2.conf new file mode 100644 index 0000000000..e248b6b255 --- /dev/null +++ b/package/jsda/qos-gargoyle/files/example-qos-config2.conf @@ -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" + diff --git a/package/jsda/qos-gargoyle/files/qos_gargoyle.conf b/package/jsda/qos-gargoyle/files/qos_gargoyle.conf new file mode 100644 index 0000000000..9c281d0b07 --- /dev/null +++ b/package/jsda/qos-gargoyle/files/qos_gargoyle.conf @@ -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' + diff --git a/package/jsda/qos-gargoyle/files/qos_gargoyle.hotplug b/package/jsda/qos-gargoyle/files/qos_gargoyle.hotplug new file mode 100644 index 0000000000..d36b15f870 --- /dev/null +++ b/package/jsda/qos-gargoyle/files/qos_gargoyle.hotplug @@ -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 diff --git a/package/jsda/qos-gargoyle/files/qos_gargoyle.init b/package/jsda/qos-gargoyle/files/qos_gargoyle.init new file mode 100644 index 0000000000..2dc6055d33 --- /dev/null +++ b/package/jsda/qos-gargoyle/files/qos_gargoyle.init @@ -0,0 +1,771 @@ +#!/bin/sh /etc/rc.common +# +# Copyright (c) 2008 Eric Bishop +# Copyright (c) 2016 GuoGuo +# Copyright (c) 2017 Xingwang Liao +# 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 +} diff --git a/package/jsda/qos-gargoyle/patches/001-musl-fixes.patch b/package/jsda/qos-gargoyle/patches/001-musl-fixes.patch new file mode 100644 index 0000000000..1aac736677 --- /dev/null +++ b/package/jsda/qos-gargoyle/patches/001-musl-fixes.patch @@ -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 + #include + #include ++#include ++#include + + #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; diff --git a/package/jsda/qos-gargoyle/src/Makefile b/package/jsda/qos-gargoyle/src/Makefile new file mode 100644 index 0000000000..d69b576521 --- /dev/null +++ b/package/jsda/qos-gargoyle/src/Makefile @@ -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 + diff --git a/package/jsda/qos-gargoyle/src/qosmon.c b/package/jsda/qos-gargoyle/src/qosmon.c new file mode 100644 index 0000000000..38a6131e68 --- /dev/null +++ b/package/jsda/qos-gargoyle/src/qosmon.c @@ -0,0 +1,1246 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ +/* qosmon - An active QoS monitor for gargoyle routers. + * Created By Paul Bixel + * http://www.gargoyle-router.com + * + * Copyright © 2010 by Paul Bixel + * + * 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 . + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "tc_util.h" +#include "tc_common.h" + +#include +#include +#include + +#ifndef ONLYBG +#include +#endif + + +#define MAXPACKET 100 /* max packet size */ +#define BACKGROUND 3 /* Detact and run in the background */ +#define ADDENTITLEMENT 4 + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +//The number of arguments needed for two of our kernel calls changed +//in iproute2 after v2.6.29 (not sure when). We will use the new define +//RTNL_FAMILY_MAX to tell us that we are linking against a version of iproute2 +//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) +#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) +#endif + + +/* use_names is required when linking to tc_util.o */ +bool use_names = false; + + +u_char packet[MAXPACKET]; +int pingflags, options; + +#define DEAMON (pingflags & BACKGROUND) + +struct rtnl_handle rth; + +int s; /* Socket file descriptor */ +struct hostent *hp; /* Pointer to host info */ +struct timezone tz; /* leftover */ + +struct sockaddr_in whereto;/* Who to ping */ +int datalen=64-8; /* How much data */ + +const char usage[] = +"Gargoyle active congestion controller version 2.3\n\n" +"Usage: qosmon [options] pingtime pingtarget bandwidth [pinglimit]\n" +" pingtime - The ping interval the monitor will use when active in ms.\n" +" pingtarget - The URL or IP address of the target host for the monitor.\n" +" bandwidth - The maximum download speed the WAN link will support in kbps.\n" +" pinglimit - Optional pinglimit to use for control, otherwise measured.\n" +" Options:\n" +" -b - Run in the background\n" +" -a - Add entitlement to pinglimt, enable auto ACTIVE/MINRTT mode switching.\n\n" +" SIGUSR1 can be used to reset the link bandwidth at anytime.\n"; + +char *hostname; +char hnamebuf[MAXHOSTNAMELEN]; + +uint16_t ntransmitted = 0; /* sequence # for outbound packets = #sent */ +uint16_t ident; +uint16_t nreceived = 0; /* # of packets we got back */ + +// For our digital filters we use Y = Y(-1) + alpha * (X - Y(-1)) +// where alpha = Sample_Period / (TC + Sample_Period) + +int fil_triptime; //Filter ping times in uS +int alpha; //Actually alpha * 1000 +int period; //PING period In milliseconds +int rawfltime; //Trip time in milliseconds +int rawfltime_max; //The maximum measured ping time we have seen in uS. +char nopingresponse; //Set to true when ping response is dropped. + + +// Struct of data we keep on our classes +struct CLASS_STATS { + int ID; //Class leaf ID + __u64 bytes; //Work bytes last query + u_char rtclass; //True if class is realtime. + u_char backlog; //Number of packets waiting + u_char actflg; //True if class is active. + long int cbw_flt; //Class bandwidth subject to filter. (bps) + long int cbw_flt_rt; //Class realtime bandwidth subject to filter. (bps) + long bwtime; //Timestamp of last byte reading. +}; + +#define STATCNT 30 +struct CLASS_STATS dnstats[STATCNT]; +struct CLASS_STATS *classptr; +u_char classcnt; +u_char errorflg; +u_char firstflg=1; //First pass flag + +u_char DCA; //Number of download classes active +u_char RTDCA; //Number of realtime download classes active +u_char pingon=0; //Set to one when pinger becomes active. +int pinglimit=0; //MinRTT mode ping time. +int pinglimit_cl=0; //Ping limit entered on the commandline. +int plimit; //Currently enforce ping limit + +float BWTC; //Time constant of the bandwidth filter +int DBW_UL; //This the absolute limit of the link passed in as a parameter. +int dbw_ul; //This is the last value of the limit sent to the kernel. +int new_dbw_ul; //The new link limit proposed by the state machine. +int saved_active_limit; //The new link limit last known to work with active mode. +int saved_realtime_limit;//The new link limit last known to work with realtime mode. +long int dbw_fil; //Filtered total download load (bps). + +#define QMON_CHK 0 +#define QMON_INIT 1 +#define QMON_ACTIVE 2 +#define QMON_REALTIME 3 +#define QMON_IDLE 4 +#define QMON_EXIT 5 +char *statename[]= {"CHECK","INIT","ACTIVE","MINRTT","IDLE","DISABLED"}; +unsigned char qstate=QMON_CHK; + +u_short cnt_mismatch=0; +u_short cnt_errorflg=0; +u_short last_errorflg=0; + +FILE *statusfd; //Filestream for updating our status to. +char sigterm=0; //Set when we get a signal to terminal +int sel_err=0; //Last error code returned by select + +#define DAEMON_NAME "qosmon" + +#ifndef DEVICE +#define DEVICE "imq0" +#endif + +#define EXIT_SUCCESS 0 +#define EXIT_FAILURE 1 + +/* In a world were size is everything we can avoid linking to libm + if we can come up with replacements for rint() and ceil(). This will + save around 64k of RAM. + + rint() is used in tc_util.c in five places. By code inspection I can see that + the parameter x will always be less than or equal to LONG_MAX so the following + simplified rint() will work for us. +*/ +double rint(double x) +{ + long i; + if (x > LONG_MAX) i = LONG_MAX; else i = x+.5; + return i; +} + +/* ceil() is used in q_hfsc.c in three places. There I can see that x is always + positive and less than LONG_MAX. This leads to a much simplified routine. +*/ +double ceil(double x) +{ + long i; + + if (x > LONG_MAX) x = LONG_MAX; + i = x; + if ((double)i != x) i++; + return i; +} + + + +/* + * F I N I S H + * + * Sets a global to cause the main loop to terminate. + */ +void finish(int parm) +{ + sigterm=parm; +} + +/* + * I N _ C K S U M + * + * Checksum routine for Internet Protocol family headers (C Version) + * + */ +int in_cksum(u_short *addr, int len) +{ + int nleft = len; + u_short *w = addr; + u_short answer; + int sum = 0; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), + * we add sequential 16 bit words to it, and at the end, fold + * back all the carry bits from the top 16 bits into the lower + * 16 bits. + */ + while( nleft > 1 ) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if( nleft == 1 ) { + u_short u = 0; + + *(u_char *)(&u) = *(u_char *)w ; + sum += u; + } + + /* + * add back carry outs from top 16 bits to low 16 bits + */ + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* truncate to 16 bits */ + return (answer); +} + +/* + * P I N G E R + * + * Compose and transmit an ICMP ECHO REQUEST packet. The IP packet + * will be added on by the kernel. The ID field is our UNIX process ID, + * and the sequence number is an ascending integer. The first 8 bytes + * of the data portion are used to hold a UNIX "timeval" struct in VAX + * byte-order, to compute the round-trip time. + */ +void pinger(void) +{ + static u_char outpack[MAXPACKET]; + struct icmp *icp = (struct icmp *) outpack; + int i, cc; + struct timeval *tp = (struct timeval *) &outpack[8]; + u_char *datap = &outpack[8+sizeof(struct timeval)]; + + icp->icmp_type = ICMP_ECHO; + icp->icmp_code = 0; + icp->icmp_cksum = 0; + icp->icmp_seq = ++ntransmitted; + icp->icmp_id = ident; /* ID */ + + cc = datalen+8; /* skips ICMP portion */ + + gettimeofday( tp, &tz ); + + for( i=8; iicmp_cksum = in_cksum( (u_short *) icp, cc ); + + /* cc = sendto(s, msg, len, flags, to, tolen) */ + i = sendto( s, outpack, cc, 0, (const struct sockaddr *) &whereto, sizeof(whereto) ); + +} + + +/* + * T V S U B + * + * Subtract 2 timeval structs: out = out - in. + * + * Out is assumed to be >= in. + */ +void tvsub(register struct timeval *out, register struct timeval *in) +{ + if( (out->tv_usec -= in->tv_usec) < 0 ) { + out->tv_sec--; + out->tv_usec += 1000000; + } + out->tv_sec -= in->tv_sec; +} + +/* + * P R _ P A C K + * + * Print out the packet, if it came from us. This logic is necessary + * because ALL readers of the ICMP socket get a copy of ALL ICMP packets + * which arrive ('tis only fair). This permits multiple copies of this + * program to be run without having intermingled output (or statistics!). + */ +char pr_pack( void *buf, int cc, struct sockaddr_in *from ) +{ + struct ip *ip; + struct icmp *icp; + struct timeval tv; + struct timeval *tp; + int hlen,triptime; + struct in_addr tip; + + from->sin_addr.s_addr = ntohl( from->sin_addr.s_addr ); + gettimeofday( &tv, &tz ); + + ip = (struct ip *) buf; + hlen = ip->ip_hl << 2; + if (cc < hlen + ICMP_MINLEN) { + tip.s_addr = ntohl(*(uint32_t *) &from->sin_addr); + return 0; + } + + icp = (struct icmp *)(buf + hlen); + if( icp->icmp_type != ICMP_ECHOREPLY ) { + tip.s_addr = ntohl(*(uint32_t *) &from->sin_addr); + return 0; + } + + if( icp->icmp_id != ident ) + return 0; /* 'Twas not our ECHO */ + + nreceived++; + + //If it was not the packet we are looking for return now. + if (icp->icmp_seq != ntransmitted) return 0; + + tp = (struct timeval *)&icp->icmp_data[0]; + tvsub( &tv, tp ); + triptime = tv.tv_sec*1000+(tv.tv_usec/1000); + + //We are now ready to update the filtered round trip time. + //Check for some possible errors first. + if (triptime > period) triptime = period; + + //If this was the most recent one we sent then update the rawfltime. + rawfltime=triptime; + + //Is this a new maximum? + if (rawfltime > rawfltime_max/1000) rawfltime_max = rawfltime*1000; + + //return 1 if we got a valid time. + return 1; + +} + +//These variables referenced but not used by the tc code we link to. +int filter_ifindex; +int use_iec = 0; +int resolve_hosts = 0; + + +int print_class(const struct sockaddr_nl *who, + struct nlmsghdr *n, void *arg) +{ + struct tcmsg *t = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr * tb[TCA_MAX+1]; + int leafid; + u_char actflg=0; + unsigned long long work=0; + struct timespec newtime; + + if (n->nlmsg_type != RTM_NEWTCLASS && n->nlmsg_type != RTM_DELTCLASS) { + fprintf(stderr, "Not a class\n"); + return 0; + } + len -= NLMSG_LENGTH(sizeof(*t)); + if (len < 0) { + fprintf(stderr, "Wrong len %d\n", len); + return -1; + } + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, TCA_MAX, TCA_RTA(t), len); + clock_gettime(CLOCK_MONOTONIC,&newtime); + + if (tb[TCA_KIND] == NULL) { + fprintf(stderr, "print_class: NULL kind\n"); + return -1; + } + + if (n->nlmsg_type == RTM_DELTCLASS) return 0; + + //We only deal with hfsc classes. + if (strcmp((char*)RTA_DATA(tb[TCA_KIND]),"hfsc")) return 0; + + //Reject the root node + if (t->tcm_parent == TC_H_ROOT) return 0; + + //A previous error backs us out. + if (errorflg) return 0; + + //If something has changed about the class structure or we reached the + //end of the array we need to reset and back out. + if (classcnt >= STATCNT) { + errorflg=1; + return 0; + } + + //Get the leafid or set to -1 if parent. + if (t->tcm_info) leafid = t->tcm_info>>16; + else leafid = -1; + + //If this is not the first pass and the leafid does not + //match then the class list changed so backout. + if ((!firstflg) && (leafid != classptr->ID) ) { + errorflg=1; + return 0; + } + + //First time through so record the ID. + if (firstflg) { + classptr->ID = leafid; + } + + //Pickup some hfsc basic stats + if (tb[TCA_STATS2]) { + + struct tc_stats st; + + /* handle case where kernel returns more/less than we know about */ + memset(&st, 0, sizeof(st)); + memcpy(&st, RTA_DATA(tb[TCA_STATS]), MIN(RTA_PAYLOAD(tb[TCA_STATS]), sizeof(st))); + work = st.bytes; + classptr->backlog = st.qlen; + + /*Checkout if this class will trigger realtime mode by looking to see if either + the realtime or fair service curves are two part. */ + if (firstflg) { + + struct tc_service_curve *sc = NULL; + struct rtattr *tbs[TCA_STATS_MAX + 1]; + + classptr->rtclass=0; + parse_rtattr_nested(tbs, TCA_HFSC_MAX, tb[TCA_OPTIONS]); + if (tbs[TCA_HFSC_RSC] && (RTA_PAYLOAD(tbs[TCA_HFSC_RSC]) >= sizeof(*sc))) { + sc = RTA_DATA(tbs[TCA_HFSC_RSC]); + classptr->rtclass |= (sc && sc->m1); + } + + if (tbs[TCA_HFSC_FSC] && (RTA_PAYLOAD(tbs[TCA_HFSC_FSC]) >= sizeof(*sc))) { + sc = RTA_DATA(tbs[TCA_HFSC_FSC]); + classptr->rtclass |= (sc && sc->m1); + } + + } + + } else { + errorflg=1; + return 0; + } + + + //Avoid a big jolt on the first pass. + if (firstflg) { + classptr->bytes = work; + } + + //Update the filtered bandwidth based on what happened unless a rollover occured. + if (work >= classptr->bytes) { + long int bw; + long bperiod; + + //Calculate an accurate time period for the bps calculation. + bperiod=(newtime.tv_nsec-classptr->bwtime)/1000000; + if (bperiodbytes)*8000/bperiod; //bps per second x 1000 here + + //Convert back to bps as part of the filter calculation + classptr->cbw_flt=(bw-classptr->cbw_flt)*BWTC/1000+classptr->cbw_flt; + + //A class is considered active if its BW exceeds 4000bps + if ((leafid != -1) && (classptr->cbw_flt > 4000)) { + DCA++;actflg=1; + if (classptr->rtclass) RTDCA++; + } + + //Calculate the total link load by adding up all the classes. + if (leafid == -1) { + dbw_fil = 0; + } else { + dbw_fil += classptr->cbw_flt; + } + + } + + classptr->bwtime=newtime.tv_nsec; + classptr->bytes = work; + classptr->actflg = actflg; + + classptr++; + classcnt++; + return 0; +} + +/*Gather stats for classes attached to device d */ +int class_list(char *d) +{ + struct tcmsg t; + + RTDCA=DCA =0; + memset(&t, 0, sizeof(t)); + t.tcm_family = AF_UNSPEC; + + ll_init_map(&rth); + + if (d[0]) { + if ((t.tcm_ifindex = ll_name_to_index(d)) == 0) { + fprintf(stderr, "Cannot find device \"%s\"\n", d); + return 1; + } + filter_ifindex = t.tcm_ifindex; + } + + if (rtnl_dump_request(&rth, RTM_GETTCLASS, &t, sizeof(t)) < 0) { + perror("Cannot send dump request"); + return 1; + } + + if (dump_filter(&rth, print_class, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + return 1; + } + + return 0; +} + + +/* + * tc_class_modify + * + * This function changes the upper limit rate of the 'DEVICE' class to match + * the rate passed in as the sole parameter. This is the throttle means + * we will use to maintian the QoS performance as the link becomes saturated. + * + * The structure of this code is gleaned from the source code of 'tc' and is + * specific the the gargoyle QoS design. + */ +int tc_class_modify(__u32 rate) +{ + struct { + struct nlmsghdr n; + struct tcmsg t; + char buf[4096]; + } req; + + char k[16]; + __u32 handle; + + if (dbw_ul == rate) return 0; + dbw_ul=rate; + + memset(&req, 0, sizeof(req)); + memset(k, 0, sizeof(k)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = RTM_NEWTCLASS; + req.t.tcm_family = AF_UNSPEC; + + //We are only going to modify the upper limit rate of the parent class. + if (get_tc_classid(&handle, "1:1")) { + fprintf(stderr,"invalid class ID"); + return 1; + } + req.t.tcm_handle = handle; + + if (get_tc_classid(&handle, "1:0")) { + fprintf(stderr,"invalid parent ID"); + return 1; + } + req.t.tcm_parent = handle; + + strcpy(k,"hsfc"); + addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1); + + { + struct tc_service_curve usc; + struct rtattr *tail; + + memset(&usc, 0, sizeof(usc)); + + usc.m2 = rate/8; + + tail = NLMSG_TAIL(&req.n); + + addattr_l(&req.n, 1024, TCA_OPTIONS, NULL, 0); + addattr_l(&req.n, 1024, TCA_HFSC_USC, &usc, sizeof(usc)); + + tail->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail; + } + + + //Communicate our change to the kernel. + ll_init_map(&rth); + + if ((req.t.tcm_ifindex = ll_name_to_index(DEVICE)) == 0) { + fprintf(stderr, "Cannot find device %s\n",DEVICE); + return 1; + } + + + if (talk(&rth, &req.n, 0, 0, NULL) < 0) + return 2; + + return 0; +} + +/* + This function is periodically called and updates the + status file for the deamon. The status file can then + be viewed by other processes to tell what is going on. +*/ +void update_status( FILE* fd ) +{ + + struct CLASS_STATS *cptr=dnstats; + u_char i; + char nstr[10]; + int dbw; + + //Link load includes the ping traffic when the pinger is on. + if (pingon) dbw = dbw_fil + 64 * 8 * 1000/period; + else dbw = dbw_fil; + + //Update the status file. + rewind(fd); + fprintf(fd,"State: %s\n",statename[qstate]); + fprintf(fd,"Link limit: %d (kbps)\n",dbw_ul/1000); + fprintf(fd,"Fair Link limit: %d (kbps)\n",new_dbw_ul/1000); + fprintf(fd,"Link load: %d (kbps)\n",dbw/1000); + + if (pingon) { + if (nopingresponse) fprintf(fd,"Ping: Dropped, assume %d mS\n",rawfltime_max/1000); + else fprintf(fd,"Ping: %d (ms)\n",rawfltime); + } + else + fprintf(fd,"Ping: off\n"); + + fprintf(fd,"Filtered/Max recent RTT: %d/%d (ms)\n",fil_triptime/1000,rawfltime_max/1000); + fprintf(fd,"RTT time limit: %d (ms) [%d/%d]\n",plimit/1000,pinglimit/1000,(pinglimit+135*pinglimit_cl/100)/1000); + fprintf(fd,"Classes Active: %u\n",DCA); + + fprintf(fd,"Errors: (mismatch,errors,last err,selerr): %u,%u,%u,%i\n", cnt_mismatch, cnt_errorflg,last_errorflg,sel_err); + + + i=0; + while ((i++ID != 0)) { + fprintf(fd,"ID %4X, Active %u, Backlog %u, BW bps (filtered): %ld\n", + (short unsigned) cptr->ID, + cptr->actflg, + cptr->backlog, + cptr->cbw_flt); + cptr++; + } + + fflush(fd); + +#ifndef ONLYBG + if (DEAMON) return; + + //Home the cursor + mvprintw(0,0,""); + printw("\nqosmon status\n"); + + if (pingon) { + sprintf(nstr,"%d",rawfltime); + } else { + strcpy(nstr,"*"); + } + + printw("ping (%s/%d) DCA=%d, RTDCA=%d, plim=%d, plim2=%d, state=%s\n",nstr,fil_triptime/1000, + DCA,RTDCA,pinglimit/1000,plimit/1000,statename[qstate]); + printw("Link Limit=%6d, Fair Limit=%6d, Current Load=%6d (kbps)\n", + dbw_ul/1000,new_dbw_ul/1000,dbw/1000); + printw("Saved Active Limit=%6d, Saved Realtime Limit=%6d\n",saved_active_limit/1000,saved_realtime_limit/1000); + printw("pings sent=%d, pings received=%d\n", + ntransmitted,nreceived); + + printw("Defined classes for %s\n",DEVICE); + printw("Errors: (mismatches,errors,last err,selerr): %u,%u,%u,%i\n", cnt_mismatch, cnt_errorflg,last_errorflg,sel_err); + cptr=dnstats; + i=0; + while ((i++ID != 0)) { + printw("ID %4X, Active %u, Realtime %u. Backlog %u, BW (filtered kbps): %ld\n", + (short unsigned) cptr->ID, + cptr->actflg, + cptr->rtclass, + cptr->backlog, + cptr->cbw_flt/1000); + cptr++; + } + + refresh(); +#endif + +} + +/* v2.3 feature allows reseting the link limit to the initial value by sending the process SIGUSR1. */ +sig_atomic_t resetbw=1; +void resetsig(int parm) +{ + resetbw=1; +} + + +/* + * M A I N + */ +int main(int argc, char *argv[]) +{ + struct sockaddr_in from; + char **av = argv; + struct sockaddr_in *to = &whereto; + int on = 1; + struct protoent *proto; + float err; + + + argc--, av++; + while (argc > 0 && *av[0] == '-') { + while (*++av[0]) switch (*av[0]) { + case 'b': + pingflags |= BACKGROUND; + break; + + case 'a': + pingflags |= ADDENTITLEMENT; + break; + } + argc--, av++; + } + if ((argc < 3) || (argc >4)) { + printf(usage); + exit(1); + } + +#ifdef ONLYBG + if (!(pingflags & BACKGROUND)) { + fprintf(stderr, "Must use the -b switch\n"); + exit(1); + } +#endif + + //The first parameter is the ping time in ms. + period = atoi( av[0] ); + if ((period > 2000) || (period < 100)) { + fprintf(stderr, "Invalid ping interval '%s'\n", av[0]); + exit(1); + } + + bzero((char *)&whereto, sizeof(whereto) ); + to->sin_family = AF_INET; + to->sin_addr.s_addr = inet_addr(av[1]); + if(to->sin_addr.s_addr != (unsigned)-1) { + strcpy(hnamebuf, av[1]); + hostname = hnamebuf; + } else { + hp = gethostbyname(av[1]); + if (hp) { + to->sin_family = hp->h_addrtype; + bcopy(hp->h_addr, (caddr_t)&to->sin_addr, hp->h_length); + hostname = hp->h_name; + } else { + fprintf(stderr, "%s: unknown host %s\n", argv[1], av[1]); + exit(1); + } + } + + //The third parameter is the maximum download speed in kbps. + DBW_UL = atoi( av[2] ); + if ((DBW_UL < 100) || (DBW_UL >= INT_MAX/1000)) { + fprintf(stderr, "Invalid download bandwidth '%s'\n", av[2]); + exit(1); + } + + //Convert kbps to bps. + dbw_ul = DBW_UL = DBW_UL*1000; + + //The fourth optional parameter is the ping limit in ms. + if (argc == 4) { + pinglimit_cl = pinglimit = atoi( av[3] )*1000; + } + + + ident = getpid() & 0xFFFF; + + if ((proto = getprotobyname("icmp")) == NULL) { + fprintf(stderr, "icmp: unknown protocol\n"); + exit(10); + } + + // where alpha = Sample_Period / (TC + Sample_Period) + // TC needs to be not less than 3 times the sample period + alpha = (period*1000. / (period*4 + period)); + + //Class bandwidth filter time constants + BWTC= (period*1000. / (7500. + period)); + + //Check that we have access to tc functions. + tc_core_init(); + if (rtnl_open(&rth, 0) < 0) { + fprintf(stderr, "Cannot open rtnetlink\n"); + exit(1); + } + + //Make sure the device is present and that we can scan it. + classptr=dnstats; + errorflg=0; + class_list(DEVICE); + if (errorflg) { + fprintf(stderr, "Cannot scan ingress device %s\n",DEVICE); + exit(1); + } + + //If running in the background fork() + if (DEAMON) { + + /* Ignore most signals in background */ + signal( SIGINT, SIG_IGN ); + signal( SIGQUIT, SIG_IGN ); + signal( SIGCHLD, SIG_IGN ); + signal( SIGALRM, SIG_IGN ); + signal( SIGUSR1, SIG_IGN ); + signal( SIGUSR2, SIG_IGN ); + signal( SIGHUP, SIG_IGN ); + signal( SIGTSTP, SIG_IGN ); + signal( SIGPIPE, (__sighandler_t) finish ); + signal( SIGSEGV, (__sighandler_t) finish ); + signal( SIGILL, (__sighandler_t) finish ); + signal( SIGFPE, (__sighandler_t) finish ); + signal( SIGSYS, (__sighandler_t) finish ); + signal( SIGURG, (__sighandler_t) finish ); + signal( SIGTTIN, (__sighandler_t) finish ); + signal( SIGTTOU, (__sighandler_t) finish ); + + //daemonize(); + if ( daemon( 0, 0) < 0 ) + { + fprintf(stderr,"deamon() failed with %i\n",errno); + exit( 1 ); + } + + /* Initialize the logging interface */ + openlog( DAEMON_NAME, LOG_PID, LOG_LOCAL5 ); + } + + //SIGTERM is what we expect to kill us. + signal( SIGTERM, (__sighandler_t) finish ); + + //SIGUSR1 resets the link speed. + signal( SIGUSR1, (__sighandler_t) resetsig ); + + //Create the status file and ping socket + //These are called here because the above daemon() call closes + //open files. + statusfd = fopen("/tmp/qosmon.status","w"); + s = socket(AF_INET, SOCK_RAW, proto->p_proto); + + + //Check that things opened correctly. + if (DEAMON) { + if (statusfd == NULL) { + syslog( LOG_CRIT, "Cannot open /tmp/qosmon.status - %i",errno ); + exit(EXIT_FAILURE); + } + + if (s < 0) { + syslog( LOG_CRIT, "Cannot open ping socket - %i",errno ); + exit(EXIT_FAILURE); + } + + syslog(LOG_INFO, "starting socketfd = %i, statusfd = %i",s,fileno(statusfd)); + } + +#ifndef ONLYBG + else { + if (statusfd == NULL) { + fprintf(stderr, "Cannot open /tmp/qosmon.status - %i",errno ); + exit(EXIT_FAILURE); + } + + if (s < 0) { + fprintf( stderr, "Cannot open ping socket - %i",errno ); + exit(EXIT_FAILURE); + } + + //Ctrl-C terminates + signal( SIGINT, (__sighandler_t) finish ); + + //Close terminal terminates + signal( SIGHUP, (__sighandler_t) finish ); + + setlinebuf( stdout ); + + //Bring up ncurses + initscr(); + + } +#endif + + //Clear all initial stats. + memset((void *)&dnstats,0,sizeof(dnstats)); + + //Initialize the max ping to something reasonable. + //We will fix it later. + rawfltime_max = period*1000; + + while (!sigterm) { + int len = sizeof (packet); + socklen_t fromlen = sizeof (from); + int cc; + u_char chill; + struct timeval timeout; + fd_set fdmask; + FD_ZERO(&fdmask); + FD_SET(s , &fdmask); + + //rawfltime variable will be set in pr_pack() if we get a pong that matched + //our ping, but clearing it here will let us know we did not get a response to our + //ping. + rawfltime=0; + + //Send the next ping + if (pingon) pinger(); + + //Wait for the pong(s). + timeout.tv_sec = 0; + timeout.tv_usec = period*1000; + + //Need a loop here to clean out any old pongs that show up. + //select() returns 1 if there is data to read. + // 0 if the time has expired. + // -1 if a signal arrived. + while (sel_err=select(s+1, &fdmask, NULL, NULL, &timeout)) { + + //Signal arrived, just loop and keep waiting. + if (sel_err == -1) continue; + + //If we got here then data must be waiting, try to read the whole packet + if ( (cc=recvfrom(s,packet,len,0,(struct sockaddr *) &from, &fromlen)) < 0) { + continue; + } + + //OK there is a whole packet, get it and record the triptime. + pr_pack( packet, cc, &from ); + + } + + //Gather new statistics + classptr=dnstats; + cc=classcnt; + classcnt=0; + errorflg=0; + class_list(DEVICE); + + //If there was an error or the number of classes changed then reset everything + if (errorflg || (!firstflg && (cc !=classcnt))) { + + if (errorflg) {cnt_errorflg++; last_errorflg=errorflg;} + else if (cc != classcnt) cnt_mismatch++; + + firstflg=1; + pingon=0; + qstate=QMON_CHK; + continue; + } + + + //Initialize or reinitialize the fair linklimit. + if (resetbw) { + saved_realtime_limit=saved_active_limit=new_dbw_ul= DBW_UL * .9; + resetbw=0; + } + + //Look at an ping response time we got. If we did not get any then it most likely + //got dropped so use the maximum value that we have recently seen as we know the downlink + //queue must be at least this long. + if (!rawfltime) { + rawfltime = rawfltime_max/1000; + nopingresponse=1; + } else + nopingresponse=0; + + //Update the filtered ping response time based on what happened. + //If we are not pinging then no change in the filtered value. + if (pingon) + fil_triptime = ((rawfltime*1000 - fil_triptime)*alpha)/1000 + fil_triptime; + + //Run the state machine + switch (qstate) { + + // Wait to see if the ping targer will respond at all before doing anything + case QMON_CHK: + pingon=1; + + //If we get two pings go ahead and lower the link speed. + if (nreceived >= 2) { + + //If the pinglimit was entered on the command line + //without the add flag then go directly to the + //IDLE state otherwise automatically determine an appropriate + //ping limit. + if ((pinglimit) && !(pingflags & ADDENTITLEMENT)) { + dbw_ul=0; //Forces an update in tc_class_modify() + tc_class_modify(new_dbw_ul); + fil_triptime = rawfltime*1000; + qstate=QMON_IDLE; + } else { + tc_class_modify(1000); //Unload the link for the measurement. + nreceived=0; + qstate=QMON_INIT; + } + } + break; + + // Take a measurement of the practical ping time we can expect in an unsaturated + // link. We do this by making pings and using the filter response after + // throttling all traffic in the link. + case QMON_INIT: + //Filter starts at ten seconds and runs until 15 seconds. + //For the first ten seconds we initialize the filter to the last ping time we saw. + //After the seventh second we start filtering. + if (nreceived < (10000/period)+1) fil_triptime = rawfltime*1000; + + //After 15 seconds we have measured our ping response entitlement. + //Move on to the active state. + if (nreceived > (15000/period)+1) { + qstate=QMON_IDLE; + tc_class_modify(new_dbw_ul); //Restore reasonable bandwidth + + //If the user specified no limit then the RTT ping limit is computed from what was + //entered on the command line. + if (pingflags & ADDENTITLEMENT) { + //Add what the user specified to the 110% of the measure ping time. + pinglimit += (fil_triptime*1.1); + } else { + //Without the '-a' flag we just use 200% of measure ping time. + //This works OK in my system but I have no evidence that it will work in other systems. + pinglimit = fil_triptime*2.0; + } + + //Sanity Checks + if (pinglimit < 10000) pinglimit=10000; + if (pinglimit > 800000) pinglimit=800000; + + //Reasonable max ping. + rawfltime_max = 2*pinglimit; + } + break; + + // In the wait state we have a nearly idle link. + // In these cases it is not necessary to monitor delay times so the active + // ping is disabled. + case QMON_IDLE: + pingon=0; + + //This limit should be the same as the dynamic range or we could get stuck + //in the IDLE state. + if (dbw_fil < 0.15 * DBW_UL) break; + + // In the ACTIVE & REALTIME states we observe ping times as long as the + // link remains active. While we are observing we adjust the + // link upper limit speed to maintain the specified pinglimit. + // If the amount of data we are recieving dies down we enter the WAIT state + case QMON_ACTIVE: + case QMON_REALTIME: + pingon=1; + + //Save the bandwidth limit for each mode. + if (qstate == QMON_REALTIME) saved_realtime_limit = new_dbw_ul; + if (qstate == QMON_ACTIVE) saved_active_limit = new_dbw_ul; + + //The pinglimit we will use depends on if any realtime classes are active + //or not. In realtime mode we only allow 'pinglimit' round trip times which + //makes our pings low but also lowers our throughput. The automatic measurement + //above set pinglimit to the average RTT of the ping assuming it has to wait on + //average for 2/3 of an single MTU sized packet to transmit. The means on + //average there is nothing in the buffer but a packet is transmitting. + + //When not in realtime mode the stradegy is that we allow enough packets in the queue + //to fully utilize the downlink. + + //We are talking about a queue controlled by the ISP so we don't know much about it. + //We make an assumption that the queue is long enough to allow full utilization of the link. + //This should be the case and often the queue is much longer than needed (bufferbloat). + //When not in realtime mode we can allow this buffer to fill but we don't want it to overflow + //because it will then drop packets which will cause our QoS to breakdown. So we want it to fill + //just enough to promote full link utilization. + + //The classical optimum queue size would be equal to the bandwidth * RTT and the + //additional time it will take our ping to pass through such a queue turns out to be the RTT. + //But Barman et all, Globecomm2004 indicates that only 20-30% of this is really needed. + // + //When we measured an RTT above that was to the ISPs gateway so we do not really know what the average + //RTT time to other IPs on the internet. And since not all hosts respond the same anyway I doubt there + //is consistant RTT that we could use. + // + //For ACTIVE mode on a 925kbps/450kbps link I measure the following + //relationship between ping limit and throughput with large packets downloading. + // + //Ping Limit Throughput Percent + // 612ms 918kbps 100 + // 525ms 915kbps 99.6 + // 437ms 898kbps 97.8 + // 350ms 875kbps 95.3 + // 262ms 862kbps 93.8 + // 81ms 870kbps 94.7 + // 60ms 680kbps 69.8 + // 50ms 630kbps 68.6 + // 40ms 490kbps 53.3 + // + //The 1500 byte packet time is 1500*10/925kbps download and 1500*10/425kbps upload for a total + //RTT of around 48ms. Idle ping times on this link are around 35ms. + // + //These results indicate that on my link not much is gained by increasing beyond 81ms. This is pretty much + //the MINRTT mode computed with the -a switch. Still other links may be different so I suspect that + //switching to active mode will benefit some people. + // + //The statedgy I will use for the ACTIVE mode limit will be to add an additional 135% packet delay over + //what we have in RTT mode. The packet delay was entered on the command line or zero if nothing was entered. + + //I hope that this will work well for a broad range of users from satellite links with RTTs of 1 second or more + //or users with hot connections that have small queue upstream of them. + + if ((RTDCA == 0) && (pingflags & ADDENTITLEMENT)) { + plimit=135*pinglimit_cl/100+pinglimit; + + //When switching into active mode for the first time initialize the bandwidth + //limit to the last value that was known to work. + if (qstate != QMON_ACTIVE) { + qstate=QMON_ACTIVE; + new_dbw_ul=saved_active_limit; + tc_class_modify(new_dbw_ul); + } + + } else { + plimit = pinglimit; + + //When switching into realtime mode for the first time initialize the bandwidth + //limit to the last value that was known to work. + if (qstate != QMON_REALTIME) { + qstate=QMON_REALTIME; + new_dbw_ul=saved_realtime_limit; + tc_class_modify(new_dbw_ul); + } + + } + + //When the downlink falls below 10% utilization we turn off the pinger. + if (dbw_fil < 0.1 * DBW_UL) qstate=QMON_IDLE; + + //Compute the ping error + err = fil_triptime - plimit; + + //Negative error means we might be able to increase the link limit. + if (err < 0) { + + //Do not increase the bandwidth until we reach 85% of the current limit. + if (dbw_fil < dbw_ul * 0.85) break; + + //Increase slowly (0.4%/sec). err is negative here. + new_dbw_ul = new_dbw_ul * (1.0 - 0.004*err*(float)period/(float)plimit/1000.0); + if (new_dbw_ul > DBW_UL) new_dbw_ul=DBW_UL; + + } else { + //Positive error means we need to decrease the bandwidth. + + new_dbw_ul = new_dbw_ul * (1.0 - 0.004*err*(float)period/(float)plimit/1000.0); + + //Dynamic range is 1/.15 or 6.67 : 1. + if (new_dbw_ul < DBW_UL*.15) new_dbw_ul=DBW_UL*.15; + } + + //Modify parent download limit as needed. + tc_class_modify(new_dbw_ul); + + //Keep downward pressure on rawfltime_max to keep it fresh. + if (rawfltime_max > plimit) rawfltime_max -= 100; + + break; + + + } + + update_status(statusfd); + + //If we get here the first pass is over. + firstflg=0; + + } //Next ping + + + qstate=QMON_EXIT; + + //We got a signal to terminate so start by restoring the root TC class to + //the original upper limit. + tc_class_modify(DBW_UL); + + update_status(statusfd); + + //Write a message in the system log + if (DEAMON) { + syslog( LOG_NOTICE, "terminated sigterm=%i, sel_err=%i", sigterm, sel_err ); + closelog(); + } + +#ifndef ONLYBG + else { + endwin(); + fflush(stdout); + } +#endif + +} + diff --git a/package/kernel/linux/modules/netfilter.mk b/package/kernel/linux/modules/netfilter.mk index d2841a4d19..9a140109c8 100755 --- a/package/kernel/linux/modules/netfilter.mk +++ b/package/kernel/linux/modules/netfilter.mk @@ -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 diff --git a/package/network/utils/iptables/Makefile b/package/network/utils/iptables/Makefile old mode 100755 new mode 100644 index 6c42774ef4..e36292edc0 --- a/package/network/utils/iptables/Makefile +++ b/package/network/utils/iptables/Makefile @@ -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))) diff --git a/package/network/utils/iptables/modules/extensions/libipt_bandwidth.c b/package/network/utils/iptables/modules/extensions/libipt_bandwidth.c new file mode 100644 index 0000000000..32d9fa8aa5 --- /dev/null +++ b/package/network/utils/iptables/modules/extensions/libipt_bandwidth.c @@ -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 + * + * 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 . + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 +#include + +#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); +} diff --git a/package/network/utils/iptables/modules/extensions/libipt_timerange.c b/package/network/utils/iptables/modules/extensions/libipt_timerange.c new file mode 100644 index 0000000000..ba0c33bb6f --- /dev/null +++ b/package/network/utils/iptables/modules/extensions/libipt_timerange.c @@ -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 + * + * 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 . + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * 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 +#include + +#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); + +} diff --git a/package/network/utils/iptables/modules/extensions/libipt_webmon.c b/package/network/utils/iptables/modules/extensions/libipt_webmon.c new file mode 100644 index 0000000000..d636246004 --- /dev/null +++ b/package/network/utils/iptables/modules/extensions/libipt_webmon.c @@ -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 + * + * 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 . + */ + + +#include +#include +#include +#include +#include + +#include + +/* + * 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 +#include + +#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; +} + diff --git a/package/network/utils/iptables/modules/extensions/libipt_weburl.c b/package/network/utils/iptables/modules/extensions/libipt_weburl.c new file mode 100644 index 0000000000..be283a7216 --- /dev/null +++ b/package/network/utils/iptables/modules/extensions/libipt_weburl.c @@ -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 + * + * 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 . + */ + + +#include +#include +#include +#include +#include + + +/* + * 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 +#include + +#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 +} + + diff --git a/package/network/utils/iptables/modules/extensions/libxt_IMQ.c b/package/network/utils/iptables/modules/extensions/libxt_IMQ.c new file mode 100644 index 0000000000..51718d95d7 --- /dev/null +++ b/package/network/utils/iptables/modules/extensions/libxt_IMQ.c @@ -0,0 +1,105 @@ +/* Shared library add-on to iptables to add IMQ target support. */ +#include +#include +#include +#include + +#include +#include +#include + +/* Function which prints out usage message. */ +static void IMQ_help(void) +{ + printf( +"IMQ target options:\n" +" --todev enqueue to imq, 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); +} diff --git a/package/network/utils/iptables/modules/extensions/libxt_IMQ.man b/package/network/utils/iptables/modules/extensions/libxt_IMQ.man new file mode 100644 index 0000000000..6e3e89673a --- /dev/null +++ b/package/network/utils/iptables/modules/extensions/libxt_IMQ.man @@ -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 diff --git a/package/network/utils/iptables/modules/include/linux/netfilter/xt_IMQ.h b/package/network/utils/iptables/modules/include/linux/netfilter/xt_IMQ.h new file mode 100644 index 0000000000..9b072300fd --- /dev/null +++ b/package/network/utils/iptables/modules/include/linux/netfilter/xt_IMQ.h @@ -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 */ + diff --git a/package/network/utils/iptables/modules/include/linux/netfilter_ipv4/ipt_bandwidth.h b/package/network/utils/iptables/modules/include/linux/netfilter_ipv4/ipt_bandwidth.h new file mode 100644 index 0000000000..9a6227a9e3 --- /dev/null +++ b/package/network/utils/iptables/modules/include/linux/netfilter_ipv4/ipt_bandwidth.h @@ -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 + * + * 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 . + */ + +#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*/ diff --git a/package/network/utils/iptables/modules/include/linux/netfilter_ipv4/ipt_timerange.h b/package/network/utils/iptables/modules/include/linux/netfilter_ipv4/ipt_timerange.h new file mode 100644 index 0000000000..4f1e8b66ec --- /dev/null +++ b/package/network/utils/iptables/modules/include/linux/netfilter_ipv4/ipt_timerange.h @@ -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 + * + * 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 . + */ + + + + +#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*/ diff --git a/package/network/utils/iptables/modules/include/linux/netfilter_ipv4/ipt_webmon.h b/package/network/utils/iptables/modules/include/linux/netfilter_ipv4/ipt_webmon.h new file mode 100644 index 0000000000..1a1bd247d4 --- /dev/null +++ b/package/network/utils/iptables/modules/include/linux/netfilter_ipv4/ipt_webmon.h @@ -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 + * + * 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 . + */ + + + + +#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*/ diff --git a/package/network/utils/iptables/modules/include/linux/netfilter_ipv4/ipt_weburl.h b/package/network/utils/iptables/modules/include/linux/netfilter_ipv4/ipt_weburl.h new file mode 100644 index 0000000000..db4c973c31 --- /dev/null +++ b/package/network/utils/iptables/modules/include/linux/netfilter_ipv4/ipt_weburl.h @@ -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 + * + * 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 . + */ + + + + +#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*/