luci-app-olsr: handle empty result for non-status tables
[oweals/luci.git] / modules / luci-mod-network / luasrc / model / cbi / admin_network / ifaces.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Copyright 2008-2011 Jo-Philipp Wich <jow@openwrt.org>
3 -- Licensed to the public under the Apache License 2.0.
4
5 local fs = require "nixio.fs"
6 local ut = require "luci.util"
7 local pt = require "luci.tools.proto"
8 local nw = require "luci.model.network"
9 local fw = require "luci.model.firewall"
10
11 arg[1] = arg[1] or ""
12
13 local has_dnsmasq  = fs.access("/etc/config/dhcp")
14 local has_firewall = fs.access("/etc/config/firewall")
15
16 m = Map("network", translate("Interfaces") .. " - " .. arg[1]:upper(), translate("On this page you can configure the network interfaces. You can bridge several interfaces by ticking the \"bridge interfaces\" field and enter the names of several network interfaces separated by spaces. You can also use <abbr title=\"Virtual Local Area Network\">VLAN</abbr> notation <samp>INTERFACE.VLANNR</samp> (<abbr title=\"for example\">e.g.</abbr>: <samp>eth0.1</samp>)."))
17 m.redirect = luci.dispatcher.build_url("admin", "network", "network")
18 m:chain("wireless")
19 m:chain("luci")
20
21 if has_firewall then
22         m:chain("firewall")
23 end
24
25 nw.init(m.uci)
26 fw.init(m.uci)
27
28
29 local net = nw:get_network(arg[1])
30
31 local function set_ifstate(name, option, value)
32         local found = false
33
34         m.uci:foreach("luci", "ifstate", function (s)
35                 if s.interface == name then
36                         m.uci:set("luci", s[".name"], option, value)
37                         found = true
38                         return false
39                 end
40         end)
41
42         if not found then
43                 local sid = m.uci:add("luci", "ifstate")
44                 m.uci:set("luci", sid, "interface", name)
45                 m.uci:set("luci", sid, option, value)
46         end
47
48         m.uci:save("luci")
49 end
50
51 local function get_ifstate(name, option)
52         local val
53
54         m.uci:foreach("luci", "ifstate", function (s)
55                 if s.interface == name then
56                         val = s[option]
57                         return false
58                 end
59         end)
60
61         return val
62 end
63
64 local function backup_ifnames(is_bridge)
65         if not net:is_floating() and not get_ifstate(net:name(), "ifname") then
66                 local ifcs = net:get_interfaces() or { net:get_interface() }
67                 if ifcs then
68                         local _, ifn
69                         local ifns = { }
70                         for _, ifn in ipairs(ifcs) do
71                                 local wif = ifn:get_wifinet()
72                                 ifns[#ifns+1] = wif and wif:id() or ifn:name()
73                         end
74                         if #ifns > 0 then
75                                 set_ifstate(net:name(), "ifname", table.concat(ifns, " "))
76                                 set_ifstate(net:name(), "bridge", tostring(net:is_bridge()))
77                         end
78                 end
79         end
80 end
81
82
83 -- redirect to overview page if network does not exist anymore (e.g. after a revert)
84 if not net then
85         luci.http.redirect(luci.dispatcher.build_url("admin/network/network"))
86         return
87 end
88
89 -- protocol switch was requested, rebuild interface config and reload page
90 if m:formvalue("cbid.network.%s._switch" % net:name()) then
91         -- get new protocol
92         local ptype = m:formvalue("cbid.network.%s.proto" % net:name()) or "-"
93         local proto = nw:get_protocol(ptype, net:name())
94         if proto then
95                 -- backup default
96                 backup_ifnames()
97
98                 -- if current proto is not floating and target proto is not floating,
99                 -- then attempt to retain the ifnames
100                 --error(net:proto() .. " > " .. proto:proto())
101                 if not net:is_floating() and not proto:is_floating() then
102                         -- if old proto is a bridge and new proto not, then clip the
103                         -- interface list to the first ifname only
104                         if net:is_bridge() and proto:is_virtual() then
105                                 local _, ifn
106                                 local first = true
107                                 for _, ifn in ipairs(net:get_interfaces() or { net:get_interface() }) do
108                                         if first then
109                                                 first = false
110                                         else
111                                                 net:del_interface(ifn)
112                                         end
113                                 end
114                                 m:del(net:name(), "type")
115                         end
116
117                 -- if the current proto is floating, the target proto not floating,
118                 -- then attempt to restore ifnames from backup
119                 elseif net:is_floating() and not proto:is_floating() then
120                         -- if we have backup data, then re-add all orphaned interfaces
121                         -- from it and restore the bridge choice
122                         local br = (get_ifstate(net:name(), "bridge") == "true")
123                         local ifn
124                         local ifns = { }
125                         for ifn in ut.imatch(get_ifstate(net:name(), "ifname")) do
126                                 ifn = nw:get_interface(ifn)
127                                 if ifn and not ifn:get_network() then
128                                         proto:add_interface(ifn)
129                                         if not br then
130                                                 break
131                                         end
132                                 end
133                         end
134                         if br then
135                                 m:set(net:name(), "type", "bridge")
136                         end
137
138                 -- in all other cases clear the ifnames
139                 else
140                         local _, ifc
141                         for _, ifc in ipairs(net:get_interfaces() or { net:get_interface() }) do
142                                 net:del_interface(ifc)
143                         end
144                         m:del(net:name(), "type")
145                 end
146
147                 -- clear options
148                 local k, v
149                 for k, v in pairs(m:get(net:name())) do
150                         if k:sub(1,1) ~= "." and
151                            k ~= "type" and
152                            k ~= "ifname"
153                         then
154                                 m:del(net:name(), k)
155                         end
156                 end
157
158                 -- set proto
159                 m:set(net:name(), "proto", proto:proto())
160                 m.uci:save("network")
161                 m.uci:save("wireless")
162
163                 -- reload page
164                 luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
165                 return
166         end
167 end
168
169 -- dhcp setup was requested, create section and reload page
170 if m:formvalue("cbid.dhcp._enable._enable") then
171         m.uci:section("dhcp", "dhcp", arg[1], {
172                 interface = arg[1],
173                 start     = "100",
174                 limit     = "150",
175                 leasetime = "12h"
176         })
177
178         m.uci:save("dhcp")
179         luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
180         return
181 end
182
183 local ifc = net:get_interface()
184
185 s = m:section(NamedSection, arg[1], "interface", translate("Common Configuration"))
186 s.addremove = false
187
188 s:tab("general",  translate("General Setup"))
189 s:tab("advanced", translate("Advanced Settings"))
190 s:tab("physical", translate("Physical Settings"))
191
192 if has_firewall then
193         s:tab("firewall", translate("Firewall Settings"))
194 end
195
196
197 st = s:taboption("general", DummyValue, "__status", translate("Status"))
198
199 local function set_status()
200         -- if current network is empty, print a warning
201         if not net:is_floating() and net:is_empty() then
202                 st.template = "cbi/dvalue"
203                 st.network  = nil
204                 st.value    = translate("There is no device assigned yet, please attach a network device in the \"Physical Settings\" tab")
205         else
206                 st.template = "admin_network/iface_status"
207                 st.network  = arg[1]
208                 st.value    = nil
209         end
210 end
211
212 m.on_init = set_status
213 m.on_after_save = set_status
214
215
216 p = s:taboption("general", ListValue, "proto", translate("Protocol"))
217 p.default = net:proto()
218
219
220 if not net:is_installed() then
221         p_install = s:taboption("general", Button, "_install")
222         p_install.title      = translate("Protocol support is not installed")
223         p_install.inputtitle = translate("Install package %q" % net:opkg_package())
224         p_install.inputstyle = "apply"
225         p_install:depends("proto", net:proto())
226
227         function p_install.write()
228                 return luci.http.redirect(
229                         luci.dispatcher.build_url("admin/system/opkg") ..
230                         "?query=%s" % net:opkg_package()
231                 )
232         end
233 end
234
235
236 p_switch = s:taboption("general", Button, "_switch")
237 p_switch.title      = translate("Really switch protocol?")
238 p_switch.inputtitle = translate("Switch protocol")
239 p_switch.inputstyle = "apply"
240
241 local _, pr
242 for _, pr in ipairs(nw:get_protocols()) do
243         p:value(pr:proto(), pr:get_i18n())
244         if pr:proto() ~= net:proto() then
245                 p_switch:depends("proto", pr:proto())
246         end
247 end
248
249
250 auto = s:taboption("general", Flag, "auto", translate("Bring up on boot"))
251 auto.default = (net:proto() == "none") and auto.disabled or auto.enabled
252
253 delegate = s:taboption("advanced", Flag, "delegate", translate("Use builtin IPv6-management"))
254 delegate.default = delegate.enabled
255
256 force_link = s:taboption("advanced", Flag, "force_link",
257         translate("Force link"),
258         translate("Set interface properties regardless of the link carrier (If set, carrier sense events do not invoke hotplug handlers)."))
259
260 force_link.default = (net:proto() == "static") and force_link.enabled or force_link.disabled
261
262
263 if not net:is_virtual() then
264         br = s:taboption("physical", Flag, "type", translate("Bridge interfaces"), translate("creates a bridge over specified interface(s)"))
265         br.enabled = "bridge"
266         br.rmempty = true
267         br:depends("proto", "static")
268         br:depends("proto", "dhcp")
269         br:depends("proto", "none")
270
271         stp = s:taboption("physical", Flag, "stp", translate("Enable <abbr title=\"Spanning Tree Protocol\">STP</abbr>"),
272                 translate("Enables the Spanning Tree Protocol on this bridge"))
273         stp:depends("type", "bridge")
274         stp.rmempty = true
275
276         igmp = s:taboption("physical", Flag, "igmp_snooping", translate("Enable <abbr title=\"Internet Group Management Protocol\">IGMP</abbr> snooping"),
277                 translate("Enables IGMP snooping on this bridge"))
278         igmp:depends("type", "bridge")
279         igmp.rmempty = true
280 end
281
282
283 if not net:is_floating() then
284         ifname_single = s:taboption("physical", Value, "ifname_single", translate("Interface"))
285         ifname_single.template = "cbi/network_ifacelist"
286         ifname_single.widget = "radio"
287         ifname_single.nobridges = net:is_bridge()
288         ifname_single.noaliases = false
289         ifname_single.rmempty = false
290         ifname_single.network = arg[1]
291         ifname_single:depends("type", "")
292
293         function ifname_single.cfgvalue(self, s)
294                 -- let the template figure out the related ifaces through the network model
295                 return nil
296         end
297
298         function ifname_single.write(self, s, val)
299                 local _, i
300                 local new_ifs = { }
301                 local old_ifs = { }
302
303                 local alias = net:is_alias()
304
305                 if alias then
306                         old_ifs[1] = '@' .. alias
307                 else
308                         for _, i in ipairs(net:get_interfaces() or { net:get_interface() }) do
309                                 old_ifs[#old_ifs+1] = i:name()
310                         end
311                 end
312
313                 for i in ut.imatch(val) do
314                         new_ifs[#new_ifs+1] = i
315
316                         -- if this is not a bridge, only assign first interface
317                         if self.option == "ifname_single" then
318                                 break
319                         end
320                 end
321
322                 table.sort(old_ifs)
323                 table.sort(new_ifs)
324
325                 for i = 1, math.max(#old_ifs, #new_ifs) do
326                         if old_ifs[i] ~= new_ifs[i] then
327                                 backup_ifnames()
328                                 for i = 1, #old_ifs do
329                                         net:del_interface(old_ifs[i])
330                                 end
331                                 for i = 1, #new_ifs do
332                                         net:add_interface(new_ifs[i])
333                                 end
334                                 break
335                         end
336                 end
337         end
338 end
339
340
341 if not net:is_virtual() then
342         ifname_multi = s:taboption("physical", Value, "ifname_multi", translate("Interface"))
343         ifname_multi.template = "cbi/network_ifacelist"
344         ifname_multi.nobridges = net:is_bridge()
345         ifname_multi.noaliases = true
346         ifname_multi.rmempty = false
347         ifname_multi.network = arg[1]
348         ifname_multi.widget = "checkbox"
349         ifname_multi:depends("type", "bridge")
350         ifname_multi.cfgvalue = ifname_single.cfgvalue
351         ifname_multi.write = ifname_single.write
352 end
353
354
355 if has_firewall then
356         fwzone = s:taboption("firewall", Value, "_fwzone",
357                 translate("Create / Assign firewall-zone"),
358                 translate("Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>create</em> field to define a new zone and attach the interface to it."))
359
360         fwzone.template = "cbi/firewall_zonelist"
361         fwzone.network = arg[1]
362
363         function fwzone.cfgvalue(self, section)
364                 self.iface = section
365                 local z = fw:get_zone_by_network(section)
366                 return z and z:name()
367         end
368
369         function fwzone.write(self, section, value)
370                 local zone = fw:get_zone(value) or fw:add_zone(value)
371                 if zone then
372                         fw:del_network(section)
373                         zone:add_network(section)
374                 end
375         end
376
377         function fwzone.remove(self, section)
378                 fw:del_network(section)
379         end
380 end
381
382
383 function p.write() end
384 function p.remove() end
385 function p.validate(self, value, section)
386         if value == net:proto() then
387                 if not net:is_floating() and net:is_empty() then
388                         local ifn = ((br and (br:formvalue(section) == "bridge"))
389                                 and ifname_multi:formvalue(section)
390                              or ifname_single:formvalue(section))
391
392                         for ifn in ut.imatch(ifn) do
393                                 return value
394                         end
395                         return nil, translate("The selected protocol needs a device assigned")
396                 end
397         end
398         return value
399 end
400
401
402 local form, ferr = loadfile(
403         ut.libpath() .. "/model/cbi/admin_network/proto_%s.lua" % net:proto()
404 )
405
406 if not form then
407         s:taboption("general", DummyValue, "_error",
408                 translate("Missing protocol extension for proto %q" % net:proto())
409         ).value = ferr
410 else
411         setfenv(form, getfenv(1))(m, s, net)
412 end
413
414
415 local _, field
416 for _, field in ipairs(s.children) do
417         if field ~= st and field ~= p and field ~= p_install and field ~= p_switch then
418                 if next(field.deps) then
419                         local _, dep
420                         for _, dep in ipairs(field.deps) do
421                                 dep.proto = net:proto()
422                         end
423                 else
424                         field:depends("proto", net:proto())
425                 end
426         end
427 end
428
429
430 --
431 -- Display DNS settings if dnsmasq is available
432 --
433
434 if has_dnsmasq and net:proto() == "static" then
435         m2 = Map("dhcp", "", "")
436
437         local has_section = false
438
439         m2.uci:foreach("dhcp", "dhcp", function(s)
440                 if s.interface == arg[1] then
441                         has_section = true
442                         return false
443                 end
444         end)
445
446         if not has_section and has_dnsmasq then
447
448                 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
449                 s.anonymous   = true
450                 s.cfgsections = function() return { "_enable" } end
451
452                 x = s:option(Button, "_enable")
453                 x.title      = translate("No DHCP Server configured for this interface")
454                 x.inputtitle = translate("Setup DHCP Server")
455                 x.inputstyle = "apply"
456
457         elseif has_section then
458
459                 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
460                 s.addremove = false
461                 s.anonymous = true
462                 s:tab("general",  translate("General Setup"))
463                 s:tab("advanced", translate("Advanced Settings"))
464                 s:tab("ipv6", translate("IPv6 Settings"))
465
466                 function s.filter(self, section)
467                         return m2.uci:get("dhcp", section, "interface") == arg[1]
468                 end
469
470                 local ignore = s:taboption("general", Flag, "ignore",
471                         translate("Ignore interface"),
472                         translate("Disable <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr> for " ..
473                                 "this interface."))
474
475                 local start = s:taboption("general", Value, "start", translate("Start"),
476                         translate("Lowest leased address as offset from the network address."))
477                 start.optional = true
478                 start.datatype = "or(uinteger,ip4addr)"
479                 start.default = "100"
480
481                 local limit = s:taboption("general", Value, "limit", translate("Limit"),
482                         translate("Maximum number of leased addresses."))
483                 limit.optional = true
484                 limit.datatype = "uinteger"
485                 limit.default = "150"
486
487                 local ltime = s:taboption("general", Value, "leasetime", translate("Lease time"),
488                         translate("Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>)."))
489                 ltime.rmempty = true
490                 ltime.default = "12h"
491
492                 local dd = s:taboption("advanced", Flag, "dynamicdhcp",
493                         translate("Dynamic <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr>"),
494                         translate("Dynamically allocate DHCP addresses for clients. If disabled, only " ..
495                                 "clients having static leases will be served."))
496                 dd.default = dd.enabled
497
498                 s:taboption("advanced", Flag, "force", translate("Force"),
499                         translate("Force DHCP on this network even if another server is detected."))
500
501                 -- XXX: is this actually useful?
502                 --s:taboption("advanced", Value, "name", translate("Name"),
503                 --      translate("Define a name for this network."))
504
505                 mask = s:taboption("advanced", Value, "netmask",
506                         translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"),
507                         translate("Override the netmask sent to clients. Normally it is calculated " ..
508                                 "from the subnet that is served."))
509
510                 mask.optional = true
511                 mask.datatype = "ip4addr"
512
513                 s:taboption("advanced", DynamicList, "dhcp_option", translate("DHCP-Options"),
514                         translate("Define additional DHCP options, for example \"<code>6,192.168.2.1," ..
515                                 "192.168.2.2</code>\" which advertises different DNS servers to clients."))
516
517                 for i, n in ipairs(s.children) do
518                         if n ~= ignore then
519                                 n:depends("ignore", "")
520                         end
521                 end
522
523                 o = s:taboption("ipv6", ListValue, "ra", translate("Router Advertisement-Service"))
524                 o:value("", translate("disabled"))
525                 o:value("server", translate("server mode"))
526                 o:value("relay", translate("relay mode"))
527                 o:value("hybrid", translate("hybrid mode"))
528
529                 o = s:taboption("ipv6", ListValue, "dhcpv6", translate("DHCPv6-Service"))
530                 o:value("", translate("disabled"))
531                 o:value("server", translate("server mode"))
532                 o:value("relay", translate("relay mode"))
533                 o:value("hybrid", translate("hybrid mode"))
534
535                 o = s:taboption("ipv6", ListValue, "ndp", translate("NDP-Proxy"))
536                 o:value("", translate("disabled"))
537                 o:value("relay", translate("relay mode"))
538                 o:value("hybrid", translate("hybrid mode"))
539
540                 o = s:taboption("ipv6", ListValue, "ra_management", translate("DHCPv6-Mode"),
541                         translate("Default is stateless + stateful"))
542                 o:value("0", translate("stateless"))
543                 o:value("1", translate("stateless + stateful"))
544                 o:value("2", translate("stateful-only"))
545                 o:depends("dhcpv6", "server")
546                 o:depends("dhcpv6", "hybrid")
547                 o.default = "1"
548
549                 o = s:taboption("ipv6", Flag, "ra_default", translate("Always announce default router"),
550                         translate("Announce as default router even if no public prefix is available."))
551                 o:depends("ra", "server")
552                 o:depends("ra", "hybrid")
553
554                 s:taboption("ipv6", DynamicList, "dns", translate("Announced DNS servers"))
555                 s:taboption("ipv6", DynamicList, "domain", translate("Announced DNS domains"))
556
557         else
558                 m2 = nil
559         end
560 end
561
562
563 return m, m2