Allow toggling local IPv6 addressmanagement
[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