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