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