Merge pull request #1250 from dibdot/luci-app-travelmate
[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.redirect = luci.dispatcher.build_url("admin", "network", "network")
18 m:chain("wireless")
19
20 if has_firewall then
21         m:chain("firewall")
22 end
23
24 nw.init(m.uci)
25 fw.init(m.uci)
26
27
28 local net = nw:get_network(arg[1])
29
30 local function backup_ifnames(is_bridge)
31         if not net:is_floating() and not m:get(net:name(), "_orig_ifname") then
32                 local ifcs = net:get_interfaces() or { net:get_interface() }
33                 if ifcs then
34                         local _, ifn
35                         local ifns = { }
36                         for _, ifn in ipairs(ifcs) do
37                                 ifns[#ifns+1] = ifn:name()
38                         end
39                         if #ifns > 0 then
40                                 m:set(net:name(), "_orig_ifname", table.concat(ifns, " "))
41                                 m:set(net:name(), "_orig_bridge", tostring(net:is_bridge()))
42                         end
43                 end
44         end
45 end
46
47
48 -- redirect to overview page if network does not exist anymore (e.g. after a revert)
49 if not net then
50         luci.http.redirect(luci.dispatcher.build_url("admin/network/network"))
51         return
52 end
53
54 -- protocol switch was requested, rebuild interface config and reload page
55 if m:formvalue("cbid.network.%s._switch" % net:name()) then
56         -- get new protocol
57         local ptype = m:formvalue("cbid.network.%s.proto" % net:name()) or "-"
58         local proto = nw:get_protocol(ptype, net:name())
59         if proto then
60                 -- backup default
61                 backup_ifnames()
62
63                 -- if current proto is not floating and target proto is not floating,
64                 -- then attempt to retain the ifnames
65                 --error(net:proto() .. " > " .. proto:proto())
66                 if not net:is_floating() and not proto:is_floating() then
67                         -- if old proto is a bridge and new proto not, then clip the
68                         -- interface list to the first ifname only
69                         if net:is_bridge() and proto:is_virtual() then
70                                 local _, ifn
71                                 local first = true
72                                 for _, ifn in ipairs(net:get_interfaces() or { net:get_interface() }) do
73                                         if first then
74                                                 first = false
75                                         else
76                                                 net:del_interface(ifn)
77                                         end
78                                 end
79                                 m:del(net:name(), "type")
80                         end
81
82                 -- if the current proto is floating, the target proto not floating,
83                 -- then attempt to restore ifnames from backup
84                 elseif net:is_floating() and not proto:is_floating() then
85                         -- if we have backup data, then re-add all orphaned interfaces
86                         -- from it and restore the bridge choice
87                         local br = (m:get(net:name(), "_orig_bridge") == "true")
88                         local ifn
89                         local ifns = { }
90                         for ifn in ut.imatch(m:get(net:name(), "_orig_ifname")) do
91                                 ifn = nw:get_interface(ifn)
92                                 if ifn and not ifn:get_network() then
93                                         proto:add_interface(ifn)
94                                         if not br then
95                                                 break
96                                         end
97                                 end
98                         end
99                         if br then
100                                 m:set(net:name(), "type", "bridge")
101                         end
102
103                 -- in all other cases clear the ifnames
104                 else
105                         local _, ifc
106                         for _, ifc in ipairs(net:get_interfaces() or { net:get_interface() }) do
107                                 net:del_interface(ifc)
108                         end
109                         m:del(net:name(), "type")
110                 end
111
112                 -- clear options
113                 local k, v
114                 for k, v in pairs(m:get(net:name())) do
115                         if k:sub(1,1) ~= "." and
116                            k ~= "type" and
117                            k ~= "ifname" and
118                            k ~= "_orig_ifname" and
119                            k ~= "_orig_bridge"
120                         then
121                                 m:del(net:name(), k)
122                         end
123                 end
124
125                 -- set proto
126                 m:set(net:name(), "proto", proto:proto())
127                 m.uci:save("network")
128                 m.uci:save("wireless")
129
130                 -- reload page
131                 luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
132                 return
133         end
134 end
135
136 -- dhcp setup was requested, create section and reload page
137 if m:formvalue("cbid.dhcp._enable._enable") then
138         m.uci:section("dhcp", "dhcp", arg[1], {
139                 interface = arg[1],
140                 start     = "100",
141                 limit     = "150",
142                 leasetime = "12h"
143         })
144
145         m.uci:save("dhcp")
146         luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
147         return
148 end
149
150 local ifc = net:get_interface()
151
152 s = m:section(NamedSection, arg[1], "interface", translate("Common Configuration"))
153 s.addremove = false
154
155 s:tab("general",  translate("General Setup"))
156 s:tab("advanced", translate("Advanced Settings"))
157 s:tab("physical", translate("Physical Settings"))
158
159 if has_firewall then
160         s:tab("firewall", translate("Firewall Settings"))
161 end
162
163
164 st = s:taboption("general", DummyValue, "__status", translate("Status"))
165
166 local function set_status()
167         -- if current network is empty, print a warning
168         if not net:is_floating() and net:is_empty() then
169                 st.template = "cbi/dvalue"
170                 st.network  = nil
171                 st.value    = translate("There is no device assigned yet, please attach a network device in the \"Physical Settings\" tab")
172         else
173                 st.template = "admin_network/iface_status"
174                 st.network  = arg[1]
175                 st.value    = nil
176         end
177 end
178
179 m.on_init = set_status
180 m.on_after_save = set_status
181
182
183 p = s:taboption("general", ListValue, "proto", translate("Protocol"))
184 p.default = net:proto()
185
186
187 if not net:is_installed() then
188         p_install = s:taboption("general", Button, "_install")
189         p_install.title      = translate("Protocol support is not installed")
190         p_install.inputtitle = translate("Install package %q" % net:opkg_package())
191         p_install.inputstyle = "apply"
192         p_install:depends("proto", net:proto())
193
194         function p_install.write()
195                 return luci.http.redirect(
196                         luci.dispatcher.build_url("admin/system/packages") ..
197                         "?submit=1&install=%s" % net:opkg_package()
198                 )
199         end
200 end
201
202
203 p_switch = s:taboption("general", Button, "_switch")
204 p_switch.title      = translate("Really switch protocol?")
205 p_switch.inputtitle = translate("Switch protocol")
206 p_switch.inputstyle = "apply"
207
208 local _, pr
209 for _, pr in ipairs(nw:get_protocols()) do
210         p:value(pr:proto(), pr:get_i18n())
211         if pr:proto() ~= net:proto() then
212                 p_switch:depends("proto", pr:proto())
213         end
214 end
215
216
217 auto = s:taboption("advanced", Flag, "auto", translate("Bring up on boot"))
218 auto.default = (net:proto() == "none") and auto.disabled or auto.enabled
219
220 delegate = s:taboption("advanced", Flag, "delegate", translate("Use builtin IPv6-management"))
221 delegate.default = delegate.enabled
222
223 force_link = s:taboption("advanced", Flag, "force_link",
224         translate("Force link"),
225         translate("Set interface properties regardless of the link carrier (If set, carrier sense events do not invoke hotplug handlers)."))
226
227 force_link.default = (net:proto() == "static") and force_link.enabled or force_link.disabled
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.proto = net:proto()
383                         end
384                 else
385                         field:depends("proto", net:proto())
386                 end
387         end
388 end
389
390
391 --
392 -- Display DNS settings if dnsmasq is available
393 --
394
395 if has_dnsmasq and net:proto() == "static" then
396         m2 = Map("dhcp", "", "")
397
398         local has_section = false
399
400         m2.uci:foreach("dhcp", "dhcp", function(s)
401                 if s.interface == arg[1] then
402                         has_section = true
403                         return false
404                 end
405         end)
406
407         if not has_section and has_dnsmasq then
408
409                 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
410                 s.anonymous   = true
411                 s.cfgsections = function() return { "_enable" } end
412
413                 x = s:option(Button, "_enable")
414                 x.title      = translate("No DHCP Server configured for this interface")
415                 x.inputtitle = translate("Setup DHCP Server")
416                 x.inputstyle = "apply"
417
418         elseif has_section then
419
420                 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
421                 s.addremove = false
422                 s.anonymous = true
423                 s:tab("general",  translate("General Setup"))
424                 s:tab("advanced", translate("Advanced Settings"))
425                 s:tab("ipv6", translate("IPv6 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("Lease time"),
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                 o = s:taboption("ipv6", ListValue, "ra", translate("Router Advertisement-Service"))
485                 o:value("", translate("disabled"))
486                 o:value("server", translate("server mode"))
487                 o:value("relay", translate("relay mode"))
488                 o:value("hybrid", translate("hybrid mode"))
489
490                 o = s:taboption("ipv6", ListValue, "dhcpv6", translate("DHCPv6-Service"))
491                 o:value("", translate("disabled"))
492                 o:value("server", translate("server mode"))
493                 o:value("relay", translate("relay mode"))
494                 o:value("hybrid", translate("hybrid mode"))
495
496                 o = s:taboption("ipv6", ListValue, "ndp", translate("NDP-Proxy"))
497                 o:value("", translate("disabled"))
498                 o:value("relay", translate("relay mode"))
499                 o:value("hybrid", translate("hybrid mode"))
500
501                 o = s:taboption("ipv6", ListValue, "ra_management", translate("DHCPv6-Mode"),
502                         translate("Default is stateless + stateful"))
503                 o:value("0", translate("stateless"))
504                 o:value("1", translate("stateless + stateful"))
505                 o:value("2", translate("stateful-only"))
506                 o:depends("dhcpv6", "server")
507                 o:depends("dhcpv6", "hybrid")
508                 o.default = "1"
509
510                 o = s:taboption("ipv6", Flag, "ra_default", translate("Always announce default router"),
511                         translate("Announce as default router even if no public prefix is available."))
512                 o:depends("ra", "server")
513                 o:depends("ra", "hybrid")
514
515                 s:taboption("ipv6", DynamicList, "dns", translate("Announced DNS servers"))
516                 s:taboption("ipv6", DynamicList, "domain", translate("Announced DNS domains"))
517
518         else
519                 m2 = nil
520         end
521 end
522
523
524 return m, m2