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