utl = require "luci.util"
sys = require "luci.sys"
+ipc = require "luci.ip"
-require("luci.model.uci")
-require("luci.sys.iptparser")
-- Init state session
-local uci = luci.model.uci.cursor_state()
-local ipt = luci.sys.iptparser.IptParser()
-local net = sys.net
+local uci = require "luci.model.uci".cursor_state()
+local ipt = require "luci.sys.iptparser".IptParser()
local fs = require "nixio.fs"
local ip = require "luci.ip"
os.execute(cmd)
end
+function esc(str)
+ return utl.shellquote(str)
+end
+
function lock()
call("lock /var/run/luci_splash.lock")
function get_physdev(interface)
local dev
- dev = utl.trim(sys.exec(". /lib/functions/network.sh; network_get_device IFNAME '" .. interface .. "'; echo $IFNAME"))
+ dev = utl.trim(sys.exec(". /lib/functions/network.sh; network_get_device IFNAME %s; echo $IFNAME" % esc(interface)))
return dev
end
function get_filter_handle(parent, direction, device, mac)
- local input = utl.split(sys.exec('/usr/sbin/tc filter show dev ' .. device .. ' parent ' .. parent) or {})
+ local input = utl.split(sys.exec('/usr/sbin/tc filter show dev %s parent %s' %{ esc(device), esc(parent) }) or {})
local tbl = {}
local handle
for k, v in pairs(input) do
return false
end
+function mac_to_ip(mac)
+ local ipaddr = nil
+ ipc.neighbors({ family = 4 }, function(n)
+ if n.mac == mac and n.dest then
+ ipaddr = n.dest:string()
+ end
+ end)
+ return ipaddr
+end
+
+function mac_to_dev(mac)
+ local dev = nil
+ ipc.neighbors({ family = 4 }, function(n)
+ if n.mac == mac and n.dev then
+ dev = n.dev
+ end
+ end)
+ return dev
+end
+
+function ip_to_mac(ip)
+ local mac = nil
+ ipc.neighbors({ family = 4 }, function(n)
+ if n.mac and n.dest and n.dest:equal(ip) then
+ mac = n.mac
+ end
+ end)
+ return mac
+end
+
function main(argv)
local cmd = table.remove(argv, 1)
local arg = argv[1]
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")
if adr:find(":") then
mac = adr:lower()
else
- for _, e in ipairs(arp_cache) do
- if e["IP address"] == adr then
- mac = e["HW address"]:lower()
- break
- end
- end
+ mac = ip_to_mac(adr)
end
if mac and cmd == "add-rules" then
if leased_macs[mac] then
- add_lease(mac, arp_cache, true)
+ add_lease(mac, true)
elseif blacklist_macs[mac] then
add_blacklist_rule(mac)
elseif whitelist_macs[mac] then
elseif whitelist_macs[mac] then
print("Removing %s from whitelist" % mac)
remove_whitelist(mac)
- whitelist_macs[mac] = nil
+ whitelist_macs[mac] = nil
elseif blacklist_macs[mac] then
print("Removing %s from blacklist" % mac)
remove_blacklist(mac)
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 current arp cache
-function get_arpcache()
- local arpcache = { }
- for _, entry in ipairs(net.arptable()) do
- arpcache[entry["HW address"]:lower()] = { entry["Device"]:lower(), entry["IP address"]:lower() }
+ os.exit(1)
end
- return arpcache
end
-- Get a list of known mac addresses
off[r.table] = off[r.table] or { }
off[r.table][r.chain] = off[r.table][r.chain] or 0
- exec("iptables -t %q -D %q %d 2>/dev/null"
- %{ r.table, r.chain, r.index - off[r.table][r.chain] })
+ exec("iptables -t %s -D %s %d 2>/dev/null"
+ %{ esc(r.table), esc(r.chain), r.index - off[r.table][r.chain] })
off[r.table][r.chain] = off[r.table][r.chain] + 1
end
off[r.table] = off[r.table] or { }
off[r.table][r.chain] = off[r.table][r.chain] or 0
- exec("ip6tables -t %q -D %q %d 2>/dev/null"
- %{ r.table, r.chain, r.index - off[r.table][r.chain] })
+ exec("ip6tables -t %s -D %s %d 2>/dev/null"
+ %{ esc(r.table), esc(r.chain), r.index - off[r.table][r.chain] })
off[r.table][r.chain] = off[r.table][r.chain] + 1
end
end
-- Add a lease to state and invoke add_rule
-function add_lease(mac, arp, no_uci)
+function add_lease(mac, no_uci)
mac = mac:lower()
-- Get current ip address
- local ipaddr
- for _, entry in ipairs(arp or net.arptable()) do
- if entry["HW address"]:lower() == mac then
- ipaddr = entry["IP address"]
- break
- end
- end
+ local ipaddr = mac_to_ip(mac)
-- Add lease if there is an ip addr
if ipaddr then
end
local handle = get_filter_handle('ffff:', 'src', device, mac)
if handle then
- exec('tc filter del dev "%s" parent ffff: protocol ip prio 1 handle %s u32' % { device, handle })
+ exec('tc filter del dev %s parent ffff: protocol ip prio 1 handle %s u32' % { esc(device), esc(handle) })
else
print('Warning! Could not get a handle for %s parent :ffff on interface %s' % { mac, device })
end
local handle = get_filter_handle('1:', 'dest', device, mac)
if handle then
- exec('tc filter del dev "%s" parent 1:0 protocol ip prio 1 handle %s u32' % { device, handle })
+ exec('tc filter del dev %s parent 1:0 protocol ip prio 1 handle %s u32' % { esc(device), esc(handle) })
else
print('Warning! Could not get a handle for %s parent 1:0 on interface %s' % { mac, device })
end
id = get_id(ipaddr)
end
- exec("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j RETURN" % mac)
+ exec("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %s -j RETURN" % esc(mac))
-- Mark incoming packets to a splashed host
-- for ipv4 - by iptables and destination
if id and device then
- exec("iptables -t mangle -I luci_splash_mark_in -d %q -j MARK --set-mark 0x1%s -m comment --comment %s" % {ipaddr, id, mac:upper()})
+ exec("iptables -t mangle -I luci_splash_mark_in -d %s -j MARK --set-mark 0x1%s -m comment --comment %s" % { esc(ipaddr), esc(id), esc(mac:upper())})
end
--for ipv6: need to use the mac here
if has_ipv6 then
- exec("ip6tables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j MARK --set-mark 79" % mac)
+ exec("ip6tables -t mangle -I luci_splash_mark_out -m mac --mac-source %s -j MARK --set-mark 79" % esc(mac))
if id and device and tonumber(limit_down) then
- exec("tc filter add dev %s parent 1:0 protocol ipv6 prio 1 u32 match ether dst %s classid 1:%s" % {device, mac:lower(), id})
+ exec("tc filter add dev %s parent 1:0 protocol ipv6 prio 1 u32 match ether dst %s classid 1:%s" % { esc(device), esc(mac:lower()), esc(id) })
end
end
if device and tonumber(limit_up) > 0 then
- exec('tc filter add dev "%s" parent ffff: protocol all prio 2 u32 match ether src %s police rate %skbit mtu 6k burst 6k drop' % {device, mac, limit_up})
+ exec('tc filter add dev %s parent ffff: protocol all prio 2 u32 match ether src %s police rate %skbit mtu 6k burst 6k drop' % { esc(device), esc(mac), esc(limit_up) })
end
if id and device and tonumber(limit_down) > 0 then
- exec("tc class add dev %s parent 1: classid 1:0x%s htb rate %skbit" % { device, id, limit_down })
- exec("tc qdisc add dev %s parent 1:%s sfq perturb 10" % { device, id })
+ exec("tc class add dev %s parent 1: classid 1:0x%s htb rate %skbit" % { esc(device), esc(id), esc(limit_down) })
+ exec("tc qdisc add dev %s parent 1:%s sfq perturb 10" % { esc(device), esc(id) })
end
- exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
- exec("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
+ exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %s -j RETURN" % esc(mac))
+ exec("iptables -t nat -I luci_splash_leases -m mac --mac-source %s -j RETURN" % esc(mac))
if has_ipv6 then
- exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
+ exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %s -j RETURN" % esc(mac))
end
end
if device and tonumber(limit_up) > 0 then
local handle = get_filter_handle('ffff:', 'src', device, mac)
if handle then
- exec('tc filter del dev "%s" parent ffff: protocol all prio 2 handle %s u32 police rate %skbit mtu 6k burst 6k drop' % {device, handle, limit_up})
+ exec('tc filter del dev %s parent ffff: protocol all prio 2 handle %s u32 police rate %skbit mtu 6k burst 6k drop' % { esc(device), esc(handle), esc(limit_up) })
else
print('Warning! Could not get a handle for %s parent :ffff on interface %s' % { mac, device })
end
end
-- remove clients class
if device and id then
- exec('tc class del dev "%s" classid 1:%s' % {device, id})
- exec('tc filter del dev "%s" parent 1:0 prio 1' % device) -- ipv6 rule
- --exec('tc qdisc del dev "%s" parent 1:%s sfq perturb 10' % { device, id })
+ exec('tc class del dev %s classid 1:%s' % { esc(device), esc(id) })
+ exec('tc filter del dev %s parent 1:0 prio 1' % esc(device)) -- ipv6 rule
+ --exec('tc qdisc del dev %s parent 1:%s sfq perturb 10' % { esc(device), esc(id) })
end
end
-- Add whitelist rules
function add_whitelist_rule(mac)
- exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
- exec("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
+ exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %s -j RETURN" % esc(mac))
+ exec("iptables -t nat -I luci_splash_leases -m mac --mac-source %s -j RETURN" % esc(mac))
if has_ipv6 then
- exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
+ exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %s -j RETURN" % esc(mac))
end
uci:foreach("luci_splash", "iface", function(s)
local device = get_physdev(s['.name'])
if device and device ~= "" then
- exec('tc filter add dev "%s" parent ffff: protocol ip prio 1 u32 match ether src %s police pass' % { device, mac })
- exec('tc filter add dev "%s" parent 1:0 protocol ip prio 1 u32 match ether dst %s classid 1:1' % { device, mac })
+ exec('tc filter add dev %s parent ffff: protocol ip prio 1 u32 match ether src %s police pass' % { esc(device), esc(mac) })
+ exec('tc filter add dev %s parent 1:0 protocol ip prio 1 u32 match ether dst %s classid 1:1' % { esc(device), esc(mac) })
end
end)
end
-- Add blacklist rules
function add_blacklist_rule(mac)
- exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
+ exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %s -j DROP" % esc(mac))
if has_ipv6 then
- exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
+ exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %s -j DROP" % esc(mac))
end
end
-- Current leases in state files
local leases = uci:get_all("luci_splash_leases")
-
+
-- Convert leasetime to seconds
local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime")) * 3600
-
+
-- Clean state file
uci:load("luci_splash_leases")
uci:revert("luci_splash_leases")
-
- local arpcache = get_arpcache()
local blackwhitelist = uci:get_all("luci_splash")
local whitelist_total = 0
leasecount = leasecount + 1
-- only count leases_online for connected clients
- if arpcache[v.mac] then
+ if mac_to_ip(v.mac) then
leases_online = leases_online + 1
end
-- Rewrite state
- uci:section("luci_splash_leases", "lease", convert_mac_to_secname(v.mac), {
+ uci:section("luci_splash_leases", "lease", convert_mac_to_secname(v.mac), {
mac = v.mac,
ipaddr = v.ipaddr,
device = v.device,
end
end
end
-
+
-- Whitelist, Blacklist
for _, s in utl.spairs(blackwhitelist,
function(a,b) return blackwhitelist[a][".type"] > blackwhitelist[b][".type"] end
whitelist_total = whitelist_total + 1
if s.mac then
local mac = s.mac:lower()
- if arpcache[mac] then
+ if mac_to_ip(mac) then
whitelist_online = whitelist_online + 1
end
end
blacklist_total = blacklist_total + 1
if s.mac then
local mac = s.mac:lower()
- if arpcache[mac] then
+ if mac_to_ip(mac) then
blacklist_online = blacklist_online + 1
end
end
-- ToDo:
-- include a new field "leases_online" in stats to differ between active clients and leases:
- -- update_stats(leasecount, leases_online, whitelist_online, whitelist_total, blacklist_online, blacklist_total) later:
+ -- update_stats(leasecount, leases_online, whitelist_online, whitelist_total, blacklist_online, blacklist_total) later:
update_stats(leases_online, whitelist_online, whitelist_total, blacklist_online, blacklist_total)
uci:save("luci_splash_leases")
-- Show client info
function list()
- local arpcache = get_arpcache()
-- Find traffic usage
local function traffic(lease)
local traffic_in = 0
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
+ mac_to_dev(mac) or "?", ti, to
))
end
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 "?", "-", "-"
+ mac, mac_to_ip(mac) or "?", s[".type"],
+ "- ", mac_to_dev(mac) or "?", "-", "-"
))
end
end