luci-base: do not assume a fixed host address in delegated prefix (#1484)
[project/luci.git] / modules / luci-base / luasrc / model / network.lua
1 -- Copyright 2009-2015 Jo-Philipp Wich <jow@openwrt.org>
2 -- Licensed to the public under the Apache License 2.0.
3
4 local type, next, pairs, ipairs, loadfile, table, select
5 = type, next, pairs, ipairs, loadfile, table, select
6
7 local tonumber, tostring, math = tonumber, tostring, math
8
9 local pcall, require, setmetatable = pcall, require, setmetatable
10
11 local nxo = require "nixio"
12 local nfs = require "nixio.fs"
13 local ipc = require "luci.ip"
14 local utl = require "luci.util"
15 local uci = require "luci.model.uci"
16 local lng = require "luci.i18n"
17 local jsc = require "luci.jsonc"
18
19 module "luci.model.network"
20
21
22 IFACE_PATTERNS_VIRTUAL = { }
23 IFACE_PATTERNS_IGNORE = { "^wmaster%d", "^wifi%d", "^hwsim%d", "^imq%d", "^ifb%d", "^mon%.wlan%d", "^sit%d", "^gre%d", "^gretap%d", "^ip6gre%d", "^ip6tnl%d", "^tunl%d", "^lo$" }
24 IFACE_PATTERNS_WIRELESS = { "^wlan%d", "^wl%d", "^ath%d", "^%w+%.network%d" }
25
26
27 protocol = utl.class()
28
29 local _protocols = { }
30
31 local _interfaces, _bridge, _switch, _tunnel, _swtopo
32 local _ubusnetcache, _ubusdevcache, _ubuswificache
33 local _uci
34
35 function _filter(c, s, o, r)
36 local val = _uci: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:set(c, s, o, table.concat(l, " "))
47 else
48 _uci: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:set(c, s, o, l)
58 else
59 _uci:delete(c, s, o)
60 end
61 end
62 end
63 end
64
65 function _append(c, s, o, a)
66 local val = _uci: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: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:set(c, s, o, l)
85 end
86 end
87
88 function _stror(s1, s2)
89 if not s1 or #s1 == 0 then
90 return s2 and #s2 > 0 and s2
91 else
92 return s1
93 end
94 end
95
96 function _get(c, s, o)
97 return _uci:get(c, s, o)
98 end
99
100 function _set(c, s, o, v)
101 if v ~= nil then
102 if type(v) == "boolean" then v = v and "1" or "0" end
103 return _uci:set(c, s, o, v)
104 else
105 return _uci:delete(c, s, o)
106 end
107 end
108
109 local function _wifi_state()
110 if not next(_ubuswificache) then
111 _ubuswificache = utl.ubus("network.wireless", "status", {}) or {}
112 end
113 return _ubuswificache
114 end
115
116 local function _wifi_state_by_sid(sid)
117 local t1, n1 = _uci:get("wireless", sid)
118 if t1 == "wifi-iface" and n1 ~= nil then
119 local radioname, radiostate
120 for radioname, radiostate in pairs(_wifi_state()) do
121 if type(radiostate) == "table" and
122 type(radiostate.interfaces) == "table"
123 then
124 local netidx, netstate
125 for netidx, netstate in ipairs(radiostate.interfaces) do
126 if type(netstate) == "table" and
127 type(netstate.section) == "string"
128 then
129 local t2, n2 = _uci:get("wireless", netstate.section)
130 if t1 == t2 and n1 == n2 then
131 return radioname, radiostate, netstate
132 end
133 end
134 end
135 end
136 end
137 end
138 end
139
140 local function _wifi_state_by_ifname(ifname)
141 if type(ifname) == "string" then
142 local radioname, radiostate
143 for radioname, radiostate in pairs(_wifi_state()) do
144 if type(radiostate) == "table" and
145 type(radiostate.interfaces) == "table"
146 then
147 local netidx, netstate
148 for netidx, netstate in ipairs(radiostate.interfaces) do
149 if type(netstate) == "table" and
150 type(netstate.ifname) == "string" and
151 netstate.ifname == ifname
152 then
153 return radioname, radiostate, netstate
154 end
155 end
156 end
157 end
158 end
159 end
160
161 function _wifi_iface(x)
162 local _, p
163 for _, p in ipairs(IFACE_PATTERNS_WIRELESS) do
164 if x:match(p) then
165 return true
166 end
167 end
168 return false
169 end
170
171 local function _wifi_iwinfo_by_ifname(ifname, force_phy_only)
172 local stat, iwinfo = pcall(require, "iwinfo")
173 local iwtype = stat and type(ifname) == "string" and iwinfo.type(ifname)
174 local is_nonphy_op = {
175 bitrate = true,
176 quality = true,
177 quality_max = true,
178 mode = true,
179 ssid = true,
180 bssid = true,
181 assoclist = true,
182 encryption = true
183 }
184
185 if iwtype then
186 -- if we got a type but no real netdev, we're referring to a phy
187 local phy_only = force_phy_only or (ipc.link(ifname).type ~= 1)
188
189 return setmetatable({}, {
190 __index = function(t, k)
191 if k == "ifname" then
192 return ifname
193 elseif phy_only and is_nonphy_op[k] then
194 return nil
195 elseif iwinfo[iwtype][k] then
196 return iwinfo[iwtype][k](ifname)
197 end
198 end
199 })
200 end
201 end
202
203 local function _wifi_sid_by_netid(netid)
204 if type(netid) == "string" then
205 local radioname, netidx = netid:match("^(%w+)%.network(%d+)$")
206 if radioname and netidx then
207 local i, n = 0, nil
208
209 netidx = tonumber(netidx)
210 _uci:foreach("wireless", "wifi-iface",
211 function(s)
212 if s.device == radioname then
213 i = i + 1
214 if i == netidx then
215 n = s[".name"]
216 return false
217 end
218 end
219 end)
220
221 return n
222 end
223 end
224 end
225
226 function _wifi_sid_by_ifname(ifn)
227 local sid = _wifi_sid_by_netid(ifn)
228 if sid then
229 return sid
230 end
231
232 local _, _, netstate = _wifi_state_by_ifname(ifn)
233 if netstate and type(netstate.section) == "string" then
234 return netstate.section
235 end
236 end
237
238 local function _wifi_netid_by_sid(sid)
239 local t, n = _uci:get("wireless", sid)
240 if t == "wifi-iface" and n ~= nil then
241 local radioname = _uci:get("wireless", n, "device")
242 if type(radioname) == "string" then
243 local i, netid = 0, nil
244
245 _uci:foreach("wireless", "wifi-iface",
246 function(s)
247 if s.device == radioname then
248 i = i + 1
249 if s[".name"] == n then
250 netid = "%s.network%d" %{ radioname, i }
251 return false
252 end
253 end
254 end)
255
256 return netid, radioname
257 end
258 end
259 end
260
261 local function _wifi_netid_by_netname(name)
262 local netid = nil
263
264 _uci:foreach("wireless", "wifi-iface",
265 function(s)
266 local net
267 for net in utl.imatch(s.network) do
268 if net == name then
269 netid = _wifi_netid_by_sid(s[".name"])
270 return false
271 end
272 end
273 end)
274
275 return netid
276 end
277
278 function _iface_virtual(x)
279 local _, p
280 for _, p in ipairs(IFACE_PATTERNS_VIRTUAL) do
281 if x:match(p) then
282 return true
283 end
284 end
285 return false
286 end
287
288 function _iface_ignore(x)
289 local _, p
290 for _, p in ipairs(IFACE_PATTERNS_IGNORE) do
291 if x:match(p) then
292 return true
293 end
294 end
295 return false
296 end
297
298 function init(cursor)
299 _uci = cursor or _uci or uci.cursor()
300
301 _interfaces = { }
302 _bridge = { }
303 _switch = { }
304 _tunnel = { }
305 _swtopo = { }
306
307 _ubusnetcache = { }
308 _ubusdevcache = { }
309 _ubuswificache = { }
310
311 -- read interface information
312 local n, i
313 for n, i in ipairs(nxo.getifaddrs()) do
314 local name = i.name:match("[^:]+")
315
316 if _iface_virtual(name) then
317 _tunnel[name] = true
318 end
319
320 if _tunnel[name] or not (_iface_ignore(name) or _iface_virtual(name)) then
321 _interfaces[name] = _interfaces[name] or {
322 idx = i.ifindex or n,
323 name = name,
324 rawname = i.name,
325 flags = { },
326 ipaddrs = { },
327 ip6addrs = { }
328 }
329
330 if i.family == "packet" then
331 _interfaces[name].flags = i.flags
332 _interfaces[name].stats = i.data
333 _interfaces[name].macaddr = i.addr
334 elseif i.family == "inet" then
335 _interfaces[name].ipaddrs[#_interfaces[name].ipaddrs+1] = ipc.IPv4(i.addr, i.netmask)
336 elseif i.family == "inet6" then
337 _interfaces[name].ip6addrs[#_interfaces[name].ip6addrs+1] = ipc.IPv6(i.addr, i.netmask)
338 end
339 end
340 end
341
342 -- read bridge informaton
343 local b, l
344 for l in utl.execi("brctl show") do
345 if not l:match("STP") then
346 local r = utl.split(l, "%s+", nil, true)
347 if #r == 4 then
348 b = {
349 name = r[1],
350 id = r[2],
351 stp = r[3] == "yes",
352 ifnames = { _interfaces[r[4]] }
353 }
354 if b.ifnames[1] then
355 b.ifnames[1].bridge = b
356 end
357 _bridge[r[1]] = b
358 elseif b then
359 b.ifnames[#b.ifnames+1] = _interfaces[r[2]]
360 b.ifnames[#b.ifnames].bridge = b
361 end
362 end
363 end
364
365 -- read switch topology
366 local boardinfo = jsc.parse(nfs.readfile("/etc/board.json") or "")
367 if type(boardinfo) == "table" and type(boardinfo.switch) == "table" then
368 local switch, layout
369 for switch, layout in pairs(boardinfo.switch) do
370 if type(layout) == "table" and type(layout.ports) == "table" then
371 local _, port
372 local ports = { }
373 local nports = { }
374 local netdevs = { }
375
376 for _, port in ipairs(layout.ports) do
377 if type(port) == "table" and
378 type(port.num) == "number" and
379 (type(port.role) == "string" or
380 type(port.device) == "string")
381 then
382 local spec = {
383 num = port.num,
384 role = port.role or "cpu",
385 index = port.index or port.num
386 }
387
388 if port.device then
389 spec.device = port.device
390 spec.tagged = port.need_tag
391 netdevs[tostring(port.num)] = port.device
392 end
393
394 ports[#ports+1] = spec
395
396 if port.role then
397 nports[port.role] = (nports[port.role] or 0) + 1
398 end
399 end
400 end
401
402 table.sort(ports, function(a, b)
403 if a.role ~= b.role then
404 return (a.role < b.role)
405 end
406
407 return (a.index < b.index)
408 end)
409
410 local pnum, role
411 for _, port in ipairs(ports) do
412 if port.role ~= role then
413 role = port.role
414 pnum = 1
415 end
416
417 if role == "cpu" then
418 port.label = "CPU (%s)" % port.device
419 elseif nports[role] > 1 then
420 port.label = "%s %d" %{ role:upper(), pnum }
421 pnum = pnum + 1
422 else
423 port.label = role:upper()
424 end
425
426 port.role = nil
427 port.index = nil
428 end
429
430 _swtopo[switch] = {
431 ports = ports,
432 netdevs = netdevs
433 }
434 end
435 end
436 end
437
438 return _M
439 end
440
441 function save(self, ...)
442 _uci:save(...)
443 _uci:load(...)
444 end
445
446 function commit(self, ...)
447 _uci:commit(...)
448 _uci:load(...)
449 end
450
451 function ifnameof(self, x)
452 if utl.instanceof(x, interface) then
453 return x:name()
454 elseif utl.instanceof(x, protocol) then
455 return x:ifname()
456 elseif type(x) == "string" then
457 return x:match("^[^:]+")
458 end
459 end
460
461 function get_protocol(self, protoname, netname)
462 local v = _protocols[protoname]
463 if v then
464 return v(netname or "__dummy__")
465 end
466 end
467
468 function get_protocols(self)
469 local p = { }
470 local _, v
471 for _, v in ipairs(_protocols) do
472 p[#p+1] = v("__dummy__")
473 end
474 return p
475 end
476
477 function register_protocol(self, protoname)
478 local proto = utl.class(protocol)
479
480 function proto.__init__(self, name)
481 self.sid = name
482 end
483
484 function proto.proto(self)
485 return protoname
486 end
487
488 _protocols[#_protocols+1] = proto
489 _protocols[protoname] = proto
490
491 return proto
492 end
493
494 function register_pattern_virtual(self, pat)
495 IFACE_PATTERNS_VIRTUAL[#IFACE_PATTERNS_VIRTUAL+1] = pat
496 end
497
498
499 function has_ipv6(self)
500 return nfs.access("/proc/net/ipv6_route")
501 end
502
503 function add_network(self, n, options)
504 local oldnet = self:get_network(n)
505 if n and #n > 0 and n:match("^[a-zA-Z0-9_]+$") and not oldnet then
506 if _uci:section("network", "interface", n, options) then
507 return network(n)
508 end
509 elseif oldnet and oldnet:is_empty() then
510 if options then
511 local k, v
512 for k, v in pairs(options) do
513 oldnet:set(k, v)
514 end
515 end
516 return oldnet
517 end
518 end
519
520 function get_network(self, n)
521 if n and _uci:get("network", n) == "interface" then
522 return network(n)
523 end
524 end
525
526 function get_networks(self)
527 local nets = { }
528 local nls = { }
529
530 _uci:foreach("network", "interface",
531 function(s)
532 nls[s['.name']] = network(s['.name'])
533 end)
534
535 local n
536 for n in utl.kspairs(nls) do
537 nets[#nets+1] = nls[n]
538 end
539
540 return nets
541 end
542
543 function del_network(self, n)
544 local r = _uci:delete("network", n)
545 if r then
546 _uci:delete_all("network", "alias",
547 function(s) return (s.interface == n) end)
548
549 _uci:delete_all("network", "route",
550 function(s) return (s.interface == n) end)
551
552 _uci:delete_all("network", "route6",
553 function(s) return (s.interface == n) end)
554
555 _uci:foreach("wireless", "wifi-iface",
556 function(s)
557 local net
558 local rest = { }
559 for net in utl.imatch(s.network) do
560 if net ~= n then
561 rest[#rest+1] = net
562 end
563 end
564 if #rest > 0 then
565 _uci:set("wireless", s['.name'], "network",
566 table.concat(rest, " "))
567 else
568 _uci:delete("wireless", s['.name'], "network")
569 end
570 end)
571 end
572 return r
573 end
574
575 function rename_network(self, old, new)
576 local r
577 if new and #new > 0 and new:match("^[a-zA-Z0-9_]+$") and not self:get_network(new) then
578 r = _uci:section("network", "interface", new, _uci:get_all("network", old))
579
580 if r then
581 _uci:foreach("network", "alias",
582 function(s)
583 if s.interface == old then
584 _uci:set("network", s['.name'], "interface", new)
585 end
586 end)
587
588 _uci:foreach("network", "route",
589 function(s)
590 if s.interface == old then
591 _uci:set("network", s['.name'], "interface", new)
592 end
593 end)
594
595 _uci:foreach("network", "route6",
596 function(s)
597 if s.interface == old then
598 _uci:set("network", s['.name'], "interface", new)
599 end
600 end)
601
602 _uci:foreach("wireless", "wifi-iface",
603 function(s)
604 local net
605 local list = { }
606 for net in utl.imatch(s.network) do
607 if net == old then
608 list[#list+1] = new
609 else
610 list[#list+1] = net
611 end
612 end
613 if #list > 0 then
614 _uci:set("wireless", s['.name'], "network",
615 table.concat(list, " "))
616 end
617 end)
618
619 _uci:delete("network", old)
620 end
621 end
622 return r or false
623 end
624
625 function get_interface(self, i)
626 if _interfaces[i] or _wifi_iface(i) then
627 return interface(i)
628 else
629 local netid = _wifi_netid_by_netname(i)
630 return netid and interface(netid)
631 end
632 end
633
634 function get_interfaces(self)
635 local iface
636 local ifaces = { }
637 local nfs = { }
638
639 -- find normal interfaces
640 _uci:foreach("network", "interface",
641 function(s)
642 for iface in utl.imatch(s.ifname) do
643 if not _iface_ignore(iface) and not _iface_virtual(iface) and not _wifi_iface(iface) then
644 nfs[iface] = interface(iface)
645 end
646 end
647 end)
648
649 for iface in utl.kspairs(_interfaces) do
650 if not (nfs[iface] or _iface_ignore(iface) or _iface_virtual(iface) or _wifi_iface(iface)) then
651 nfs[iface] = interface(iface)
652 end
653 end
654
655 -- find vlan interfaces
656 _uci:foreach("network", "switch_vlan",
657 function(s)
658 if type(s.ports) ~= "string" or
659 type(s.device) ~= "string" or
660 type(_swtopo[s.device]) ~= "table"
661 then
662 return
663 end
664
665 local pnum, ptag
666 for pnum, ptag in s.ports:gmatch("(%d+)([tu]?)") do
667 local netdev = _swtopo[s.device].netdevs[pnum]
668 if netdev then
669 if not nfs[netdev] then
670 nfs[netdev] = interface(netdev)
671 end
672 _switch[netdev] = true
673
674 if ptag == "t" then
675 local vid = tonumber(s.vid or s.vlan)
676 if vid ~= nil and vid >= 0 and vid <= 4095 then
677 local iface = "%s.%d" %{ netdev, vid }
678 if not nfs[iface] then
679 nfs[iface] = interface(iface)
680 end
681 _switch[iface] = true
682 end
683 end
684 end
685 end
686 end)
687
688 for iface in utl.kspairs(nfs) do
689 ifaces[#ifaces+1] = nfs[iface]
690 end
691
692 -- find wifi interfaces
693 local num = { }
694 local wfs = { }
695 _uci:foreach("wireless", "wifi-iface",
696 function(s)
697 if s.device then
698 num[s.device] = num[s.device] and num[s.device] + 1 or 1
699 local i = "%s.network%d" %{ s.device, num[s.device] }
700 wfs[i] = interface(i)
701 end
702 end)
703
704 for iface in utl.kspairs(wfs) do
705 ifaces[#ifaces+1] = wfs[iface]
706 end
707
708 return ifaces
709 end
710
711 function ignore_interface(self, x)
712 return _iface_ignore(x)
713 end
714
715 function get_wifidev(self, dev)
716 if _uci:get("wireless", dev) == "wifi-device" then
717 return wifidev(dev)
718 end
719 end
720
721 function get_wifidevs(self)
722 local devs = { }
723 local wfd = { }
724
725 _uci:foreach("wireless", "wifi-device",
726 function(s) wfd[#wfd+1] = s['.name'] end)
727
728 local dev
729 for _, dev in utl.vspairs(wfd) do
730 devs[#devs+1] = wifidev(dev)
731 end
732
733 return devs
734 end
735
736 function get_wifinet(self, net)
737 local wnet = _wifi_sid_by_ifname(net)
738 if wnet then
739 return wifinet(wnet)
740 end
741 end
742
743 function add_wifinet(self, net, options)
744 if type(options) == "table" and options.device and
745 _uci:get("wireless", options.device) == "wifi-device"
746 then
747 local wnet = _uci:section("wireless", "wifi-iface", nil, options)
748 return wifinet(wnet)
749 end
750 end
751
752 function del_wifinet(self, net)
753 local wnet = _wifi_sid_by_ifname(net)
754 if wnet then
755 _uci:delete("wireless", wnet)
756 return true
757 end
758 return false
759 end
760
761 function get_status_by_route(self, addr, mask)
762 local _, object
763 for _, object in ipairs(utl.ubus()) do
764 local net = object:match("^network%.interface%.(.+)")
765 if net then
766 local s = utl.ubus(object, "status", {})
767 if s and s.route then
768 local rt
769 for _, rt in ipairs(s.route) do
770 if not rt.table and rt.target == addr and rt.mask == mask then
771 return net, s
772 end
773 end
774 end
775 end
776 end
777 end
778
779 function get_status_by_address(self, addr)
780 local _, object
781 for _, object in ipairs(utl.ubus()) do
782 local net = object:match("^network%.interface%.(.+)")
783 if net then
784 local s = utl.ubus(object, "status", {})
785 if s and s['ipv4-address'] then
786 local a
787 for _, a in ipairs(s['ipv4-address']) do
788 if a.address == addr then
789 return net, s
790 end
791 end
792 end
793 if s and s['ipv6-address'] then
794 local a
795 for _, a in ipairs(s['ipv6-address']) do
796 if a.address == addr then
797 return net, s
798 end
799 end
800 end
801 end
802 end
803 end
804
805 function get_wannet(self)
806 local net, stat = self:get_status_by_route("0.0.0.0", 0)
807 return net and network(net, stat.proto)
808 end
809
810 function get_wandev(self)
811 local _, stat = self:get_status_by_route("0.0.0.0", 0)
812 return stat and interface(stat.l3_device or stat.device)
813 end
814
815 function get_wan6net(self)
816 local net, stat = self:get_status_by_route("::", 0)
817 return net and network(net, stat.proto)
818 end
819
820 function get_wan6dev(self)
821 local _, stat = self:get_status_by_route("::", 0)
822 return stat and interface(stat.l3_device or stat.device)
823 end
824
825 function get_switch_topologies(self)
826 return _swtopo
827 end
828
829
830 function network(name, proto)
831 if name then
832 local p = proto or _uci:get("network", name, "proto")
833 local c = p and _protocols[p] or protocol
834 return c(name)
835 end
836 end
837
838 function protocol.__init__(self, name)
839 self.sid = name
840 end
841
842 function protocol._get(self, opt)
843 local v = _uci:get("network", self.sid, opt)
844 if type(v) == "table" then
845 return table.concat(v, " ")
846 end
847 return v or ""
848 end
849
850 function protocol._ubus(self, field)
851 if not _ubusnetcache[self.sid] then
852 _ubusnetcache[self.sid] = utl.ubus("network.interface.%s" % self.sid,
853 "status", { })
854 end
855 if _ubusnetcache[self.sid] and field then
856 return _ubusnetcache[self.sid][field]
857 end
858 return _ubusnetcache[self.sid]
859 end
860
861 function protocol.get(self, opt)
862 return _get("network", self.sid, opt)
863 end
864
865 function protocol.set(self, opt, val)
866 return _set("network", self.sid, opt, val)
867 end
868
869 function protocol.ifname(self)
870 local ifname
871 if self:is_floating() then
872 ifname = self:_ubus("l3_device")
873 else
874 ifname = self:_ubus("device")
875 end
876 if not ifname then
877 ifname = _wifi_netid_by_netname(self.sid)
878 end
879 return ifname
880 end
881
882 function protocol.proto(self)
883 return "none"
884 end
885
886 function protocol.get_i18n(self)
887 local p = self:proto()
888 if p == "none" then
889 return lng.translate("Unmanaged")
890 elseif p == "static" then
891 return lng.translate("Static address")
892 elseif p == "dhcp" then
893 return lng.translate("DHCP client")
894 else
895 return lng.translate("Unknown")
896 end
897 end
898
899 function protocol.type(self)
900 return self:_get("type")
901 end
902
903 function protocol.name(self)
904 return self.sid
905 end
906
907 function protocol.uptime(self)
908 return self:_ubus("uptime") or 0
909 end
910
911 function protocol.expires(self)
912 local u = self:_ubus("uptime")
913 local d = self:_ubus("data")
914
915 if type(u) == "number" and type(d) == "table" and
916 type(d.leasetime) == "number"
917 then
918 local r = (d.leasetime - (u % d.leasetime))
919 return r > 0 and r or 0
920 end
921
922 return -1
923 end
924
925 function protocol.metric(self)
926 return self:_ubus("metric") or 0
927 end
928
929 function protocol.ipaddr(self)
930 local addrs = self:_ubus("ipv4-address")
931 return addrs and #addrs > 0 and addrs[1].address
932 end
933
934 function protocol.ipaddrs(self)
935 local addrs = self:_ubus("ipv4-address")
936 local rv = { }
937
938 if type(addrs) == "table" then
939 local n, addr
940 for n, addr in ipairs(addrs) do
941 rv[#rv+1] = "%s/%d" %{ addr.address, addr.mask }
942 end
943 end
944
945 return rv
946 end
947
948 function protocol.netmask(self)
949 local addrs = self:_ubus("ipv4-address")
950 return addrs and #addrs > 0 and
951 ipc.IPv4("0.0.0.0/%d" % addrs[1].mask):mask():string()
952 end
953
954 function protocol.gwaddr(self)
955 local _, route
956 for _, route in ipairs(self:_ubus("route") or { }) do
957 if route.target == "0.0.0.0" and route.mask == 0 then
958 return route.nexthop
959 end
960 end
961 end
962
963 function protocol.dnsaddrs(self)
964 local dns = { }
965 local _, addr
966 for _, addr in ipairs(self:_ubus("dns-server") or { }) do
967 if not addr:match(":") then
968 dns[#dns+1] = addr
969 end
970 end
971 return dns
972 end
973
974 function protocol.ip6addr(self)
975 local addrs = self:_ubus("ipv6-address")
976 if addrs and #addrs > 0 then
977 return "%s/%d" %{ addrs[1].address, addrs[1].mask }
978 else
979 addrs = self:_ubus("ipv6-prefix-assignment")
980 if addrs and #addrs > 0 then
981 return "%s/%d" %{ addrs[1].address, addrs[1].mask }
982 end
983 end
984 end
985
986 function protocol.ip6addrs(self)
987 local addrs = self:_ubus("ipv6-address")
988 local rv = { }
989 local n, addr
990
991 if type(addrs) == "table" then
992 for n, addr in ipairs(addrs) do
993 rv[#rv+1] = "%s/%d" %{ addr.address, addr.mask }
994 end
995 end
996
997 addrs = self:_ubus("ipv6-prefix-assignment")
998
999 if type(addrs) == "table" then
1000 for n, addr in ipairs(addrs) do
1001 if type(addr["local-address"]) == "table" then
1002 rv[#rv+1] = "%s/%d" %{
1003 addr["local-address"].address,
1004 addr["local-address"].mask
1005 }
1006 end
1007 end
1008 end
1009
1010 return rv
1011 end
1012
1013 function protocol.gw6addr(self)
1014 local _, route
1015 for _, route in ipairs(self:_ubus("route") or { }) do
1016 if route.target == "::" and route.mask == 0 then
1017 return ipc.IPv6(route.nexthop):string()
1018 end
1019 end
1020 end
1021
1022 function protocol.dns6addrs(self)
1023 local dns = { }
1024 local _, addr
1025 for _, addr in ipairs(self:_ubus("dns-server") or { }) do
1026 if addr:match(":") then
1027 dns[#dns+1] = addr
1028 end
1029 end
1030 return dns
1031 end
1032
1033 function protocol.ip6prefix(self)
1034 local prefix = self:_ubus("ipv6-prefix")
1035 if prefix and #prefix > 0 then
1036 return "%s/%d" %{ prefix[1].address, prefix[1].mask }
1037 end
1038 end
1039
1040 function protocol.is_bridge(self)
1041 return (not self:is_virtual() and self:type() == "bridge")
1042 end
1043
1044 function protocol.opkg_package(self)
1045 return nil
1046 end
1047
1048 function protocol.is_installed(self)
1049 return true
1050 end
1051
1052 function protocol.is_virtual(self)
1053 return false
1054 end
1055
1056 function protocol.is_floating(self)
1057 return false
1058 end
1059
1060 function protocol.is_empty(self)
1061 if self:is_floating() then
1062 return false
1063 else
1064 local empty = true
1065
1066 if (self:_get("ifname") or ""):match("%S+") then
1067 empty = false
1068 end
1069
1070 if empty and _wifi_netid_by_netname(self.sid) then
1071 empty = false
1072 end
1073
1074 return empty
1075 end
1076 end
1077
1078 function protocol.add_interface(self, ifname)
1079 ifname = _M:ifnameof(ifname)
1080 if ifname and not self:is_floating() then
1081 -- if its a wifi interface, change its network option
1082 local wif = _wifi_sid_by_ifname(ifname)
1083 if wif then
1084 _append("wireless", wif, "network", self.sid)
1085
1086 -- add iface to our iface list
1087 else
1088 _append("network", self.sid, "ifname", ifname)
1089 end
1090 end
1091 end
1092
1093 function protocol.del_interface(self, ifname)
1094 ifname = _M:ifnameof(ifname)
1095 if ifname and not self:is_floating() then
1096 -- if its a wireless interface, clear its network option
1097 local wif = _wifi_sid_by_ifname(ifname)
1098 if wif then _filter("wireless", wif, "network", self.sid) end
1099
1100 -- remove the interface
1101 _filter("network", self.sid, "ifname", ifname)
1102 end
1103 end
1104
1105 function protocol.get_interface(self)
1106 if self:is_virtual() then
1107 _tunnel[self:proto() .. "-" .. self.sid] = true
1108 return interface(self:proto() .. "-" .. self.sid, self)
1109 elseif self:is_bridge() then
1110 _bridge["br-" .. self.sid] = true
1111 return interface("br-" .. self.sid, self)
1112 else
1113 local ifn = nil
1114 local num = { }
1115 for ifn in utl.imatch(_uci:get("network", self.sid, "ifname")) do
1116 ifn = ifn:match("^[^:/]+")
1117 return ifn and interface(ifn, self)
1118 end
1119 ifn = _wifi_netid_by_netname(self.sid)
1120 return ifn and interface(ifn, self)
1121 end
1122 end
1123
1124 function protocol.get_interfaces(self)
1125 if self:is_bridge() or (self:is_virtual() and not self:is_floating()) then
1126 local ifaces = { }
1127
1128 local ifn
1129 local nfs = { }
1130 for ifn in utl.imatch(self:get("ifname")) do
1131 ifn = ifn:match("^[^:/]+")
1132 nfs[ifn] = interface(ifn, self)
1133 end
1134
1135 for ifn in utl.kspairs(nfs) do
1136 ifaces[#ifaces+1] = nfs[ifn]
1137 end
1138
1139 local wfs = { }
1140 _uci:foreach("wireless", "wifi-iface",
1141 function(s)
1142 if s.device then
1143 local net
1144 for net in utl.imatch(s.network) do
1145 if net == self.sid then
1146 ifn = _wifi_netid_by_sid(s[".name"])
1147 if ifn then
1148 wfs[ifn] = interface(ifn, self)
1149 end
1150 end
1151 end
1152 end
1153 end)
1154
1155 for ifn in utl.kspairs(wfs) do
1156 ifaces[#ifaces+1] = wfs[ifn]
1157 end
1158
1159 return ifaces
1160 end
1161 end
1162
1163 function protocol.contains_interface(self, ifname)
1164 ifname = _M:ifnameof(ifname)
1165 if not ifname then
1166 return false
1167 elseif self:is_virtual() and self:proto() .. "-" .. self.sid == ifname then
1168 return true
1169 elseif self:is_bridge() and "br-" .. self.sid == ifname then
1170 return true
1171 else
1172 local ifn
1173 for ifn in utl.imatch(self:get("ifname")) do
1174 ifn = ifn:match("[^:]+")
1175 if ifn == ifname then
1176 return true
1177 end
1178 end
1179
1180 local wif = _wifi_sid_by_ifname(ifname)
1181 if wif then
1182 local n
1183 for n in utl.imatch(_uci:get("wireless", wif, "network")) do
1184 if n == self.sid then
1185 return true
1186 end
1187 end
1188 end
1189 end
1190
1191 return false
1192 end
1193
1194 function protocol.adminlink(self)
1195 local stat, dsp = pcall(require, "luci.dispatcher")
1196 return stat and dsp.build_url("admin", "network", "network", self.sid)
1197 end
1198
1199
1200 interface = utl.class()
1201
1202 function interface.__init__(self, ifname, network)
1203 local wif = _wifi_sid_by_ifname(ifname)
1204 if wif then
1205 self.wif = wifinet(wif)
1206 self.ifname = self.wif:ifname()
1207 end
1208
1209 self.ifname = self.ifname or ifname
1210 self.dev = _interfaces[self.ifname]
1211 self.network = network
1212 end
1213
1214 function interface._ubus(self, field)
1215 if not _ubusdevcache[self.ifname] then
1216 _ubusdevcache[self.ifname] = utl.ubus("network.device", "status",
1217 { name = self.ifname })
1218 end
1219 if _ubusdevcache[self.ifname] and field then
1220 return _ubusdevcache[self.ifname][field]
1221 end
1222 return _ubusdevcache[self.ifname]
1223 end
1224
1225 function interface.name(self)
1226 return self.wif and self.wif:ifname() or self.ifname
1227 end
1228
1229 function interface.mac(self)
1230 local mac = self:_ubus("macaddr")
1231 return mac and mac:upper()
1232 end
1233
1234 function interface.ipaddrs(self)
1235 return self.dev and self.dev.ipaddrs or { }
1236 end
1237
1238 function interface.ip6addrs(self)
1239 return self.dev and self.dev.ip6addrs or { }
1240 end
1241
1242 function interface.type(self)
1243 if self.wif or _wifi_iface(self.ifname) then
1244 return "wifi"
1245 elseif _bridge[self.ifname] then
1246 return "bridge"
1247 elseif _tunnel[self.ifname] then
1248 return "tunnel"
1249 elseif self.ifname:match("%.") then
1250 return "vlan"
1251 elseif _switch[self.ifname] then
1252 return "switch"
1253 else
1254 return "ethernet"
1255 end
1256 end
1257
1258 function interface.shortname(self)
1259 if self.wif then
1260 return self.wif:shortname()
1261 else
1262 return self.ifname
1263 end
1264 end
1265
1266 function interface.get_i18n(self)
1267 if self.wif then
1268 return "%s: %s %q" %{
1269 lng.translate("Wireless Network"),
1270 self.wif:active_mode(),
1271 self.wif:active_ssid() or self.wif:active_bssid() or self.wif:id()
1272 }
1273 else
1274 return "%s: %q" %{ self:get_type_i18n(), self:name() }
1275 end
1276 end
1277
1278 function interface.get_type_i18n(self)
1279 local x = self:type()
1280 if x == "wifi" then
1281 return lng.translate("Wireless Adapter")
1282 elseif x == "bridge" then
1283 return lng.translate("Bridge")
1284 elseif x == "switch" then
1285 return lng.translate("Ethernet Switch")
1286 elseif x == "vlan" then
1287 if _switch[self.ifname] then
1288 return lng.translate("Switch VLAN")
1289 else
1290 return lng.translate("Software VLAN")
1291 end
1292 elseif x == "tunnel" then
1293 return lng.translate("Tunnel Interface")
1294 else
1295 return lng.translate("Ethernet Adapter")
1296 end
1297 end
1298
1299 function interface.adminlink(self)
1300 if self.wif then
1301 return self.wif:adminlink()
1302 end
1303 end
1304
1305 function interface.ports(self)
1306 local members = self:_ubus("bridge-members")
1307 if members then
1308 local _, iface
1309 local ifaces = { }
1310 for _, iface in ipairs(members) do
1311 ifaces[#ifaces+1] = interface(iface)
1312 end
1313 end
1314 end
1315
1316 function interface.bridge_id(self)
1317 if self.br then
1318 return self.br.id
1319 else
1320 return nil
1321 end
1322 end
1323
1324 function interface.bridge_stp(self)
1325 if self.br then
1326 return self.br.stp
1327 else
1328 return false
1329 end
1330 end
1331
1332 function interface.is_up(self)
1333 return self:_ubus("up") or false
1334 end
1335
1336 function interface.is_bridge(self)
1337 return (self:type() == "bridge")
1338 end
1339
1340 function interface.is_bridgeport(self)
1341 return self.dev and self.dev.bridge and true or false
1342 end
1343
1344 function interface.tx_bytes(self)
1345 local stat = self:_ubus("statistics")
1346 return stat and stat.tx_bytes or 0
1347 end
1348
1349 function interface.rx_bytes(self)
1350 local stat = self:_ubus("statistics")
1351 return stat and stat.rx_bytes or 0
1352 end
1353
1354 function interface.tx_packets(self)
1355 local stat = self:_ubus("statistics")
1356 return stat and stat.tx_packets or 0
1357 end
1358
1359 function interface.rx_packets(self)
1360 local stat = self:_ubus("statistics")
1361 return stat and stat.rx_packets or 0
1362 end
1363
1364 function interface.get_network(self)
1365 return self:get_networks()[1]
1366 end
1367
1368 function interface.get_networks(self)
1369 if not self.networks then
1370 local nets = { }
1371 local _, net
1372 for _, net in ipairs(_M:get_networks()) do
1373 if net:contains_interface(self.ifname) or
1374 net:ifname() == self.ifname
1375 then
1376 nets[#nets+1] = net
1377 end
1378 end
1379 table.sort(nets, function(a, b) return a.sid < b.sid end)
1380 self.networks = nets
1381 return nets
1382 else
1383 return self.networks
1384 end
1385 end
1386
1387 function interface.get_wifinet(self)
1388 return self.wif
1389 end
1390
1391
1392 wifidev = utl.class()
1393
1394 function wifidev.__init__(self, name)
1395 local t, n = _uci:get("wireless", name)
1396 if t == "wifi-device" and n ~= nil then
1397 self.sid = n
1398 self.iwinfo = _wifi_iwinfo_by_ifname(self.sid, true)
1399 end
1400 self.sid = self.sid or name
1401 self.iwinfo = self.iwinfo or { ifname = self.sid }
1402 end
1403
1404 function wifidev.get(self, opt)
1405 return _get("wireless", self.sid, opt)
1406 end
1407
1408 function wifidev.set(self, opt, val)
1409 return _set("wireless", self.sid, opt, val)
1410 end
1411
1412 function wifidev.name(self)
1413 return self.sid
1414 end
1415
1416 function wifidev.hwmodes(self)
1417 local l = self.iwinfo.hwmodelist
1418 if l and next(l) then
1419 return l
1420 else
1421 return { b = true, g = true }
1422 end
1423 end
1424
1425 function wifidev.get_i18n(self)
1426 local t = "Generic"
1427 if self.iwinfo.type == "wl" then
1428 t = "Broadcom"
1429 end
1430
1431 local m = ""
1432 local l = self:hwmodes()
1433 if l.a then m = m .. "a" end
1434 if l.b then m = m .. "b" end
1435 if l.g then m = m .. "g" end
1436 if l.n then m = m .. "n" end
1437 if l.ac then m = "ac" end
1438
1439 return "%s 802.11%s Wireless Controller (%s)" %{ t, m, self:name() }
1440 end
1441
1442 function wifidev.is_up(self)
1443 if _ubuswificache[self.sid] then
1444 return (_ubuswificache[self.sid].up == true)
1445 end
1446
1447 return false
1448 end
1449
1450 function wifidev.get_wifinet(self, net)
1451 if _uci:get("wireless", net) == "wifi-iface" then
1452 return wifinet(net)
1453 else
1454 local wnet = _wifi_sid_by_ifname(net)
1455 if wnet then
1456 return wifinet(wnet)
1457 end
1458 end
1459 end
1460
1461 function wifidev.get_wifinets(self)
1462 local nets = { }
1463
1464 _uci:foreach("wireless", "wifi-iface",
1465 function(s)
1466 if s.device == self.sid then
1467 nets[#nets+1] = wifinet(s['.name'])
1468 end
1469 end)
1470
1471 return nets
1472 end
1473
1474 function wifidev.add_wifinet(self, options)
1475 options = options or { }
1476 options.device = self.sid
1477
1478 local wnet = _uci:section("wireless", "wifi-iface", nil, options)
1479 if wnet then
1480 return wifinet(wnet, options)
1481 end
1482 end
1483
1484 function wifidev.del_wifinet(self, net)
1485 if utl.instanceof(net, wifinet) then
1486 net = net.sid
1487 elseif _uci:get("wireless", net) ~= "wifi-iface" then
1488 net = _wifi_sid_by_ifname(net)
1489 end
1490
1491 if net and _uci:get("wireless", net, "device") == self.sid then
1492 _uci:delete("wireless", net)
1493 return true
1494 end
1495
1496 return false
1497 end
1498
1499
1500 wifinet = utl.class()
1501
1502 function wifinet.__init__(self, name, data)
1503 local sid, netid, radioname, radiostate, netstate
1504
1505 -- lookup state by radio#.network# notation
1506 sid = _wifi_sid_by_netid(name)
1507 if sid then
1508 netid = name
1509 radioname, radiostate, netstate = _wifi_state_by_sid(sid)
1510 else
1511 -- lookup state by ifname (e.g. wlan0)
1512 radioname, radiostate, netstate = _wifi_state_by_ifname(name)
1513 if radioname and radiostate and netstate then
1514 sid = netstate.section
1515 netid = _wifi_netid_by_sid(sid)
1516 else
1517 -- lookup state by uci section id (e.g. cfg053579)
1518 radioname, radiostate, netstate = _wifi_state_by_sid(name)
1519 if radioname and radiostate and netstate then
1520 sid = name
1521 netid = _wifi_netid_by_sid(sid)
1522 else
1523 -- no state available, try to resolve from uci
1524 netid, radioname = _wifi_netid_by_sid(name)
1525 if netid and radioname then
1526 sid = name
1527 end
1528 end
1529 end
1530 end
1531
1532 local iwinfo =
1533 (netstate and _wifi_iwinfo_by_ifname(netstate.ifname)) or
1534 (radioname and _wifi_iwinfo_by_ifname(radioname)) or
1535 { ifname = (netid or sid or name) }
1536
1537 self.sid = sid or name
1538 self.wdev = iwinfo.ifname
1539 self.iwinfo = iwinfo
1540 self.netid = netid
1541 self._ubusdata = {
1542 radio = radioname,
1543 dev = radiostate,
1544 net = netstate
1545 }
1546 end
1547
1548 function wifinet.ubus(self, ...)
1549 local n, v = self._ubusdata
1550 for n = 1, select('#', ...) do
1551 if type(v) == "table" then
1552 v = v[select(n, ...)]
1553 else
1554 return nil
1555 end
1556 end
1557 return v
1558 end
1559
1560 function wifinet.get(self, opt)
1561 return _get("wireless", self.sid, opt)
1562 end
1563
1564 function wifinet.set(self, opt, val)
1565 return _set("wireless", self.sid, opt, val)
1566 end
1567
1568 function wifinet.mode(self)
1569 return self:ubus("net", "config", "mode") or self:get("mode") or "ap"
1570 end
1571
1572 function wifinet.ssid(self)
1573 return self:ubus("net", "config", "ssid") or self:get("ssid")
1574 end
1575
1576 function wifinet.bssid(self)
1577 return self:ubus("net", "config", "bssid") or self:get("bssid")
1578 end
1579
1580 function wifinet.network(self)
1581 local net, networks = nil, { }
1582 for net in utl.imatch(self:ubus("net", "config", "network") or self:get("network")) do
1583 networks[#networks+1] = net
1584 end
1585 return networks
1586 end
1587
1588 function wifinet.id(self)
1589 return self.netid
1590 end
1591
1592 function wifinet.name(self)
1593 return self.sid
1594 end
1595
1596 function wifinet.ifname(self)
1597 local ifname = self:ubus("net", "ifname") or self.iwinfo.ifname
1598 if not ifname or ifname:match("^wifi%d") or ifname:match("^radio%d") then
1599 ifname = self.wdev
1600 end
1601 return ifname
1602 end
1603
1604 function wifinet.get_device(self)
1605 local dev = self:ubus("radio") or self:get("device")
1606 return dev and wifidev(dev) or nil
1607 end
1608
1609 function wifinet.is_up(self)
1610 local ifc = self:get_interface()
1611 return (ifc and ifc:is_up() or false)
1612 end
1613
1614 function wifinet.active_mode(self)
1615 local m = self.iwinfo.mode or self:ubus("net", "config", "mode") or self:get("mode") or "ap"
1616
1617 if m == "ap" then m = "Master"
1618 elseif m == "sta" then m = "Client"
1619 elseif m == "adhoc" then m = "Ad-Hoc"
1620 elseif m == "mesh" then m = "Mesh"
1621 elseif m == "monitor" then m = "Monitor"
1622 end
1623
1624 return m
1625 end
1626
1627 function wifinet.active_mode_i18n(self)
1628 return lng.translate(self:active_mode())
1629 end
1630
1631 function wifinet.active_ssid(self)
1632 return self.iwinfo.ssid or self:ubus("net", "config", "ssid") or self:get("ssid")
1633 end
1634
1635 function wifinet.active_bssid(self)
1636 return self.iwinfo.bssid or self:ubus("net", "config", "bssid") or self:get("bssid")
1637 end
1638
1639 function wifinet.active_encryption(self)
1640 local enc = self.iwinfo and self.iwinfo.encryption
1641 return enc and enc.description or "-"
1642 end
1643
1644 function wifinet.assoclist(self)
1645 return self.iwinfo.assoclist or { }
1646 end
1647
1648 function wifinet.frequency(self)
1649 local freq = self.iwinfo.frequency
1650 if freq and freq > 0 then
1651 return "%.03f" % (freq / 1000)
1652 end
1653 end
1654
1655 function wifinet.bitrate(self)
1656 local rate = self.iwinfo.bitrate
1657 if rate and rate > 0 then
1658 return (rate / 1000)
1659 end
1660 end
1661
1662 function wifinet.channel(self)
1663 return self.iwinfo.channel or self:ubus("dev", "config", "channel") or
1664 tonumber(self:get("channel"))
1665 end
1666
1667 function wifinet.signal(self)
1668 return self.iwinfo.signal or 0
1669 end
1670
1671 function wifinet.noise(self)
1672 return self.iwinfo.noise or 0
1673 end
1674
1675 function wifinet.country(self)
1676 return self.iwinfo.country or self:ubus("dev", "config", "country") or "00"
1677 end
1678
1679 function wifinet.txpower(self)
1680 local pwr = (self.iwinfo.txpower or 0)
1681 return pwr + self:txpower_offset()
1682 end
1683
1684 function wifinet.txpower_offset(self)
1685 return self.iwinfo.txpower_offset or 0
1686 end
1687
1688 function wifinet.signal_level(self, s, n)
1689 if self:active_bssid() ~= "00:00:00:00:00:00" then
1690 local signal = s or self:signal()
1691 local noise = n or self:noise()
1692
1693 if signal < 0 and noise < 0 then
1694 local snr = -1 * (noise - signal)
1695 return math.floor(snr / 5)
1696 else
1697 return 0
1698 end
1699 else
1700 return -1
1701 end
1702 end
1703
1704 function wifinet.signal_percent(self)
1705 local qc = self.iwinfo.quality or 0
1706 local qm = self.iwinfo.quality_max or 0
1707
1708 if qc > 0 and qm > 0 then
1709 return math.floor((100 / qm) * qc)
1710 else
1711 return 0
1712 end
1713 end
1714
1715 function wifinet.shortname(self)
1716 return "%s %q" %{
1717 lng.translate(self:active_mode()),
1718 self:active_ssid() or self:active_bssid() or self:id()
1719 }
1720 end
1721
1722 function wifinet.get_i18n(self)
1723 return "%s: %s %q (%s)" %{
1724 lng.translate("Wireless Network"),
1725 lng.translate(self:active_mode()),
1726 self:active_ssid() or self:active_bssid() or self:id(),
1727 self:ifname()
1728 }
1729 end
1730
1731 function wifinet.adminlink(self)
1732 local stat, dsp = pcall(require, "luci.dispatcher")
1733 return dsp and dsp.build_url("admin", "network", "wireless", self.netid)
1734 end
1735
1736 function wifinet.get_network(self)
1737 return self:get_networks()[1]
1738 end
1739
1740 function wifinet.get_networks(self)
1741 local nets = { }
1742 local net
1743 for net in utl.imatch(self:ubus("net", "config", "network") or self:get("network")) do
1744 if _uci:get("network", net) == "interface" then
1745 nets[#nets+1] = network(net)
1746 end
1747 end
1748 table.sort(nets, function(a, b) return a.sid < b.sid end)
1749 return nets
1750 end
1751
1752 function wifinet.get_interface(self)
1753 return interface(self:ifname())
1754 end
1755
1756
1757 -- setup base protocols
1758 _M:register_protocol("static")
1759 _M:register_protocol("dhcp")
1760 _M:register_protocol("none")
1761
1762 -- load protocol extensions
1763 local exts = nfs.dir(utl.libpath() .. "/model/network")
1764 if exts then
1765 local ext
1766 for ext in exts do
1767 if ext:match("%.lua$") then
1768 require("luci.model.network." .. ext:gsub("%.lua$", ""))
1769 end
1770 end
1771 end