luci-0.10: Merge r8706
[project/luci.git] / applications / luci-splash / root / usr / sbin / luci-splash
1 #!/usr/bin/lua
2
3 require("luci.util")
4 require("luci.model.uci")
5 require("luci.sys")
6 require("luci.sys.iptparser")
7
8 -- Init state session
9 local uci = luci.model.uci.cursor_state()
10 local ipt = luci.sys.iptparser.IptParser()
11 local net = luci.sys.net
12
13 local limit_up = 0
14 local limit_down = 0
15
16 function lock()
17 os.execute("lock /var/run/luci_splash.lock")
18 end
19
20 function unlock()
21 os.execute("lock -u /var/run/luci_splash.lock")
22 end
23
24 function main(argv)
25 local cmd = table.remove(argv, 1)
26 local arg = argv[1]
27
28 limit_up = tonumber(uci:get("luci_splash", "general", "limit_up")) or 0
29 limit_down = tonumber(uci:get("luci_splash", "general", "limit_down")) or 0
30
31 if ( cmd == "lease" or cmd == "add-rules" or cmd == "remove" or
32 cmd == "whitelist" or cmd == "blacklist" or cmd == "status" ) and #argv > 0
33 then
34 lock()
35
36 local arp_cache = net.arptable()
37 local leased_macs = get_known_macs("lease")
38 local blacklist_macs = get_known_macs("blacklist")
39 local whitelist_macs = get_known_macs("whitelist")
40
41 for i, adr in ipairs(argv) do
42 local mac = nil
43 if adr:find(":") then
44 mac = adr:lower()
45 else
46 for _, e in ipairs(arp_cache) do
47 if e["IP address"] == adr then
48 mac = e["HW address"]:lower()
49 break
50 end
51 end
52 end
53
54 if mac and cmd == "add-rules" then
55 if leased_macs[mac] then
56 add_lease(mac, arp_cache, true)
57 elseif blacklist_macs[mac] then
58 add_blacklist_rule(mac)
59 elseif whitelist_macs[mac] then
60 add_whitelist_rule(mac)
61 end
62 elseif mac and cmd == "status" then
63 print(leased_macs[mac] and "lease"
64 or whitelist_macs[mac] and "whitelist"
65 or blacklist_macs[mac] and "blacklist"
66 or "new")
67 elseif mac and ( cmd == "whitelist" or cmd == "blacklist" or cmd == "lease" ) then
68 if cmd ~= "lease" and leased_macs[mac] then
69 print("Removing %s from leases" % mac)
70 remove_lease(mac)
71 leased_macs[mac] = nil
72 end
73
74 if cmd ~= "whitelist" and whitelist_macs[mac] then
75 print("Removing %s from whitelist" % mac)
76 remove_whitelist(mac)
77 whitelist_macs[mac] = nil
78 end
79
80 if cmd ~= "blacklist" and blacklist_macs[mac] then
81 print("Removing %s from blacklist" % mac)
82 remove_blacklist(mac)
83 blacklist_macs[mac] = nil
84 end
85
86 if cmd == "lease" and not leased_macs[mac] then
87 print("Adding %s to leases" % mac)
88 add_lease(mac)
89 leased_macs[mac] = true
90 elseif cmd == "whitelist" and not whitelist_macs[mac] then
91 print("Adding %s to whitelist" % mac)
92 add_whitelist(mac)
93 whitelist_macs[mac] = true
94 elseif cmd == "blacklist" and not blacklist_macs[mac] then
95 print("Adding %s to blacklist" % mac)
96 add_blacklist(mac)
97 blacklist_macs[mac] = true
98 else
99 print("The mac %s is already %sed" %{ mac, cmd })
100 end
101 elseif mac and cmd == "remove" then
102 if leased_macs[mac] then
103 print("Removing %s from leases" % mac)
104 remove_lease(mac)
105 leased_macs[mac] = nil
106 elseif whitelist_macs[mac] then
107 print("Removing %s from whitelist" % mac)
108 remove_whitelist(mac)
109 whitelist_macs[mac] = nil
110 elseif blacklist_macs[mac] then
111 print("Removing %s from blacklist" % mac)
112 remove_blacklist(mac)
113 blacklist_macs[mac] = nil
114 else
115 print("The mac %s is not known" % mac)
116 end
117 else
118 print("Can not find mac for ip %s" % argv[i])
119 end
120 end
121
122 unlock()
123 os.exit(0)
124 elseif cmd == "sync" then
125 sync()
126 os.exit(0)
127 elseif cmd == "list" then
128 list()
129 os.exit(0)
130 else
131 print("Usage:")
132 print("\n luci-splash list\n List connected, black- and whitelisted clients")
133 print("\n luci-splash sync\n Synchronize firewall rules and clear expired leases")
134 print("\n luci-splash lease <MAC-or-IP>\n Create a lease for the given address")
135 print("\n luci-splash blacklist <MAC-or-IP>\n Add given address to blacklist")
136 print("\n luci-splash whitelist <MAC-or-IP>\n Add given address to whitelist")
137 print("\n luci-splash remove <MAC-or-IP>\n Remove given address from the lease-, black- or whitelist")
138 print("")
139
140 os.exit(1)
141 end
142 end
143
144 -- Get a list of known mac addresses
145 function get_known_macs(list)
146 local leased_macs = { }
147
148 if not list or list == "lease" then
149 uci:foreach("luci_splash", "lease",
150 function(s) leased_macs[s.mac:lower()] = true end)
151 end
152
153 if not list or list == "whitelist" then
154 uci:foreach("luci_splash", "whitelist",
155 function(s) leased_macs[s.mac:lower()] = true end)
156 end
157
158 if not list or list == "blacklist" then
159 uci:foreach("luci_splash", "blacklist",
160 function(s) leased_macs[s.mac:lower()] = true end)
161 end
162
163 return leased_macs
164 end
165
166
167 -- Get a list of known ip addresses
168 function get_known_ips(macs, arp)
169 local leased_ips = { }
170 if not macs then macs = get_known_macs() end
171 for _, e in ipairs(arp or net.arptable()) do
172 if macs[e["HW address"]:lower()] then leased_ips[e["IP address"]] = true end
173 end
174 return leased_ips
175 end
176
177
178 -- Helper to delete iptables rules
179 function ipt_delete_all(args, comp, off)
180 off = off or { }
181 for i, r in ipairs(ipt:find(args)) do
182 if comp == nil or comp(r) then
183 off[r.table] = off[r.table] or { }
184 off[r.table][r.chain] = off[r.table][r.chain] or 0
185
186 os.execute("iptables -t %q -D %q %d 2>/dev/null"
187 %{ r.table, r.chain, r.index - off[r.table][r.chain] })
188
189 off[r.table][r.chain] = off[r.table][r.chain] + 1
190 end
191 end
192 end
193
194 -- Convert mac to uci-compatible section name
195 function convert_mac_to_secname(mac)
196 return string.gsub(mac, ":", "")
197 end
198
199 -- Add a lease to state and invoke add_rule
200 function add_lease(mac, arp, no_uci)
201 mac = mac:lower()
202
203 -- Get current ip address
204 local ipaddr
205 for _, entry in ipairs(arp or net.arptable()) do
206 if entry["HW address"]:lower() == mac then
207 ipaddr = entry["IP address"]
208 break
209 end
210 end
211
212 -- Add lease if there is an ip addr
213 if ipaddr then
214 if not no_uci then
215 uci:section("luci_splash", "lease", convert_mac_to_secname(mac), {
216 mac = mac,
217 ipaddr = ipaddr,
218 start = os.time()
219 })
220 uci:save("luci_splash")
221 end
222 add_lease_rule(mac, ipaddr)
223 else
224 print("Found no active IP for %s, lease not added" % mac)
225 end
226 end
227
228
229 -- Remove a lease from state and invoke remove_rule
230 function remove_lease(mac)
231 mac = mac:lower()
232
233 uci:delete_all("luci_splash", "lease",
234 function(s)
235 if s.mac:lower() == mac then
236 remove_lease_rule(mac, s.ipaddr)
237 return true
238 end
239 return false
240 end)
241
242 uci:save("luci_splash")
243 end
244
245
246 -- Add a whitelist entry
247 function add_whitelist(mac)
248 uci:section("luci_splash", "whitelist", convert_mac_to_secname(mac), { mac = mac })
249 uci:save("luci_splash")
250 uci:commit("luci_splash")
251 add_whitelist_rule(mac)
252 end
253
254
255 -- Add a blacklist entry
256 function add_blacklist(mac)
257 uci:section("luci_splash", "blacklist", convert_mac_to_secname(mac), { mac = mac })
258 uci:save("luci_splash")
259 uci:commit("luci_splash")
260 add_blacklist_rule(mac)
261 end
262
263
264 -- Remove a whitelist entry
265 function remove_whitelist(mac)
266 mac = mac:lower()
267 uci:delete_all("luci_splash", "whitelist",
268 function(s) return not s.mac or s.mac:lower() == mac end)
269 uci:save("luci_splash")
270 uci:commit("luci_splash")
271 remove_lease_rule(mac)
272 end
273
274
275 -- Remove a blacklist entry
276 function remove_blacklist(mac)
277 mac = mac:lower()
278 uci:delete_all("luci_splash", "blacklist",
279 function(s) return not s.mac or s.mac:lower() == mac end)
280 uci:save("luci_splash")
281 uci:commit("luci_splash")
282 remove_lease_rule(mac)
283 end
284
285
286 -- Add an iptables rule
287 function add_lease_rule(mac, ipaddr)
288 if limit_up > 0 and limit_down > 0 then
289 os.execute("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j MARK --set-mark 79" % mac)
290 os.execute("iptables -t mangle -I luci_splash_mark_in -d %q -j MARK --set-mark 80" % ipaddr)
291 end
292
293 os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
294 os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
295 end
296
297
298 -- Remove lease, black- or whitelist rules
299 function remove_lease_rule(mac, ipaddr)
300 ipt:resync()
301
302 if ipaddr then
303 ipt_delete_all({table="mangle", chain="luci_splash_mark_in", destination=ipaddr})
304 ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
305 end
306
307 ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC", mac:upper()}})
308 ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC", mac:upper()}})
309 end
310
311
312 -- Add whitelist rules
313 function add_whitelist_rule(mac)
314 os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
315 os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
316 end
317
318
319 -- Add blacklist rules
320 function add_blacklist_rule(mac)
321 os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
322 os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source %q -j DROP" % mac)
323 end
324
325
326 -- Synchronise leases, remove abandoned rules
327 function sync()
328 lock()
329
330 local time = os.time()
331
332 -- Current leases in state files
333 local leases = uci:get_all("luci_splash")
334
335 -- Convert leasetime to seconds
336 local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime")) * 3600
337
338 -- Clean state file
339 uci:load("luci_splash")
340 uci:revert("luci_splash")
341
342 -- For all leases
343 for k, v in pairs(leases) do
344 if v[".type"] == "lease" then
345 if os.difftime(time, tonumber(v.start)) > leasetime then
346 -- Remove expired
347 remove_lease_rule(v.mac, v.ipaddr)
348 else
349 -- Rewrite state
350 uci:section("luci_splash", "lease", convert_mac_to_secname(v.mac), {
351 mac = v.mac,
352 ipaddr = v.ipaddr,
353 start = v.start
354 })
355 end
356 end
357 end
358
359 uci:save("luci_splash")
360
361 -- Get current IPs and MAC addresses
362 local macs = get_known_macs()
363 local ips = get_known_ips(macs)
364
365 ipt:resync()
366
367 ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
368 function(r) return not macs[r.options[2]:lower()] end)
369
370 ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC"}},
371 function(r) return not macs[r.options[2]:lower()] end)
372
373 ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
374 function(r) return not macs[r.options[2]:lower()] end)
375
376 ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"MARK", "set"}},
377 function(r) return not ips[r.destination] end)
378
379 unlock()
380 end
381
382 -- Show client info
383 function list()
384 -- Get current arp cache
385 local arpcache = { }
386 for _, entry in ipairs(net.arptable()) do
387 arpcache[entry["HW address"]:lower()] = { entry["Device"]:lower(), entry["IP address"]:lower() }
388 end
389
390 -- Find traffic usage
391 local function traffic(lease)
392 local traffic_in = 0
393 local traffic_out = 0
394
395 local rin = ipt:find({table="mangle", chain="luci_splash_mark_in", destination=lease.ipaddr})
396 local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", lease.mac:upper()}})
397
398 if rin and #rin > 0 then traffic_in = math.floor( rin[1].bytes / 1024) end
399 if rout and #rout > 0 then traffic_out = math.floor(rout[1].bytes / 1024) end
400
401 return traffic_in, traffic_out
402 end
403
404 -- Print listings
405 local leases = uci:get_all("luci_splash")
406
407 print(string.format(
408 "%-17s %-15s %-9s %-4s %-7s %20s",
409 "MAC", "IP", "State", "Dur.", "Intf.", "Traffic down/up"
410 ))
411
412 -- Leases
413 for _, s in pairs(leases) do
414 if s[".type"] == "lease" and s.mac then
415 local ti, to = traffic(s)
416 local mac = s.mac:lower()
417 local arp = arpcache[mac]
418 print(string.format(
419 "%-17s %-15s %-9s %3dm %-7s %7dKB %7dKB",
420 mac, s.ipaddr, "leased",
421 math.floor(( os.time() - tonumber(s.start) ) / 60),
422 arp and arp[1] or "?", ti, to
423 ))
424 end
425 end
426
427 -- Whitelist, Blacklist
428 for _, s in luci.util.spairs(leases,
429 function(a,b) return leases[a][".type"] > leases[b][".type"] end
430 ) do
431 if (s[".type"] == "whitelist" or s[".type"] == "blacklist") and s.mac then
432 local mac = s.mac:lower()
433 local arp = arpcache[mac]
434 print(string.format(
435 "%-17s %-15s %-9s %4s %-7s %9s %9s",
436 mac, arp and arp[2] or "?", s[".type"],
437 "- ", arp and arp[1] or "?", "-", "-"
438 ))
439 end
440 end
441 end
442
443 main(arg)