c2e5c72285dcd9b2a972dae8b36d4d0ba5b4d97a
[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", nil, {
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
224 if not net:is_virtual() then
225 br = s:taboption("physical", Flag, "type", translate("Bridge interfaces"), translate("creates a bridge over specified interface(s)"))
226 br.enabled = "bridge"
227 br.rmempty = true
228 br:depends("proto", "static")
229 br:depends("proto", "dhcp")
230 br:depends("proto", "none")
231
232 stp = s:taboption("physical", Flag, "stp", translate("Enable <abbr title=\"Spanning Tree Protocol\">STP</abbr>"),
233 translate("Enables the Spanning Tree Protocol on this bridge"))
234 stp:depends("type", "bridge")
235 stp.rmempty = true
236 end
237
238
239 if not net:is_floating() then
240 ifname_single = s:taboption("physical", Value, "ifname_single", translate("Interface"))
241 ifname_single.template = "cbi/network_ifacelist"
242 ifname_single.widget = "radio"
243 ifname_single.nobridges = true
244 ifname_single.rmempty = false
245 ifname_single.network = arg[1]
246 ifname_single:depends("type", "")
247
248 function ifname_single.cfgvalue(self, s)
249 -- let the template figure out the related ifaces through the network model
250 return nil
251 end
252
253 function ifname_single.write(self, s, val)
254 local i
255 local new_ifs = { }
256 local old_ifs = { }
257
258 for _, i in ipairs(net:get_interfaces() or { net:get_interface() }) do
259 old_ifs[#old_ifs+1] = i:name()
260 end
261
262 for i in ut.imatch(val) do
263 new_ifs[#new_ifs+1] = i
264
265 -- if this is not a bridge, only assign first interface
266 if self.option == "ifname_single" then
267 break
268 end
269 end
270
271 table.sort(old_ifs)
272 table.sort(new_ifs)
273
274 for i = 1, math.max(#old_ifs, #new_ifs) do
275 if old_ifs[i] ~= new_ifs[i] then
276 backup_ifnames()
277 for i = 1, #old_ifs do
278 net:del_interface(old_ifs[i])
279 end
280 for i = 1, #new_ifs do
281 net:add_interface(new_ifs[i])
282 end
283 break
284 end
285 end
286 end
287 end
288
289
290 if not net:is_virtual() then
291 ifname_multi = s:taboption("physical", Value, "ifname_multi", translate("Interface"))
292 ifname_multi.template = "cbi/network_ifacelist"
293 ifname_multi.nobridges = true
294 ifname_multi.rmempty = false
295 ifname_multi.network = arg[1]
296 ifname_multi.widget = "checkbox"
297 ifname_multi:depends("type", "bridge")
298 ifname_multi.cfgvalue = ifname_single.cfgvalue
299 ifname_multi.write = ifname_single.write
300 end
301
302
303 if has_firewall then
304 fwzone = s:taboption("firewall", Value, "_fwzone",
305 translate("Create / Assign firewall-zone"),
306 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."))
307
308 fwzone.template = "cbi/firewall_zonelist"
309 fwzone.network = arg[1]
310 fwzone.rmempty = false
311
312 function fwzone.cfgvalue(self, section)
313 self.iface = section
314 local z = fw:get_zone_by_network(section)
315 return z and z:name()
316 end
317
318 function fwzone.write(self, section, value)
319 local zone = fw:get_zone(value)
320
321 if not zone and value == '-' then
322 value = m:formvalue(self:cbid(section) .. ".newzone")
323 if value and #value > 0 then
324 zone = fw:add_zone(value)
325 else
326 fw:del_network(section)
327 end
328 end
329
330 if zone then
331 fw:del_network(section)
332 zone:add_network(section)
333 end
334 end
335 end
336
337
338 function p.write() end
339 function p.remove() end
340 function p.validate(self, value, section)
341 if value == net:proto() then
342 if not net:is_floating() and net:is_empty() then
343 local ifn = ((br and (br:formvalue(section) == "bridge"))
344 and ifname_multi:formvalue(section)
345 or ifname_single:formvalue(section))
346
347 for ifn in ut.imatch(ifn) do
348 return value
349 end
350 return nil, translate("The selected protocol needs a device assigned")
351 end
352 end
353 return value
354 end
355
356
357 local form, ferr = loadfile(
358 ut.libpath() .. "/model/cbi/admin_network/proto_%s.lua" % net:proto()
359 )
360
361 if not form then
362 s:taboption("general", DummyValue, "_error",
363 translate("Missing protocol extension for proto %q" % net:proto())
364 ).value = ferr
365 else
366 setfenv(form, getfenv(1))(m, s, net)
367 end
368
369
370 local _, field
371 for _, field in ipairs(s.children) do
372 if field ~= st and field ~= p and field ~= p_install and field ~= p_switch then
373 if next(field.deps) then
374 local _, dep
375 for _, dep in ipairs(field.deps) do
376 dep.deps.proto = net:proto()
377 end
378 else
379 field:depends("proto", net:proto())
380 end
381 end
382 end
383
384
385 --
386 -- Display DNS settings if dnsmasq is available
387 --
388
389 if has_dnsmasq and net:proto() == "static" then
390 m2 = Map("dhcp", "", "")
391
392 local has_section = false
393
394 m2.uci:foreach("dhcp", "dhcp", function(s)
395 if s.interface == arg[1] then
396 has_section = true
397 return false
398 end
399 end)
400
401 if not has_section and has_dnsmasq then
402
403 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
404 s.anonymous = true
405 s.cfgsections = function() return { "_enable" } end
406
407 x = s:option(Button, "_enable")
408 x.title = translate("No DHCP Server configured for this interface")
409 x.inputtitle = translate("Setup DHCP Server")
410 x.inputstyle = "apply"
411
412 elseif has_section then
413
414 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
415 s.addremove = false
416 s.anonymous = true
417 s:tab("general", translate("General Setup"))
418 s:tab("advanced", translate("Advanced Settings"))
419 s:tab("ipv6", translate("IPv6 Settings"))
420
421 function s.filter(self, section)
422 return m2.uci:get("dhcp", section, "interface") == arg[1]
423 end
424
425 local ignore = s:taboption("general", Flag, "ignore",
426 translate("Ignore interface"),
427 translate("Disable <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr> for " ..
428 "this interface."))
429
430 local start = s:taboption("general", Value, "start", translate("Start"),
431 translate("Lowest leased address as offset from the network address."))
432 start.optional = true
433 start.datatype = "or(uinteger,ip4addr)"
434 start.default = "100"
435
436 local limit = s:taboption("general", Value, "limit", translate("Limit"),
437 translate("Maximum number of leased addresses."))
438 limit.optional = true
439 limit.datatype = "uinteger"
440 limit.default = "150"
441
442 local ltime = s:taboption("general", Value, "leasetime", translate("Leasetime"),
443 translate("Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>)."))
444 ltime.rmempty = true
445 ltime.default = "12h"
446
447 local dd = s:taboption("advanced", Flag, "dynamicdhcp",
448 translate("Dynamic <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr>"),
449 translate("Dynamically allocate DHCP addresses for clients. If disabled, only " ..
450 "clients having static leases will be served."))
451 dd.default = dd.enabled
452
453 s:taboption("advanced", Flag, "force", translate("Force"),
454 translate("Force DHCP on this network even if another server is detected."))
455
456 -- XXX: is this actually useful?
457 --s:taboption("advanced", Value, "name", translate("Name"),
458 -- translate("Define a name for this network."))
459
460 mask = s:taboption("advanced", Value, "netmask",
461 translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"),
462 translate("Override the netmask sent to clients. Normally it is calculated " ..
463 "from the subnet that is served."))
464
465 mask.optional = true
466 mask.datatype = "ip4addr"
467
468 s:taboption("advanced", DynamicList, "dhcp_option", translate("DHCP-Options"),
469 translate("Define additional DHCP options, for example \"<code>6,192.168.2.1," ..
470 "192.168.2.2</code>\" which advertises different DNS servers to clients."))
471
472 for i, n in ipairs(s.children) do
473 if n ~= ignore then
474 n:depends("ignore", "")
475 end
476 end
477
478 o = s:taboption("ipv6", ListValue, "ra", translate("Router Advertisement-Service"))
479 o:value("", translate("disabled"))
480 o:value("server", translate("server mode"))
481 o:value("relay", translate("relay mode"))
482 o:value("hybrid", translate("hybrid mode"))
483
484 o = s:taboption("ipv6", ListValue, "dhcpv6", translate("DHCPv6-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, "ndp", translate("NDP-Proxy"))
491 o:value("", translate("disabled"))
492 o:value("relay", translate("relay mode"))
493 o:value("hybrid", translate("hybrid mode"))
494
495 o = s:taboption("ipv6", ListValue, "ra_management", translate("DHCPv6-Mode"))
496 o:value("", translate("stateless"))
497 o:value("1", translate("stateless + stateful"))
498 o:value("2", translate("stateful-only"))
499 o:depends("dhcpv6", "server")
500 o:depends("dhcpv6", "hybrid")
501 o.default = "1"
502
503 o = s:taboption("ipv6", Flag, "ra_default", translate("Always announce default router"),
504 translate("Announce as default router even if no public prefix is available."))
505 o:depends("ra", "server")
506 o:depends("ra", "hybrid")
507
508 s:taboption("ipv6", DynamicList, "dns", translate("Announced DNS servers"))
509 s:taboption("ipv6", DynamicList, "domain", translate("Announced DNS domains"))
510
511 else
512 m2 = nil
513 end
514 end
515
516
517 return m, m2