1d83eb32063e361b4c5db63181fc568dac1fe7cc
[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 nw = require "luci.model.network"
19 local fw = require "luci.model.firewall"
20
21 arg[1] = arg[1] or ""
22
23 local has_dnsmasq = fs.access("/etc/config/dhcp")
24 local has_firewall = fs.access("/etc/config/firewall")
25
26 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>)."))
27 m:chain("wireless")
28
29 if has_firewall then
30 m:chain("firewall")
31 end
32
33 nw.init(m.uci)
34 fw.init(m.uci)
35
36
37 local net = nw:get_network(arg[1])
38
39 local function backup_ifnames(is_bridge)
40 if not net:is_floating() and not m:get(net:name(), "_orig_ifname") then
41 local ifcs = net:get_interfaces() or { net:get_interface() }
42 if ifcs then
43 local _, ifn
44 local ifns = { }
45 for _, ifn in ipairs(ifcs) do
46 ifns[#ifns+1] = ifn:name()
47 end
48 if #ifns > 0 then
49 m:set(net:name(), "_orig_ifname", table.concat(ifns, " "))
50 m:set(net:name(), "_orig_bridge", tostring(net:is_bridge()))
51 end
52 end
53 end
54 end
55
56
57 -- redirect to overview page if network does not exist anymore (e.g. after a revert)
58 if not net then
59 luci.http.redirect(luci.dispatcher.build_url("admin/network/network"))
60 return
61 end
62
63 -- protocol switch was requested, rebuild interface config and reload page
64 if m:formvalue("cbid.network.%s._switch" % net:name()) then
65 -- get new protocol
66 local ptype = m:formvalue("cbid.network.%s.proto" % net:name()) or "-"
67 local proto = nw:get_protocol(ptype, net:name())
68 if proto then
69 -- backup default
70 backup_ifnames()
71
72 -- if current proto is not floating and target proto is not floating,
73 -- then attempt to retain the ifnames
74 --error(net:proto() .. " > " .. proto:proto())
75 if not net:is_floating() and not proto:is_floating() then
76 -- if old proto is a bridge and new proto not, then clip the
77 -- interface list to the first ifname only
78 if net:is_bridge() and proto:is_virtual() then
79 local _, ifn
80 local first = true
81 for _, ifn in ipairs(net:get_interfaces() or { net:get_interface() }) do
82 if first then
83 first = false
84 else
85 net:del_interface(ifn)
86 end
87 end
88 m:del(net:name(), "type")
89 end
90
91 -- if the current proto is floating, the target proto not floating,
92 -- then attempt to restore ifnames from backup
93 elseif net:is_floating() and not proto:is_floating() then
94 -- if we have backup data, then re-add all orphaned interfaces
95 -- from it and restore the bridge choice
96 local br = (m:get(net:name(), "_orig_bridge") == "true")
97 local ifn
98 local ifns = { }
99 for ifn in ut.imatch(m:get(net:name(), "_orig_ifname")) do
100 ifn = nw:get_interface(ifn)
101 if ifn and not ifn:get_network() then
102 proto:add_interface(ifn)
103 if not br then
104 break
105 end
106 end
107 end
108 if br then
109 m:set(net:name(), "type", "bridge")
110 end
111
112 -- in all other cases clear the ifnames
113 else
114 local _, ifc
115 for _, ifc in ipairs(net:get_interfaces() or { net:get_interface() }) do
116 net:del_interface(ifc)
117 end
118 m:del(net:name(), "type")
119 end
120
121 -- clear options
122 local k, v
123 for k, v in pairs(m:get(net:name())) do
124 if k:sub(1,1) ~= "." and
125 k ~= "type" and
126 k ~= "ifname" and
127 k ~= "_orig_ifname" and
128 k ~= "_orig_bridge"
129 then
130 m:del(net:name(), k)
131 end
132 end
133
134 -- set proto
135 m:set(net:name(), "proto", proto:proto())
136 m.uci:save("network")
137 m.uci:save("wireless")
138
139 -- reload page
140 luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
141 return
142 end
143 end
144
145 -- dhcp setup was requested, create section and reload page
146 if m:formvalue("cbid.dhcp._enable._enable") then
147 m.uci:section("dhcp", "dhcp", nil, {
148 interface = arg[1],
149 start = "100",
150 limit = "150",
151 leasetime = "12h"
152 })
153
154 m.uci:save("dhcp")
155 luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
156 return
157 end
158
159 local ifc = net:get_interface()
160
161 s = m:section(NamedSection, arg[1], "interface", translate("Common Configuration"))
162 s.addremove = false
163
164 s:tab("general", translate("General Setup"))
165 s:tab("advanced", translate("Advanced Settings"))
166 s:tab("physical", translate("Physical Settings"))
167
168 if has_firewall then
169 s:tab("firewall", translate("Firewall Settings"))
170 end
171
172
173 st = s:taboption("general", DummyValue, "__status", translate("Status"))
174
175 local function set_status()
176 -- if current network is empty, print a warning
177 if not net:is_floating() and net:is_empty() then
178 st.template = "cbi/dvalue"
179 st.network = nil
180 st.value = translate("There is no device assigned yet, please attach a network device in the \"Physical Settings\" tab")
181 else
182 st.template = "admin_network/iface_status"
183 st.network = arg[1]
184 st.value = nil
185 end
186 end
187
188 m.on_init = set_status
189 m.on_after_save = set_status
190
191
192 p = s:taboption("general", ListValue, "proto", translate("Protocol"))
193 p.default = net:proto()
194
195
196 if not net:is_installed() then
197 p_install = s:taboption("general", Button, "_install")
198 p_install.title = translate("Protocol support is not installed")
199 p_install.inputtitle = translate("Install package %q" % net:opkg_package())
200 p_install.inputstyle = "apply"
201 p_install:depends("proto", net:proto())
202
203 function p_install.write()
204 return luci.http.redirect(
205 luci.dispatcher.build_url("admin/system/packages") ..
206 "?submit=1&install=%s" % net:opkg_package()
207 )
208 end
209 end
210
211
212 p_switch = s:taboption("general", Button, "_switch")
213 p_switch.title = translate("Really switch protocol?")
214 p_switch.inputtitle = translate("Switch protocol")
215 p_switch.inputstyle = "apply"
216
217 local _, pr
218 for _, pr in ipairs(nw:get_protocols()) do
219 p:value(pr:proto(), pr:get_i18n())
220 if pr:proto() ~= net:proto() then
221 p_switch:depends("proto", pr:proto())
222 end
223 end
224
225
226 auto = s:taboption("advanced", Flag, "auto", translate("Bring up on boot"))
227 auto.default = (net:proto() == "none") and auto.disabled or auto.enabled
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.deps.proto = net:proto()
383 end
384 else
385 field:depends("proto", net:proto())
386 end
387 end
388 end
389
390
391 --
392 -- Display IP Aliases
393 --
394
395 if not net:is_floating() then
396 s2 = m:section(TypedSection, "alias", translate("IP-Aliases"))
397 s2.addremove = true
398
399 s2:depends("interface", arg[1])
400 s2.defaults.interface = arg[1]
401
402 s2:tab("general", translate("General Setup"))
403 s2.defaults.proto = "static"
404
405 ip = s2:taboption("general", Value, "ipaddr", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Address"))
406 ip.optional = true
407 ip.datatype = "ip4addr"
408
409 nm = s2:taboption("general", Value, "netmask", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"))
410 nm.optional = true
411 nm.datatype = "ip4addr"
412 nm:value("255.255.255.0")
413 nm:value("255.255.0.0")
414 nm:value("255.0.0.0")
415
416 gw = s2:taboption("general", Value, "gateway", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Gateway"))
417 gw.optional = true
418 gw.datatype = "ip4addr"
419
420 if has_ipv6 then
421 s2:tab("ipv6", translate("IPv6 Setup"))
422
423 ip6 = s2:taboption("ipv6", Value, "ip6addr", translate("<abbr title=\"Internet Protocol Version 6\">IPv6</abbr>-Address"), translate("<abbr title=\"Classless Inter-Domain Routing\">CIDR</abbr>-Notation: address/prefix"))
424 ip6.optional = true
425 ip6.datatype = "ip6addr"
426
427 gw6 = s2:taboption("ipv6", Value, "ip6gw", translate("<abbr title=\"Internet Protocol Version 6\">IPv6</abbr>-Gateway"))
428 gw6.optional = true
429 gw6.datatype = "ip6addr"
430 end
431
432 s2:tab("advanced", translate("Advanced Settings"))
433
434 bcast = s2:taboption("advanced", Value, "bcast", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Broadcast"))
435 bcast.optional = true
436 bcast.datatype = "ip4addr"
437
438 dns = s2:taboption("advanced", Value, "dns", translate("<abbr title=\"Domain Name System\">DNS</abbr>-Server"))
439 dns.optional = true
440 dns.datatype = "ip4addr"
441 end
442
443
444 --
445 -- Display DNS settings if dnsmasq is available
446 --
447
448 if has_dnsmasq and net:proto() == "static" then
449 m2 = Map("dhcp", "", "")
450
451 local has_section = false
452
453 m2.uci:foreach("dhcp", "dhcp", function(s)
454 if s.interface == arg[1] then
455 has_section = true
456 return false
457 end
458 end)
459
460 if not has_section then
461
462 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
463 s.anonymous = true
464 s.cfgsections = function() return { "_enable" } end
465
466 x = s:option(Button, "_enable")
467 x.title = translate("No DHCP Server configured for this interface")
468 x.inputtitle = translate("Setup DHCP Server")
469 x.inputstyle = "apply"
470
471 else
472
473 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
474 s.addremove = false
475 s.anonymous = true
476 s:tab("general", translate("General Setup"))
477 s:tab("advanced", translate("Advanced Settings"))
478
479 function s.filter(self, section)
480 return m2.uci:get("dhcp", section, "interface") == arg[1]
481 end
482
483 local ignore = s:taboption("general", Flag, "ignore",
484 translate("Ignore interface"),
485 translate("Disable <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr> for " ..
486 "this interface."))
487
488 local start = s:taboption("general", Value, "start", translate("Start"),
489 translate("Lowest leased address as offset from the network address."))
490 start.optional = true
491 start.datatype = "or(uinteger,ip4addr)"
492 start.default = "100"
493
494 local limit = s:taboption("general", Value, "limit", translate("Limit"),
495 translate("Maximum number of leased addresses."))
496 limit.optional = true
497 limit.datatype = "uinteger"
498 limit.default = "150"
499
500 local ltime = s:taboption("general", Value, "leasetime", translate("Leasetime"),
501 translate("Expiry time of leased addresses, minimum is 2 Minutes (<code>2m</code>)."))
502 ltime.rmempty = true
503 ltime.default = "12h"
504
505 local dd = s:taboption("advanced", Flag, "dynamicdhcp",
506 translate("Dynamic <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr>"),
507 translate("Dynamically allocate DHCP addresses for clients. If disabled, only " ..
508 "clients having static leases will be served."))
509 dd.default = dd.enabled
510
511 s:taboption("advanced", Flag, "force", translate("Force"),
512 translate("Force DHCP on this network even if another server is detected."))
513
514 -- XXX: is this actually useful?
515 --s:taboption("advanced", Value, "name", translate("Name"),
516 -- translate("Define a name for this network."))
517
518 mask = s:taboption("advanced", Value, "netmask",
519 translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"),
520 translate("Override the netmask sent to clients. Normally it is calculated " ..
521 "from the subnet that is served."))
522
523 mask.optional = true
524 mask.datatype = "ip4addr"
525
526 s:taboption("advanced", DynamicList, "dhcp_option", translate("DHCP-Options"),
527 translate("Define additional DHCP options, for example \"<code>6,192.168.2.1," ..
528 "192.168.2.2</code>\" which advertises different DNS servers to clients."))
529
530 for i, n in ipairs(s.children) do
531 if n ~= ignore then
532 n:depends("ignore", "")
533 end
534 end
535
536 end
537 end
538
539
540 return m, m2