8be354b448c17a4c2a943b86c8e0661ab43fea85
[project/luci.git] / modules / luci-mod-admin-full / luasrc / model / cbi / admin_network / ifaces.lua
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.
4
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"
10
11 arg[1] = arg[1] or ""
12
13 local has_dnsmasq = fs.access("/etc/config/dhcp")
14 local has_firewall = fs.access("/etc/config/firewall")
15
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")
18 m:chain("wireless")
19 m:chain("luci")
20
21 if has_firewall then
22 m:chain("firewall")
23 end
24
25 nw.init(m.uci)
26 fw.init(m.uci)
27
28
29 local net = nw:get_network(arg[1])
30
31 local function set_ifstate(name, option, value)
32 local found = false
33
34 m.uci:foreach("luci", "ifstate", function (s)
35 if s.interface == name then
36 m.uci:set("luci", s[".name"], option, value)
37 found = true
38 return false
39 end
40 end)
41
42 if not found then
43 local sid = m.uci:add("luci", "ifstate")
44 m.uci:set("luci", sid, "interface", name)
45 m.uci:set("luci", sid, option, value)
46 end
47
48 m.uci:save("luci")
49 end
50
51 local function get_ifstate(name, option)
52 local val
53
54 m.uci:foreach("luci", "ifstate", function (s)
55 if s.interface == name then
56 val = s[option]
57 return false
58 end
59 end)
60
61 return val
62 end
63
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() }
67 if ifcs then
68 local _, ifn
69 local ifns = { }
70 for _, ifn in ipairs(ifcs) do
71 local wif = ifn:get_wifinet()
72 ifns[#ifns+1] = wif and wif:id() or ifn:name()
73 end
74 if #ifns > 0 then
75 set_ifstate(net:name(), "ifname", table.concat(ifns, " "))
76 set_ifstate(net:name(), "bridge", tostring(net:is_bridge()))
77 end
78 end
79 end
80 end
81
82
83 -- redirect to overview page if network does not exist anymore (e.g. after a revert)
84 if not net then
85 luci.http.redirect(luci.dispatcher.build_url("admin/network/network"))
86 return
87 end
88
89 -- protocol switch was requested, rebuild interface config and reload page
90 if m:formvalue("cbid.network.%s._switch" % net:name()) then
91 -- get new protocol
92 local ptype = m:formvalue("cbid.network.%s.proto" % net:name()) or "-"
93 local proto = nw:get_protocol(ptype, net:name())
94 if proto then
95 -- backup default
96 backup_ifnames()
97
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
105 local _, ifn
106 local first = true
107 for _, ifn in ipairs(net:get_interfaces() or { net:get_interface() }) do
108 if first then
109 first = false
110 else
111 net:del_interface(ifn)
112 end
113 end
114 m:del(net:name(), "type")
115 end
116
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")
123 local ifn
124 local ifns = { }
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)
129 if not br then
130 break
131 end
132 end
133 end
134 if br then
135 m:set(net:name(), "type", "bridge")
136 end
137
138 -- in all other cases clear the ifnames
139 else
140 local _, ifc
141 for _, ifc in ipairs(net:get_interfaces() or { net:get_interface() }) do
142 net:del_interface(ifc)
143 end
144 m:del(net:name(), "type")
145 end
146
147 -- clear options
148 local k, v
149 for k, v in pairs(m:get(net:name())) do
150 if k:sub(1,1) ~= "." and
151 k ~= "type" and
152 k ~= "ifname"
153 then
154 m:del(net:name(), k)
155 end
156 end
157
158 -- set proto
159 m:set(net:name(), "proto", proto:proto())
160 m.uci:save("network")
161 m.uci:save("wireless")
162
163 -- reload page
164 luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
165 return
166 end
167 end
168
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], {
172 interface = arg[1],
173 start = "100",
174 limit = "150",
175 leasetime = "12h"
176 })
177
178 m.uci:save("dhcp")
179 luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
180 return
181 end
182
183 local ifc = net:get_interface()
184
185 s = m:section(NamedSection, arg[1], "interface", translate("Common Configuration"))
186 s.addremove = false
187
188 s:tab("general", translate("General Setup"))
189 s:tab("advanced", translate("Advanced Settings"))
190 s:tab("physical", translate("Physical Settings"))
191
192 if has_firewall then
193 s:tab("firewall", translate("Firewall Settings"))
194 end
195
196
197 st = s:taboption("general", DummyValue, "__status", translate("Status"))
198
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"
203 st.network = nil
204 st.value = translate("There is no device assigned yet, please attach a network device in the \"Physical Settings\" tab")
205 else
206 st.template = "admin_network/iface_status"
207 st.network = arg[1]
208 st.value = nil
209 end
210 end
211
212 m.on_init = set_status
213 m.on_after_save = set_status
214
215
216 p = s:taboption("general", ListValue, "proto", translate("Protocol"))
217 p.default = net:proto()
218
219
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())
226
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()
231 )
232 end
233 end
234
235
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"
240
241 local _, pr
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())
246 end
247 end
248
249
250 auto = s:taboption("advanced", Flag, "auto", translate("Bring up on boot"))
251 auto.default = (net:proto() == "none") and auto.disabled or auto.enabled
252
253 delegate = s:taboption("advanced", Flag, "delegate", translate("Use builtin IPv6-management"))
254 delegate.default = delegate.enabled
255
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)."))
259
260 force_link.default = (net:proto() == "static") and force_link.enabled or force_link.disabled
261
262
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"
266 br.rmempty = true
267 br:depends("proto", "static")
268 br:depends("proto", "dhcp")
269 br:depends("proto", "none")
270
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")
274 stp.rmempty = true
275
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")
279 igmp.rmempty = true
280 end
281
282
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.noaliases = false
289 ifname_single.rmempty = false
290 ifname_single.network = arg[1]
291 ifname_single:depends("type", "")
292
293 function ifname_single.cfgvalue(self, s)
294 -- let the template figure out the related ifaces through the network model
295 return nil
296 end
297
298 function ifname_single.write(self, s, val)
299 local _, i
300 local new_ifs = { }
301 local old_ifs = { }
302
303 local alias = net:is_alias()
304
305 if alias then
306 old_ifs[1] = '@' .. alias
307 else
308 for _, i in ipairs(net:get_interfaces() or { net:get_interface() }) do
309 old_ifs[#old_ifs+1] = i:name()
310 end
311 end
312
313 for i in ut.imatch(val) do
314 new_ifs[#new_ifs+1] = i
315
316 -- if this is not a bridge, only assign first interface
317 if self.option == "ifname_single" then
318 break
319 end
320 end
321
322 table.sort(old_ifs)
323 table.sort(new_ifs)
324
325 for i = 1, math.max(#old_ifs, #new_ifs) do
326 if old_ifs[i] ~= new_ifs[i] then
327 backup_ifnames()
328 for i = 1, #old_ifs do
329 net:del_interface(old_ifs[i])
330 end
331 for i = 1, #new_ifs do
332 net:add_interface(new_ifs[i])
333 end
334 break
335 end
336 end
337 end
338 end
339
340
341 if not net:is_virtual() then
342 ifname_multi = s:taboption("physical", Value, "ifname_multi", translate("Interface"))
343 ifname_multi.template = "cbi/network_ifacelist"
344 ifname_multi.nobridges = true
345 ifname_multi.noaliases = true
346 ifname_multi.rmempty = false
347 ifname_multi.network = arg[1]
348 ifname_multi.widget = "checkbox"
349 ifname_multi:depends("type", "bridge")
350 ifname_multi.cfgvalue = ifname_single.cfgvalue
351 ifname_multi.write = ifname_single.write
352 end
353
354
355 if has_firewall then
356 fwzone = s:taboption("firewall", Value, "_fwzone",
357 translate("Create / Assign firewall-zone"),
358 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."))
359
360 fwzone.template = "cbi/firewall_zonelist"
361 fwzone.network = arg[1]
362
363 function fwzone.cfgvalue(self, section)
364 self.iface = section
365 local z = fw:get_zone_by_network(section)
366 return z and z:name()
367 end
368
369 function fwzone.write(self, section, value)
370 local zone = fw:get_zone(value) or fw:add_zone(value)
371 if zone then
372 fw:del_network(section)
373 zone:add_network(section)
374 end
375 end
376
377 function fwzone.remove(self, section)
378 fw:del_network(section)
379 end
380 end
381
382
383 function p.write() end
384 function p.remove() end
385 function p.validate(self, value, section)
386 if value == net:proto() then
387 if not net:is_floating() and net:is_empty() then
388 local ifn = ((br and (br:formvalue(section) == "bridge"))
389 and ifname_multi:formvalue(section)
390 or ifname_single:formvalue(section))
391
392 for ifn in ut.imatch(ifn) do
393 return value
394 end
395 return nil, translate("The selected protocol needs a device assigned")
396 end
397 end
398 return value
399 end
400
401
402 local form, ferr = loadfile(
403 ut.libpath() .. "/model/cbi/admin_network/proto_%s.lua" % net:proto()
404 )
405
406 if not form then
407 s:taboption("general", DummyValue, "_error",
408 translate("Missing protocol extension for proto %q" % net:proto())
409 ).value = ferr
410 else
411 setfenv(form, getfenv(1))(m, s, net)
412 end
413
414
415 local _, field
416 for _, field in ipairs(s.children) do
417 if field ~= st and field ~= p and field ~= p_install and field ~= p_switch then
418 if next(field.deps) then
419 local _, dep
420 for _, dep in ipairs(field.deps) do
421 dep.proto = net:proto()
422 end
423 else
424 field:depends("proto", net:proto())
425 end
426 end
427 end
428
429
430 --
431 -- Display DNS settings if dnsmasq is available
432 --
433
434 if has_dnsmasq and net:proto() == "static" then
435 m2 = Map("dhcp", "", "")
436
437 local has_section = false
438
439 m2.uci:foreach("dhcp", "dhcp", function(s)
440 if s.interface == arg[1] then
441 has_section = true
442 return false
443 end
444 end)
445
446 if not has_section and has_dnsmasq then
447
448 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
449 s.anonymous = true
450 s.cfgsections = function() return { "_enable" } end
451
452 x = s:option(Button, "_enable")
453 x.title = translate("No DHCP Server configured for this interface")
454 x.inputtitle = translate("Setup DHCP Server")
455 x.inputstyle = "apply"
456
457 elseif has_section then
458
459 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
460 s.addremove = false
461 s.anonymous = true
462 s:tab("general", translate("General Setup"))
463 s:tab("advanced", translate("Advanced Settings"))
464 s:tab("ipv6", translate("IPv6 Settings"))
465
466 function s.filter(self, section)
467 return m2.uci:get("dhcp", section, "interface") == arg[1]
468 end
469
470 local ignore = s:taboption("general", Flag, "ignore",
471 translate("Ignore interface"),
472 translate("Disable <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr> for " ..
473 "this interface."))
474
475 local start = s:taboption("general", Value, "start", translate("Start"),
476 translate("Lowest leased address as offset from the network address."))
477 start.optional = true
478 start.datatype = "or(uinteger,ip4addr)"
479 start.default = "100"
480
481 local limit = s:taboption("general", Value, "limit", translate("Limit"),
482 translate("Maximum number of leased addresses."))
483 limit.optional = true
484 limit.datatype = "uinteger"
485 limit.default = "150"
486
487 local ltime = s:taboption("general", Value, "leasetime", translate("Lease time"),
488 translate("Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>)."))
489 ltime.rmempty = true
490 ltime.default = "12h"
491
492 local dd = s:taboption("advanced", Flag, "dynamicdhcp",
493 translate("Dynamic <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr>"),
494 translate("Dynamically allocate DHCP addresses for clients. If disabled, only " ..
495 "clients having static leases will be served."))
496 dd.default = dd.enabled
497
498 s:taboption("advanced", Flag, "force", translate("Force"),
499 translate("Force DHCP on this network even if another server is detected."))
500
501 -- XXX: is this actually useful?
502 --s:taboption("advanced", Value, "name", translate("Name"),
503 -- translate("Define a name for this network."))
504
505 mask = s:taboption("advanced", Value, "netmask",
506 translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"),
507 translate("Override the netmask sent to clients. Normally it is calculated " ..
508 "from the subnet that is served."))
509
510 mask.optional = true
511 mask.datatype = "ip4addr"
512
513 s:taboption("advanced", DynamicList, "dhcp_option", translate("DHCP-Options"),
514 translate("Define additional DHCP options, for example \"<code>6,192.168.2.1," ..
515 "192.168.2.2</code>\" which advertises different DNS servers to clients."))
516
517 for i, n in ipairs(s.children) do
518 if n ~= ignore then
519 n:depends("ignore", "")
520 end
521 end
522
523 o = s:taboption("ipv6", ListValue, "ra", translate("Router Advertisement-Service"))
524 o:value("", translate("disabled"))
525 o:value("server", translate("server mode"))
526 o:value("relay", translate("relay mode"))
527 o:value("hybrid", translate("hybrid mode"))
528
529 o = s:taboption("ipv6", ListValue, "dhcpv6", translate("DHCPv6-Service"))
530 o:value("", translate("disabled"))
531 o:value("server", translate("server mode"))
532 o:value("relay", translate("relay mode"))
533 o:value("hybrid", translate("hybrid mode"))
534
535 o = s:taboption("ipv6", ListValue, "ndp", translate("NDP-Proxy"))
536 o:value("", translate("disabled"))
537 o:value("relay", translate("relay mode"))
538 o:value("hybrid", translate("hybrid mode"))
539
540 o = s:taboption("ipv6", ListValue, "ra_management", translate("DHCPv6-Mode"),
541 translate("Default is stateless + stateful"))
542 o:value("0", translate("stateless"))
543 o:value("1", translate("stateless + stateful"))
544 o:value("2", translate("stateful-only"))
545 o:depends("dhcpv6", "server")
546 o:depends("dhcpv6", "hybrid")
547 o.default = "1"
548
549 o = s:taboption("ipv6", Flag, "ra_default", translate("Always announce default router"),
550 translate("Announce as default router even if no public prefix is available."))
551 o:depends("ra", "server")
552 o:depends("ra", "hybrid")
553
554 s:taboption("ipv6", DynamicList, "dns", translate("Announced DNS servers"))
555 s:taboption("ipv6", DynamicList, "domain", translate("Announced DNS domains"))
556
557 else
558 m2 = nil
559 end
560 end
561
562
563 return m, m2