2
0

luci-splash 22 KB

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