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.
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>)."))
17 m.redirect = luci.dispatcher.build_url("admin", "network", "network")
29 local net = nw:get_network(arg[1])
31 local function set_ifstate(name, option, value)
34 m.uci:foreach("luci", "ifstate", function (s)
35 if s.interface == name then
36 m.uci:set("luci", s[".name"], option, value)
43 local sid = m.uci:add("luci", "ifstate")
44 m.uci:set("luci", sid, "interface", name)
45 m.uci:set("luci", sid, option, value)
51 local function get_ifstate(name, option)
54 m.uci:foreach("luci", "ifstate", function (s)
55 if s.interface == name then
56 val = m.uci:get("luci", s[".name"], option)
64 local function backup_ifnames(is_bridge)
65 if not net:is_floating() and not get_ifstate(net:name(), "ifname") then
66 local ifcs = net:get_interfaces() or { net:get_interface() }
70 for _, ifn in ipairs(ifcs) do
71 local wif = ifn:get_wifinet()
72 ifns[#ifns+1] = wif and wif:id() or ifn:name()
75 set_ifstate(net:name(), "ifname", table.concat(ifns, " "))
76 set_ifstate(net:name(), "bridge", tostring(net:is_bridge()))
83 -- redirect to overview page if network does not exist anymore (e.g. after a revert)
85 luci.http.redirect(luci.dispatcher.build_url("admin/network/network"))
89 -- protocol switch was requested, rebuild interface config and reload page
90 if m:formvalue("cbid.network.%s._switch" % net:name()) then
92 local ptype = m:formvalue("cbid.network.%s.proto" % net:name()) or "-"
93 local proto = nw:get_protocol(ptype, net:name())
98 -- if current proto is not floating and target proto is not floating,
99 -- then attempt to retain the ifnames
100 --error(net:proto() .. " > " .. proto:proto())
101 if not net:is_floating() and not proto:is_floating() then
102 -- if old proto is a bridge and new proto not, then clip the
103 -- interface list to the first ifname only
104 if net:is_bridge() and proto:is_virtual() then
107 for _, ifn in ipairs(net:get_interfaces() or { net:get_interface() }) do
111 net:del_interface(ifn)
114 m:del(net:name(), "type")
117 -- if the current proto is floating, the target proto not floating,
118 -- then attempt to restore ifnames from backup
119 elseif net:is_floating() and not proto:is_floating() then
120 -- if we have backup data, then re-add all orphaned interfaces
121 -- from it and restore the bridge choice
122 local br = (get_ifstate(net:name(), "bridge") == "true")
125 for ifn in ut.imatch(get_ifstate(net:name(), "ifname")) do
126 ifn = nw:get_interface(ifn)
127 if ifn and not ifn:get_network() then
128 proto:add_interface(ifn)
135 m:set(net:name(), "type", "bridge")
138 -- in all other cases clear the ifnames
141 for _, ifc in ipairs(net:get_interfaces() or { net:get_interface() }) do
142 net:del_interface(ifc)
144 m:del(net:name(), "type")
149 for k, v in pairs(m:get(net:name())) do
150 if k:sub(1,1) ~= "." and
159 m:set(net:name(), "proto", proto:proto())
160 m.uci:save("network")
161 m.uci:save("wireless")
164 luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
169 -- dhcp setup was requested, create section and reload page
170 if m:formvalue("cbid.dhcp._enable._enable") then
171 m.uci:section("dhcp", "dhcp", arg[1], {
179 luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
183 local ifc = net:get_interface()
185 s = m:section(NamedSection, arg[1], "interface", translate("Common Configuration"))
188 s:tab("general", translate("General Setup"))
189 s:tab("advanced", translate("Advanced Settings"))
190 s:tab("physical", translate("Physical Settings"))
193 s:tab("firewall", translate("Firewall Settings"))
197 st = s:taboption("general", DummyValue, "__status", translate("Status"))
199 local function set_status()
200 -- if current network is empty, print a warning
201 if not net:is_floating() and net:is_empty() then
202 st.template = "cbi/dvalue"
204 st.value = translate("There is no device assigned yet, please attach a network device in the \"Physical Settings\" tab")
206 st.template = "admin_network/iface_status"
212 m.on_init = set_status
213 m.on_after_save = set_status
216 p = s:taboption("general", ListValue, "proto", translate("Protocol"))
217 p.default = net:proto()
220 if not net:is_installed() then
221 p_install = s:taboption("general", Button, "_install")
222 p_install.title = translate("Protocol support is not installed")
223 p_install.inputtitle = translate("Install package %q" % net:opkg_package())
224 p_install.inputstyle = "apply"
225 p_install:depends("proto", net:proto())
227 function p_install.write()
228 return luci.http.redirect(
229 luci.dispatcher.build_url("admin/system/packages") ..
230 "?submit=1&install=%s" % net:opkg_package()
236 p_switch = s:taboption("general", Button, "_switch")
237 p_switch.title = translate("Really switch protocol?")
238 p_switch.inputtitle = translate("Switch protocol")
239 p_switch.inputstyle = "apply"
242 for _, pr in ipairs(nw:get_protocols()) do
243 p:value(pr:proto(), pr:get_i18n())
244 if pr:proto() ~= net:proto() then
245 p_switch:depends("proto", pr:proto())
250 auto = s:taboption("advanced", Flag, "auto", translate("Bring up on boot"))
251 auto.default = (net:proto() == "none") and auto.disabled or auto.enabled
253 delegate = s:taboption("advanced", Flag, "delegate", translate("Use builtin IPv6-management"))
254 delegate.default = delegate.enabled
256 force_link = s:taboption("advanced", Flag, "force_link",
257 translate("Force link"),
258 translate("Set interface properties regardless of the link carrier (If set, carrier sense events do not invoke hotplug handlers)."))
260 force_link.default = (net:proto() == "static") and force_link.enabled or force_link.disabled
263 if not net:is_virtual() then
264 br = s:taboption("physical", Flag, "type", translate("Bridge interfaces"), translate("creates a bridge over specified interface(s)"))
265 br.enabled = "bridge"
267 br:depends("proto", "static")
268 br:depends("proto", "dhcp")
269 br:depends("proto", "none")
271 stp = s:taboption("physical", Flag, "stp", translate("Enable <abbr title=\"Spanning Tree Protocol\">STP</abbr>"),
272 translate("Enables the Spanning Tree Protocol on this bridge"))
273 stp:depends("type", "bridge")
276 igmp = s:taboption("physical", Flag, "igmp_snooping", translate("Enable <abbr title=\"Internet Group Management Protocol\">IGMP</abbr> snooping"),
277 translate("Enables IGMP snooping on this bridge"))
278 igmp:depends("type", "bridge")
283 if not net:is_floating() then
284 ifname_single = s:taboption("physical", Value, "ifname_single", translate("Interface"))
285 ifname_single.template = "cbi/network_ifacelist"
286 ifname_single.widget = "radio"
287 ifname_single.nobridges = true
288 ifname_single.rmempty = false
289 ifname_single.network = arg[1]
290 ifname_single:depends("type", "")
292 function ifname_single.cfgvalue(self, s)
293 -- let the template figure out the related ifaces through the network model
297 function ifname_single.write(self, s, val)
302 for _, i in ipairs(net:get_interfaces() or { net:get_interface() }) do
303 old_ifs[#old_ifs+1] = i:name()
306 for i in ut.imatch(val) do
307 new_ifs[#new_ifs+1] = i
309 -- if this is not a bridge, only assign first interface
310 if self.option == "ifname_single" then
318 for i = 1, math.max(#old_ifs, #new_ifs) do
319 if old_ifs[i] ~= new_ifs[i] then
321 for i = 1, #old_ifs do
322 net:del_interface(old_ifs[i])
324 for i = 1, #new_ifs do
325 net:add_interface(new_ifs[i])
334 if not net:is_virtual() then
335 ifname_multi = s:taboption("physical", Value, "ifname_multi", translate("Interface"))
336 ifname_multi.template = "cbi/network_ifacelist"
337 ifname_multi.nobridges = true
338 ifname_multi.rmempty = false
339 ifname_multi.network = arg[1]
340 ifname_multi.widget = "checkbox"
341 ifname_multi:depends("type", "bridge")
342 ifname_multi.cfgvalue = ifname_single.cfgvalue
343 ifname_multi.write = ifname_single.write
348 fwzone = s:taboption("firewall", Value, "_fwzone",
349 translate("Create / Assign firewall-zone"),
350 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."))
352 fwzone.template = "cbi/firewall_zonelist"
353 fwzone.network = arg[1]
354 fwzone.rmempty = false
356 function fwzone.cfgvalue(self, section)
358 local z = fw:get_zone_by_network(section)
359 return z and z:name()
362 function fwzone.write(self, section, value)
363 local zone = fw:get_zone(value)
365 if not zone and value == '-' then
366 value = m:formvalue(self:cbid(section) .. ".newzone")
367 if value and #value > 0 then
368 zone = fw:add_zone(value)
370 fw:del_network(section)
375 fw:del_network(section)
376 zone:add_network(section)
382 function p.write() end
383 function p.remove() end
384 function p.validate(self, value, section)
385 if value == net:proto() then
386 if not net:is_floating() and net:is_empty() then
387 local ifn = ((br and (br:formvalue(section) == "bridge"))
388 and ifname_multi:formvalue(section)
389 or ifname_single:formvalue(section))
391 for ifn in ut.imatch(ifn) do
394 return nil, translate("The selected protocol needs a device assigned")
401 local form, ferr = loadfile(
402 ut.libpath() .. "/model/cbi/admin_network/proto_%s.lua" % net:proto()
406 s:taboption("general", DummyValue, "_error",
407 translate("Missing protocol extension for proto %q" % net:proto())
410 setfenv(form, getfenv(1))(m, s, net)
415 for _, field in ipairs(s.children) do
416 if field ~= st and field ~= p and field ~= p_install and field ~= p_switch then
417 if next(field.deps) then
419 for _, dep in ipairs(field.deps) do
420 dep.proto = net:proto()
423 field:depends("proto", net:proto())
430 -- Display DNS settings if dnsmasq is available
433 if has_dnsmasq and net:proto() == "static" then
434 m2 = Map("dhcp", "", "")
436 local has_section = false
438 m2.uci:foreach("dhcp", "dhcp", function(s)
439 if s.interface == arg[1] then
445 if not has_section and has_dnsmasq then
447 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
449 s.cfgsections = function() return { "_enable" } end
451 x = s:option(Button, "_enable")
452 x.title = translate("No DHCP Server configured for this interface")
453 x.inputtitle = translate("Setup DHCP Server")
454 x.inputstyle = "apply"
456 elseif has_section then
458 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
461 s:tab("general", translate("General Setup"))
462 s:tab("advanced", translate("Advanced Settings"))
463 s:tab("ipv6", translate("IPv6 Settings"))
465 function s.filter(self, section)
466 return m2.uci:get("dhcp", section, "interface") == arg[1]
469 local ignore = s:taboption("general", Flag, "ignore",
470 translate("Ignore interface"),
471 translate("Disable <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr> for " ..
474 local start = s:taboption("general", Value, "start", translate("Start"),
475 translate("Lowest leased address as offset from the network address."))
476 start.optional = true
477 start.datatype = "or(uinteger,ip4addr)"
478 start.default = "100"
480 local limit = s:taboption("general", Value, "limit", translate("Limit"),
481 translate("Maximum number of leased addresses."))
482 limit.optional = true
483 limit.datatype = "uinteger"
484 limit.default = "150"
486 local ltime = s:taboption("general", Value, "leasetime", translate("Lease time"),
487 translate("Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>)."))
489 ltime.default = "12h"
491 local dd = s:taboption("advanced", Flag, "dynamicdhcp",
492 translate("Dynamic <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr>"),
493 translate("Dynamically allocate DHCP addresses for clients. If disabled, only " ..
494 "clients having static leases will be served."))
495 dd.default = dd.enabled
497 s:taboption("advanced", Flag, "force", translate("Force"),
498 translate("Force DHCP on this network even if another server is detected."))
500 -- XXX: is this actually useful?
501 --s:taboption("advanced", Value, "name", translate("Name"),
502 -- translate("Define a name for this network."))
504 mask = s:taboption("advanced", Value, "netmask",
505 translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"),
506 translate("Override the netmask sent to clients. Normally it is calculated " ..
507 "from the subnet that is served."))
510 mask.datatype = "ip4addr"
512 s:taboption("advanced", DynamicList, "dhcp_option", translate("DHCP-Options"),
513 translate("Define additional DHCP options, for example \"<code>6,192.168.2.1," ..
514 "192.168.2.2</code>\" which advertises different DNS servers to clients."))
516 for i, n in ipairs(s.children) do
518 n:depends("ignore", "")
522 o = s:taboption("ipv6", ListValue, "ra", translate("Router Advertisement-Service"))
523 o:value("", translate("disabled"))
524 o:value("server", translate("server mode"))
525 o:value("relay", translate("relay mode"))
526 o:value("hybrid", translate("hybrid mode"))
528 o = s:taboption("ipv6", ListValue, "dhcpv6", translate("DHCPv6-Service"))
529 o:value("", translate("disabled"))
530 o:value("server", translate("server mode"))
531 o:value("relay", translate("relay mode"))
532 o:value("hybrid", translate("hybrid mode"))
534 o = s:taboption("ipv6", ListValue, "ndp", translate("NDP-Proxy"))
535 o:value("", translate("disabled"))
536 o:value("relay", translate("relay mode"))
537 o:value("hybrid", translate("hybrid mode"))
539 o = s:taboption("ipv6", ListValue, "ra_management", translate("DHCPv6-Mode"),
540 translate("Default is stateless + stateful"))
541 o:value("0", translate("stateless"))
542 o:value("1", translate("stateless + stateful"))
543 o:value("2", translate("stateful-only"))
544 o:depends("dhcpv6", "server")
545 o:depends("dhcpv6", "hybrid")
548 o = s:taboption("ipv6", Flag, "ra_default", translate("Always announce default router"),
549 translate("Announce as default router even if no public prefix is available."))
550 o:depends("ra", "server")
551 o:depends("ra", "hybrid")
553 s:taboption("ipv6", DynamicList, "dns", translate("Announced DNS servers"))
554 s:taboption("ipv6", DynamicList, "domain", translate("Announced DNS domains"))