eabb257680524bdc612abb0c1b679c2801c6de60
[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