1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Copyright 2008-2011 Jo-Philipp Wich <xm@subsignal.org>
3 -- Licensed to the public under the Apache License 2.0.
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"
13 local has_dnsmasq = fs.access("/etc/config/dhcp")
14 local has_firewall = fs.access("/etc/config/firewall")
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>)."))
27 local net = nw:get_network(arg[1])
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() }
35 for _, ifn in ipairs(ifcs) do
36 ifns[#ifns+1] = ifn:name()
39 m:set(net:name(), "_orig_ifname", table.concat(ifns, " "))
40 m:set(net:name(), "_orig_bridge", tostring(net:is_bridge()))
47 -- redirect to overview page if network does not exist anymore (e.g. after a revert)
49 luci.http.redirect(luci.dispatcher.build_url("admin/network/network"))
53 -- protocol switch was requested, rebuild interface config and reload page
54 if m:formvalue("cbid.network.%s._switch" % net:name()) then
56 local ptype = m:formvalue("cbid.network.%s.proto" % net:name()) or "-"
57 local proto = nw:get_protocol(ptype, net:name())
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
71 for _, ifn in ipairs(net:get_interfaces() or { net:get_interface() }) do
75 net:del_interface(ifn)
78 m:del(net:name(), "type")
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")
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)
99 m:set(net:name(), "type", "bridge")
102 -- in all other cases clear the ifnames
105 for _, ifc in ipairs(net:get_interfaces() or { net:get_interface() }) do
106 net:del_interface(ifc)
108 m:del(net:name(), "type")
113 for k, v in pairs(m:get(net:name())) do
114 if k:sub(1,1) ~= "." and
117 k ~= "_orig_ifname" and
125 m:set(net:name(), "proto", proto:proto())
126 m.uci:save("network")
127 m.uci:save("wireless")
130 luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
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, {
145 luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
149 local ifc = net:get_interface()
151 s = m:section(NamedSection, arg[1], "interface", translate("Common Configuration"))
154 s:tab("general", translate("General Setup"))
155 s:tab("advanced", translate("Advanced Settings"))
156 s:tab("physical", translate("Physical Settings"))
159 s:tab("firewall", translate("Firewall Settings"))
163 st = s:taboption("general", DummyValue, "__status", translate("Status"))
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"
170 st.value = translate("There is no device assigned yet, please attach a network device in the \"Physical Settings\" tab")
172 st.template = "admin_network/iface_status"
178 m.on_init = set_status
179 m.on_after_save = set_status
182 p = s:taboption("general", ListValue, "proto", translate("Protocol"))
183 p.default = net:proto()
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())
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()
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"
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())
216 auto = s:taboption("advanced", Flag, "auto", translate("Bring up on boot"))
217 auto.default = (net:proto() == "none") and auto.disabled or auto.enabled
219 delegate = s:taboption("advanced", Flag, "delegate", translate("Use builtin IPv6-management"))
220 delegate.default = delegate.enabled
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"
227 br:depends("proto", "static")
228 br:depends("proto", "dhcp")
229 br:depends("proto", "none")
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")
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", "")
247 function ifname_single.cfgvalue(self, s)
248 -- let the template figure out the related ifaces through the network model
252 function ifname_single.write(self, s, val)
257 for _, i in ipairs(net:get_interfaces() or { net:get_interface() }) do
258 old_ifs[#old_ifs+1] = i:name()
261 for i in ut.imatch(val) do
262 new_ifs[#new_ifs+1] = i
264 -- if this is not a bridge, only assign first interface
265 if self.option == "ifname_single" then
273 for i = 1, math.max(#old_ifs, #new_ifs) do
274 if old_ifs[i] ~= new_ifs[i] then
276 for i = 1, #old_ifs do
277 net:del_interface(old_ifs[i])
279 for i = 1, #new_ifs do
280 net:add_interface(new_ifs[i])
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
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."))
307 fwzone.template = "cbi/firewall_zonelist"
308 fwzone.network = arg[1]
309 fwzone.rmempty = false
311 function fwzone.cfgvalue(self, section)
313 local z = fw:get_zone_by_network(section)
314 return z and z:name()
317 function fwzone.write(self, section, value)
318 local zone = fw:get_zone(value)
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)
325 fw:del_network(section)
330 fw:del_network(section)
331 zone:add_network(section)
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))
346 for ifn in ut.imatch(ifn) do
349 return nil, translate("The selected protocol needs a device assigned")
356 local form, ferr = loadfile(
357 ut.libpath() .. "/model/cbi/admin_network/proto_%s.lua" % net:proto()
361 s:taboption("general", DummyValue, "_error",
362 translate("Missing protocol extension for proto %q" % net:proto())
365 setfenv(form, getfenv(1))(m, s, net)
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
374 for _, dep in ipairs(field.deps) do
375 dep.deps.proto = net:proto()
378 field:depends("proto", net:proto())
385 -- Display DNS settings if dnsmasq is available
388 if has_dnsmasq and net:proto() == "static" then
389 m2 = Map("dhcp", "", "")
391 local has_section = false
393 m2.uci:foreach("dhcp", "dhcp", function(s)
394 if s.interface == arg[1] then
400 if not has_section and has_dnsmasq then
402 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
404 s.cfgsections = function() return { "_enable" } end
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"
411 elseif has_section then
413 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
416 s:tab("general", translate("General Setup"))
417 s:tab("advanced", translate("Advanced Settings"))
418 s:tab("ipv6", translate("IPv6 Settings"))
420 function s.filter(self, section)
421 return m2.uci:get("dhcp", section, "interface") == arg[1]
424 local ignore = s:taboption("general", Flag, "ignore",
425 translate("Ignore interface"),
426 translate("Disable <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr> for " ..
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"
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"
441 local ltime = s:taboption("general", Value, "leasetime", translate("Leasetime"),
442 translate("Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>)."))
444 ltime.default = "12h"
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
452 s:taboption("advanced", Flag, "force", translate("Force"),
453 translate("Force DHCP on this network even if another server is detected."))
455 -- XXX: is this actually useful?
456 --s:taboption("advanced", Value, "name", translate("Name"),
457 -- translate("Define a name for this network."))
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."))
465 mask.datatype = "ip4addr"
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."))
471 for i, n in ipairs(s.children) do
473 n:depends("ignore", "")
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"))
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"))
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"))
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")
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")
507 s:taboption("ipv6", DynamicList, "dns", translate("Announced DNS servers"))
508 s:taboption("ipv6", DynamicList, "domain", translate("Announced DNS domains"))