root/luci/trunk/applications/luci-splash/root/usr/sbin/luci-splash @ 6048

Revision 6048, 11.7 KB (checked in by ben, 3 years ago)

splash: fix exception when listing whitelist entries

Fixes ticket #123.

  • Property svn:executable set to *
Line 
1#!/usr/bin/lua
2
3require("luci.util")
4require("luci.model.uci")
5require("luci.sys")
6require("luci.sys.iptparser")
7
8-- Init state session
9local uci = luci.model.uci.cursor_state()
10local ipt = luci.sys.iptparser.IptParser()
11local net = luci.sys.net
12
13local limit_up = 0
14local limit_down = 0
15
16function lock()
17    os.execute("lock -w /var/run/luci_splash.lock && lock /var/run/luci_splash.lock")
18end
19
20function unlock()
21    os.execute("lock -u /var/run/luci_splash.lock")
22end
23
24function 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
142end
143
144-- Get a list of known mac addresses
145function 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
164end
165
166
167-- Get a list of known ip addresses
168function 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
175end
176
177
178-- Helper to delete iptables rules
179function 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
192end
193
194
195-- Add a lease to state and invoke add_rule
196function add_lease(mac, arp, no_uci)
197    mac = mac:lower()
198
199    -- Get current ip address
200    local ipaddr
201    for _, entry in ipairs(arp or net.arptable()) do
202        if entry["HW address"]:lower() == mac then
203            ipaddr = entry["IP address"]
204            break
205        end
206    end
207
208    -- Add lease if there is an ip addr
209    if ipaddr then
210        if not no_uci then
211            uci:section("luci_splash", "lease", nil, {
212                mac    = mac,
213                ipaddr = ipaddr,
214                start  = os.time()
215            })
216            uci:save("luci_splash")
217        end
218        add_lease_rule(mac, ipaddr)
219    else
220        print("Found no active IP for %s, lease not added" % mac)
221    end
222end
223
224
225-- Remove a lease from state and invoke remove_rule
226function remove_lease(mac)
227    mac = mac:lower()
228
229    uci:delete_all("luci_splash", "lease",
230        function(s)
231            if s.mac:lower() == mac then
232                remove_lease_rule(mac, s.ipaddr)
233                return true
234            end
235            return false
236        end)
237       
238    uci:save("luci_splash")
239end
240
241
242-- Add a whitelist entry
243function add_whitelist(mac)
244    uci:section("luci_splash", "whitelist", nil, { mac = mac })
245    uci:save("luci_splash")
246    uci:commit("luci_splash")
247    add_whitelist_rule(mac)
248end
249
250
251-- Add a blacklist entry
252function add_blacklist(mac)
253    uci:section("luci_splash", "blacklist", nil, { mac = mac })
254    uci:save("luci_splash")
255    uci:commit("luci_splash")
256    add_blacklist_rule(mac)
257end
258
259
260-- Remove a whitelist entry
261function remove_whitelist(mac)
262    mac = mac:lower()
263    uci:delete_all("luci_splash", "whitelist",
264        function(s) return not s.mac or s.mac:lower() == mac end)
265    uci:save("luci_splash")
266    uci:commit("luci_splash")
267    remove_lease_rule(mac)
268end
269
270
271-- Remove a blacklist entry
272function remove_blacklist(mac)
273    mac = mac:lower()
274    uci:delete_all("luci_splash", "blacklist",
275        function(s) return not s.mac or s.mac:lower() == mac end)
276    uci:save("luci_splash")
277    uci:commit("luci_splash")
278    remove_lease_rule(mac)
279end
280
281
282-- Add an iptables rule
283function add_lease_rule(mac, ipaddr)
284    if limit_up > 0 and limit_down > 0 then
285        os.execute("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j MARK --set-mark 79" % mac)
286        os.execute("iptables -t mangle -I luci_splash_mark_in -d %q -j MARK --set-mark 80" % ipaddr)
287    end
288
289    os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
290    os.execute("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
291end
292
293
294-- Remove lease, black- or whitelist rules
295function remove_lease_rule(mac, ipaddr)
296    ipt:resync()
297
298    if ipaddr then
299        ipt_delete_all({table="mangle", chain="luci_splash_mark_in",  destination=ipaddr})
300        ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
301    end
302
303    ipt_delete_all({table="filter", chain="luci_splash_filter",   options={"MAC", mac:upper()}})
304    ipt_delete_all({table="nat",    chain="luci_splash_leases",   options={"MAC", mac:upper()}})
305end
306
307
308-- Add whitelist rules
309function add_whitelist_rule(mac)
310    os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
311    os.execute("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
312end
313
314
315-- Add blacklist rules
316function add_blacklist_rule(mac)
317    os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
318    os.execute("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j DROP" % mac)
319end
320
321
322-- Synchronise leases, remove abandoned rules
323function sync()
324    lock()
325
326    local time = os.time()
327
328    -- Current leases in state files
329    local leases = uci:get_all("luci_splash")
330   
331    -- Convert leasetime to seconds
332    local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime")) * 3600
333   
334    -- Clean state file
335    uci:load("luci_splash")
336    uci:revert("luci_splash")
337   
338    -- For all leases
339    for k, v in pairs(leases) do
340        if v[".type"] == "lease" then
341            if os.difftime(time, tonumber(v.start)) > leasetime then
342                -- Remove expired
343                remove_lease_rule(v.mac, v.ipaddr)
344            else
345                -- Rewrite state
346                uci:section("luci_splash", "lease", nil, {     
347                    mac    = v.mac,
348                    ipaddr = v.ipaddr,
349                    start  = v.start
350                })
351            end
352        end
353    end
354
355    uci:save("luci_splash")
356
357    -- Get current IPs and MAC addresses
358    local macs = get_known_macs()
359    local ips  = get_known_ips(macs)
360
361    ipt:resync()
362
363    ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
364        function(r) return not macs[r.options[2]:lower()] end)
365
366    ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC"}},
367        function(r) return not macs[r.options[2]:lower()] end)
368
369    ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
370        function(r) return not macs[r.options[2]:lower()] end)
371
372    ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"MARK", "set"}},
373        function(r) return not ips[r.destination] end)
374
375    unlock()
376end
377
378-- Show client info
379function list()
380    -- Get current arp cache
381    local arpcache = { }
382    for _, entry in ipairs(net.arptable()) do
383        arpcache[entry["HW address"]:lower()] = { entry["Device"], entry["IP address"] }
384    end
385
386    -- Find traffic usage
387    local function traffic(lease)
388        local traffic_in  = 0
389        local traffic_out = 0
390
391        local rin  = ipt:find({table="mangle", chain="luci_splash_mark_in", destination=lease.ipaddr})
392        local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", lease.mac:upper()}})
393
394        if rin  and #rin  > 0 then traffic_in  = math.floor( rin[1].bytes / 1024) end
395        if rout and #rout > 0 then traffic_out = math.floor(rout[1].bytes / 1024) end
396
397        return traffic_in, traffic_out
398    end
399
400    -- Print listings
401    local leases = uci:get_all("luci_splash")
402
403    print(string.format(
404        "%-17s  %-15s  %-9s  %-4s  %-7s  %20s",
405        "MAC", "IP", "State", "Dur.", "Intf.", "Traffic down/up"
406    ))
407
408    -- Leases
409    for _, s in pairs(leases) do
410        if s[".type"] == "lease" and s.mac then
411            local ti, to = traffic(s)
412            local mac = s.mac:lower()
413            local arp = arpcache[mac]:lower()
414            print(string.format(
415                "%-17s  %-15s  %-9s  %3dm  %-7s  %7dKB  %7dKB",
416                mac, s.ipaddr, "leased",
417                math.floor(( os.time() - tonumber(s.start) ) / 60),
418                arp and arp[1] or "?", ti, to
419            ))
420        end
421    end
422
423    -- Whitelist, Blacklist
424    for _, s in luci.util.spairs(leases,
425        function(a,b) return leases[a][".type"] > leases[b][".type"] end
426    ) do
427        if (s[".type"] == "whitelist" or s[".type"] == "blacklist") and s.mac then
428            local mac = s.mac:lower()
429            local arp = arpcache[mac]
430            print(string.format(
431                "%-17s  %-15s  %-9s  %4s  %-7s  %9s  %9s",
432                mac, arp and arp[2]:lower() or "?", s[".type"],
433                "- ", arp and arp[1]:lower() or "?", "-", "-"
434            ))
435        end
436    end
437end
438
439main(arg)
Note: See TracBrowser for help on using the browser.