applications/luci-splash: Fix a crash when removing whitelisted clients which could...
[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 = false
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                                         print("Removing %s from whitelist" % mac)
187                                         remove_whitelist(mac)
188                                         whitelist_macs[mac] = nil                                       
189                                 end
190
191                                 if cmd ~= "blacklist" and blacklist_macs[mac] then
192                                         print("Removing %s from blacklist" % mac)
193                                         remove_blacklist(mac)
194                                         blacklist_macs[mac] = nil
195                                 end
196
197                                 if cmd == "lease" and not leased_macs[mac] then
198                                         print("Adding %s to leases" % mac)
199                                         add_lease(mac)
200                                         leased_macs[mac] = true
201                                 elseif cmd == "whitelist" and not whitelist_macs[mac] then
202                                         print("Adding %s to whitelist" % mac)
203                                         add_whitelist(mac)
204                                         whitelist_macs[mac] = true
205                                 elseif cmd == "blacklist" and not blacklist_macs[mac] then
206                                         print("Adding %s to blacklist" % mac)
207                                         add_blacklist(mac)
208                                         blacklist_macs[mac] = true
209                                 else
210                                         print("The mac %s is already %sed" %{ mac, cmd })
211                                 end
212                         elseif mac and cmd == "remove" then
213                                 if leased_macs[mac] then
214                                         print("Removing %s from leases" % mac)
215                                         remove_lease(mac)
216                                         leased_macs[mac] = nil
217                                 elseif whitelist_macs[mac] then
218                                         print("Removing %s from whitelist" % mac)
219                                         remove_whitelist(mac)
220                                         whitelist_macs[mac] = nil                                       
221                                 elseif blacklist_macs[mac] then
222                                         print("Removing %s from blacklist" % mac)
223                                         remove_blacklist(mac)
224                                         blacklist_macs[mac] = nil
225                                 else
226                                         print("The mac %s is not known" % mac)
227                                 end
228                         else
229                                 print("Can not find mac for ip %s" % argv[i])
230                         end
231                 end
232
233                 unlock()
234                 os.exit(0)      
235         elseif cmd == "sync" then
236                 sync()
237                 os.exit(0)
238         elseif cmd == "list" then
239                 list()
240                 os.exit(0)
241         else
242                 print("Usage:")
243                 print("\n  luci-splash list\n    List connected, black- and whitelisted clients")
244                 print("\n  luci-splash sync\n    Synchronize firewall rules and clear expired leases")
245                 print("\n  luci-splash lease <MAC-or-IP>\n    Create a lease for the given address")
246                 print("\n  luci-splash blacklist <MAC-or-IP>\n    Add given address to blacklist")
247                 print("\n  luci-splash whitelist <MAC-or-IP>\n    Add given address to whitelist")
248                 print("\n  luci-splash remove <MAC-or-IP>\n    Remove given address from the lease-, black- or whitelist")
249                 print("")
250
251                 os.exit(1)      
252         end
253 end
254
255 -- Get current arp cache
256 function get_arpcache()
257         local arpcache = { }
258         for _, entry in ipairs(net.arptable()) do
259                 arpcache[entry["HW address"]:lower()] = { entry["Device"]:lower(), entry["IP address"]:lower() }
260         end
261         return arpcache
262 end
263
264 -- Get a list of known mac addresses
265 function get_known_macs(list)
266         local leased_macs = { }
267
268         if not list or list == "lease" then
269                 uci:foreach("luci_splash_leases", "lease",
270                         function(s) leased_macs[s.mac:lower()] = true end)
271         end
272
273         if not list or list == "whitelist" then
274                 uci:foreach("luci_splash", "whitelist",
275                         function(s) leased_macs[s.mac:lower()] = true end)
276         end
277
278         if not list or list == "blacklist" then
279                 uci:foreach("luci_splash", "blacklist",
280                         function(s) leased_macs[s.mac:lower()] = true end)
281         end
282
283         return leased_macs
284 end
285
286
287 -- Helper to delete iptables rules
288 function ipt_delete_all(args, comp, off)
289         off = off or { }
290         for i, r in ipairs(ipt:find(args)) do
291                 if comp == nil or comp(r) then
292                         off[r.table] = off[r.table] or { }
293                         off[r.table][r.chain] = off[r.table][r.chain] or 0
294
295                         exec("iptables -t %q -D %q %d 2>/dev/null"
296                                 %{ r.table, r.chain, r.index - off[r.table][r.chain] })
297
298                         off[r.table][r.chain] = off[r.table][r.chain] + 1
299                 end
300         end
301 end
302
303 function ipt6_delete_all(args, comp, off)
304         off = off or { }
305         for i, r in ipairs(ipt:find(args)) do
306                 if comp == nil or comp(r) then
307                         off[r.table] = off[r.table] or { }
308                         off[r.table][r.chain] = off[r.table][r.chain] or 0
309
310                         exec("ip6tables -t %q -D %q %d 2>/dev/null"
311                                 %{ r.table, r.chain, r.index - off[r.table][r.chain] })
312
313                         off[r.table][r.chain] = off[r.table][r.chain] + 1
314                 end
315         end
316 end
317
318
319 -- Convert mac to uci-compatible section name
320 function convert_mac_to_secname(mac)
321         return string.gsub(mac, ":", "")
322 end
323
324 -- Add a lease to state and invoke add_rule
325 function add_lease(mac, arp, no_uci)
326         mac = mac:lower()
327
328         -- Get current ip address
329         local ipaddr
330         for _, entry in ipairs(arp or net.arptable()) do
331                 if entry["HW address"]:lower() == mac then
332                         ipaddr = entry["IP address"]
333                         break
334                 end
335         end
336
337         -- Add lease if there is an ip addr
338         if ipaddr then
339                 local device = get_device_for_ip(ipaddr)
340                 if not no_uci then
341                         local leased = uci:get("luci_splash_leases", "stats", "leases")
342                         if type(tonumber(leased)) == "number" then
343                                 update_stats(leased + 1, nil, nil, nil, nil)
344                         end
345
346                         uci:section("luci_splash_leases", "lease", convert_mac_to_secname(mac), {
347                                 mac    = mac,
348                                 ipaddr = ipaddr,
349                                 device = device,
350                                 limit_up = limit_up,
351                                 limit_down = limit_down,
352                                 start  = os.time()
353                         })
354                         uci:save("luci_splash_leases")
355                 end
356                 add_lease_rule(mac, ipaddr, device)
357         else
358                 print("Found no active IP for %s, lease not added" % mac)
359         end
360 end
361
362
363 -- Remove a lease from state and invoke remove_rule
364 function remove_lease(mac)
365         mac = mac:lower()
366
367         uci:delete_all("luci_splash_leases", "lease",
368                 function(s)
369                         if s.mac:lower() == mac then
370                                 remove_lease_rule(mac, s.ipaddr, s.device, tonumber(s.limit_up), tonumber(s.limit_down))
371                                 local leased = uci:get("luci_splash_leases", "stats", "leases")
372                                 if type(tonumber(leased)) == "number" and tonumber(leased) > 0 then
373                                         update_stats(leased - 1, nil, nil, nil, nil)
374                                 end
375                                 return true
376                         end
377                         return false
378                 end)
379                 
380         uci:save("luci_splash_leases")
381 end
382
383
384 -- Add a whitelist entry
385 function add_whitelist(mac)
386         uci:section("luci_splash", "whitelist", convert_mac_to_secname(mac), { mac = mac })
387         uci:save("luci_splash")
388         uci:commit("luci_splash")
389         add_whitelist_rule(mac)
390 end
391
392
393 -- Add a blacklist entry
394 function add_blacklist(mac)
395         uci:section("luci_splash", "blacklist", convert_mac_to_secname(mac), { mac = mac })
396         uci:save("luci_splash")
397         uci:commit("luci_splash")
398         add_blacklist_rule(mac)
399 end
400
401
402 -- Remove a whitelist entry
403 function remove_whitelist(mac)
404         mac = mac:lower()
405         uci:delete_all("luci_splash", "whitelist",
406                 function(s) return not s.mac or s.mac:lower() == mac end)
407         uci:save("luci_splash")
408         uci:commit("luci_splash")
409         remove_lease_rule(mac)
410         remove_whitelist_tc(mac)
411 end
412
413 function remove_whitelist_tc(mac)
414         uci:foreach("luci_splash", "iface", function(s)
415                 local device = get_physdev(s['.name'])
416                 if device and device ~= "" then
417                         if debug then
418                                 print("Removing whitelist filters for %s interface %s." % {mac, device})
419                         end
420                         local handle = get_filter_handle('ffff:', 'src', device, mac)
421                         if handle then
422                                 exec('tc filter del dev "%s" parent ffff: protocol ip prio 1 handle %s u32' % { device, handle })
423                         else
424                                 print('Warning! Could not get a handle for %s parent :ffff on interface %s' % { mac, device })
425                         end
426                         local handle = get_filter_handle('1:', 'dest', device, mac)
427                         if handle then
428                                 exec('tc filter del dev "%s" parent 1:0 protocol ip prio 1 handle %s u32' % { device, handle })
429                         else
430                                 print('Warning! Could not get a handle for %s parent 1:0 on interface %s' % { mac, device })
431                         end
432                 end
433         end)
434 end
435
436 -- Remove a blacklist entry
437 function remove_blacklist(mac)
438         mac = mac:lower()
439         uci:delete_all("luci_splash", "blacklist",
440                 function(s) return not s.mac or s.mac:lower() == mac end)
441         uci:save("luci_splash")
442         uci:commit("luci_splash")
443         remove_lease_rule(mac)
444 end
445
446
447 -- Add an iptables rule
448 function add_lease_rule(mac, ipaddr, device)
449         local id
450         if ipaddr then
451                 id = get_id(ipaddr)
452         end
453
454         exec("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j RETURN" % mac)
455
456         if id and device then
457                 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()})
458         end
459
460         if has_ipv6 then
461                 exec("ip6tables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j MARK --set-mark 79" % mac)
462                 -- not working yet, needs the ip6addr
463                 --exec("ip6tables -t mangle -I luci_splash_mark_in -d %q -j MARK --set-mark 80 -m comment --comment %s" % {ipaddr, mac:upper()})
464         end
465
466
467         if device and tonumber(limit_up) > 0 then
468                 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})
469         end
470
471         if id and device and tonumber(limit_down) > 0 then
472                 exec("tc class add dev %s parent 1: classid 1:0x%s htb rate %skbit" % { device, id, limit_down })
473                 exec("tc qdisc add dev %s parent 1:%s sfq perturb 10" % { device, id })
474         end
475
476         exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
477         exec("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
478         if has_ipv6 then
479                 exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
480         end
481 end
482
483
484 -- Remove lease, black- or whitelist rules
485 function remove_lease_rule(mac, ipaddr, device, limit_up, limit_down)
486         local id
487         if ipaddr then
488                 id = get_id(ipaddr)
489         end
490
491         ipt:resync()
492         ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"/*", mac:upper()}})
493         ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
494         ipt_delete_all({table="filter", chain="luci_splash_filter",   options={"MAC", mac:upper()}})
495         ipt_delete_all({table="nat",    chain="luci_splash_leases",   options={"MAC", mac:upper()}})
496         if has_ipv6 then
497                 ipt6_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
498                 ipt6_delete_all({table="filter", chain="luci_splash_filter",   options={"MAC", mac:upper()}})
499         end
500         if device and tonumber(limit_up) > 0 then
501                 local handle = get_filter_handle('ffff:', 'src', device, mac)
502                 if handle then
503                         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})
504                 else
505                         print('Warning! Could not get a handle for %s parent :ffff on interface %s' % { mac, device })
506                 end
507         end
508
509         -- remove clients class
510         if device and id then
511                 exec('tc class del dev "%s" classid 1:%s' % {device, id})
512                 exec('tc qdisc del dev "%s" parent 1:%s sfq perturb 10' % { device, id })
513         end
514
515 end
516
517
518 -- Add whitelist rules
519 function add_whitelist_rule(mac)
520         exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
521         exec("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
522         if has_ipv6 then
523                 exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
524         end
525         uci:foreach("luci_splash", "iface", function(s)
526                 local device = get_physdev(s['.name'])
527                 if device and device ~= "" then
528                         exec('tc filter add dev "%s" parent ffff: protocol ip prio 1 u32 match ether src %s police pass' % { device, mac })
529                         exec('tc filter add dev "%s" parent 1:0 protocol ip prio 1 u32 match ether dst %s classid 1:1' % { device, mac })
530                 end
531         end)
532 end
533
534
535 -- Add blacklist rules
536 function add_blacklist_rule(mac)
537         exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
538         if has_ipv6 then
539                 exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
540         end
541 end
542
543
544 -- Synchronise leases, remove abandoned rules
545 function sync()
546         lock()
547
548         local time = os.time()
549
550         -- Current leases in state files
551         local leases = uci:get_all("luci_splash_leases")
552         
553         -- Convert leasetime to seconds
554         local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime")) * 3600
555         
556         -- Clean state file
557         uci:load("luci_splash_leases")
558         uci:revert("luci_splash_leases")
559         
560         -- For all leases
561         local leasecount = 0
562         for k, v in pairs(leases) do
563                 if v[".type"] == "lease" then
564                         if os.difftime(time, tonumber(v.start)) > leasetime then
565                                 -- Remove expired
566                                 remove_lease_rule(v.mac, v.ipaddr, v.device, tonumber(v.limit_up), tonumber(v.limit_down))
567                         else
568                                 leasecount = leasecount + 1
569                                 -- Rewrite state
570                                 uci:section("luci_splash_leases", "lease", convert_mac_to_secname(v.mac), {             
571                                         mac    = v.mac,
572                                         ipaddr = v.ipaddr,
573                                         device = v.device,
574                                         limit_up = limit_up,
575                                         limit_down = limit_down,
576                                         start  = v.start
577                                 })
578                         end
579                 end
580         end
581         
582         -- Get the mac addresses of current leases
583         local macs = get_known_macs()
584         local arpcache = get_arpcache()
585
586         local blackwhitelist = uci:get_all("luci_splash")
587         local whitelist_total = 0
588         local whitelist_online = 0
589         local blacklist_total = 0
590         local blacklist_online = 0
591
592         -- Whitelist, Blacklist
593         for _, s in utl.spairs(blackwhitelist,
594                 function(a,b) return blackwhitelist[a][".type"] > blackwhitelist[b][".type"] end
595         ) do
596                 if (s[".type"] == "whitelist") then
597                         whitelist_total = whitelist_total + 1
598                         if s.mac then
599                                 local mac = s.mac:lower()
600                                 if arpcache[mac] then
601                                         whitelist_online = whitelist_online + 1
602                                 end
603                         end
604                 end
605                 if (s[".type"] == "blacklist") then
606                         blacklist_total = blacklist_total + 1
607                         if s.mac then
608                                 local mac = s.mac:lower()
609                                 if arpcache[mac] then
610                                         blacklist_online = blacklist_online + 1
611                                 end
612                         end
613                 end
614         end
615
616         update_stats(leasecount, whitelist_online, whitelist_total, blacklist_online, blacklist_total)
617
618         uci:save("luci_splash_leases")
619
620         ipt:resync()
621
622         ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
623                 function(r) return not macs[r.options[2]:lower()] end)
624         ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC"}},
625                 function(r) return not macs[r.options[2]:lower()] end)
626         ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
627                 function(r) return not macs[r.options[2]:lower()] end)
628         ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"/*", "MARK", "set"}},
629                 function(r) return not macs[r.options[2]:lower()] end)
630
631
632         if has_ipv6 then
633                 ipt6_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
634                         function(r) return not macs[r.options[2]:lower()] end)
635                 ipt6_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
636                         function(r) return not macs[r.options[2]:lower()] end)
637         end
638
639         unlock()
640 end
641
642 -- Show client info
643 function list()
644         local arpcache = get_arpcache()
645         -- Find traffic usage
646         local function traffic(lease)
647                 local traffic_in  = 0
648                 local traffic_out = 0
649
650                 local rin  = ipt:find({table="mangle", chain="luci_splash_mark_in", destination=lease.ipaddr})
651                 local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", lease.mac:upper()}})
652
653                 if rin  and #rin  > 0 then traffic_in  = math.floor( rin[1].bytes / 1024) end
654                 if rout and #rout > 0 then traffic_out = math.floor(rout[1].bytes / 1024) end
655
656                 return traffic_in, traffic_out
657         end
658
659         -- Print listings
660         local leases = uci:get_all("luci_splash_leases")
661         local blackwhitelist = uci:get_all("luci_splash")
662
663         print(string.format(
664                 "%-17s  %-15s  %-9s  %-4s  %-7s  %20s",
665                 "MAC", "IP", "State", "Dur.", "Intf.", "Traffic down/up"
666         ))
667
668         -- Leases
669         for _, s in pairs(leases) do
670                 if s[".type"] == "lease" and s.mac then
671                         local ti, to = traffic(s)
672                         local mac = s.mac:lower()
673                         local arp = arpcache[mac]
674                         print(string.format(
675                                 "%-17s  %-15s  %-9s  %3dm  %-7s  %7dKB  %7dKB",
676                                 mac, s.ipaddr, "leased",
677                                 math.floor(( os.time() - tonumber(s.start) ) / 60),
678                                 arp and arp[1] or "?", ti, to
679                         ))
680                 end
681         end
682
683         -- Whitelist, Blacklist
684         for _, s in utl.spairs(blackwhitelist,
685                 function(a,b) return blackwhitelist[a][".type"] > blackwhitelist[b][".type"] end
686         ) do
687                 if (s[".type"] == "whitelist" or s[".type"] == "blacklist") and s.mac then
688                         local mac = s.mac:lower()
689                         local arp = arpcache[mac]
690                         print(string.format(
691                                 "%-17s  %-15s  %-9s  %4s  %-7s  %9s  %9s",
692                                 mac, arp and arp[2] or "?", s[".type"],
693                                 "- ", arp and arp[1] or "?", "-", "-"
694                         ))
695                 end
696         end
697 end
698
699 main(arg)