#!/bin/sh /etc/rc.common
+
START=70
EXTRA_COMMANDS=clear_leases
-SPLASH_INTERFACES=""
LIMIT_DOWN=0
LIMIT_DOWN_BURST=0
LIMIT_UP=0
+IPT_REPLAY=/var/run/luci_splash.iptlog
+LOCK=/var/run/luci_splash.lock
+
+include /lib/network
+scan_interfaces
+config_load luci_splash
+
+set -x
+
silent() {
"$@" 2>/dev/null
}
+ipt_log() {
+ iptables -I "$@"
+ echo iptables -D "$@" >> $IPT_REPLAY
+}
+
iface_add() {
local cfg="$1"
eval "$(ipcalc.sh $ipaddr $netmask)"
- iptables -t nat -A prerouting_${zone} -j luci_splash_prerouting
- iptables -t nat -A luci_splash_prerouting -j luci_splash_portal
-
- iptables -t filter -I luci_splash_filter -s ! "$NETWORK/$PREFIX" -j RETURN
- iptables -t nat -I luci_splash_leases -s ! "$NETWORK/$PREFIX" -j RETURN
+ ### Add interface specific chain entry rules
+ ipt_log "zone_${zone}_prerouting" -i "${ifname%:*}" -s "$NETWORK/$PREFIX" -j luci_splash_prerouting -t nat
+ ipt_log "zone_${zone}_forward" -i "${ifname%:*}" -s "$NETWORK/$PREFIX" -j luci_splash_forwarding -t filter
- iptables -t filter -I luci_splash_filter -s "$NETWORK/$PREFIX" -d "$ipaddr/${netmask:-32}" -j RETURN
- iptables -t nat -I luci_splash_leases -s "$NETWORK/$PREFIX" -d "$ipaddr/${netmask:-32}" -j RETURN
+ ### Allow traffic to the same subnet
+ iptables -t nat -I luci_splash_prerouting -d "$ipaddr/${netmask:-32}" -j RETURN
+ iptables -t filter -I luci_splash_forwarding -d "$ipaddr/${netmask:-32}" -j RETURN
+ ### Allow traffic to the mesh subnet
[ "$parentproto" = "static" -a -n "$parentipaddr" ] && {
- iptables -t filter -I luci_splash_filter -s "$NETWORK/$PREFIX" -d "$parentipaddr/${parentnetmask:-32}" -j RETURN
- iptables -t nat -I luci_splash_leases -s "$NETWORK/$PREFIX" -d "$parentipaddr/${parentnetmask:-32}" -j RETURN
+ iptables -t nat -I luci_splash_prerouting -d "$parentipaddr/${parentnetmask:-32}" -j RETURN
+ iptables -t filter -I luci_splash_forwarding -d "$parentipaddr/${parentnetmask:-32}" -j RETURN
}
- iptables -t filter -A luci_splash_filter -s "$NETWORK/$PREFIX" -p udp --dport 53 -j RETURN
- iptables -t filter -A luci_splash_filter -s "$NETWORK/$PREFIX" -p tcp --dport 22 -j RETURN # XXX: ssh really needed?
- iptables -t filter -A luci_splash_filter -s "$NETWORK/$PREFIX" -p tcp --dport 80 -j RETURN
- iptables -t filter -A luci_splash_filter -s "$NETWORK/$PREFIX" -j REJECT --reject-with icmp-admin-prohibited
-
qos_iface_add "$ifname"
-
- append SPLASH_INTERFACES "$ifname"
}
iface_del() {
config_get zone "$1" zone
[ -n "$zone" ] || return 0
- while iptables -t nat -D prerouting_${zone} -j luci_splash_prerouting 2>&-; do :; done
-
config_get net "$1" network
[ -n "$net" ] || return 0
config_get ifname "$net" ifname
[ -n "$ifname" ] || return 0
- qos_iface_del "$ifname"
-}
-
-blacklist_add() {
- local cfg="$1"
-
- config_get mac "$cfg" mac
- [ -n "$mac" ] && {
- iptables -t filter -I luci_splash_filter -m mac --mac-source "$mac" -j DROP
- iptables -t nat -I luci_splash_leases -m mac --mac-source "$mac" -j DROP
+ # Clear interface specific rules
+ [ -s $IPT_REPLAY ] && {
+ grep -- "-i ${ifname%:*}" $IPT_REPLAY | while read ln; do silent $ln; done
+ sed -ie "/-i ${ifname%:*}/d" $IPT_REPLAY
}
-}
-whitelist_add() {
- local cfg="$1"
-
- config_get mac "$cfg" mac
- [ -n "$mac" ] && {
- iptables -t filter -I luci_splash_filter -m mac --mac-source "$mac" -j RETURN
- iptables -t nat -I luci_splash_leases -m mac --mac-source "$mac" -j RETURN
- }
+ qos_iface_del "$ifname"
}
-lease_add() {
- local cfg="$1"
-
- config_get mac "$cfg" mac
- config_get ban "$cfg" kicked
-
- ban=${ban:+DROP}
-
- [ -n "$mac" ] && {
- 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_filter -m mac --mac-source "$mac" -j RETURN
- iptables -t nat -I luci_splash_leases -m mac --mac-source "$mac" -j "${ban:-RETURN}"
-
- [ "$LIMIT_UP" -gt 0 -a "$LIMIT_DOWN" -gt 0 ] && {
- iptables -t mangle -I luci_splash_mark -m mac --mac-source "$mac" -j MARK --set-mark 79
-
- 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
- }
- }
+mac_add() {
+ config_get mac "$1" mac
+ append MACS "$mac"
}
subnet_add() {
config_get netmask "$cfg" netmask
[ -n "$ipaddr" ] && {
- iptables -t filter -I luci_splash_filter -d "$ipaddr/${netmask:-32}" -j RETURN
- iptables -t nat -I luci_splash_portal -d "$ipaddr/${netmask:-32}" -j RETURN
+ iptables -t nat -I luci_splash_prerouting -d "$ipaddr/${netmask:-32}" -j RETURN
+ iptables -t filter -I luci_splash_forwarding -d "$ipaddr/${netmask:-32}" -j RETURN
}
}
# 77 -> download root qdisc
# 78 -> upload root qdisc
- # 79 -> fwmark
+ # 79 -> fwmark: client->inet
+ # 80 -> fwmark: inet->client
silent tc qdisc del dev "$iface" root handle 77:
# set download limit and burst
tc class add dev "$iface" parent 77:1 classid 77:10 htb \
- rate ${LIMIT_DOWN}kb ceil ${LIMIT_DOWN_BURST}kb prio 2
+ 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 download speed
+ tc filter add dev "$iface" parent 77: protocol ip prio 2 \
+ handle 80 fw flowid 77:10
+
# set client upload speed
tc filter add dev "$iface" parent ffff: protocol ip prio 1 \
- handle 79 fw police rate ${LIMIT_UP}kb mtu 6k burst 6k drop
+ handle 79 fw police rate ${LIMIT_UP}kbit mtu 6k burst 6k drop
fi
}
boot() {
### Setup splash-relay
- uci get lucid.splashr || {
+ uci get lucid.splashr 2>/dev/null || {
uci batch <<EOF
set lucid.splashr=daemon
set lucid.splashr.slave=httpd
}
start() {
- ### Read chains from config
- include /lib/network
- scan_interfaces
- config_load luci_splash
-
+ lock -w $LOCK && lock $LOCK
+
### Find QoS limits
config_get LIMIT_UP general limit_up
config_get LIMIT_DOWN general limit_down
config_get LIMIT_DOWN_BURST general limit_down_burst
- LIMIT_UP="${LIMIT_UP:-0}"
- LIMIT_DOWN="${LIMIT_DOWN:-0}"
- LIMIT_DOWN_BURST="${LIMIT_DOWN_BURST:-$(($LIMIT_DOWN * 2))}"
+ LIMIT_UP="$((8*${LIMIT_UP:-0}))"
+ LIMIT_DOWN="$((8*${LIMIT_DOWN:-0}))"
+ LIMIT_DOWN_BURST="${LIMIT_DOWN_BURST:+$((8*$LIMIT_DOWN_BURST))}"
+ LIMIT_DOWN_BURST="${LIMIT_DOWN_BURST:-$(($LIMIT_DOWN / 5 * 6))}"
### Load required modules
[ "$LIMIT_UP" -gt 0 -a "$LIMIT_DOWN" -gt 0 ] && {
silent insmod cls_fw
silent insmod cls_u32
silent insmod sch_htb
+ silent insmod sch_sfq
silent insmod sch_ingress
}
### Create subchains
- iptables -t filter -N luci_splash_filter
- iptables -t nat -N luci_splash_portal
- iptables -t nat -N luci_splash_leases
iptables -t nat -N luci_splash_prerouting
+ iptables -t nat -N luci_splash_leases
+ iptables -t filter -N luci_splash_forwarding
+ iptables -t filter -N luci_splash_filter
- [ "$LIMIT_UP" -gt 0 -a "$LIMIT_DOWN" -gt 0 ] && \
- iptables -t mangle -N luci_splash_mark
+ ### Clear iptables replay log
+ [ -s $IPT_REPLAY ] && . $IPT_REPLAY
+ echo -n > $IPT_REPLAY
### Build the main and portal rule
config_foreach iface_add iface
config_foreach subnet_add subnet
- config_foreach blacklist_add blacklist
- config_foreach whitelist_add whitelist
- config_foreach lease_add lease
-
- ### Build the portal rule
- iptables -t filter -I INPUT -j luci_splash_filter
- iptables -t filter -I FORWARD -j luci_splash_filter
- [ "$LIMIT_UP" -gt 0 -a "$LIMIT_DOWN" -gt 0 ] && \
- iptables -t mangle -I PREROUTING -j luci_splash_mark
+ ### Add interface independant prerouting rules
+ iptables -t nat -A luci_splash_prerouting -j luci_splash_leases
+ iptables -t nat -A luci_splash_leases -p udp --dport 53 -j REDIRECT --to-ports 53
+ iptables -t nat -A luci_splash_leases -p tcp --dport 80 -j REDIRECT --to-ports 8082
- ### Allow icmp, dns and traceroute
- iptables -t nat -A luci_splash_portal -p udp --dport 33434:33523 -j RETURN
- iptables -t nat -A luci_splash_portal -p icmp -j RETURN
- iptables -t nat -A luci_splash_portal -p udp --dport 53 -j RETURN
+ ### Add interface independant forwarding rules
+ iptables -t filter -A luci_splash_forwarding -j luci_splash_filter
+ iptables -t filter -A luci_splash_filter -p tcp -j REJECT --reject-with tcp-reset
+ iptables -t filter -A luci_splash_filter -j REJECT --reject-with icmp-net-prohibited
+
+ ### Add QoS chain
+ [ "$LIMIT_UP" -gt 0 -a "$LIMIT_DOWN" -gt 0 ] && {
+ iptables -t mangle -N luci_splash_mark_out
+ iptables -t mangle -N luci_splash_mark_in
+ iptables -t mangle -I PREROUTING -j luci_splash_mark_out
+ iptables -t mangle -I POSTROUTING -j luci_splash_mark_in
+ }
+
+ ### Find active mac addresses
+ MACS=""
+ config_foreach mac_add lease
+ config_foreach mac_add blacklist
+ config_foreach mac_add whitelist
- ### Redirect the rest into the lease chain
- iptables -t nat -A luci_splash_portal -j luci_splash_leases
-
- ### Build the leases rule
- iptables -t nat -A luci_splash_leases -p tcp --dport 80 -j REDIRECT --to-ports 8082
-
### Add crontab entry
test -f /etc/crontabs/root || touch /etc/crontabs/root
grep -q luci-splash /etc/crontabs/root || {
echo '*/5 * * * * /usr/sbin/luci-splash sync' >> /etc/crontabs/root
}
+
+ lock -u $LOCK
+
+ ### Populate iptables
+ [ -n "$MACS" ] && luci-splash add-rules $MACS
}
stop() {
+ lock -w $LOCK && lock $LOCK
+
### Clear interface rules
- include /lib/network
- scan_interfaces
- config_load luci_splash
config_foreach iface_del iface
- silent iptables -t filter -D INPUT -j luci_splash_filter
- silent iptables -t filter -D FORWARD -j luci_splash_filter
- silent iptables -t mangle -D PREROUTING -j luci_splash_mark
+ silent iptables -t mangle -D PREROUTING -j luci_splash_mark_out
+ silent iptables -t mangle -D POSTROUTING -j luci_splash_mark_in
### Clear subchains
- 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 nat -F luci_splash_leases
+ silent iptables -t filter -F luci_splash_forwarding
silent iptables -t filter -F luci_splash_filter
- silent iptables -t mangle -F luci_splash_mark
+ silent iptables -t mangle -F luci_splash_mark_out
+ silent iptables -t mangle -F luci_splash_mark_in
### Delete subchains
- 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 nat -X luci_splash_leases
+ silent iptables -t filter -X luci_splash_forwarding
silent iptables -t filter -X luci_splash_filter
- silent iptables -t mangle -X luci_splash_mark
+ silent iptables -t mangle -X luci_splash_mark_out
+ silent iptables -t mangle -X luci_splash_mark_in
sed -ie '/\/usr\/sbin\/luci-splash sync/d' /var/spool/cron/crontabs/root
+
+ lock -u $LOCK
}
-
clear_leases() {
- stop
- while uci -P /var/state del luci_splash.@lease[0] 2>&-;do :; done
- start
+ ### Find active mac addresses
+ MACS=""
+ config_foreach mac_add lease
+
+ ### Clear leases
+ [ -n "$MACS" ] && luci-splash remove $MACS
}
require("luci.util")
require("luci.model.uci")
+require("luci.sys")
require("luci.sys.iptparser")
-- Init state session
local uci = luci.model.uci.cursor_state()
local ipt = luci.sys.iptparser.IptParser()
+local net = luci.sys.net
-local splash_interfaces = { }
local limit_up = 0
local limit_down = 0
+function lock()
+ os.execute("lock -w /var/run/luci_splash.lock && lock /var/run/luci_splash.lock")
+end
+
+function unlock()
+ os.execute("lock -u /var/run/luci_splash.lock")
+end
+
function main(argv)
- local cmd = argv[1]
- local arg = argv[2]
+ local cmd = table.remove(argv, 1)
+ local arg = argv[1]
limit_up = tonumber(uci:get("luci_splash", "general", "limit_up")) or 0
limit_down = tonumber(uci:get("luci_splash", "general", "limit_down")) or 0
-
- 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")
- elseif islisted("blacklist", arg) then
- print("blacklisted")
- else
- local lease = haslease(arg)
- if lease and lease.kicked then
- print("kicked")
- elseif lease then
- print("lease")
+
+ if ( cmd == "lease" or cmd == "add-rules" or cmd == "remove" or
+ cmd == "whitelist" or cmd == "blacklist" ) and #argv > 0
+ then
+ lock()
+
+ local arp_cache = net.arptable()
+ local leased_macs = get_known_macs("lease")
+ local blacklist_macs = get_known_macs("blacklist")
+ local whitelist_macs = get_known_macs("whitelist")
+
+ for i, adr in ipairs(argv) do
+ local mac = nil
+ if adr:find(":") then
+ mac = adr:lower()
else
- print("unknown")
+ for _, e in ipairs(arp_cache) do
+ if e["IP address"] == adr then
+ mac = e["HW address"]
+ break
+ end
+ end
+ end
+
+ if mac and cmd == "add-rules" then
+ if leased_macs[mac] then
+ add_lease(mac, arp_cache, true)
+ elseif blacklist_macs[mac] then
+ add_blacklist_rule(mac)
+ elseif whitelist_macs[mac] then
+ add_whitelist_rule(mac)
+ end
+ elseif mac and ( cmd == "whitelist" or cmd == "blacklist" or cmd == "lease" ) then
+ if cmd ~= "lease" and leased_macs[mac] then
+ print("Removing %s from leases" % mac)
+ remove_lease(mac)
+ leased_macs[mac] = nil
+ end
+
+ if cmd ~= "whitelist" and whitelist_macs[mac] then
+ print("Removing %s from whitelist" % mac)
+ remove_whitelist(mac)
+ whitelist_macs[mac] = nil
+ end
+
+ if cmd ~= "blacklist" and blacklist_macs[mac] then
+ print("Removing %s from blacklist" % mac)
+ remove_blacklist(mac)
+ blacklist_macs[mac] = nil
+ end
+
+ if cmd == "lease" and not leased_macs[mac] then
+ print("Adding %s to leases" % mac)
+ add_lease(mac)
+ leased_macs[mac] = true
+ elseif cmd == "whitelist" and not whitelist_macs[mac] then
+ print("Adding %s to whitelist" % mac)
+ add_whitelist(mac)
+ whitelist_macs[mac] = true
+ elseif cmd == "blacklist" and not blacklist_macs[mac] then
+ print("Adding %s to blacklist" % mac)
+ add_blacklist(mac)
+ blacklist_macs[mac] = true
+ else
+ print("The mac %s is already %sed" %{ mac, cmd })
+ end
+ elseif mac and cmd == "remove" then
+ if leased_macs[mac] then
+ print("Removing %s from leases" % mac)
+ remove_lease(mac)
+ leased_macs[mac] = nil
+ elseif whitelist_macs[mac] then
+ print("Removing %s from whitelist" % mac)
+ remove_whitelist(mac)
+ whitelist_macs[mac] = nil
+ elseif blacklist_macs[mac] then
+ print("Removing %s from blacklist" % mac)
+ remove_blacklist(mac)
+ blacklist_macs[mac] = nil
+ else
+ print("The mac %s is not known" % mac)
+ end
+ else
+ print("Can not find mac for ip %s" % argv[i])
end
end
- os.exit(0)
- elseif cmd == "add" and arg then
- if not haslease(arg) then
- add_lease(arg)
- else
- print("already leased!")
- os.exit(2)
- end
- os.exit(0)
- elseif cmd == "remove" and arg then
- remove_lease(arg)
- os.exit(0)
+
+ unlock()
+ os.exit(0)
elseif cmd == "sync" then
sync()
os.exit(0)
+ elseif cmd == "list" then
+ list()
+ os.exit(0)
else
- print("Usage: " .. argv[0] .. " <status|add|remove|sync> [MAC]")
+ print("Usage:")
+ print("\n luci-splash list\n List connected, black- and whitelisted clients")
+ print("\n luci-splash sync\n Synchronize firewall rules and clear expired leases")
+ print("\n luci-splash lease <MAC-or-IP>\n Create a lease for the given address")
+ print("\n luci-splash blacklist <MAC-or-IP>\n Add given address to blacklist")
+ print("\n luci-splash whitelist <MAC-or-IP>\n Add given address to whitelist")
+ print("\n luci-splash remove <MAC-or-IP>\n Remove given address from the lease-, black- or whitelist")
+ print("")
+
os.exit(1)
end
end
--- Add a lease to state and invoke add_rule
-function add_lease(mac)
- uci:section("luci_splash", "lease", nil, {
- mac = mac,
- start = os.time()
- })
- add_rule(mac)
-
- uci:save("luci_splash")
-end
+-- Get a list of known mac addresses
+function get_known_macs(list)
+ local leased_macs = { }
+ if not list or list == "lease" then
+ uci:foreach("luci_splash", "lease",
+ function(s) leased_macs[s.mac:lower()] = true end)
+ end
--- Remove a lease from state and invoke remove_rule
-function remove_lease(mac)
- mac = mac:lower()
- remove_rule(mac)
+ if not list or list == "whitelist" then
+ uci:foreach("luci_splash", "whitelist",
+ function(s) leased_macs[s.mac:lower()] = true end)
+ end
- uci:delete_all("luci_splash", "lease",
- function(s) return ( s.mac:lower() == mac ) end)
-
- uci:save("luci_splash")
+ if not list or list == "blacklist" then
+ uci:foreach("luci_splash", "blacklist",
+ function(s) leased_macs[s.mac:lower()] = true end)
+ end
+
+ return leased_macs
end
--- Add an iptables rule
-function add_rule(mac)
- 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
+-- Get a list of known ip addresses
+function get_known_ips(macs, arp)
+ local leased_ips = { }
+ if not macs then macs = get_known_macs() end
+ for _, e in ipairs(arp or net.arptable()) do
+ if macs[e["HW address"]] then leased_ips[e["IP address"]] = true end
+ end
+ return leased_ips
+end
- 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)
+-- Helper to delete iptables rules
+function ipt_delete_all(args, comp, off)
+ off = off or { }
+ for i, r in ipairs(ipt:find(args)) do
+ if comp == nil or comp(r) then
+ off[r.table] = off[r.table] or { }
+ off[r.table][r.chain] = off[r.table][r.chain] or 0
+
+ os.execute("iptables -t %q -D %q %d 2>/dev/null"
+ %{ r.table, r.chain, r.index - off[r.table][r.chain] })
+
+ off[r.table][r.chain] = off[r.table][r.chain] + 1
end
end
-
- os.execute("iptables -t filter -I luci_splash_filter -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)
- local handle = mac:match("%w+:%w+:%w+:%w+:%w+:(%w+)")
+-- Add a lease to state and invoke add_rule
+function add_lease(mac, arp, no_uci)
+ mac = mac:lower()
- local function ipt_delete_foreach(args)
- for _, r in ipairs(ipt:find(args)) do
- 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 "" })
+ -- Get current ip address
+ local ipaddr
+ for _, entry in ipairs(arp or net.arptable()) do
+ if entry["HW address"] == mac then
+ ipaddr = entry["IP address"]
+ break
end
end
- ipt_delete_foreach({table="filter", chain="luci_splash_filter", options={"MAC", mac:upper()}})
- ipt_delete_foreach({table="mangle", chain="luci_splash_mark", options={"MAC", mac:upper()}})
- ipt_delete_foreach({table="nat", chain="luci_splash_leases", options={"MAC", mac:upper()}})
-
- 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)
+ -- Add lease if there is an ip addr
+ if ipaddr then
+ if not no_uci then
+ uci:section("luci_splash", "lease", nil, {
+ mac = mac,
+ ipaddr = ipaddr,
+ start = os.time()
+ })
+ uci:save("luci_splash")
+ end
+ add_lease_rule(mac, ipaddr)
+ else
+ print("Found no active IP for %s, lease not added" % mac)
end
-
- ipt:resync()
end
--- Check whether a MAC-Address is listed in the lease state list
-function haslease(mac)
+-- Remove a lease from state and invoke remove_rule
+function remove_lease(mac)
mac = mac:lower()
- local lease = nil
- uci:foreach("luci_splash", "lease",
- function (section)
- if section.mac:lower() == mac then
- lease = section
+ uci:delete_all("luci_splash", "lease",
+ function(s)
+ if s.mac:lower() == mac then
+ remove_lease_rule(mac, s.ipaddr)
+ return true
end
+ return false
end)
+
+ uci:save("luci_splash")
+end
- return lease
+
+-- Add a whitelist entry
+function add_whitelist(mac)
+ uci:section("luci_splash", "whitelist", nil, { mac = mac })
+ uci:save("luci_splash")
+ uci:commit("luci_splash")
+ add_whitelist_rule(mac)
end
--- Check whether a MAC-Address is in given list
-function islisted(what, mac)
+-- Add a blacklist entry
+function add_blacklist(mac)
+ uci:section("luci_splash", "blacklist", nil, { mac = mac })
+ uci:save("luci_splash")
+ uci:commit("luci_splash")
+ add_blacklist_rule(mac)
+end
+
+
+-- Remove a whitelist entry
+function remove_whitelist(mac)
mac = mac:lower()
+ uci:delete_all("luci_splash", "whitelist",
+ function(s) return not s.mac or s.mac:lower() == mac end)
+ uci:save("luci_splash")
+ uci:commit("luci_splash")
+ remove_lease_rule(mac)
+end
- uci:foreach("luci_splash", what,
- function (section)
- if section.mac:lower() == mac then
- stat = true
- return
- end
- end)
- return false
+-- Remove a blacklist entry
+function remove_blacklist(mac)
+ mac = mac:lower()
+ uci:delete_all("luci_splash", "blacklist",
+ function(s) return not s.mac or s.mac:lower() == mac end)
+ uci:save("luci_splash")
+ uci:commit("luci_splash")
+ remove_lease_rule(mac)
end
--- Returns a list of MAC-Addresses for which a rule is existing
-function listrules()
- local macs = { }
- for i, r in ipairs(ipt:find({table="nat", chain="luci_splash_leases", options={"MAC"}})) do
- macs[r.options[2]:lower()] = true
+-- Add an iptables rule
+function add_lease_rule(mac, ipaddr)
+ if limit_up > 0 and limit_down > 0 then
+ os.execute("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j MARK --set-mark 79" % mac)
+ os.execute("iptables -t mangle -I luci_splash_mark_in -d %q -j MARK --set-mark 80" % ipaddr)
end
- return luci.util.keys(macs)
+
+ os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
+ os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
+end
+
+
+-- Remove lease, black- or whitelist rules
+function remove_lease_rule(mac, ipaddr)
+ ipt:resync()
+
+ if ipaddr then
+ ipt_delete_all({table="mangle", chain="luci_splash_mark_in", destination=ipaddr})
+ ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
+ end
+
+ ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC", mac:upper()}})
+ ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC", mac:upper()}})
+end
+
+
+-- Add whitelist rules
+function add_whitelist_rule(mac)
+ os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
+ os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
+end
+
+
+-- Add blacklist rules
+function add_blacklist_rule(mac)
+ os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
+ os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j DROP" % mac)
end
-- Synchronise leases, remove abandoned rules
function sync()
- local written = {}
+ lock()
+
local time = os.time()
-
+
-- Current leases in state files
local leases = uci:get_all("luci_splash")
uci:load("luci_splash")
uci:revert("luci_splash")
-
-- For all leases
for k, v in pairs(leases) do
if v[".type"] == "lease" then
if os.difftime(time, tonumber(v.start)) > leasetime then
-- Remove expired
- remove_rule(v.mac)
+ remove_lease_rule(v.mac, v.ipaddr)
else
-- Rewrite state
uci:section("luci_splash", "lease", nil, {
mac = v.mac,
- start = v.start,
- kicked = v.kicked
+ ipaddr = v.ipaddr,
+ start = v.start
})
- written[v.mac:lower()] = 1
end
- elseif v[".type"] == "whitelist" or v[".type"] == "blacklist" then
- written[v.mac:lower()] = 1
end
end
-
-
- -- Delete rules without state
- for i, r in ipairs(listrules()) do
- if #r > 0 and not written[r:lower()] then
- remove_rule(r)
+
+ uci:save("luci_splash")
+
+ -- Get current IPs and MAC addresses
+ local macs = get_known_macs()
+ local ips = get_known_ips(macs)
+
+ ipt:resync()
+
+ ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
+ function(r) return not macs[r.options[2]:lower()] end)
+
+ ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC"}},
+ function(r) return not macs[r.options[2]:lower()] end)
+
+ ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
+ function(r) return not macs[r.options[2]:lower()] end)
+
+ ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"MARK", "set"}},
+ function(r) return not ips[r.destination] end)
+
+ unlock()
+end
+
+-- Show client info
+function list()
+ -- Get current arp cache
+ local arpcache = { }
+ for _, entry in ipairs(net.arptable()) do
+ arpcache[entry["HW address"]] = { entry["Device"], entry["IP address"] }
+ end
+
+ -- Find traffic usage
+ local function traffic(lease)
+ local traffic_in = 0
+ local traffic_out = 0
+
+ local rin = ipt:find({table="mangle", chain="luci_splash_mark_in", destination=lease.ipaddr})
+ local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", lease.mac:upper()}})
+
+ if rin and #rin > 0 then traffic_in = math.floor( rin[1].bytes / 1024) end
+ if rout and #rout > 0 then traffic_out = math.floor(rout[1].bytes / 1024) end
+
+ return traffic_in, traffic_out
+ end
+
+ -- Print listings
+ local leases = uci:get_all("luci_splash")
+
+ print(string.format(
+ "%-17s %-15s %-9s %-4s %-7s %20s",
+ "MAC", "IP", "State", "Dur.", "Intf.", "Traffic down/up"
+ ))
+
+ -- Leases
+ for _, s in pairs(leases) do
+ if s[".type"] == "lease" and s.mac then
+ local ti, to = traffic(s)
+ local mac = s.mac:lower()
+ local arp = arpcache[mac]
+ print(string.format(
+ "%-17s %-15s %-9s %3dm %-7s %7dKB %7dKB",
+ mac, s.ipaddr, "leased",
+ math.floor(( os.time() - tonumber(s.start) ) / 60),
+ arp and arp[1] or "?", ti, to
+ ))
+ end
+ end
+
+ -- Whitelist, Blacklist
+ for _, s in luci.util.spairs(leases,
+ function(a,b) return leases[a][".type"] > leases[b][".type"] end
+ ) do
+ if (s[".type"] == "whitelist" or s[".type"] == "blacklist") and s.mac then
+ local mac = s.mac:lower()
+ local arp = arpcache[mac]
+ print(string.format(
+ "%-17s %-15s %-9s %4s %-7s %9s %9s",
+ mac, arp and arp[2] or "?", s[".type"], "- ",
+ arp and arp[1] or "?", "-", "-"
+ ))
end
end
-
- uci:save("luci_splash")
end
main(arg)