From e385f91ff6b0faeea8a9ef04d306bbb888b42513 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Tue, 9 Jun 2009 12:15:31 +0000 Subject: [PATCH 1/1] applications/luci-splash: add optional QoS rate limiting for splash clients --- .../luci-splash/root/etc/init.d/luci_splash | 187 ++++++++++++++++++--- applications/luci-splash/root/usr/sbin/luci-splash | 68 ++++++-- 2 files changed, 211 insertions(+), 44 deletions(-) diff --git a/applications/luci-splash/root/etc/init.d/luci_splash b/applications/luci-splash/root/etc/init.d/luci_splash index ffcd6f883..582c2e2f9 100755 --- a/applications/luci-splash/root/etc/init.d/luci_splash +++ b/applications/luci-splash/root/etc/init.d/luci_splash @@ -1,6 +1,14 @@ #!/bin/sh /etc/rc.common START=70 EXTRA_COMMANDS=clear_leases +SPLASH_INTERFACES="" +LIMIT_DOWN=0 +LIMIT_DOWN_BURST=0 +LIMIT_UP=0 + +silent() { + "$@" 2>/dev/null +} iface_add() { local cfg="$1" @@ -10,6 +18,9 @@ iface_add() { config_get net "$cfg" network [ -n "$net" ] || return 0 + + config_get ifname "$net" ifname + [ -n "$ifname" ] || return 0 config_get ipaddr "$net" ipaddr [ -n "$ipaddr" ] || return 0 @@ -23,12 +34,22 @@ iface_add() { iptables -t nat -A luci_splash_prerouting -s "$NETWORK/$PREFIX" -p ! tcp -j luci_splash_portal iptables -t nat -A luci_splash_prerouting -s "$NETWORK/$PREFIX" -d ! "$ipaddr" -j luci_splash_portal iptables -t nat -A luci_splash_prerouting -s "$NETWORK/$PREFIX" -d "$ipaddr" -p tcp -m multiport ! --dport 22,80,443 -j luci_splash_portal + + qos_iface_add "$ifname" + + append SPLASH_INTERFACES "$ifname" } iface_del() { config_get zone "$1" zone [ -n "$zone" ] || return 0 + + config_get ifname "$1" ifname + [ -n "$ifname" ] || return 0 + while iptables -t nat -D prerouting_${zone} -j luci_splash_prerouting 2>&-; do :; done + + qos_iface_del "$ifname" } blacklist_add() { @@ -36,8 +57,8 @@ blacklist_add() { config_get mac "$cfg" mac [ -n "$mac" ] && { - iptables -I luci_splash_counter -m mac --mac-source "$mac" -j RETURN - iptables -t nat -I luci_splash_leases -m mac --mac-source "$mac" -j DROP + iptables -t filter -I luci_splash_counter -m mac --mac-source "$mac" -j RETURN + iptables -t nat -I luci_splash_leases -m mac --mac-source "$mac" -j DROP } } @@ -45,19 +66,81 @@ whitelist_add() { local cfg="$1" config_get mac "$cfg" mac + [ -n "$mac" ] && { + iptables -t filter -I luci_splash_counter -m mac --mac-source "$mac" -j RETURN + iptables -t nat -I luci_splash_leases -m mac --mac-source "$mac" -j RETURN + } +} + +lease_add() { + local cfg="$1" + + config_get mac "$cfg" mac config_get ban "$cfg" kicked ban=${ban:+DROP} [ -n "$mac" ] && { - iptables -I luci_splash_counter -m mac --mac-source "$mac" -j RETURN - iptables -t nat -I luci_splash_leases -m mac --mac-source "$mac" -j "${ban:-RETURN}" + local oIFS="$IFS"; IFS=":" + set -- $mac + IFS="$oIFS"; unset oIFS + + local mac_pre="$1$2" + local mac_post="$3$4$5$6" + local handle="$6" + + iptables -t filter -I luci_splash_counter -m mac --mac-source "$mac" -j RETURN + iptables -t mangle -I luci_splash_mark -m mac --mac-source "$mac" -j MARK --set-mark 79 + iptables -t nat -I luci_splash_leases -m mac --mac-source "$mac" -j "${ban:-RETURN}" + + for i in $SPLASH_INTERFACES; do + tc filter add dev $i parent 77:0 protocol ip prio 2 handle ::$handle u32 \ + match u16 0x0800 0xFFFF at -2 match u32 0x$mac_post 0xFFFFFFFF at -12 \ + match u16 0x$mac_pre 0xFFFF at -14 flowid 77:10 + done } } -boot() { - ### We are started by the firewall include +qos_iface_add() { + local iface="$1" + + # 77 -> download root qdisc + # 78 -> upload root qdisc + # 79 -> fwmark + + tc qdisc del dev "$iface" root handle 77: + + if [ "$LIMIT_UP" -gt 0 -a "$LIMIT_DOWN" -gt 0 ]; then + tc qdisc add dev "$iface" root handle 77: htb + + # assume maximum rate of 20.000 kilobit for wlan + tc class add dev "$iface" parent 77: classid 77:1 htb rate 20000kbit + + # set download limit and burst + tc class add dev "$iface" parent 77:1 classid 77:10 htb \ + rate ${LIMIT_DOWN}kbit ceil ${LIMIT_DOWN_BURST}kbit prio 2 + + tc qdisc add dev "$iface" parent 77:10 handle 78: sfq perturb 10 + + # adding ingress can result in "File exists" if qos-scripts are active + silent tc qdisc add dev "$iface" ingress + # set client upload speed + tc filter add dev "$iface" parent ffff: protocol ip prio 1 \ + handle 79 fw police rate ${LIMIT_UP}kbit mtu 6k burst 6k drop + fi +} + +qos_iface_del() { + local iface="$1" + + tc qdisc del dev "$iface" root handle 77: + tc qdisc del dev "$iface" root handle 78: + tc filter del dev "$iface" parent ffff: protocol ip prio 1 handle 79 fw +} + +boot() { + ### Setup splash-relay uci get lucid.splashr || { uci batch <> /etc/crontabs/root } + + ### Start the splash httpd + start-stop-daemon -S -m -p /var/run/luci-splashd.pid -b -q -x /usr/bin/luci-splashd } stop() { ### Clear interface rules config_load luci_splash config_foreach iface_del iface - iptables -D INPUT -j luci_splash_counter - iptables -D FORWARD -j luci_splash_counter + silent iptables -t filter -D INPUT -j luci_splash_counter + silent iptables -t filter -D FORWARD -j luci_splash_counter + silent iptables -t mangle -D PREROUTING -j luci_splash_mark + ### Clear subchains - iptables -t nat -F luci_splash_leases - iptables -t nat -F luci_splash_portal - iptables -t nat -F luci_splash_prerouting - iptables -F luci_splash_counter - + silent iptables -t nat -F luci_splash_leases + silent iptables -t nat -F luci_splash_portal + silent iptables -t nat -F luci_splash_prerouting + silent iptables -t filter -F luci_splash_counter + silent iptables -t mangle -F luci_splash_mark + ### Delete subchains - iptables -t nat -X luci_splash_leases - iptables -t nat -X luci_splash_portal - iptables -t nat -X luci_splash_prerouting - iptables -X luci_splash_counter + silent iptables -t nat -X luci_splash_leases + silent iptables -t nat -X luci_splash_portal + silent iptables -t nat -X luci_splash_prerouting + silent iptables -t filter -X luci_splash_counter + silent iptables -t mangle -X luci_splash_mark + + ### Stop the splash httpd + start-stop-daemon -K -p /var/run/luci-splashd.pid -s KILL -q sed -ie '/\/usr\/sbin\/luci-splash sync/d' /var/spool/cron/crontabs/root } diff --git a/applications/luci-splash/root/usr/sbin/luci-splash b/applications/luci-splash/root/usr/sbin/luci-splash index 3f6356a9b..e2bcc557d 100755 --- a/applications/luci-splash/root/usr/sbin/luci-splash +++ b/applications/luci-splash/root/usr/sbin/luci-splash @@ -8,11 +8,25 @@ require("luci.sys.iptparser") local uci = luci.model.uci.cursor_state() local ipt = luci.sys.iptparser.IptParser() +local splash_interfaces = { } +local limit_up = 0 +local limit_down = 0 +local limit_down_burst = 0 function main(argv) local cmd = argv[1] local arg = argv[2] + + limit_up = tonumber(uci:get("luci_splash", "general", "limit_up") or 0) + limit_down = tonumber(uci:get("luci_splash", "general", "limit_down") or 0) + limit_down_burst = tonumber(uci:get("luci_splash", "general", "limit_down_burst") or limit_down * 2) + uci:foreach("luci_splash", "iface", function(s) + if s.network then + splash_interfaces[#splash_interfaces+1] = uci:get("network", s.network, "ifname") + end + end) + if cmd == "status" and arg then if islisted("whitelist", arg) then print("whitelisted") @@ -75,29 +89,51 @@ end -- Add an iptables rule function add_rule(mac) - os.execute("iptables -I luci_splash_counter -m mac --mac-source '"..mac.."' -j RETURN") - return os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source '"..mac.."' -j RETURN") + local a, b, c, d, e, f = mac:match("(%w+):(%w+):(%w+):(%w+):(%w+):(%w+)") + local mac_pre = "%s%s" %{ a, b } + local mac_post = "%s%s%s%s" %{ c, d, e, f } + local handle = f + + if limit_up > 0 and limit_down > 0 then + os.execute("iptables -t mangle -I luci_splash_mark -m mac --mac-source %q -j MARK --set-mark 79" % mac) + + for _, i in ipairs(splash_interfaces) do + os.execute("tc filter add dev %q parent 77:0 protocol ip prio 2 " % i .. + "handle ::%q u32 " % handle .. + "match u16 0x0800 0xFFFF at -2 match u32 0x%q 0xFFFFFFFF at -12 " % mac_post .. + "match u16 0x%q 0xFFFF at -14 flowid 77:10" % mac_pre) + end + end + + os.execute("iptables -t filter -I luci_splash_counter -m mac --mac-source %q -j RETURN" % mac) + return os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac) end -- Remove an iptables rule function remove_rule(mac) - for _, r in ipairs(ipt:find({table="filter", chain="luci_splash_counter"})) do - if r.options and #r.options >= 2 and r.options[1] == "MAC" and - r.options[2]:lower() == mac:lower() - then - os.execute("iptables -D luci_splash_counter -m mac --mac-source %q -j %s" - %{ mac, r.target }) + local handle = mac:match("%w+:%w+:%w+:%w+:%w+:(%w+)") + + local function ipt_delete_foreach(args) + for _, r in ipairs(ipt:find(args)) do + if r.options and #r.options >= 2 and r.options[1] == "MAC" and + r.options[2]:lower() == mac:lower() + then + os.execute("iptables -t %q -D %q -m mac --mac-source %q %s 2>/dev/null" + %{ r.table, r.chain, mac, + r.target == "MARK" and "-j MARK --set-mark 79" or + r.target and "-j %q" % r.target or "" }) + end end end - for _, r in ipairs(ipt:find({table="nat", chain="luci_splash_leases"})) do - if r.options and #r.options >= 2 and r.options[1] == "MAC" and - r.options[2]:lower() == mac:lower() - then - os.execute("iptables -t nat -D luci_splash_leases -m mac --mac-source %q -j %s" - %{ mac, r.target }) - end + ipt_delete_foreach({table="filter", chain="luci_splash_counter"}) + ipt_delete_foreach({table="mangle", chain="luci_splash_mark"}) + ipt_delete_foreach({table="nat", chain="luci_splash_leases"}) + + for _, i in ipairs(splash_interfaces) do + os.execute("tc filter del dev %q parent 77:0 protocol ip prio 2 " % i .. + "handle 800::%q u32 2>/dev/null" % handle) end ipt:resync() @@ -169,7 +205,7 @@ function sync() if v[".type"] == "lease" then if os.difftime(time, tonumber(v.start)) > leasetime then -- Remove expired - remove_rule(v.mac) + remove_rule(v.mac, v.kicked) else -- Rewrite state uci:section("luci_splash", "lease", nil, { -- 2.11.0