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
17 os.execute("lock -w /var/run/luci_splash.lock && lock /var/run/luci_splash.lock")
21 os.execute("lock -u /var/run/luci_splash.lock")
25 local cmd = table.remove(argv, 1)
28 limit_up = tonumber(uci:get("luci_splash", "general", "limit_up")) or 0
29 limit_down = tonumber(uci:get("luci_splash", "general", "limit_down")) or 0
31 if ( cmd == "lease" or cmd == "add-rules" or cmd == "remove" or
32 cmd == "whitelist" or cmd == "blacklist" ) and #argv > 0
36 local arp_cache = net.arptable()
37 local leased_macs = get_known_macs("lease")
38 local blacklist_macs = get_known_macs("blacklist")
39 local whitelist_macs = get_known_macs("whitelist")
41 for i, adr in ipairs(argv) do
46 for _, e in ipairs(arp_cache) do
47 if e["IP address"] == adr then
54 if mac and cmd == "add-rules" then
55 if leased_macs[mac] then
56 add_lease(mac, arp_cache, true)
57 elseif blacklist_macs[mac] then
58 add_blacklist_rule(mac)
59 elseif whitelist_macs[mac] then
60 add_whitelist_rule(mac)
62 elseif mac and ( cmd == "whitelist" or cmd == "blacklist" or cmd == "lease" ) then
63 if cmd ~= "lease" and leased_macs[mac] then
64 print("Removing %s from leases" % mac)
66 leased_macs[mac] = nil
69 if cmd ~= "whitelist" and whitelist_macs[mac] then
70 print("Removing %s from whitelist" % mac)
72 whitelist_macs[mac] = nil
75 if cmd ~= "blacklist" and blacklist_macs[mac] then
76 print("Removing %s from blacklist" % mac)
78 blacklist_macs[mac] = nil
81 if cmd == "lease" and not leased_macs[mac] then
82 print("Adding %s to leases" % mac)
84 leased_macs[mac] = true
85 elseif cmd == "whitelist" and not whitelist_macs[mac] then
86 print("Adding %s to whitelist" % mac)
88 whitelist_macs[mac] = true
89 elseif cmd == "blacklist" and not blacklist_macs[mac] then
90 print("Adding %s to blacklist" % mac)
92 blacklist_macs[mac] = true
94 print("The mac %s is already %sed" %{ mac, cmd })
96 elseif mac and cmd == "remove" then
97 if leased_macs[mac] then
98 print("Removing %s from leases" % mac)
100 leased_macs[mac] = nil
101 elseif whitelist_macs[mac] then
102 print("Removing %s from whitelist" % mac)
103 remove_whitelist(mac)
104 whitelist_macs[mac] = nil
105 elseif blacklist_macs[mac] then
106 print("Removing %s from blacklist" % mac)
107 remove_blacklist(mac)
108 blacklist_macs[mac] = nil
110 print("The mac %s is not known" % mac)
113 print("Can not find mac for ip %s" % argv[i])
119 elseif cmd == "sync" then
122 elseif cmd == "list" then
127 print("\n luci-splash list\n List connected, black- and whitelisted clients")
128 print("\n luci-splash sync\n Synchronize firewall rules and clear expired leases")
129 print("\n luci-splash lease <MAC-or-IP>\n Create a lease for the given address")
130 print("\n luci-splash blacklist <MAC-or-IP>\n Add given address to blacklist")
131 print("\n luci-splash whitelist <MAC-or-IP>\n Add given address to whitelist")
132 print("\n luci-splash remove <MAC-or-IP>\n Remove given address from the lease-, black- or whitelist")
139 -- Get a list of known mac addresses
140 function get_known_macs(list)
141 local leased_macs = { }
143 if not list or list == "lease" then
144 uci:foreach("luci_splash", "lease",
145 function(s) leased_macs[s.mac:lower()] = true end)
148 if not list or list == "whitelist" then
149 uci:foreach("luci_splash", "whitelist",
150 function(s) leased_macs[s.mac:lower()] = true end)
153 if not list or list == "blacklist" then
154 uci:foreach("luci_splash", "blacklist",
155 function(s) leased_macs[s.mac:lower()] = true end)
162 -- Get a list of known ip addresses
163 function get_known_ips(macs, arp)
164 local leased_ips = { }
165 if not macs then macs = get_known_macs() end
166 for _, e in ipairs(arp or net.arptable()) do
167 if macs[e["HW address"]] then leased_ips[e["IP address"]] = true end
173 -- Helper to delete iptables rules
174 function ipt_delete_all(args, comp, off)
176 for i, r in ipairs(ipt:find(args)) do
177 if comp == nil or comp(r) then
178 off[r.table] = off[r.table] or { }
179 off[r.table][r.chain] = off[r.table][r.chain] or 0
181 os.execute("iptables -t %q -D %q %d 2>/dev/null"
182 %{ r.table, r.chain, r.index - off[r.table][r.chain] })
184 off[r.table][r.chain] = off[r.table][r.chain] + 1
190 -- Add a lease to state and invoke add_rule
191 function add_lease(mac, arp, no_uci)
194 -- Get current ip address
196 for _, entry in ipairs(arp or net.arptable()) do
197 if entry["HW address"] == mac then
198 ipaddr = entry["IP address"]
203 -- Add lease if there is an ip addr
206 uci:section("luci_splash", "lease", nil, {
211 uci:save("luci_splash")
213 add_lease_rule(mac, ipaddr)
215 print("Found no active IP for %s, lease not added" % mac)
220 -- Remove a lease from state and invoke remove_rule
221 function remove_lease(mac)
224 uci:delete_all("luci_splash", "lease",
226 if s.mac:lower() == mac then
227 remove_lease_rule(mac, s.ipaddr)
233 uci:save("luci_splash")
237 -- Add a whitelist entry
238 function add_whitelist(mac)
239 uci:section("luci_splash", "whitelist", nil, { mac = mac })
240 uci:save("luci_splash")
241 uci:commit("luci_splash")
242 add_whitelist_rule(mac)
246 -- Add a blacklist entry
247 function add_blacklist(mac)
248 uci:section("luci_splash", "blacklist", nil, { mac = mac })
249 uci:save("luci_splash")
250 uci:commit("luci_splash")
251 add_blacklist_rule(mac)
255 -- Remove a whitelist entry
256 function remove_whitelist(mac)
258 uci:delete_all("luci_splash", "whitelist",
259 function(s) return not s.mac or s.mac:lower() == mac end)
260 uci:save("luci_splash")
261 uci:commit("luci_splash")
262 remove_lease_rule(mac)
266 -- Remove a blacklist entry
267 function remove_blacklist(mac)
269 uci:delete_all("luci_splash", "blacklist",
270 function(s) return not s.mac or s.mac:lower() == mac end)
271 uci:save("luci_splash")
272 uci:commit("luci_splash")
273 remove_lease_rule(mac)
277 -- Add an iptables rule
278 function add_lease_rule(mac, ipaddr)
279 if limit_up > 0 and limit_down > 0 then
280 os.execute("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j MARK --set-mark 79" % mac)
281 os.execute("iptables -t mangle -I luci_splash_mark_in -d %q -j MARK --set-mark 80" % ipaddr)
284 os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
285 os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
289 -- Remove lease, black- or whitelist rules
290 function remove_lease_rule(mac, ipaddr)
294 ipt_delete_all({table="mangle", chain="luci_splash_mark_in", destination=ipaddr})
295 ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
298 ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC", mac:upper()}})
299 ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC", mac:upper()}})
303 -- Add whitelist rules
304 function add_whitelist_rule(mac)
305 os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
306 os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
310 -- Add blacklist rules
311 function add_blacklist_rule(mac)
312 os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
313 os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j DROP" % mac)
317 -- Synchronise leases, remove abandoned rules
321 local time = os.time()
323 -- Current leases in state files
324 local leases = uci:get_all("luci_splash")
326 -- Convert leasetime to seconds
327 local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime")) * 3600
330 uci:load("luci_splash")
331 uci:revert("luci_splash")
334 for k, v in pairs(leases) do
335 if v[".type"] == "lease" then
336 if os.difftime(time, tonumber(v.start)) > leasetime then
338 remove_lease_rule(v.mac, v.ipaddr)
341 uci:section("luci_splash", "lease", nil, {
350 uci:save("luci_splash")
352 -- Get current IPs and MAC addresses
353 local macs = get_known_macs()
354 local ips = get_known_ips(macs)
358 ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
359 function(r) return not macs[r.options[2]:lower()] end)
361 ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC"}},
362 function(r) return not macs[r.options[2]:lower()] end)
364 ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
365 function(r) return not macs[r.options[2]:lower()] end)
367 ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"MARK", "set"}},
368 function(r) return not ips[r.destination] end)
375 -- Get current arp cache
377 for _, entry in ipairs(net.arptable()) do
378 arpcache[entry["HW address"]] = { entry["Device"], entry["IP address"] }
381 -- Find traffic usage
382 local function traffic(lease)
384 local traffic_out = 0
386 local rin = ipt:find({table="mangle", chain="luci_splash_mark_in", destination=lease.ipaddr})
387 local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", lease.mac:upper()}})
389 if rin and #rin > 0 then traffic_in = math.floor( rin[1].bytes / 1024) end
390 if rout and #rout > 0 then traffic_out = math.floor(rout[1].bytes / 1024) end
392 return traffic_in, traffic_out
396 local leases = uci:get_all("luci_splash")
399 "%-17s %-15s %-9s %-4s %-7s %20s",
400 "MAC", "IP", "State", "Dur.", "Intf.", "Traffic down/up"
404 for _, s in pairs(leases) do
405 if s[".type"] == "lease" and s.mac then
406 local ti, to = traffic(s)
407 local mac = s.mac:lower()
408 local arp = arpcache[mac]
410 "%-17s %-15s %-9s %3dm %-7s %7dKB %7dKB",
411 mac, s.ipaddr, "leased",
412 math.floor(( os.time() - tonumber(s.start) ) / 60),
413 arp and arp[1] or "?", ti, to
418 -- Whitelist, Blacklist
419 for _, s in luci.util.spairs(leases,
420 function(a,b) return leases[a][".type"] > leases[b][".type"] end
422 if (s[".type"] == "whitelist" or s[".type"] == "blacklist") and s.mac then
423 local mac = s.mac:lower()
424 local arp = arpcache[mac]
426 "%-17s %-15s %-9s %4s %-7s %9s %9s",
427 mac, arp and arp[2] or "?", s[".type"], "- ",
428 arp and arp[1] or "?", "-", "-"