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