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