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 -- if current network is empty, print a warning
174 if not net:is_floating() and net:is_empty() then
175 st = s:taboption("general", DummyValue, "__status", translate("Status"))
176 st.value = translate("There is no device assigned yet, please attach a network device in the \"Physical Settings\" tab")
178 st = s:taboption("general", DummyValue, "__status", translate("Status"))
179 st.template = "admin_network/iface_status"
184 p = s:taboption("general", ListValue, "proto", translate("Protocol"))
185 p.default = net:proto()
188 if not net:is_installed() then
189 p_install = s:taboption("general", Button, "_install")
190 p_install.title = translate("Protocol support is not installed")
191 p_install.inputtitle = translate("Install package %q" % net:opkg_package())
192 p_install.inputstyle = "apply"
193 p_install:depends("proto", net:proto())
195 function p_install.write()
196 return luci.http.redirect(
197 luci.dispatcher.build_url("admin/system/packages") ..
198 "?submit=1&install=%s" % net:opkg_package()
204 p_switch = s:taboption("general", Button, "_switch")
205 p_switch.title = translate("Really switch protocol?")
206 p_switch.inputtitle = translate("Switch protocol")
207 p_switch.inputstyle = "apply"
210 for _, pr in ipairs(nw:get_protocols()) do
211 p:value(pr:proto(), pr:get_i18n())
212 if pr:proto() ~= net:proto() then
213 p_switch:depends("proto", pr:proto())
218 auto = s:taboption("advanced", Flag, "auto", translate("Bring up on boot"))
219 auto.default = (net:proto() == "none") and auto.disabled or auto.enabled
222 if not net:is_virtual() then
223 br = s:taboption("physical", Flag, "type", translate("Bridge interfaces"), translate("creates a bridge over specified interface(s)"))
224 br.enabled = "bridge"
226 br:depends("proto", "static")
227 br:depends("proto", "dhcp")
228 br:depends("proto", "none")
230 stp = s:taboption("physical", Flag, "stp", translate("Enable <abbr title=\"Spanning Tree Protocol\">STP</abbr>"),
231 translate("Enables the Spanning Tree Protocol on this bridge"))
232 stp:depends("type", "bridge")
237 if not net:is_floating() then
238 ifname_single = s:taboption("physical", Value, "ifname_single", translate("Interface"))
239 ifname_single.template = "cbi/network_ifacelist"
240 ifname_single.widget = "radio"
241 ifname_single.nobridges = true
242 ifname_single.rmempty = false
243 ifname_single.network = arg[1]
244 ifname_single:depends("type", "")
246 function ifname_single.cfgvalue(self, s)
247 -- let the template figure out the related ifaces through the network model
251 function ifname_single.write(self, s, val)
256 for _, i in ipairs(net:get_interfaces() or { net:get_interface() }) do
257 old_ifs[#old_ifs+1] = i:name()
260 for i in ut.imatch(val) do
261 new_ifs[#new_ifs+1] = i
263 -- if this is not a bridge, only assign first interface
264 if self.option == "ifname_single" then
272 for i = 1, math.max(#old_ifs, #new_ifs) do
273 if old_ifs[i] ~= new_ifs[i] then
275 for i = 1, #old_ifs do
276 net:del_interface(old_ifs[i])
278 for i = 1, #new_ifs do
279 net:add_interface(new_ifs[i])
288 if not net:is_virtual() then
289 ifname_multi = s:taboption("physical", Value, "ifname_multi", translate("Interface"))
290 ifname_multi.template = "cbi/network_ifacelist"
291 ifname_multi.nobridges = true
292 ifname_multi.rmempty = false
293 ifname_multi.network = arg[1]
294 ifname_multi.widget = "checkbox"
295 ifname_multi:depends("type", "bridge")
296 ifname_multi.cfgvalue = ifname_single.cfgvalue
297 ifname_multi.write = ifname_single.write
302 fwzone = s:taboption("firewall", Value, "_fwzone",
303 translate("Create / Assign firewall-zone"),
304 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."))
306 fwzone.template = "cbi/firewall_zonelist"
307 fwzone.network = arg[1]
308 fwzone.rmempty = false
310 function fwzone.cfgvalue(self, section)
312 local z = fw:get_zone_by_network(section)
313 return z and z:name()
316 function fwzone.write(self, section, value)
317 local zone = fw:get_zone(value)
319 if not zone and value == '-' then
320 value = m:formvalue(self:cbid(section) .. ".newzone")
321 if value and #value > 0 then
322 zone = fw:add_zone(value)
324 fw:del_network(section)
329 fw:del_network(section)
330 zone:add_network(section)
336 function p.write() end
337 function p.remove() end
338 function p.validate(self, value, section)
339 if value == net:proto() then
340 if not net:is_floating() and net:is_empty() then
341 local ifn = ((br and (br:formvalue(section) == "bridge"))
342 and ifname_multi:formvalue(section)
343 or ifname_single:formvalue(section))
345 for ifn in ut.imatch(ifn) do
348 return nil, translate("The selected protocol needs a device assigned")
355 local form, ferr = loadfile(
356 ut.libpath() .. "/model/cbi/admin_network/proto_%s.lua" % net:proto()
360 s:taboption("general", DummyValue, "_error",
361 translate("Missing protocol extension for proto %q" % net:proto())
364 setfenv(form, getfenv(1))(m, s, net)
369 for _, field in ipairs(s.children) do
370 if field ~= st and field ~= p and field ~= p_install and field ~= p_switch then
371 if next(field.deps) then
373 for _, dep in ipairs(field.deps) do
374 dep.deps.proto = net:proto()
377 field:depends("proto", net:proto())
384 -- Display IP Aliases
387 if not net:is_floating() then
388 s2 = m:section(TypedSection, "alias", translate("IP-Aliases"))
391 s2:depends("interface", arg[1])
392 s2.defaults.interface = arg[1]
394 s2:tab("general", translate("General Setup"))
395 s2.defaults.proto = "static"
397 ip = s2:taboption("general", Value, "ipaddr", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Address"))
399 ip.datatype = "ip4addr"
401 nm = s2:taboption("general", Value, "netmask", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"))
403 nm.datatype = "ip4addr"
404 nm:value("255.255.255.0")
405 nm:value("255.255.0.0")
406 nm:value("255.0.0.0")
408 gw = s2:taboption("general", Value, "gateway", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Gateway"))
410 gw.datatype = "ip4addr"
413 s2:tab("ipv6", translate("IPv6 Setup"))
415 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"))
417 ip6.datatype = "ip6addr"
419 gw6 = s2:taboption("ipv6", Value, "ip6gw", translate("<abbr title=\"Internet Protocol Version 6\">IPv6</abbr>-Gateway"))
421 gw6.datatype = "ip6addr"
424 s2:tab("advanced", translate("Advanced Settings"))
426 bcast = s2:taboption("advanced", Value, "bcast", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Broadcast"))
427 bcast.optional = true
428 bcast.datatype = "ip4addr"
430 dns = s2:taboption("advanced", Value, "dns", translate("<abbr title=\"Domain Name System\">DNS</abbr>-Server"))
432 dns.datatype = "ip4addr"
437 -- Display DNS settings if dnsmasq is available
440 if has_dnsmasq and net:proto() == "static" then
441 m2 = Map("dhcp", "", "")
443 local has_section = false
445 m2.uci:foreach("dhcp", "dhcp", function(s)
446 if s.interface == arg[1] then
452 if not has_section then
454 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
456 s.cfgsections = function() return { "_enable" } end
458 x = s:option(Button, "_enable")
459 x.title = translate("No DHCP Server configured for this interface")
460 x.inputtitle = translate("Setup DHCP Server")
461 x.inputstyle = "apply"
465 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
468 s:tab("general", translate("General Setup"))
469 s:tab("advanced", translate("Advanced Settings"))
471 function s.filter(self, section)
472 return m2.uci:get("dhcp", section, "interface") == arg[1]
475 local ignore = s:taboption("general", Flag, "ignore",
476 translate("Ignore interface"),
477 translate("Disable <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr> for " ..
480 local start = s:taboption("general", Value, "start", translate("Start"),
481 translate("Lowest leased address as offset from the network address."))
482 start.optional = true
483 start.datatype = "uinteger"
484 start.default = "100"
486 local limit = s:taboption("general", Value, "limit", translate("Limit"),
487 translate("Maximum number of leased addresses."))
488 limit.optional = true
489 limit.datatype = "uinteger"
490 limit.default = "150"
492 local ltime = s:taboption("general", Value, "leasetime", translate("Leasetime"),
493 translate("Expiry time of leased addresses, minimum is 2 Minutes (<code>2m</code>)."))
495 ltime.default = "12h"
497 local dd = s:taboption("advanced", Flag, "dynamicdhcp",
498 translate("Dynamic <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr>"),
499 translate("Dynamically allocate DHCP addresses for clients. If disabled, only " ..
500 "clients having static leases will be served."))
501 dd.default = dd.enabled
503 s:taboption("advanced", Flag, "force", translate("Force"),
504 translate("Force DHCP on this network even if another server is detected."))
506 -- XXX: is this actually useful?
507 --s:taboption("advanced", Value, "name", translate("Name"),
508 -- translate("Define a name for this network."))
510 mask = s:taboption("advanced", Value, "netmask",
511 translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"),
512 translate("Override the netmask sent to clients. Normally it is calculated " ..
513 "from the subnet that is served."))
516 mask.datatype = "ip4addr"
518 s:taboption("advanced", DynamicList, "dhcp_option", translate("DHCP-Options"),
519 translate("Define additional DHCP options, for example \"<code>6,192.168.2.1," ..
520 "192.168.2.2</code>\" which advertises different DNS servers to clients."))
522 for i, n in ipairs(s.children) do
524 n:depends("ignore", "")