4 require("luci.model.uci")
6 require("luci.sys.iptparser")
9 local uci = luci.model.uci.cursor_state()
10 local ipt = luci.sys.iptparser.IptParser()
11 local net = luci.sys.net
12 local fs = require "luci.fs"
17 local has_ipv6 = fs.access("/proc/net/ipv6_route") and fs.access("/usr/sbin/ip6tables")
20 os.execute("lock /var/run/luci_splash.lock")
24 os.execute("lock -u /var/run/luci_splash.lock")
28 local cmd = table.remove(argv, 1)
31 limit_up = tonumber(uci:get("luci_splash", "general", "limit_up")) or 0
32 limit_down = tonumber(uci:get("luci_splash", "general", "limit_down")) or 0
34 if ( cmd == "lease" or cmd == "add-rules" or cmd == "remove" or
35 cmd == "whitelist" or cmd == "blacklist" or cmd == "status" ) and #argv > 0
39 local arp_cache = net.arptable()
40 local leased_macs = get_known_macs("lease")
41 local blacklist_macs = get_known_macs("blacklist")
42 local whitelist_macs = get_known_macs("whitelist")
44 for i, adr in ipairs(argv) do
49 for _, e in ipairs(arp_cache) do
50 if e["IP address"] == adr then
51 mac = e["HW address"]:lower()
57 if mac and cmd == "add-rules" then
58 if leased_macs[mac] then
59 add_lease(mac, arp_cache, true)
60 elseif blacklist_macs[mac] then
61 add_blacklist_rule(mac)
62 elseif whitelist_macs[mac] then
63 add_whitelist_rule(mac)
65 elseif mac and cmd == "status" then
66 print(leased_macs[mac] and "lease"
67 or whitelist_macs[mac] and "whitelist"
68 or blacklist_macs[mac] and "blacklist"
70 elseif mac and ( cmd == "whitelist" or cmd == "blacklist" or cmd == "lease" ) then
71 if cmd ~= "lease" and leased_macs[mac] then
72 print("Removing %s from leases" % mac)
74 leased_macs[mac] = nil
77 if cmd ~= "whitelist" and whitelist_macs[mac] then
78 print("Removing %s from whitelist" % mac)
80 whitelist_macs[mac] = nil
83 if cmd ~= "blacklist" and blacklist_macs[mac] then
84 print("Removing %s from blacklist" % mac)
86 blacklist_macs[mac] = nil
89 if cmd == "lease" and not leased_macs[mac] then
90 print("Adding %s to leases" % mac)
92 leased_macs[mac] = true
93 elseif cmd == "whitelist" and not whitelist_macs[mac] then
94 print("Adding %s to whitelist" % mac)
96 whitelist_macs[mac] = true
97 elseif cmd == "blacklist" and not blacklist_macs[mac] then
98 print("Adding %s to blacklist" % mac)
100 blacklist_macs[mac] = true
102 print("The mac %s is already %sed" %{ mac, cmd })
104 elseif mac and cmd == "remove" then
105 if leased_macs[mac] then
106 print("Removing %s from leases" % mac)
108 leased_macs[mac] = nil
109 elseif whitelist_macs[mac] then
110 print("Removing %s from whitelist" % mac)
111 remove_whitelist(mac)
112 whitelist_macs[mac] = nil
113 elseif blacklist_macs[mac] then
114 print("Removing %s from blacklist" % mac)
115 remove_blacklist(mac)
116 blacklist_macs[mac] = nil
118 print("The mac %s is not known" % mac)
121 print("Can not find mac for ip %s" % argv[i])
127 elseif cmd == "sync" then
130 elseif cmd == "list" then
135 print("\n luci-splash list\n List connected, black- and whitelisted clients")
136 print("\n luci-splash sync\n Synchronize firewall rules and clear expired leases")
137 print("\n luci-splash lease <MAC-or-IP>\n Create a lease for the given address")
138 print("\n luci-splash blacklist <MAC-or-IP>\n Add given address to blacklist")
139 print("\n luci-splash whitelist <MAC-or-IP>\n Add given address to whitelist")
140 print("\n luci-splash remove <MAC-or-IP>\n Remove given address from the lease-, black- or whitelist")
147 -- Get a list of known mac addresses
148 function get_known_macs(list)
149 local leased_macs = { }
151 if not list or list == "lease" then
152 uci:foreach("luci_splash_leases", "lease",
153 function(s) leased_macs[s.mac:lower()] = true end)
156 if not list or list == "whitelist" then
157 uci:foreach("luci_splash", "whitelist",
158 function(s) leased_macs[s.mac:lower()] = true end)
161 if not list or list == "blacklist" then
162 uci:foreach("luci_splash", "blacklist",
163 function(s) leased_macs[s.mac:lower()] = true end)
170 -- Helper to delete iptables rules
171 function ipt_delete_all(args, comp, off)
173 for i, r in ipairs(ipt:find(args)) do
174 if comp == nil or comp(r) then
175 off[r.table] = off[r.table] or { }
176 off[r.table][r.chain] = off[r.table][r.chain] or 0
178 os.execute("iptables -t %q -D %q %d 2>/dev/null"
179 %{ r.table, r.chain, r.index - off[r.table][r.chain] })
181 off[r.table][r.chain] = off[r.table][r.chain] + 1
186 function ipt6_delete_all(args, comp, off)
188 for i, r in ipairs(ipt:find(args)) do
189 if comp == nil or comp(r) then
190 off[r.table] = off[r.table] or { }
191 off[r.table][r.chain] = off[r.table][r.chain] or 0
193 os.execute("ip6tables -t %q -D %q %d 2>/dev/null"
194 %{ r.table, r.chain, r.index - off[r.table][r.chain] })
196 off[r.table][r.chain] = off[r.table][r.chain] + 1
202 -- Convert mac to uci-compatible section name
203 function convert_mac_to_secname(mac)
204 return string.gsub(mac, ":", "")
207 -- Add a lease to state and invoke add_rule
208 function add_lease(mac, arp, no_uci)
211 -- Get current ip address
213 for _, entry in ipairs(arp or net.arptable()) do
214 if entry["HW address"]:lower() == mac then
215 ipaddr = entry["IP address"]
220 -- Add lease if there is an ip addr
223 uci:section("luci_splash_leases", "lease", convert_mac_to_secname(mac), {
228 uci:save("luci_splash_leases")
230 add_lease_rule(mac, ipaddr)
232 print("Found no active IP for %s, lease not added" % mac)
237 -- Remove a lease from state and invoke remove_rule
238 function remove_lease(mac)
241 uci:delete_all("luci_splash_leases", "lease",
243 if s.mac:lower() == mac then
244 remove_lease_rule(mac, s.ipaddr)
250 uci:save("luci_splash_leases")
254 -- Add a whitelist entry
255 function add_whitelist(mac)
256 uci:section("luci_splash", "whitelist", convert_mac_to_secname(mac), { mac = mac })
257 uci:save("luci_splash")
258 uci:commit("luci_splash")
259 add_whitelist_rule(mac)
263 -- Add a blacklist entry
264 function add_blacklist(mac)
265 uci:section("luci_splash", "blacklist", convert_mac_to_secname(mac), { mac = mac })
266 uci:save("luci_splash")
267 uci:commit("luci_splash")
268 add_blacklist_rule(mac)
272 -- Remove a whitelist entry
273 function remove_whitelist(mac)
275 uci:delete_all("luci_splash", "whitelist",
276 function(s) return not s.mac or s.mac:lower() == mac end)
277 uci:save("luci_splash")
278 uci:commit("luci_splash")
279 remove_lease_rule(mac)
283 -- Remove a blacklist entry
284 function remove_blacklist(mac)
286 uci:delete_all("luci_splash", "blacklist",
287 function(s) return not s.mac or s.mac:lower() == mac end)
288 uci:save("luci_splash")
289 uci:commit("luci_splash")
290 remove_lease_rule(mac)
294 -- Add an iptables rule
295 function add_lease_rule(mac, ipaddr)
296 os.execute("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j MARK --set-mark 79" % mac)
297 os.execute("iptables -t mangle -I luci_splash_mark_in -d %q -j MARK --set-mark 80 -m comment --comment %s" % {ipaddr, mac:upper()})
300 os.execute("ip6tables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j MARK --set-mark 79" % mac)
301 -- not working yet, needs the ip6addr
302 --os.execute("ip6tables -t mangle -I luci_splash_mark_in -d %q -j MARK --set-mark 80 -m comment --comment %s" % {ipaddr, mac:upper()})
306 os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
307 os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
309 os.execute("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
314 -- Remove lease, black- or whitelist rules
315 function remove_lease_rule(mac, ipaddr)
317 ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"/*", mac:upper()}})
318 ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
319 ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC", mac:upper()}})
320 ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC", mac:upper()}})
322 --ipt6_delete_all({table="mangle", chain="luci_splash_mark_in", options={"/*", mac:upper()}})
323 ipt6_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
324 ipt6_delete_all({table="filter", chain="luci_splash_filter", options={"MAC", mac:upper()}})
329 -- Add whitelist rules
330 function add_whitelist_rule(mac)
331 os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
332 os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
334 os.execute("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
339 -- Add blacklist rules
340 function add_blacklist_rule(mac)
341 os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
343 os.execute("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
348 -- Synchronise leases, remove abandoned rules
352 local time = os.time()
354 -- Current leases in state files
355 local leases = uci:get_all("luci_splash_leases")
357 -- Convert leasetime to seconds
358 local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime")) * 3600
361 uci:load("luci_splash_leases")
362 uci:revert("luci_splash_leases")
365 for k, v in pairs(leases) do
366 if v[".type"] == "lease" then
367 if os.difftime(time, tonumber(v.start)) > leasetime then
369 remove_lease_rule(v.mac, v.ipaddr)
372 uci:section("luci_splash_leases", "lease", convert_mac_to_secname(v.mac), {
381 uci:save("luci_splash_leases")
383 -- Get the mac addresses of current leases
384 local macs = get_known_macs()
388 ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
389 function(r) return not macs[r.options[2]:lower()] end)
390 ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC"}},
391 function(r) return not macs[r.options[2]:lower()] end)
392 ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
393 function(r) return not macs[r.options[2]:lower()] end)
394 ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"/*", "MARK", "set"}},
395 function(r) return not macs[r.options[2]:lower()] end)
399 ipt6_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
400 function(r) return not macs[r.options[2]:lower()] end)
401 ipt6_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
402 function(r) return not macs[r.options[2]:lower()] end)
403 --ipt6_delete_all({table="mangle", chain="luci_splash_mark_in", options={"/*", "MARK", "set"}},
404 -- function(r) return not macs[r.options[2]:lower()] end)
412 -- Get current arp cache
414 for _, entry in ipairs(net.arptable()) do
415 arpcache[entry["HW address"]:lower()] = { entry["Device"]:lower(), entry["IP address"]:lower() }
418 -- Find traffic usage
419 local function traffic(lease)
421 local traffic_out = 0
423 local rin = ipt:find({table="mangle", chain="luci_splash_mark_in", destination=lease.ipaddr})
424 local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", lease.mac:upper()}})
426 if rin and #rin > 0 then traffic_in = math.floor( rin[1].bytes / 1024) end
427 if rout and #rout > 0 then traffic_out = math.floor(rout[1].bytes / 1024) end
429 return traffic_in, traffic_out
433 local leases = uci:get_all("luci_splash_leases")
436 "%-17s %-15s %-9s %-4s %-7s %20s",
437 "MAC", "IP", "State", "Dur.", "Intf.", "Traffic down/up"
441 for _, s in pairs(leases) do
442 if s[".type"] == "lease" and s.mac then
443 local ti, to = traffic(s)
444 local mac = s.mac:lower()
445 local arp = arpcache[mac]
447 "%-17s %-15s %-9s %3dm %-7s %7dKB %7dKB",
448 mac, s.ipaddr, "leased",
449 math.floor(( os.time() - tonumber(s.start) ) / 60),
450 arp and arp[1] or "?", ti, to
455 -- Whitelist, Blacklist
456 for _, s in luci.util.spairs(leases,
457 function(a,b) return leases[a][".type"] > leases[b][".type"] end
459 if (s[".type"] == "whitelist" or s[".type"] == "blacklist") and s.mac then
460 local mac = s.mac:lower()
461 local arp = arpcache[mac]
463 "%-17s %-15s %-9s %4s %-7s %9s %9s",
464 mac, arp and arp[2] or "?", s[".type"],
465 "- ", arp and arp[1] or "?", "-", "-"