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