applications/luci-splash: Do not allow to lease whitelisted clients
[project/luci.git] / applications / luci-splash / root / usr / sbin / luci-splash
1 #!/usr/bin/lua
2
3 utl = require "luci.util"
4 sys = require "luci.sys"
5
6 require("luci.model.uci")
7 require("luci.sys.iptparser")
8
9 -- Init state session
10 local uci = luci.model.uci.cursor_state()
11 local ipt = luci.sys.iptparser.IptParser()
12 local net = sys.net
13 local fs = require "luci.fs"
14 local ip = require "luci.ip"
15
16 local debug = true
17
18 local has_ipv6 = fs.access("/proc/net/ipv6_route") and fs.access("/usr/sbin/ip6tables")
19
20 function lock()
21         os.execute("lock /var/run/luci_splash.lock")
22 end
23
24 function unlock()
25         os.execute("lock -u /var/run/luci_splash.lock")
26 end
27
28 function exec(cmd)
29         local ret = sys.exec(cmd)
30         if debug then
31                 print('+ ' .. cmd)
32                 if ret and ret ~= "" then
33                         print(ret)
34                 end
35         end
36 end
37
38 function get_id(ip)
39         local o3, o4 = ip:match("[0-9]+%.[0-9]+%.([0-9]+)%.([0-9]+)")
40         if o3 and 04 then
41                 return string.format("%02X%s", tonumber(o3), "") .. string.format("%02X%s", tonumber(o4), "")
42         else
43                 return false
44         end
45 end
46
47 function update_stats(leased, whitelisted, whitelisttotal, blacklisted, blacklisttotal) 
48         local leases = uci:get_all("luci_splash_leases", "stats")
49         uci:delete("luci_splash_leases", "stats")
50         uci:section("luci_splash_leases", "stats", "stats", {
51                 leases    = leased or (leases and leases.leases) or 0,
52                 whitelisttotal = whitelisttotal or (leased and leases.whitelisttotal) or 0,
53                 whitelistonline = whitelisted or (leases and leases.whitelistonline) or 0,
54                 blacklisttotal = blacklisttotal or (leases and leases.blacklisttotal) or 0,
55                 blacklistonline = blacklisted or (leases and leases.blacklistonline) or 0,
56         })
57         uci:save("luci_splash_leases")
58 end
59
60 function get_device_for_ip(ipaddr)
61         local dev
62         uci:foreach("network", "interface", function(s)
63                 if s.ipaddr and s.netmask then
64                         local network = ip.IPv4(s.ipaddr, s.netmask)
65                         if network:contains(ip.IPv4(ipaddr)) then
66                                 -- this should be rewritten to luci functions if possible
67                                 dev = utl.trim(sys.exec(". /lib/functions/network.sh; network_get_device IFNAME '" ..  s['.name'] .. "'; echo $IFNAME"))
68                         end
69                 end
70         end)
71         return dev
72 end
73
74 function get_physdev(interface)
75         local dev
76         dev = utl.trim(sys.exec(". /lib/functions/network.sh; network_get_device IFNAME '" ..  interface .. "'; echo $IFNAME"))
77         return dev
78 end
79
80
81
82 function get_filter_handle(parent, direction, device, mac)
83         local input = utl.split(sys.exec('/usr/sbin/tc filter show dev ' .. device .. ' parent ' .. parent) or {})
84         local tbl = {}
85         local handle
86         for k, v in pairs(input) do
87                 handle = v:match('filter protocol ip pref %d+ u32 fh (%d*:%d*:%d*) order')
88                 if handle then
89                         local mac, mac1, mac2, mac3, mac4, mac5, mac6
90                         if direction == 'src' then
91                                 mac1, mac2, mac3, mac4 = input[k+1]:match('match ([%a%d][%a%d])([%a%d][%a%d])([%a%d][%a%d])([%a%d][%a%d])/ffffffff')
92                                 mac5, mac6 = input[k+2]:match('match ([%a%d][%a%d])([%a%d][%a%d])0000/ffff0000')
93                         else
94                                 mac1, mac2 = input[k+1]:match('match 0000([%a%d][%a%d])([%a%d][%a%d])/0000ffff')
95                                 mac3, mac4, mac5, mac6 = input[k+2]:match('match ([%a%d][%a%d])([%a%d][%a%d])([%a%d][%a%d])([%a%d][%a%d])/ffffffff')
96                         end
97                         if mac1 and mac2 and mac3 and mac4 and mac5 and mac6 then
98                                 mac = "%s:%s:%s:%s:%s:%s" % { mac1, mac2, mac3, mac4, mac5, mac6 }
99                                 tbl[mac] = handle
100                         end
101                 end
102         end
103         if tbl[mac] then
104                 handle = tbl[mac]
105         end
106         return handle
107 end
108
109 function macvalid(mac)
110         if mac and mac:match(
111                 "^[a-fA-F0-9][a-fA-F0-9]:[a-fA-F0-9][a-fA-F0-9]:" ..
112                 "[a-fA-F0-9][a-fA-F0-9]:[a-fA-F0-9][a-fA-F0-9]:" ..
113                 "[a-fA-F0-9][a-fA-F0-9]:[a-fA-F0-9][a-fA-F0-9]$"
114         ) then
115                 return true
116         end
117
118         return false
119 end
120
121 function ipvalid(ipaddr)
122         if ipaddr then
123                 return ip.IPv4(ipaddr) and true or false
124         end
125
126         return false
127 end
128
129 function main(argv)
130         local cmd = table.remove(argv, 1)
131         local arg = argv[1]
132
133         limit_up = (tonumber(uci:get("luci_splash", "general", "limit_up")) or 0) * 8
134         limit_down = (tonumber(uci:get("luci_splash", "general", "limit_down")) or 0) * 8
135
136         if ( cmd == "lease" or cmd == "add-rules" or cmd == "remove" or
137              cmd == "whitelist" or cmd == "blacklist" or cmd == "status" ) and #argv > 0
138         then
139                 if not (macvalid(arg) or ipvalid(arg)) then
140                         print("Invalid argument. The second argument must " ..
141                                 "be a valid IPv4 or Mac Address.")
142                         os.exit(1)
143                 end
144
145                 lock()
146
147                 local arp_cache      = net.arptable()
148                 local leased_macs    = get_known_macs("lease")
149                 local blacklist_macs = get_known_macs("blacklist")
150                 local whitelist_macs = get_known_macs("whitelist")
151
152                 for i, adr in ipairs(argv) do
153                         local mac = nil
154                         if adr:find(":") then
155                                 mac = adr:lower()
156                         else
157                                 for _, e in ipairs(arp_cache) do
158                                         if e["IP address"] == adr then
159                                                 mac = e["HW address"]:lower()
160                                                 break
161                                         end
162                                 end
163                         end
164
165                         if mac and cmd == "add-rules" then
166                                 if leased_macs[mac] then
167                                         add_lease(mac, arp_cache, true)
168                                 elseif blacklist_macs[mac] then
169                                         add_blacklist_rule(mac)
170                                 elseif whitelist_macs[mac] then
171                                         add_whitelist_rule(mac)
172                                 end
173                         elseif mac and cmd == "status" then
174                                 print(leased_macs[mac] and "lease"
175                                         or whitelist_macs[mac] and "whitelist"
176                                         or blacklist_macs[mac] and "blacklist"
177                                         or "new")
178                         elseif mac and ( cmd == "whitelist" or cmd == "blacklist" or cmd == "lease" ) then
179                                 if cmd ~= "lease" and leased_macs[mac] then
180                                         print("Removing %s from leases" % mac)
181                                         remove_lease(mac)
182                                         leased_macs[mac] = nil
183                                 end
184
185                                 if cmd ~= "whitelist" and whitelist_macs[mac] then
186                                         if cmd == "lease" then
187                                                 print('%s is whitelisted. Remove it before you can lease it.' % mac)
188                                         else
189                                                 print("Removing %s from whitelist" % mac)
190                                                 remove_whitelist(mac)
191                                                 whitelist_macs[mac] = nil
192                                         end
193                                 end
194
195                                 if cmd == "whitelist" and leased_macs[mac] then
196                                         print("Removing %s from leases" % mac)
197                                         remove_lease(mac)
198                                         leased_macs[mac] = nil
199                                 end
200
201                                 if cmd ~= "blacklist" and blacklist_macs[mac] then
202                                         print("Removing %s from blacklist" % mac)
203                                         remove_blacklist(mac)
204                                         blacklist_macs[mac] = nil
205                                 end
206
207                                 if cmd == "lease" and not leased_macs[mac] then
208                                         if not whitelist_macs[mac] then
209                                                 print("Adding %s to leases" % mac)
210                                                 add_lease(mac)
211                                                 leased_macs[mac] = true
212                                         end
213                                 elseif cmd == "whitelist" and not whitelist_macs[mac] then
214                                         print("Adding %s to whitelist" % mac)
215                                         add_whitelist(mac)
216                                         whitelist_macs[mac] = true
217                                 elseif cmd == "blacklist" and not blacklist_macs[mac] then
218                                         print("Adding %s to blacklist" % mac)
219                                         add_blacklist(mac)
220                                         blacklist_macs[mac] = true
221                                 else
222                                         print("The mac %s is already %sed" %{ mac, cmd })
223                                 end
224                         elseif mac and cmd == "remove" then
225                                 if leased_macs[mac] then
226                                         print("Removing %s from leases" % mac)
227                                         remove_lease(mac)
228                                         leased_macs[mac] = nil
229                                 elseif whitelist_macs[mac] then
230                                         print("Removing %s from whitelist" % mac)
231                                         remove_whitelist(mac)
232                                         whitelist_macs[mac] = nil                                       
233                                 elseif blacklist_macs[mac] then
234                                         print("Removing %s from blacklist" % mac)
235                                         remove_blacklist(mac)
236                                         blacklist_macs[mac] = nil
237                                 else
238                                         print("The mac %s is not known" % mac)
239                                 end
240                         else
241                                 print("Can not find mac for ip %s" % argv[i])
242                         end
243                 end
244
245                 unlock()
246                 os.exit(0)      
247         elseif cmd == "sync" then
248                 sync()
249                 os.exit(0)
250         elseif cmd == "list" then
251                 list()
252                 os.exit(0)
253         else
254                 print("Usage:")
255                 print("\n  luci-splash list\n    List connected, black- and whitelisted clients")
256                 print("\n  luci-splash sync\n    Synchronize firewall rules and clear expired leases")
257                 print("\n  luci-splash lease <MAC-or-IP>\n    Create a lease for the given address")
258                 print("\n  luci-splash blacklist <MAC-or-IP>\n    Add given address to blacklist")
259                 print("\n  luci-splash whitelist <MAC-or-IP>\n    Add given address to whitelist")
260                 print("\n  luci-splash remove <MAC-or-IP>\n    Remove given address from the lease-, black- or whitelist")
261                 print("")
262
263                 os.exit(1)      
264         end
265 end
266
267 -- Get current arp cache
268 function get_arpcache()
269         local arpcache = { }
270         for _, entry in ipairs(net.arptable()) do
271                 arpcache[entry["HW address"]:lower()] = { entry["Device"]:lower(), entry["IP address"]:lower() }
272         end
273         return arpcache
274 end
275
276 -- Get a list of known mac addresses
277 function get_known_macs(list)
278         local leased_macs = { }
279
280         if not list or list == "lease" then
281                 uci:foreach("luci_splash_leases", "lease",
282                         function(s) leased_macs[s.mac:lower()] = true end)
283         end
284
285         if not list or list == "whitelist" then
286                 uci:foreach("luci_splash", "whitelist",
287                         function(s) leased_macs[s.mac:lower()] = true end)
288         end
289
290         if not list or list == "blacklist" then
291                 uci:foreach("luci_splash", "blacklist",
292                         function(s) leased_macs[s.mac:lower()] = true end)
293         end
294
295         return leased_macs
296 end
297
298
299 -- Helper to delete iptables rules
300 function ipt_delete_all(args, comp, off)
301         off = off or { }
302         for i, r in ipairs(ipt:find(args)) do
303                 if comp == nil or comp(r) then
304                         off[r.table] = off[r.table] or { }
305                         off[r.table][r.chain] = off[r.table][r.chain] or 0
306
307                         exec("iptables -t %q -D %q %d 2>/dev/null"
308                                 %{ r.table, r.chain, r.index - off[r.table][r.chain] })
309
310                         off[r.table][r.chain] = off[r.table][r.chain] + 1
311                 end
312         end
313 end
314
315 function ipt6_delete_all(args, comp, off)
316         off = off or { }
317         for i, r in ipairs(ipt:find(args)) do
318                 if comp == nil or comp(r) then
319                         off[r.table] = off[r.table] or { }
320                         off[r.table][r.chain] = off[r.table][r.chain] or 0
321
322                         exec("ip6tables -t %q -D %q %d 2>/dev/null"
323                                 %{ r.table, r.chain, r.index - off[r.table][r.chain] })
324
325                         off[r.table][r.chain] = off[r.table][r.chain] + 1
326                 end
327         end
328 end
329
330
331 -- Convert mac to uci-compatible section name
332 function convert_mac_to_secname(mac)
333         return string.gsub(mac, ":", "")
334 end
335
336 -- Add a lease to state and invoke add_rule
337 function add_lease(mac, arp, no_uci)
338         mac = mac:lower()
339
340         -- Get current ip address
341         local ipaddr
342         for _, entry in ipairs(arp or net.arptable()) do
343                 if entry["HW address"]:lower() == mac then
344                         ipaddr = entry["IP address"]
345                         break
346                 end
347         end
348
349         -- Add lease if there is an ip addr
350         if ipaddr then
351                 local device = get_device_for_ip(ipaddr)
352                 if not no_uci then
353                         local leased = uci:get("luci_splash_leases", "stats", "leases")
354                         if type(tonumber(leased)) == "number" then
355                                 update_stats(leased + 1, nil, nil, nil, nil)
356                         end
357
358                         uci:section("luci_splash_leases", "lease", convert_mac_to_secname(mac), {
359                                 mac    = mac,
360                                 ipaddr = ipaddr,
361                                 device = device,
362                                 limit_up = limit_up,
363                                 limit_down = limit_down,
364                                 start  = os.time()
365                         })
366                         uci:save("luci_splash_leases")
367                 end
368                 add_lease_rule(mac, ipaddr, device)
369         else
370                 print("Found no active IP for %s, lease not added" % mac)
371         end
372 end
373
374
375 -- Remove a lease from state and invoke remove_rule
376 function remove_lease(mac)
377         mac = mac:lower()
378
379         uci:delete_all("luci_splash_leases", "lease",
380                 function(s)
381                         if s.mac:lower() == mac then
382                                 remove_lease_rule(mac, s.ipaddr, s.device, tonumber(s.limit_up), tonumber(s.limit_down))
383                                 local leased = uci:get("luci_splash_leases", "stats", "leases")
384                                 if type(tonumber(leased)) == "number" and tonumber(leased) > 0 then
385                                         update_stats(leased - 1, nil, nil, nil, nil)
386                                 end
387                                 return true
388                         end
389                         return false
390                 end)
391                 
392         uci:save("luci_splash_leases")
393 end
394
395
396 -- Add a whitelist entry
397 function add_whitelist(mac)
398         uci:section("luci_splash", "whitelist", convert_mac_to_secname(mac), { mac = mac })
399         uci:save("luci_splash")
400         uci:commit("luci_splash")
401         add_whitelist_rule(mac)
402 end
403
404
405 -- Add a blacklist entry
406 function add_blacklist(mac)
407         uci:section("luci_splash", "blacklist", convert_mac_to_secname(mac), { mac = mac })
408         uci:save("luci_splash")
409         uci:commit("luci_splash")
410         add_blacklist_rule(mac)
411 end
412
413
414 -- Remove a whitelist entry
415 function remove_whitelist(mac)
416         mac = mac:lower()
417         uci:delete_all("luci_splash", "whitelist",
418                 function(s) return not s.mac or s.mac:lower() == mac end)
419         uci:save("luci_splash")
420         uci:commit("luci_splash")
421         remove_lease_rule(mac)
422         remove_whitelist_tc(mac)
423 end
424
425 function remove_whitelist_tc(mac)
426         uci:foreach("luci_splash", "iface", function(s)
427                 local device = get_physdev(s['.name'])
428                 if device and device ~= "" then
429                         if debug then
430                                 print("Removing whitelist filters for %s interface %s." % {mac, device})
431                         end
432                         local handle = get_filter_handle('ffff:', 'src', device, mac)
433                         if handle then
434                                 exec('tc filter del dev "%s" parent ffff: protocol ip prio 1 handle %s u32' % { device, handle })
435                         else
436                                 print('Warning! Could not get a handle for %s parent :ffff on interface %s' % { mac, device })
437                         end
438                         local handle = get_filter_handle('1:', 'dest', device, mac)
439                         if handle then
440                                 exec('tc filter del dev "%s" parent 1:0 protocol ip prio 1 handle %s u32' % { device, handle })
441                         else
442                                 print('Warning! Could not get a handle for %s parent 1:0 on interface %s' % { mac, device })
443                         end
444                 end
445         end)
446 end
447
448 -- Remove a blacklist entry
449 function remove_blacklist(mac)
450         mac = mac:lower()
451         uci:delete_all("luci_splash", "blacklist",
452                 function(s) return not s.mac or s.mac:lower() == mac end)
453         uci:save("luci_splash")
454         uci:commit("luci_splash")
455         remove_lease_rule(mac)
456 end
457
458
459 -- Add an iptables rule
460 function add_lease_rule(mac, ipaddr, device)
461         local id
462         if ipaddr then
463                 id = get_id(ipaddr)
464         end
465
466         exec("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j RETURN" % mac)
467
468         if id and device then
469                 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()})
470         end
471
472         if has_ipv6 then
473                 exec("ip6tables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j MARK --set-mark 79" % mac)
474                 -- not working yet, needs the ip6addr
475                 --exec("ip6tables -t mangle -I luci_splash_mark_in -d %q -j MARK --set-mark 80 -m comment --comment %s" % {ipaddr, mac:upper()})
476         end
477
478
479         if device and tonumber(limit_up) > 0 then
480                 exec('tc filter add dev "%s" parent ffff: protocol ip prio 2 u32 match ether src %s police rate %skbit mtu 6k burst 6k drop' % {device, mac, limit_up})
481         end
482
483         if id and device and tonumber(limit_down) > 0 then
484                 exec("tc class add dev %s parent 1: classid 1:0x%s htb rate %skbit" % { device, id, limit_down })
485                 exec("tc qdisc add dev %s parent 1:%s sfq perturb 10" % { device, id })
486         end
487
488         exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
489         exec("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
490         if has_ipv6 then
491                 exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
492         end
493 end
494
495
496 -- Remove lease, black- or whitelist rules
497 function remove_lease_rule(mac, ipaddr, device, limit_up, limit_down)
498         local id
499         if ipaddr then
500                 id = get_id(ipaddr)
501         end
502
503         ipt:resync()
504         ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"/*", mac:upper()}})
505         ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
506         ipt_delete_all({table="filter", chain="luci_splash_filter",   options={"MAC", mac:upper()}})
507         ipt_delete_all({table="nat",    chain="luci_splash_leases",   options={"MAC", mac:upper()}})
508         if has_ipv6 then
509                 ipt6_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
510                 ipt6_delete_all({table="filter", chain="luci_splash_filter",   options={"MAC", mac:upper()}})
511         end
512         if device and tonumber(limit_up) > 0 then
513                 local handle = get_filter_handle('ffff:', 'src', device, mac)
514                 if handle then
515                         exec('tc filter del dev "%s" parent ffff: protocol ip prio 2 handle %s u32 police rate %skbit mtu 6k burst 6k drop' % {device, handle, limit_up})
516                 else
517                         print('Warning! Could not get a handle for %s parent :ffff on interface %s' % { mac, device })
518                 end
519         end
520
521         -- remove clients class
522         if device and id then
523                 exec('tc class del dev "%s" classid 1:%s' % {device, id})
524                 exec('tc qdisc del dev "%s" parent 1:%s sfq perturb 10' % { device, id })
525         end
526
527 end
528
529
530 -- Add whitelist rules
531 function add_whitelist_rule(mac)
532         exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
533         exec("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
534         if has_ipv6 then
535                 exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
536         end
537         uci:foreach("luci_splash", "iface", function(s)
538                 local device = get_physdev(s['.name'])
539                 if device and device ~= "" then
540                         exec('tc filter add dev "%s" parent ffff: protocol ip prio 1 u32 match ether src %s police pass' % { device, mac })
541                         exec('tc filter add dev "%s" parent 1:0 protocol ip prio 1 u32 match ether dst %s classid 1:1' % { device, mac })
542                 end
543         end)
544 end
545
546
547 -- Add blacklist rules
548 function add_blacklist_rule(mac)
549         exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
550         if has_ipv6 then
551                 exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
552         end
553 end
554
555
556 -- Synchronise leases, remove abandoned rules
557 function sync()
558         lock()
559
560         local time = os.time()
561
562         -- Current leases in state files
563         local leases = uci:get_all("luci_splash_leases")
564         
565         -- Convert leasetime to seconds
566         local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime")) * 3600
567         
568         -- Clean state file
569         uci:load("luci_splash_leases")
570         uci:revert("luci_splash_leases")
571         
572         -- For all leases
573         local leasecount = 0
574         for k, v in pairs(leases) do
575                 if v[".type"] == "lease" then
576                         if os.difftime(time, tonumber(v.start)) > leasetime then
577                                 -- Remove expired
578                                 remove_lease_rule(v.mac, v.ipaddr, v.device, tonumber(v.limit_up), tonumber(v.limit_down))
579                         else
580                                 leasecount = leasecount + 1
581                                 -- Rewrite state
582                                 uci:section("luci_splash_leases", "lease", convert_mac_to_secname(v.mac), {             
583                                         mac    = v.mac,
584                                         ipaddr = v.ipaddr,
585                                         device = v.device,
586                                         limit_up = limit_up,
587                                         limit_down = limit_down,
588                                         start  = v.start
589                                 })
590                         end
591                 end
592         end
593         
594         -- Get the mac addresses of current leases
595         local macs = get_known_macs()
596         local arpcache = get_arpcache()
597
598         local blackwhitelist = uci:get_all("luci_splash")
599         local whitelist_total = 0
600         local whitelist_online = 0
601         local blacklist_total = 0
602         local blacklist_online = 0
603
604         -- Whitelist, Blacklist
605         for _, s in utl.spairs(blackwhitelist,
606                 function(a,b) return blackwhitelist[a][".type"] > blackwhitelist[b][".type"] end
607         ) do
608                 if (s[".type"] == "whitelist") then
609                         whitelist_total = whitelist_total + 1
610                         if s.mac then
611                                 local mac = s.mac:lower()
612                                 if arpcache[mac] then
613                                         whitelist_online = whitelist_online + 1
614                                 end
615                         end
616                 end
617                 if (s[".type"] == "blacklist") then
618                         blacklist_total = blacklist_total + 1
619                         if s.mac then
620                                 local mac = s.mac:lower()
621                                 if arpcache[mac] then
622                                         blacklist_online = blacklist_online + 1
623                                 end
624                         end
625                 end
626         end
627
628         update_stats(leasecount, whitelist_online, whitelist_total, blacklist_online, blacklist_total)
629
630         uci:save("luci_splash_leases")
631
632         ipt:resync()
633
634         ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
635                 function(r) return not macs[r.options[2]:lower()] end)
636         ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC"}},
637                 function(r) return not macs[r.options[2]:lower()] end)
638         ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
639                 function(r) return not macs[r.options[2]:lower()] end)
640         ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"/*", "MARK", "set"}},
641                 function(r) return not macs[r.options[2]:lower()] end)
642
643
644         if has_ipv6 then
645                 ipt6_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
646                         function(r) return not macs[r.options[2]:lower()] end)
647                 ipt6_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
648                         function(r) return not macs[r.options[2]:lower()] end)
649         end
650
651         unlock()
652 end
653
654 -- Show client info
655 function list()
656         local arpcache = get_arpcache()
657         -- Find traffic usage
658         local function traffic(lease)
659                 local traffic_in  = 0
660                 local traffic_out = 0
661
662                 local rin  = ipt:find({table="mangle", chain="luci_splash_mark_in", destination=lease.ipaddr})
663                 local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", lease.mac:upper()}})
664
665                 if rin  and #rin  > 0 then traffic_in  = math.floor( rin[1].bytes / 1024) end
666                 if rout and #rout > 0 then traffic_out = math.floor(rout[1].bytes / 1024) end
667
668                 return traffic_in, traffic_out
669         end
670
671         -- Print listings
672         local leases = uci:get_all("luci_splash_leases")
673         local blackwhitelist = uci:get_all("luci_splash")
674
675         print(string.format(
676                 "%-17s  %-15s  %-9s  %-4s  %-7s  %20s",
677                 "MAC", "IP", "State", "Dur.", "Intf.", "Traffic down/up"
678         ))
679
680         -- Leases
681         for _, s in pairs(leases) do
682                 if s[".type"] == "lease" and s.mac then
683                         local ti, to = traffic(s)
684                         local mac = s.mac:lower()
685                         local arp = arpcache[mac]
686                         print(string.format(
687                                 "%-17s  %-15s  %-9s  %3dm  %-7s  %7dKB  %7dKB",
688                                 mac, s.ipaddr, "leased",
689                                 math.floor(( os.time() - tonumber(s.start) ) / 60),
690                                 arp and arp[1] or "?", ti, to
691                         ))
692                 end
693         end
694
695         -- Whitelist, Blacklist
696         for _, s in utl.spairs(blackwhitelist,
697                 function(a,b) return blackwhitelist[a][".type"] > blackwhitelist[b][".type"] end
698         ) do
699                 if (s[".type"] == "whitelist" or s[".type"] == "blacklist") and s.mac then
700                         local mac = s.mac:lower()
701                         local arp = arpcache[mac]
702                         print(string.format(
703                                 "%-17s  %-15s  %-9s  %4s  %-7s  %9s  %9s",
704                                 mac, arp and arp[2] or "?", s[".type"],
705                                 "- ", arp and arp[1] or "?", "-", "-"
706                         ))
707                 end
708         end
709 end
710
711 main(arg)