modules/admin-full: also require luci.tools.proto from parent cbi model
[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
231 if not net:is_virtual() then
232 br = s:taboption("physical", Flag, "type", translate("Bridge interfaces"), translate("creates a bridge over specified interface(s)"))
233 br.enabled = "bridge"
234 br.rmempty = true
235 br:depends("proto", "static")
236 br:depends("proto", "dhcp")
237 br:depends("proto", "none")
238
239 stp = s:taboption("physical", Flag, "stp", translate("Enable <abbr title=\"Spanning Tree Protocol\">STP</abbr>"),
240 translate("Enables the Spanning Tree Protocol on this bridge"))
241 stp:depends("type", "bridge")
242 stp.rmempty = true
243 end
244
245
246 if not net:is_floating() then
247 ifname_single = s:taboption("physical", Value, "ifname_single", translate("Interface"))
248 ifname_single.template = "cbi/network_ifacelist"
249 ifname_single.widget = "radio"
250 ifname_single.nobridges = true
251 ifname_single.rmempty = false
252 ifname_single.network = arg[1]
253 ifname_single:depends("type", "")
254
255 function ifname_single.cfgvalue(self, s)
256 -- let the template figure out the related ifaces through the network model
257 return nil
258 end
259
260 function ifname_single.write(self, s, val)
261 local i
262 local new_ifs = { }
263 local old_ifs = { }
264
265 for _, i in ipairs(net:get_interfaces() or { net:get_interface() }) do
266 old_ifs[#old_ifs+1] = i:name()
267 end
268
269 for i in ut.imatch(val) do
270 new_ifs[#new_ifs+1] = i
271
272 -- if this is not a bridge, only assign first interface
273 if self.option == "ifname_single" then
274 break
275 end
276 end
277
278 table.sort(old_ifs)
279 table.sort(new_ifs)
280
281 for i = 1, math.max(#old_ifs, #new_ifs) do
282 if old_ifs[i] ~= new_ifs[i] then
283 backup_ifnames()
284 for i = 1, #old_ifs do
285 net:del_interface(old_ifs[i])
286 end
287 for i = 1, #new_ifs do
288 net:add_interface(new_ifs[i])
289 end
290 break
291 end
292 end
293 end
294 end
295
296
297 if not net:is_virtual() then
298 ifname_multi = s:taboption("physical", Value, "ifname_multi", translate("Interface"))
299 ifname_multi.template = "cbi/network_ifacelist"
300 ifname_multi.nobridges = true
301 ifname_multi.rmempty = false
302 ifname_multi.network = arg[1]
303 ifname_multi.widget = "checkbox"
304 ifname_multi:depends("type", "bridge")
305 ifname_multi.cfgvalue = ifname_single.cfgvalue
306 ifname_multi.write = ifname_single.write
307 end
308
309
310 if has_firewall then
311 fwzone = s:taboption("firewall", Value, "_fwzone",
312 translate("Create / Assign firewall-zone"),
313 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."))
314
315 fwzone.template = "cbi/firewall_zonelist"
316 fwzone.network = arg[1]
317 fwzone.rmempty = false
318
319 function fwzone.cfgvalue(self, section)
320 self.iface = section
321 local z = fw:get_zone_by_network(section)
322 return z and z:name()
323 end
324
325 function fwzone.write(self, section, value)
326 local zone = fw:get_zone(value)
327
328 if not zone and value == '-' then
329 value = m:formvalue(self:cbid(section) .. ".newzone")
330 if value and #value > 0 then
331 zone = fw:add_zone(value)
332 else
333 fw:del_network(section)
334 end
335 end
336
337 if zone then
338 fw:del_network(section)
339 zone:add_network(section)
340 end
341 end
342 end
343
344
345 function p.write() end
346 function p.remove() end
347 function p.validate(self, value, section)
348 if value == net:proto() then
349 if not net:is_floating() and net:is_empty() then
350 local ifn = ((br and (br:formvalue(section) == "bridge"))
351 and ifname_multi:formvalue(section)
352 or ifname_single:formvalue(section))
353
354 for ifn in ut.imatch(ifn) do
355 return value
356 end
357 return nil, translate("The selected protocol needs a device assigned")
358 end
359 end
360 return value
361 end
362
363
364 local form, ferr = loadfile(
365 ut.libpath() .. "/model/cbi/admin_network/proto_%s.lua" % net:proto()
366 )
367
368 if not form then
369 s:taboption("general", DummyValue, "_error",
370 translate("Missing protocol extension for proto %q" % net:proto())
371 ).value = ferr
372 else
373 setfenv(form, getfenv(1))(m, s, net)
374 end
375
376
377 local _, field
378 for _, field in ipairs(s.children) do
379 if field ~= st and field ~= p and field ~= p_install and field ~= p_switch then
380 if next(field.deps) then
381 local _, dep
382 for _, dep in ipairs(field.deps) do
383 dep.deps.proto = net:proto()
384 end
385 else
386 field:depends("proto", net:proto())
387 end
388 end
389 end
390
391
392 --
393 -- Display DNS settings if dnsmasq is available
394 --
395
396 if has_dnsmasq and net:proto() == "static" then
397 m2 = Map("dhcp", "", "")
398
399 local has_section = false
400
401 m2.uci:foreach("dhcp", "dhcp", function(s)
402 if s.interface == arg[1] then
403 has_section = true
404 return false
405 end
406 end)
407
408 if not has_section then
409
410 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
411 s.anonymous = true
412 s.cfgsections = function() return { "_enable" } end
413
414 x = s:option(Button, "_enable")
415 x.title = translate("No DHCP Server configured for this interface")
416 x.inputtitle = translate("Setup DHCP Server")
417 x.inputstyle = "apply"
418
419 else
420
421 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
422 s.addremove = false
423 s.anonymous = true
424 s:tab("general", translate("General Setup"))
425 s:tab("advanced", translate("Advanced 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 end
485 end
486
487
488 return m, m2