applications/luci-splash: remove unneeded tc exec
[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         -- For all leases
581         local leasecount = 0
582         for k, v in pairs(leases) do
583                 if v[".type"] == "lease" then
584                         if os.difftime(time, tonumber(v.start)) > leasetime then
585                                 -- Remove expired
586                                 remove_lease_rule(v.mac, v.ipaddr, v.device, tonumber(v.limit_up), tonumber(v.limit_down))
587                         else
588                                 leasecount = leasecount + 1
589                                 -- Rewrite state
590                                 uci:section("luci_splash_leases", "lease", convert_mac_to_secname(v.mac), {             
591                                         mac    = v.mac,
592                                         ipaddr = v.ipaddr,
593                                         device = v.device,
594                                         limit_up = limit_up,
595                                         limit_down = limit_down,
596                                         start  = v.start
597                                 })
598                         end
599                 end
600         end
601         
602         -- Get the mac addresses of current leases
603         local macs = get_known_macs()
604         local arpcache = get_arpcache()
605
606         local blackwhitelist = uci:get_all("luci_splash")
607         local whitelist_total = 0
608         local whitelist_online = 0
609         local blacklist_total = 0
610         local blacklist_online = 0
611
612         -- Whitelist, Blacklist
613         for _, s in utl.spairs(blackwhitelist,
614                 function(a,b) return blackwhitelist[a][".type"] > blackwhitelist[b][".type"] end
615         ) do
616                 if (s[".type"] == "whitelist") then
617                         whitelist_total = whitelist_total + 1
618                         if s.mac then
619                                 local mac = s.mac:lower()
620                                 if arpcache[mac] then
621                                         whitelist_online = whitelist_online + 1
622                                 end
623                         end
624                 end
625                 if (s[".type"] == "blacklist") then
626                         blacklist_total = blacklist_total + 1
627                         if s.mac then
628                                 local mac = s.mac:lower()
629                                 if arpcache[mac] then
630                                         blacklist_online = blacklist_online + 1
631                                 end
632                         end
633                 end
634         end
635
636         update_stats(leasecount, whitelist_online, whitelist_total, blacklist_online, blacklist_total)
637
638         uci:save("luci_splash_leases")
639
640         ipt:resync()
641
642         ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
643                 function(r) return not macs[r.options[2]:lower()] end)
644         ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC"}},
645                 function(r) return not macs[r.options[2]:lower()] end)
646         ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
647                 function(r) return not macs[r.options[2]:lower()] end)
648         ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"/*", "MARK", "set"}},
649                 function(r) return not macs[r.options[2]:lower()] end)
650
651
652         if has_ipv6 then
653                 ipt6_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
654                         function(r) return not macs[r.options[2]:lower()] end)
655                 ipt6_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
656                         function(r) return not macs[r.options[2]:lower()] end)
657         end
658
659         unlock()
660 end
661
662 -- Show client info
663 function list()
664         local arpcache = get_arpcache()
665         -- Find traffic usage
666         local function traffic(lease)
667                 local traffic_in  = 0
668                 local traffic_out = 0
669
670                 local rin  = ipt:find({table="mangle", chain="luci_splash_mark_in", destination=lease.ipaddr})
671                 local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", lease.mac:upper()}})
672
673                 if rin  and #rin  > 0 then traffic_in  = math.floor( rin[1].bytes / 1024) end
674                 if rout and #rout > 0 then traffic_out = math.floor(rout[1].bytes / 1024) end
675
676                 return traffic_in, traffic_out
677         end
678
679         -- Print listings
680         local leases = uci:get_all("luci_splash_leases")
681         local blackwhitelist = uci:get_all("luci_splash")
682
683         print(string.format(
684                 "%-17s  %-15s  %-9s  %-4s  %-7s  %20s",
685                 "MAC", "IP", "State", "Dur.", "Intf.", "Traffic down/up"
686         ))
687
688         -- Leases
689         for _, s in pairs(leases) do
690                 if s[".type"] == "lease" and s.mac then
691                         local ti, to = traffic(s)
692                         local mac = s.mac:lower()
693                         local arp = arpcache[mac]
694                         print(string.format(
695                                 "%-17s  %-15s  %-9s  %3dm  %-7s  %7dKB  %7dKB",
696                                 mac, s.ipaddr, "leased",
697                                 math.floor(( os.time() - tonumber(s.start) ) / 60),
698                                 arp and arp[1] or "?", ti, to
699                         ))
700                 end
701         end
702
703         -- Whitelist, Blacklist
704         for _, s in utl.spairs(blackwhitelist,
705                 function(a,b) return blackwhitelist[a][".type"] > blackwhitelist[b][".type"] end
706         ) do
707                 if (s[".type"] == "whitelist" or s[".type"] == "blacklist") and s.mac then
708                         local mac = s.mac:lower()
709                         local arp = arpcache[mac]
710                         print(string.format(
711                                 "%-17s  %-15s  %-9s  %4s  %-7s  %9s  %9s",
712                                 mac, arp and arp[2] or "?", s[".type"],
713                                 "- ", arp and arp[1] or "?", "-", "-"
714                         ))
715                 end
716         end
717 end
718
719 main(arg)