luci-base: luci.tools.status: add host_hints to DHCPv6 leases
[oweals/luci.git] / modules / luci-base / luasrc / tools / status.lua
1 -- Copyright 2011 Jo-Philipp Wich <jow@openwrt.org>
2 -- Licensed to the public under the Apache License 2.0.
3
4 module("luci.tools.status", package.seeall)
5
6 local uci = require "luci.model.uci".cursor()
7 local ipc = require "luci.ip"
8
9 local function duid_to_mac(duid)
10         local b1, b2, b3, b4, b5, b6
11
12         -- DUID-LLT / Ethernet
13         if type(duid) == "string" and #duid == 28 then
14                 b1, b2, b3, b4, b5, b6 = duid:match("^00010001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)%x%x%x%x%x%x%x%x$")
15
16         -- DUID-LL / Ethernet
17         elseif type(duid) == "string" and #duid == 20 then
18                 b1, b2, b3, b4, b5, b6 = duid:match("^00030001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
19         end
20
21         return b1 and ipc.checkmac(table.concat({ b1, b2, b3, b4, b5, b6 }, ":"))
22 end
23
24 local function dhcp_leases_common(family)
25         local rv = { }
26         local nfs = require "nixio.fs"
27         local sys = require "luci.sys"
28         local leasefile = "/tmp/dhcp.leases"
29
30         uci:foreach("dhcp", "dnsmasq",
31                 function(s)
32                         if s.leasefile and nfs.access(s.leasefile) then
33                                 leasefile = s.leasefile
34                                 return false
35                         end
36                 end)
37
38         local fd = io.open(leasefile, "r")
39         if fd then
40                 while true do
41                         local ln = fd:read("*l")
42                         if not ln then
43                                 break
44                         else
45                                 local ts, mac, ip, name, duid = ln:match("^(%d+) (%S+) (%S+) (%S+) (%S+)")
46                                 local expire = tonumber(ts) or 0
47                                 if ts and mac and ip and name and duid then
48                                         if family == 4 and not ip:match(":") then
49                                                 rv[#rv+1] = {
50                                                         expires  = (expire ~= 0) and os.difftime(expire, os.time()),
51                                                         macaddr  = ipc.checkmac(mac) or "00:00:00:00:00:00",
52                                                         ipaddr   = ip,
53                                                         hostname = (name ~= "*") and name
54                                                 }
55                                         elseif family == 6 and ip:match(":") then
56                                                 rv[#rv+1] = {
57                                                         expires  = (expire ~= 0) and os.difftime(expire, os.time()),
58                                                         ip6addr  = ip,
59                                                         duid     = (duid ~= "*") and duid,
60                                                         hostname = (name ~= "*") and name
61                                                 }
62                                         end
63                                 end
64                         end
65                 end
66                 fd:close()
67         end
68
69         local lease6file = "/tmp/hosts/odhcpd"
70         uci:foreach("dhcp", "odhcpd",
71                 function(t)
72                         if t.leasefile and nfs.access(t.leasefile) then
73                                 lease6file = t.leasefile
74                                 return false
75                         end
76                 end)
77         local fd = io.open(lease6file, "r")
78         if fd then
79                 while true do
80                         local ln = fd:read("*l")
81                         if not ln then
82                                 break
83                         else
84                                 local iface, duid, iaid, name, ts, id, length, ip = ln:match("^# (%S+) (%S+) (%S+) (%S+) (-?%d+) (%S+) (%S+) (.*)")
85                                 local expire = tonumber(ts) or 0
86                                 if ip and iaid ~= "ipv4" and family == 6 then
87                                         rv[#rv+1] = {
88                                                 expires  = (expire >= 0) and os.difftime(expire, os.time()),
89                                                 duid     = duid,
90                                                 ip6addr  = ip,
91                                                 hostname = (name ~= "-") and name
92                                         }
93                                 elseif ip and iaid == "ipv4" and family == 4 then
94                                         rv[#rv+1] = {
95                                                 expires  = (expire >= 0) and os.difftime(expire, os.time()),
96                                                 macaddr  = ipc.checkmac(duid:gsub("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$", "%1:%2:%3:%4:%5:%6")) or "00:00:00:00:00:00",
97                                                 ipaddr   = ip,
98                                                 hostname = (name ~= "-") and name
99                                         }
100                                 end
101                         end
102                 end
103                 fd:close()
104         end
105
106         if family == 6 then
107                 local _, lease
108                 local hosts = sys.net.host_hints()
109                 for _, lease in ipairs(rv) do
110                         local mac = duid_to_mac(lease.duid)
111                         local host = mac and hosts[mac]
112                         if host then
113                                 if not lease.name then
114                                         lease.host_hint = host.name or host.ipv4 or host.ipv6
115                                 elseif host.name and lease.hostname ~= host.name then
116                                         lease.host_hint = host.name
117                                 end
118                         end
119                 end
120         end
121
122         return rv
123 end
124
125 function dhcp_leases()
126         return dhcp_leases_common(4)
127 end
128
129 function dhcp6_leases()
130         return dhcp_leases_common(6)
131 end
132
133 function wifi_networks()
134         local rv = { }
135         local ntm = require "luci.model.network".init()
136
137         local dev
138         for _, dev in ipairs(ntm:get_wifidevs()) do
139                 local rd = {
140                         up       = dev:is_up(),
141                         device   = dev:name(),
142                         name     = dev:get_i18n(),
143                         networks = { }
144                 }
145
146                 local net
147                 for _, net in ipairs(dev:get_wifinets()) do
148                         local a, an = nil, 0
149                         for _, a in pairs(net:assoclist() or {}) do
150                                 an = an + 1
151                         end
152
153                         rd.networks[#rd.networks+1] = {
154                                 name       = net:shortname(),
155                                 link       = net:adminlink(),
156                                 up         = net:is_up(),
157                                 mode       = net:active_mode(),
158                                 ssid       = net:active_ssid(),
159                                 bssid      = net:active_bssid(),
160                                 encryption = net:active_encryption(),
161                                 frequency  = net:frequency(),
162                                 channel    = net:channel(),
163                                 signal     = net:signal(),
164                                 quality    = net:signal_percent(),
165                                 noise      = net:noise(),
166                                 bitrate    = net:bitrate(),
167                                 ifname     = net:ifname(),
168                                 country    = net:country(),
169                                 txpower    = net:txpower(),
170                                 txpoweroff = net:txpower_offset(),
171                                 num_assoc  = an,
172                                 disabled   = (dev:get("disabled") == "1" or
173                                              net:get("disabled") == "1")
174                         }
175                 end
176
177                 rv[#rv+1] = rd
178         end
179
180         return rv
181 end
182
183 function wifi_network(id)
184         local ntm = require "luci.model.network".init()
185         local net = ntm:get_wifinet(id)
186         if net then
187                 local dev = net:get_device()
188                 if dev then
189                         return {
190                                 id         = id,
191                                 name       = net:shortname(),
192                                 link       = net:adminlink(),
193                                 up         = net:is_up(),
194                                 mode       = net:active_mode(),
195                                 ssid       = net:active_ssid(),
196                                 bssid      = net:active_bssid(),
197                                 encryption = net:active_encryption(),
198                                 frequency  = net:frequency(),
199                                 channel    = net:channel(),
200                                 signal     = net:signal(),
201                                 quality    = net:signal_percent(),
202                                 noise      = net:noise(),
203                                 bitrate    = net:bitrate(),
204                                 ifname     = net:ifname(),
205                                 country    = net:country(),
206                                 txpower    = net:txpower(),
207                                 txpoweroff = net:txpower_offset(),
208                                 disabled   = (dev:get("disabled") == "1" or
209                                               net:get("disabled") == "1"),
210                                 device     = {
211                                         up     = dev:is_up(),
212                                         device = dev:name(),
213                                         name   = dev:get_i18n()
214                                 }
215                         }
216                 end
217         end
218         return { }
219 end
220
221 function wifi_assoclist()
222         local sys = require "luci.sys"
223         local ntm = require "luci.model.network".init()
224         local hosts = sys.net.host_hints()
225
226         local assoc = {}
227         local _, dev, net, bss
228
229         for _, dev in ipairs(ntm:get_wifidevs()) do
230                 local radioname = dev:get_i18n()
231
232                 for _, net in ipairs(dev:get_wifinets()) do
233                         local netname = net:shortname()
234                         local netlink = net:adminlink()
235                         local ifname  = net:ifname()
236
237                         for _, bss in pairs(net:assoclist() or {}) do
238                                 local host = hosts[_]
239
240                                 bss.bssid  = _
241                                 bss.ifname = ifname
242                                 bss.radio  = radioname
243                                 bss.name   = netname
244                                 bss.link   = netlink
245
246                                 bss.host_name = (host) and (host.name or host.ipv4 or host.ipv6)
247                                 bss.host_hint = (host and host.name and (host.ipv4 or host.ipv6)) and (host.ipv4 or host.ipv6)
248
249                                 assoc[#assoc+1] = bss
250                         end
251                 end
252         end
253
254         table.sort(assoc, function(a, b)
255                 if a.radio ~= b.radio then
256                         return a.radio < b.radio
257                 elseif a.ifname ~= b.ifname then
258                         return a.ifname < b.ifname
259                 else
260                         return a.bssid < b.bssid
261                 end
262         end)
263
264         return assoc
265 end
266
267 function switch_status(devs)
268         local dev
269         local switches = { }
270         for dev in devs:gmatch("[^%s,]+") do
271                 local ports = { }
272                 local swc = io.popen("swconfig dev %s show"
273                         % luci.util.shellquote(dev), "r")
274
275                 if swc then
276                         local l
277                         repeat
278                                 l = swc:read("*l")
279                                 if l then
280                                         local port, up = l:match("port:(%d+) link:(%w+)")
281                                         if port then
282                                                 local speed  = l:match(" speed:(%d+)")
283                                                 local duplex = l:match(" (%w+)-duplex")
284                                                 local txflow = l:match(" (txflow)")
285                                                 local rxflow = l:match(" (rxflow)")
286                                                 local auto   = l:match(" (auto)")
287
288                                                 ports[#ports+1] = {
289                                                         port   = tonumber(port) or 0,
290                                                         speed  = tonumber(speed) or 0,
291                                                         link   = (up == "up"),
292                                                         duplex = (duplex == "full"),
293                                                         rxflow = (not not rxflow),
294                                                         txflow = (not not txflow),
295                                                         auto   = (not not auto)
296                                                 }
297                                         end
298                                 end
299                         until not l
300                         swc:close()
301                 end
302                 switches[dev] = ports
303         end
304         return switches
305 end