53b3cf56b43559b06401ce039859217f30419029
[project/luci.git] / libs / core / luasrc / model / network.lua
1 --[[
2 LuCI - Network model
3
4 Copyright 2009-2010 Jo-Philipp Wich <xm@subsignal.org>
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10         http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17
18 ]]--
19
20 local type, pairs, ipairs, loadfile, table, tonumber, i18n
21         = type, pairs, ipairs, loadfile, table, tonumber, luci.i18n
22
23 local nxo = require "nixio"
24 local ipc = require "luci.ip"
25 local sys = require "luci.sys"
26 local utl = require "luci.util"
27 local dsp = require "luci.dispatcher"
28 local uci = require "luci.model.uci"
29
30 module "luci.model.network"
31
32
33 local ifs, brs, sws, uci_r, uci_s
34
35 function list_remove(c, s, o, r)
36         local val = uci_r:get(c, s, o)
37         if val then
38                 local l = { }
39                 if type(val) == "string" then
40                         for val in val:gmatch("%S+") do
41                                 if val ~= r then
42                                         l[#l+1] = val
43                                 end
44                         end
45                         if #l > 0 then
46                                 uci_r:set(c, s, o, table.concat(l, " "))
47                         else
48                                 uci_r:delete(c, s, o)
49                         end
50                 elseif type(val) == "table" then
51                         for _, val in ipairs(val) do
52                                 if val ~= r then
53                                         l[#l+1] = val
54                                 end
55                         end
56                         if #l > 0 then
57                                 uci_r:set(c, s, o, l)
58                         else
59                                 uci_r:delete(c, s, o)
60                         end
61                 end
62         end
63 end
64
65 function list_add(c, s, o, a)
66         local val = uci_r:get(c, s, o) or ""
67         if type(val) == "string" then
68                 local l = { }
69                 for val in val:gmatch("%S+") do
70                         if val ~= a then
71                                 l[#l+1] = val
72                         end
73                 end
74                 l[#l+1] = a
75                 uci_r:set(c, s, o, table.concat(l, " "))
76         elseif type(val) == "table" then
77                 local l = { }
78                 for _, val in ipairs(val) do
79                         if val ~= a then
80                                 l[#l+1] = val
81                         end
82                 end
83                 l[#l+1] = a
84                 uci_r:set(c, s, o, l)
85         end
86 end
87
88 function wifi_iface(x)
89         return (
90                 x:match("^wlan%d") or x:match("^wl%d") or x:match("^ath%d") or
91                 x:match("^%w+%.network%d")
92         )
93 end
94
95 function wifi_lookup(ifn)
96         -- got a radio#.network# pseudo iface, locate the corresponding section
97         local radio, ifnidx = ifn:match("^(%w+)%.network(%d+)$")
98         if radio and ifnidx then
99                 local sid = nil
100                 local num = 0
101
102                 ifnidx = tonumber(ifnidx)
103                 uci_r:foreach("wireless", "wifi-iface",
104                         function(s)
105                                 if s.device == radio then
106                                         num = num + 1
107                                         if num == ifnidx then
108                                                 sid = s['.name']
109                                                 return false
110                                         end
111                                 end
112                         end)
113
114                 return sid
115
116         -- looks like wifi, try to locate the section via state vars
117         elseif wifi_iface(ifn) then
118                 local sid = nil
119
120                 uci_s:foreach("wireless", "wifi-iface",
121                         function(s)
122                                 if s.ifname == ifn then
123                                         sid = s['.name']
124                                         return false
125                                 end
126                         end)
127
128                 return sid
129         end
130 end
131
132 function iface_ignore(x)
133         return (
134                 x:match("^wmaster%d") or x:match("^wifi%d") or x:match("^hwsim%d") or
135                 x:match("^imq%d") or x:match("^mon.wlan%d") or x:match("^6in4-%w") or
136                 x:match("^3g-%w") or x:match("^ppp-%w") or x:match("^pppoe-%w") or
137                 x:match("^pppoa-%w") or x == "lo"
138         )
139 end
140
141
142 function init(cursor)
143         if cursor then
144                 uci_r = cursor
145                 uci_s = cursor:substate()
146
147                 ifs = { }
148                 brs = { }
149                 sws = { }
150
151                 -- read interface information
152                 local n, i
153                 for n, i in ipairs(nxo.getifaddrs()) do
154                         local name = i.name:match("[^:]+")
155                         local prnt = name:match("^([^%.]+)%.")
156
157                         if not iface_ignore(name) then
158                                 ifs[name] = ifs[name] or {
159                                         idx      = i.ifindex or n,
160                                         name     = name,
161                                         rawname  = i.name,
162                                         flags    = { },
163                                         ipaddrs  = { },
164                                         ip6addrs = { }
165                                 }
166
167                                 if prnt then
168                                         sws[name] = true
169                                         sws[prnt] = true
170                                 end
171
172                                 if i.family == "packet" then
173                                         ifs[name].flags   = i.flags
174                                         ifs[name].stats   = i.data
175                                         ifs[name].macaddr = i.addr
176                                 elseif i.family == "inet" then
177                                         ifs[name].ipaddrs[#ifs[name].ipaddrs+1] = ipc.IPv4(i.addr, i.netmask)
178                                 elseif i.family == "inet6" then
179                                         ifs[name].ip6addrs[#ifs[name].ip6addrs+1] = ipc.IPv6(i.addr, i.netmask)
180                                 end
181                         end
182                 end
183
184                 -- read bridge informaton
185                 local b, l
186                 for l in utl.execi("brctl show") do
187                         if not l:match("STP") then
188                                 local r = utl.split(l, "%s+", nil, true)
189                                 if #r == 4 then
190                                         b = {
191                                                 name    = r[1],
192                                                 id      = r[2],
193                                                 stp     = r[3] == "yes",
194                                                 ifnames = { ifs[r[4]] }
195                                         }
196                                         if b.ifnames[1] then
197                                                 b.ifnames[1].bridge = b
198                                         end
199                                         brs[r[1]] = b
200                                 elseif b then
201                                         b.ifnames[#b.ifnames+1] = ifs[r[2]]
202                                         b.ifnames[#b.ifnames].bridge = b
203                                 end
204                         end
205                 end
206         end
207 end
208
209 function has_ipv6(self)
210         return nfs.access("/proc/net/ipv6_route")
211 end
212
213 function add_network(self, n, options)
214         if n and #n > 0 and n:match("^[a-zA-Z0-9_]+$") and not self:get_network(n) then
215                 if uci_r:section("network", "interface", n, options) then
216                         return network(n)
217                 end
218         end
219 end
220
221 function get_network(self, n)
222         if n and uci_r:get("network", n) == "interface" then
223                 return network(n)
224         end
225 end
226
227 function get_networks(self)
228         local nets = { }
229         uci_r:foreach("network", "interface",
230                 function(s)
231                         nets[#nets+1] = network(s['.name'])
232                 end)
233         return nets
234 end
235
236 function del_network(self, n)
237         local r = uci_r:delete("network", n)
238         if r then
239                 uci_r:foreach("network", "alias",
240                         function(s)
241                                 if s.interface == n then
242                                         uci_r:delete("network", s['.name'])
243                                 end
244                         end)
245
246                 uci_r:foreach("network", "route",
247                         function(s)
248                                 if s.interface == n then
249                                         uci_r:delete("network", s['.name'])
250                                 end
251                         end)
252
253                 uci_r:foreach("network", "route6",
254                         function(s)
255                                 if s.interface == n then
256                                         uci_r:delete("network", s['.name'])
257                                 end
258                         end)
259
260                 uci_r:foreach("wireless", "wifi-iface",
261                         function(s)
262                                 if s.network == n then
263                                         uci_r:delete("wireless", s['.name'], "network")
264                                 end
265                         end)
266
267                 uci_r:delete("network", n)
268         end
269         return r
270 end
271
272 function rename_network(self, old, new)
273         local r
274         if new and #new > 0 and new:match("^[a-zA-Z0-9_]+$") and not self:get_network(new) then
275                 r = uci_r:section("network", "interface", new, uci_r:get_all("network", old))
276
277                 if r then
278                         uci_r:foreach("network", "alias",
279                                 function(s)
280                                         if s.interface == old then
281                                                 uci_r:set("network", s['.name'], "interface", new)
282                                         end
283                                 end)
284
285                         uci_r:foreach("network", "route",
286                                 function(s)
287                                         if s.interface == old then
288                                                 uci_r:set("network", s['.name'], "interface", new)
289                                         end
290                                 end)
291
292                         uci_r:foreach("network", "route6",
293                                 function(s)
294                                         if s.interface == old then
295                                                 uci_r:set("network", s['.name'], "interface", new)
296                                         end
297                                 end)
298
299                         uci_r:foreach("wireless", "wifi-iface",
300                                 function(s)
301                                         if s.network == old then
302                                                 uci_r:set("wireless", s['.name'], "network", new)
303                                         end
304                                 end)
305
306                         uci_r:delete("network", old)
307                 end
308         end
309         return r or false
310 end
311
312 function get_interface(self, i)
313         if ifs[i] or wifi_iface(i) then
314                 return interface(i)
315         else
316                 local ifc
317                 local num = { }
318                 uci_r:foreach("wireless", "wifi-iface",
319                         function(s)
320                                 if s.device then
321                                         num[s.device] = num[s.device] and num[s.device] + 1 or 1
322                                         if s['.name'] == i then
323                                                 ifc = interface(
324                                                         "%s.network%d" %{s.device, num[s.device] })
325                                                 return false
326                                         end
327                                 end
328                         end)
329                 return ifc
330         end
331 end
332
333 function get_interfaces(self)
334         local iface
335         local ifaces = { }
336
337         -- find normal interfaces
338         for iface in utl.kspairs(ifs) do
339                 if not iface_ignore(iface) and not wifi_iface(iface) then
340                         ifaces[#ifaces+1] = interface(iface)
341                 end
342         end
343
344         -- find wifi interfaces
345         local num = { }
346         local wfs = { }
347         uci_r:foreach("wireless", "wifi-iface",
348                 function(s)
349                         if s.device then
350                                 num[s.device] = num[s.device] and num[s.device] + 1 or 1
351                                 local i = "%s.network%d" %{ s.device, num[s.device] }
352                                 wfs[i] = interface(i)
353                         end
354                 end)
355
356         for iface in utl.kspairs(wfs) do
357                 ifaces[#ifaces+1] = wfs[iface]
358         end
359
360         return ifaces
361 end
362
363 function ignore_interface(self, x)
364         return iface_ignore(x)
365 end
366
367
368 network = utl.class()
369
370 function network.__init__(self, name)
371         self.sid = name
372 end
373
374 function network._get(self, opt)
375         local v = uci_r:get("network", self.sid, opt)
376         if type(v) == "table" then
377                 return table.concat(v, " ")
378         end
379         return v or ""
380 end
381
382 function network.ifname(self)
383         local p = self:proto()
384         if self:is_bridge() then
385                 return "br-" .. self.sid
386         elseif self:is_virtual() then
387                 return p .. "-" .. self.sid
388         else
389                 local dev = self:_get("ifname") or
390                         uci_r:get("network", self.sid, "ifname")
391
392                 dev = dev and dev:match("%S+")
393
394                 if not dev then
395                         uci_r:foreach("wireless", "wifi-iface",
396                                 function(s)
397                                         if s.device then
398                                                 num[s.device] = num[s.device]
399                                                         and num[s.device] + 1 or 1
400
401                                                 if s.network == self.sid then
402                                                         dev = "%s.network%d" %{ s.device, num[s.device] }
403                                                         return false
404                                                 end
405                                         end
406                                 end)
407                 end
408
409                 return dev
410         end
411 end
412
413 function network.device(self)
414         local dev = self:_get("device")
415         if not dev or dev:match("[^%w%-%.%s]") then
416                 dev = uci_r:get("network", self.sid, "ifname")
417         end
418         return dev
419 end
420
421 function network.proto(self)
422         return self:_get("proto") or "none"
423 end
424
425 function network.type(self)
426         return self:_get("type")
427 end
428
429 function network.name(self)
430         return self.sid
431 end
432
433 function network.is_bridge(self)
434         return (self:type() == "bridge")
435 end
436
437 function network.is_virtual(self)
438         local p = self:proto()
439         return (
440                 p == "3g" or p == "6in4" or p == "ppp" or
441                 p == "pppoe" or p == "pppoa"
442         )
443 end
444
445 function network.is_empty(self)
446         if self:is_virtual() then
447                 return false
448         else
449                 local rv = true
450
451                 if (self:_get("ifname") or ""):match("%S+") then
452                         rv = false
453                 end
454
455                 uci_r:foreach("wireless", "wifi-iface",
456                         function(s)
457                                 if s.network == self.sid then
458                                         rv = false
459                                         return false
460                                 end
461                         end)
462
463                 return rv
464         end
465 end
466
467 function network.add_interface(self, ifname)
468         if not self:is_virtual() then
469                 if type(ifname) ~= "string" then
470                         ifname = ifname:name()
471                 else
472                         ifname = ifname:match("[^%s:]+")
473                 end
474
475                 -- remove the interface from all ifaces
476                 uci_r:foreach("network", "interface",
477                         function(s)
478                                 list_remove("network", s['.name'], "ifname", ifname)
479                         end)
480
481                 -- if its a wifi interface, change its network option
482                 local wif = wifi_lookup(ifname)
483                 if wif then
484                         uci_r:set("wireless", wif, "network", self.sid)
485
486                 -- add iface to our iface list
487                 else
488                         list_add("network", self.sid, "ifname", ifname)
489                 end
490         end
491 end
492
493 function network.del_interface(self, ifname)
494         if not self:is_virtual() then
495                 if type(ifname) ~= "string" then
496                         ifname = ifname:name()
497                 else
498                         ifname = ifname:match("[^%s:]+")
499                 end
500
501                 -- if its a wireless interface, clear its network option
502                 local wif = wifi_lookup(ifname)
503                 if wif then     uci_r:delete("wireless", wif, "network") end
504
505                 -- remove the interface
506                 list_remove("network", self.sid, "ifname", ifname)
507         end
508 end
509
510 function network.get_interfaces(self)
511         local ifaces = { }
512
513         local ifn
514         if self:is_virtual() then
515                 ifn = self:proto() .. "-" .. self.sid
516                 ifaces = { interface(ifn) }
517         else
518                 local nfs = { }
519                 for ifn in self:_get("ifname"):gmatch("%S+") do
520                         ifn = ifn:match("[^:]+")
521                         nfs[ifn] = interface(ifn)
522                 end
523
524                 for ifn in utl.kspairs(nfs) do
525                         ifaces[#ifaces+1] = nfs[ifn]
526                 end
527
528                 local num = { }
529                 local wfs = { }
530                 uci_r:foreach("wireless", "wifi-iface",
531                         function(s)
532                                 if s.device then
533                                         num[s.device] = num[s.device] and num[s.device] + 1 or 1
534                                         if s.network == self.sid then
535                                                 ifn = "%s.network%d" %{ s.device, num[s.device] }
536                                                 wfs[ifn] = interface(ifn)
537                                         end
538                                 end
539                         end)
540
541                 for ifn in utl.kspairs(wfs) do
542                         ifaces[#ifaces+1] = wfs[ifn]
543                 end
544         end
545
546         return ifaces
547 end
548
549 function network.contains_interface(self, ifname)
550         if type(ifname) ~= "string" then
551                 ifname = ifname:name()
552         else
553                 ifname = ifname:match("[^%s:]+")
554         end
555
556         local ifn
557         if self:is_virtual() then
558                 ifn = self:proto() .. "-" .. self.sid
559                 return ifname == ifn
560         else
561                 for ifn in self:_get("ifname"):gmatch("%S+") do
562                         ifn = ifn:match("[^:]+")
563                         if ifn == ifname then
564                                 return true
565                         end
566                 end
567
568                 local wif = wifi_lookup(ifname)
569                 if wif then
570                         return (uci_r:get("wireless", wif, "network") == self.sid)
571                 end
572         end
573
574         return false
575 end
576
577 function network.adminlink(self)
578         return dsp.build_url("admin", "network", "network", self.sid)
579 end
580
581
582 interface = utl.class()
583 function interface.__init__(self, ifname)
584         self.wif = wifi_lookup(ifname)
585
586         if self.wif then
587                 self.ifname = uci_s:get("wireless", self.wif, "ifname")
588                 self.iwinfo = self.ifname and sys.wifi.getiwinfo(self.ifname) or { }
589                 self.iwdata = uci_s:get_all("wireless", self.wif) or { }
590                 self.iwname = ifname
591         end
592
593         self.ifname = self.ifname or ifname
594         self.dev    = ifs[self.ifname]
595 end
596
597 function interface.name(self)
598         return self.wif and uci_s:get("wireless", self.wif, "ifname") or self.ifname
599 end
600
601 function interface.mac(self)
602         return self.dev and self.dev or "00:00:00:00:00:00"
603 end
604
605 function interface.ipaddrs(self)
606         return self.dev and self.dev.ipaddrs or { }
607 end
608
609 function interface.ip6addrs(self)
610         return self.dev and self.dev.ip6addrs or { }
611 end
612
613 function interface.type(self)
614         if wifi_iface(self.ifname) then
615                 return "wifi"
616         elseif brs[self.ifname] then
617                 return "bridge"
618         elseif sws[self.ifname] or self.ifname:match("%.") then
619                 return "switch"
620         else
621                 return "ethernet"
622         end
623 end
624
625 function _choose(s1, s2)
626         if not s1 or #s1 == 0 then
627                 return s2 and #s2 > 0 and s2
628         else
629                 return s1
630         end
631 end
632
633 function interface.shortname(self)
634         if self.iwinfo or self.iwdata then
635                 return "%s %q" %{
636                         i18n.translate(self.iwinfo.mode),
637                         _choose(self.iwinfo.ssid,  self.iwdata.ssid ) or
638                         _choose(self.iwinfo.bssid, self.iwdata.bssid) or
639                         "%s (%s)" %{ i18n.translate("unknown"), self.ifname }
640                 }
641         else
642                 return self.ifname
643         end
644 end
645
646 function interface.get_i18n(self)
647         if self.iwinfo or self.iwdata then
648                 return "%s: %s %q" %{
649                         i18n.translate("Wireless Network"),
650                         _choose(self.iwinfo.mode,  self.iwdata.mode ),
651                         _choose(self.iwinfo.ssid,  self.iwdata.ssid ) or
652                         _choose(self.iwinfo.bssid, self.iwdata.bssid) or
653                         "%s (%s)" %{ i18n.translate("unknown"), self.ifname }
654                 }
655         else
656                 return "%s: %q" %{ self:get_type_i18n(), self:name() }
657         end
658 end
659
660 function interface.get_type_i18n(self)
661         local x = self:type()
662         if x == "wifi" then
663                 return i18n.translate("Wireless Adapter")
664         elseif x == "bridge" then
665                 return i18n.translate("Bridge")
666         elseif x == "switch" then
667                 return i18n.translate("Ethernet Switch")
668         else
669                 return i18n.translate("Ethernet Adapter")
670         end
671 end
672
673 function interface.adminlink(self)
674         if self:type() == "wifi" then
675                 return dsp.build_url("admin", "network", "wireless",
676                         self.iwdata.device, self.iwname)
677         end
678 end
679
680 function interface.ports(self)
681         if self.br then
682                 local iface
683                 local ifaces = { }
684                 for _, iface in ipairs(self.br.ifnames) do
685                         ifaces[#ifaces+1] = interface(iface.name)
686                 end
687                 return ifaces
688         end
689 end
690
691 function interface.bridge_id(self)
692         if self.br then
693                 return self.br.id
694         else
695                 return nil
696         end
697 end
698
699 function interface.bridge_stp(self)
700         if self.br then
701                 return self.br.stp
702         else
703                 return false
704         end
705 end
706
707 function interface.is_up(self)
708         return self.dev and self.dev.flags and self.dev.flags.up or false
709 end
710
711 function interface.is_bridge(self)
712         return (self:type() == "bridge")
713 end
714
715 function interface.is_bridgeport(self)
716         return self.dev and self.dev.bridge and true or false
717 end
718
719 function interface.tx_bytes(self)
720         return self.dev and self.dev.stats
721                 and self.dev.stats.tx_bytes or 0
722 end
723
724 function interface.rx_bytes(self)
725         return self.dev and self.dev.stats
726                 and self.dev.stats.rx_bytes or 0
727 end
728
729 function interface.tx_packets(self)
730         return self.dev and self.dev.stats
731                 and self.dev.stats.tx_packets or 0
732 end
733
734 function interface.rx_packets(self)
735         return self.dev and self.dev.stats
736                 and self.dev.stats.rx_packets or 0
737 end
738
739 function interface.get_network(self)
740         if self.dev and self.dev.network then
741                 self.network = _M:get_network(self.dev.network)
742         end
743
744         if not self.network then
745                 local net
746                 for _, net in ipairs(_M:get_networks()) do
747                         if net:contains_interface(self.ifname) then
748                                 self.network = net
749                                 return net
750                         end
751                 end
752         else
753                 return self.network
754         end
755 end