2 LuCI - Lua Configuration Interface
4 Copyright 2008 Steven Barth <steven@midlink.org>
5 Copyright 2008-2011 Jo-Philipp Wich <xm@subsignal.org>
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
11 http://www.apache.org/licenses/LICENSE-2.0
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"
23 local has_dnsmasq = fs.access("/etc/config/dhcp")
24 local has_firewall = fs.access("/etc/config/firewall")
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>)."))
37 local net = nw:get_network(arg[1])
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() }
45 for _, ifn in ipairs(ifcs) do
46 ifns[#ifns+1] = ifn:name()
49 m:set(net:name(), "_orig_ifname", table.concat(ifns, " "))
50 m:set(net:name(), "_orig_bridge", tostring(net:is_bridge()))
57 -- redirect to overview page if network does not exist anymore (e.g. after a revert)
59 luci.http.redirect(luci.dispatcher.build_url("admin/network/network"))
63 -- protocol switch was requested, rebuild interface config and reload page
64 if m:formvalue("cbid.network.%s._switch" % net:name()) then
66 local ptype = m:formvalue("cbid.network.%s.proto" % net:name()) or "-"
67 local proto = nw:get_protocol(ptype, net:name())
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
81 for _, ifn in ipairs(net:get_interfaces() or { net:get_interface() }) do
85 net:del_interface(ifn)
88 m:del(net:name(), "type")
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")
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)
109 m:set(net:name(), "type", "bridge")
112 -- in all other cases clear the ifnames
115 for _, ifc in ipairs(net:get_interfaces() or { net:get_interface() }) do
116 net:del_interface(ifc)
118 m:del(net:name(), "type")
123 for k, v in pairs(m:get(net:name())) do
124 if k:sub(1,1) ~= "." and
127 k ~= "_orig_ifname" and
135 m:set(net:name(), "proto", proto:proto())
136 m.uci:save("network")
137 m.uci:save("wireless")
140 luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
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, {
155 luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
159 local ifc = net:get_interface()
161 s = m:section(NamedSection, arg[1], "interface", translate("Common Configuration"))
164 s:tab("general", translate("General Setup"))
165 s:tab("advanced", translate("Advanced Settings"))
166 s:tab("physical", translate("Physical Settings"))
169 s:tab("firewall", translate("Firewall Settings"))
173 st = s:taboption("general", DummyValue, "__status", translate("Status"))
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"
180 st.value = translate("There is no device assigned yet, please attach a network device in the \"Physical Settings\" tab")
182 st.template = "admin_network/iface_status"
188 m.on_init = set_status
189 m.on_after_save = set_status
192 p = s:taboption("general", ListValue, "proto", translate("Protocol"))
193 p.default = net:proto()
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())
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()
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"
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())
226 auto = s:taboption("advanced", Flag, "auto", translate("Bring up on boot"))
227 auto.default = (net:proto() == "none") and auto.disabled or auto.enabled
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"
234 br:depends("proto", "static")
235 br:depends("proto", "dhcp")
236 br:depends("proto", "none")
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")
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", "")
254 function ifname_single.cfgvalue(self, s)
255 -- let the template figure out the related ifaces through the network model
259 function ifname_single.write(self, s, val)
264 for _, i in ipairs(net:get_interfaces() or { net:get_interface() }) do
265 old_ifs[#old_ifs+1] = i:name()
268 for i in ut.imatch(val) do
269 new_ifs[#new_ifs+1] = i
271 -- if this is not a bridge, only assign first interface
272 if self.option == "ifname_single" then
280 for i = 1, math.max(#old_ifs, #new_ifs) do
281 if old_ifs[i] ~= new_ifs[i] then
283 for i = 1, #old_ifs do
284 net:del_interface(old_ifs[i])
286 for i = 1, #new_ifs do
287 net:add_interface(new_ifs[i])
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
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."))
314 fwzone.template = "cbi/firewall_zonelist"
315 fwzone.network = arg[1]
316 fwzone.rmempty = false
318 function fwzone.cfgvalue(self, section)
320 local z = fw:get_zone_by_network(section)
321 return z and z:name()
324 function fwzone.write(self, section, value)
325 local zone = fw:get_zone(value)
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)
332 fw:del_network(section)
337 fw:del_network(section)
338 zone:add_network(section)
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))
353 for ifn in ut.imatch(ifn) do
356 return nil, translate("The selected protocol needs a device assigned")
363 local form, ferr = loadfile(
364 ut.libpath() .. "/model/cbi/admin_network/proto_%s.lua" % net:proto()
368 s:taboption("general", DummyValue, "_error",
369 translate("Missing protocol extension for proto %q" % net:proto())
372 setfenv(form, getfenv(1))(m, s, net)
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
381 for _, dep in ipairs(field.deps) do
382 dep.deps.proto = net:proto()
385 field:depends("proto", net:proto())
392 -- Display DNS settings if dnsmasq is available
395 if has_dnsmasq and net:proto() == "static" then
396 m2 = Map("dhcp", "", "")
398 local has_section = false
400 m2.uci:foreach("dhcp", "dhcp", function(s)
401 if s.interface == arg[1] then
407 if not has_section then
409 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
411 s.cfgsections = function() return { "_enable" } end
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"
420 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
423 s:tab("general", translate("General Setup"))
424 s:tab("advanced", translate("Advanced Settings"))
426 function s.filter(self, section)
427 return m2.uci:get("dhcp", section, "interface") == arg[1]
430 local ignore = s:taboption("general", Flag, "ignore",
431 translate("Ignore interface"),
432 translate("Disable <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr> for " ..
435 local start = s:taboption("general", Value, "start", translate("Start"),
436 translate("Lowest leased address as offset from the network address."))
437 start.optional = true
438 start.datatype = "or(uinteger,ip4addr)"
439 start.default = "100"
441 local limit = s:taboption("general", Value, "limit", translate("Limit"),
442 translate("Maximum number of leased addresses."))
443 limit.optional = true
444 limit.datatype = "uinteger"
445 limit.default = "150"
447 local ltime = s:taboption("general", Value, "leasetime", translate("Leasetime"),
448 translate("Expiry time of leased addresses, minimum is 2 Minutes (<code>2m</code>)."))
450 ltime.default = "12h"
452 local dd = s:taboption("advanced", Flag, "dynamicdhcp",
453 translate("Dynamic <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr>"),
454 translate("Dynamically allocate DHCP addresses for clients. If disabled, only " ..
455 "clients having static leases will be served."))
456 dd.default = dd.enabled
458 s:taboption("advanced", Flag, "force", translate("Force"),
459 translate("Force DHCP on this network even if another server is detected."))
461 -- XXX: is this actually useful?
462 --s:taboption("advanced", Value, "name", translate("Name"),
463 -- translate("Define a name for this network."))
465 mask = s:taboption("advanced", Value, "netmask",
466 translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"),
467 translate("Override the netmask sent to clients. Normally it is calculated " ..
468 "from the subnet that is served."))
471 mask.datatype = "ip4addr"
473 s:taboption("advanced", DynamicList, "dhcp_option", translate("DHCP-Options"),
474 translate("Define additional DHCP options, for example \"<code>6,192.168.2.1," ..
475 "192.168.2.2</code>\" which advertises different DNS servers to clients."))
477 for i, n in ipairs(s.children) do
479 n:depends("ignore", "")