applications/luci-splash:

- rewrote init script, cli
	- introduce download traffic counters
	- adept user interface
This commit is contained in:
Jo-Philipp Wich 2009-07-06 21:14:59 +00:00
parent 9525fb76bd
commit b0771c43ea
4 changed files with 506 additions and 322 deletions

View file

@ -24,7 +24,7 @@ function action_activate()
local ip = luci.http.getenv("REMOTE_ADDR") or "127.0.0.1"
local mac = luci.sys.net.ip4mac(ip:match("^[\[::ffff:]*(%d+.%d+%.%d+%.%d+)\]*$"))
if mac and luci.http.formvalue("accept") then
os.execute("luci-splash add "..mac.." >/dev/null 2>&1")
os.execute("luci-splash lease "..mac.." >/dev/null 2>&1")
luci.http.redirect(luci.model.uci.cursor():get("freifunk", "community", "homepage"))
else
luci.http.redirect(luci.dispatcher.build_url())
@ -35,63 +35,44 @@ function action_status_admin()
local uci = luci.model.uci.cursor_state()
local macs = luci.http.formvaluetable("save")
local function delete_mac(what, mac)
uci:delete_all("luci_splash", what,
function(s)
return ( s.mac and s.mac:lower() == mac )
end)
end
local function leases(mac)
local leases = { }
uci:foreach("luci_splash", "lease", function(s)
if s.start and s.mac and s.mac:lower() ~= mac then
leases[#leases+1] = {
start = s.start,
mac = s.mac
}
end
end)
uci:revert("luci_splash")
return leases
end
local function commit(leases, no_commit)
if not no_commit then
uci:save("luci_splash")
uci:commit("luci_splash")
end
for _, l in ipairs(leases) do
uci:section("luci_splash", "lease", nil, l)
end
uci:save("luci_splash")
os.execute("/etc/init.d/luci_splash restart")
end
local changes = {
whitelist = { },
blacklist = { },
lease = { },
remove = { }
}
for key, _ in pairs(macs) do
local policy = luci.http.formvalue("policy.%s" % key)
local mac = luci.http.protocol.urldecode(key)
local lslist = leases(policy ~= "kick" and mac)
delete_mac("blacklist", mac)
delete_mac("whitelist", mac)
if policy == "whitelist" or policy == "blacklist" then
uci:section("luci_splash", policy, nil, { mac = mac })
elseif policy == "normal" then
lslist[#lslist+1] = { mac = mac, start = os.time() }
elseif policy == "kick" then
for _, l in ipairs(lslist) do
if l.mac:lower() == mac then l.kicked="1" end
end
changes[policy][#changes[policy]+1] = mac
elseif policy == "normal" then
changes["lease"][#changes["lease"]+1] = mac
elseif policy == "kicked" then
changes["remove"][#changes["remove"]+1] = mac
end
end
commit(lslist)
if #changes.whitelist > 0 then
os.execute("luci-splash whitelist %s >/dev/null"
% table.concat(changes.whitelist))
end
if #changes.blacklist > 0 then
os.execute("luci-splash blacklist %s >/dev/null"
% table.concat(changes.blacklist))
end
if #changes.lease > 0 then
os.execute("luci-splash lease %s >/dev/null"
% table.concat(changes.lease))
end
if #changes.remove > 0 then
os.execute("luci-splash remove %s >/dev/null"
% table.concat(changes.remove))
end
luci.template.render("admin_status/splash", { is_admin = true })

View file

@ -35,10 +35,10 @@ uci:foreach("luci_splash", "lease",
start = tonumber(s.start),
limit = ( tonumber(s.start) + leasetime ),
mac = s.mac:upper(),
ipaddr = s.ipaddr,
policy = "normal",
packets = 0,
bytes = 0,
kicked = s.kicked and true or false
}
end
end)
@ -58,11 +58,25 @@ for _, r in ipairs(ipt:find({table="nat", chain="luci_splash_leases"})) do
end
end
for _, r in ipairs(ipt:find({table="filter", chain="luci_splash_filter", options={"MAC"}})) do
local c = clients[r.options[2]:lower()]
if c and c.packets == 0 then
c.bytes = tonumber(r.bytes)
c.packets = tonumber(r.packets)
for mac, client in pairs(clients) do
client.bytes_in = 0
client.bytes_out = 0
client.packets_in = 0
client.packets_out = 0
if client.ipaddr then
local rin = ipt:find({table="mangle", chain="luci_splash_mark_in", destination=client.ipaddr})
local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", client.mac:upper()}})
if rin and #rin > 0 then
client.bytes_in = rin[1].bytes
client.packets_in = rin[1].packets
end
if rout and #rout > 0 then
client.bytes_out = rout[1].bytes
client.packets_out = rout[1].packets
end
end
end
@ -123,7 +137,7 @@ end
<th class="cbi-section-table-cell"><%:ff_splash_ip IP Address%></th>
<th class="cbi-section-table-cell"><%:ff_splash_mac MAC Address%></th>
<th class="cbi-section-table-cell"><%:ff_splash_timeleft Time remaining%></th>
<th class="cbi-section-table-cell"><%:ff_splash_traffic Outgoing traffic%></th>
<th class="cbi-section-table-cell"><%:ff_splash_traffic Traffic in/out%></th>
<th class="cbi-section-table-cell"><%:ff_splash_policy Policy%></th>
</tr>
@ -149,7 +163,7 @@ end
(c.limit >= os.time()) and wat.date_format(c.limit-os.time()) or
(c.policy ~= "normal") and "-" or "<em>" .. translate("ff_splash_expired", "expired") .. "</em>"
%></td>
<td class="cbi-section-table-cell"><%=wat.byte_format(c.bytes)%></td>
<td class="cbi-section-table-cell"><%=wat.byte_format(c.bytes_in)%> / <%=wat.byte_format(c.bytes_out)%></td>
<td class="cbi-section-table-cell">
<% if is_admin then %>
<select name="policy.<%=c.mac:lower()%>" style="width:200px">
@ -157,7 +171,7 @@ end
<option value="normal"<%=c.policy=="normal" and not c.kicked and ' selected="selected"'%>><%:ff_splash_splashed splashed%></option>
<option value="blacklist"<%=c.policy=="blacklist" and ' selected="selected"'%>><%:ff_splash_blacklisted blacklisted%></option>
<% if c.policy == "normal" then -%>
<option value="kick"<%=c.kicked and ' selected="selected"'%>><%:ff_splash_tempblock temporarily blocked%> (<%=wat.date_format(c.limit-os.time())%>)</option>
<option value="kicked"><%:ff_splash_tempblock temporarily blocked%></option>
<%- end %>
</select>
<input type="submit" class="cbi-button cbi-button-save" name="save.<%=c.mac:lower()%>" value="<%:save Save%>" />

View file

@ -1,15 +1,29 @@
#!/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"
@ -37,95 +51,45 @@ iface_add() {
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
### 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" -j RETURN
iptables -t nat -I luci_splash_leases -s ! "$NETWORK/$PREFIX" -j RETURN
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
# 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
}
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
}
}
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
}
}
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() {
@ -135,8 +99,8 @@ 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
}
}
@ -145,7 +109,8 @@ qos_iface_add() {
# 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:
@ -157,16 +122,20 @@ qos_iface_add() {
# 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
}
@ -180,7 +149,7 @@ qos_iface_del() {
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
@ -202,101 +171,113 @@ EOF
}
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
### 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
### Redirect the rest into the lease chain
iptables -t nat -A luci_splash_portal -j luci_splash_leases
### Build the leases rule
### 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
### 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
### 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
}

View file

@ -2,185 +2,324 @@
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
-- 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
if not list or list == "whitelist" then
uci:foreach("luci_splash", "whitelist",
function(s) leased_macs[s.mac:lower()] = true end)
end
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
-- 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
-- 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
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")
function add_lease(mac, arp, no_uci)
mac = mac:lower()
-- 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
-- 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
end
-- Remove a lease from state and invoke remove_rule
function remove_lease(mac)
mac = mac:lower()
remove_rule(mac)
uci:delete_all("luci_splash", "lease",
function(s) return ( s.mac:lower() == mac ) end)
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
-- 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
-- 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
-- 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
-- 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
function add_lease_rule(mac, ipaddr)
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
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
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)
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+)")
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 "" })
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)
end
-- Remove lease, black- or whitelist rules
function remove_lease_rule(mac, ipaddr)
ipt:resync()
end
-- Check whether a MAC-Address is listed in the lease state list
function haslease(mac)
mac = mac:lower()
local lease = nil
uci:foreach("luci_splash", "lease",
function (section)
if section.mac:lower() == mac then
lease = section
end
end)
return lease
end
-- Check whether a MAC-Address is in given list
function islisted(what, mac)
mac = mac:lower()
uci:foreach("luci_splash", what,
function (section)
if section.mac:lower() == mac then
stat = true
return
end
end)
return false
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
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
return luci.util.keys(macs)
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")
@ -191,36 +330,105 @@ function sync()
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)
end
end
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
end
main(arg)