ifaces.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  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. local fs = require "nixio.fs"
  5. local ut = require "luci.util"
  6. local pt = require "luci.tools.proto"
  7. local nw = require "luci.model.network"
  8. local fw = require "luci.model.firewall"
  9. arg[1] = arg[1] or ""
  10. local has_dnsmasq = fs.access("/etc/config/dhcp")
  11. local has_firewall = fs.access("/etc/config/firewall")
  12. 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>)."))
  13. m.redirect = luci.dispatcher.build_url("admin", "network", "network")
  14. m:chain("wireless")
  15. if has_firewall then
  16. m:chain("firewall")
  17. end
  18. nw.init(m.uci)
  19. fw.init(m.uci)
  20. local net = nw:get_network(arg[1])
  21. local function backup_ifnames(is_bridge)
  22. if not net:is_floating() and not m:get(net:name(), "_orig_ifname") then
  23. local ifcs = net:get_interfaces() or { net:get_interface() }
  24. if ifcs then
  25. local _, ifn
  26. local ifns = { }
  27. for _, ifn in ipairs(ifcs) do
  28. ifns[#ifns+1] = ifn:name()
  29. end
  30. if #ifns > 0 then
  31. m:set(net:name(), "_orig_ifname", table.concat(ifns, " "))
  32. m:set(net:name(), "_orig_bridge", tostring(net:is_bridge()))
  33. end
  34. end
  35. end
  36. end
  37. -- redirect to overview page if network does not exist anymore (e.g. after a revert)
  38. if not net then
  39. luci.http.redirect(luci.dispatcher.build_url("admin/network/network"))
  40. return
  41. end
  42. -- protocol switch was requested, rebuild interface config and reload page
  43. if m:formvalue("cbid.network.%s._switch" % net:name()) then
  44. -- get new protocol
  45. local ptype = m:formvalue("cbid.network.%s.proto" % net:name()) or "-"
  46. local proto = nw:get_protocol(ptype, net:name())
  47. if proto then
  48. -- backup default
  49. backup_ifnames()
  50. -- if current proto is not floating and target proto is not floating,
  51. -- then attempt to retain the ifnames
  52. --error(net:proto() .. " > " .. proto:proto())
  53. if not net:is_floating() and not proto:is_floating() then
  54. -- if old proto is a bridge and new proto not, then clip the
  55. -- interface list to the first ifname only
  56. if net:is_bridge() and proto:is_virtual() then
  57. local _, ifn
  58. local first = true
  59. for _, ifn in ipairs(net:get_interfaces() or { net:get_interface() }) do
  60. if first then
  61. first = false
  62. else
  63. net:del_interface(ifn)
  64. end
  65. end
  66. m:del(net:name(), "type")
  67. end
  68. -- if the current proto is floating, the target proto not floating,
  69. -- then attempt to restore ifnames from backup
  70. elseif net:is_floating() and not proto:is_floating() then
  71. -- if we have backup data, then re-add all orphaned interfaces
  72. -- from it and restore the bridge choice
  73. local br = (m:get(net:name(), "_orig_bridge") == "true")
  74. local ifn
  75. local ifns = { }
  76. for ifn in ut.imatch(m:get(net:name(), "_orig_ifname")) do
  77. ifn = nw:get_interface(ifn)
  78. if ifn and not ifn:get_network() then
  79. proto:add_interface(ifn)
  80. if not br then
  81. break
  82. end
  83. end
  84. end
  85. if br then
  86. m:set(net:name(), "type", "bridge")
  87. end
  88. -- in all other cases clear the ifnames
  89. else
  90. local _, ifc
  91. for _, ifc in ipairs(net:get_interfaces() or { net:get_interface() }) do
  92. net:del_interface(ifc)
  93. end
  94. m:del(net:name(), "type")
  95. end
  96. -- clear options
  97. local k, v
  98. for k, v in pairs(m:get(net:name())) do
  99. if k:sub(1,1) ~= "." and
  100. k ~= "type" and
  101. k ~= "ifname" and
  102. k ~= "_orig_ifname" and
  103. k ~= "_orig_bridge"
  104. then
  105. m:del(net:name(), k)
  106. end
  107. end
  108. -- set proto
  109. m:set(net:name(), "proto", proto:proto())
  110. m.uci:save("network")
  111. m.uci:save("wireless")
  112. -- reload page
  113. luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
  114. return
  115. end
  116. end
  117. -- dhcp setup was requested, create section and reload page
  118. if m:formvalue("cbid.dhcp._enable._enable") then
  119. m.uci:section("dhcp", "dhcp", arg[1], {
  120. interface = arg[1],
  121. start = "100",
  122. limit = "150",
  123. leasetime = "12h"
  124. })
  125. m.uci:save("dhcp")
  126. luci.http.redirect(luci.dispatcher.build_url("admin/network/network", arg[1]))
  127. return
  128. end
  129. local ifc = net:get_interface()
  130. s = m:section(NamedSection, arg[1], "interface", translate("Common Configuration"))
  131. s.addremove = false
  132. s:tab("general", translate("General Setup"))
  133. s:tab("advanced", translate("Advanced Settings"))
  134. s:tab("physical", translate("Physical Settings"))
  135. if has_firewall then
  136. s:tab("firewall", translate("Firewall Settings"))
  137. end
  138. st = s:taboption("general", DummyValue, "__status", translate("Status"))
  139. local function set_status()
  140. -- if current network is empty, print a warning
  141. if not net:is_floating() and net:is_empty() then
  142. st.template = "cbi/dvalue"
  143. st.network = nil
  144. st.value = translate("There is no device assigned yet, please attach a network device in the \"Physical Settings\" tab")
  145. else
  146. st.template = "admin_network/iface_status"
  147. st.network = arg[1]
  148. st.value = nil
  149. end
  150. end
  151. m.on_init = set_status
  152. m.on_after_save = set_status
  153. p = s:taboption("general", ListValue, "proto", translate("Protocol"))
  154. p.default = net:proto()
  155. if not net:is_installed() then
  156. p_install = s:taboption("general", Button, "_install")
  157. p_install.title = translate("Protocol support is not installed")
  158. p_install.inputtitle = translate("Install package %q" % net:opkg_package())
  159. p_install.inputstyle = "apply"
  160. p_install:depends("proto", net:proto())
  161. function p_install.write()
  162. return luci.http.redirect(
  163. luci.dispatcher.build_url("admin/system/packages") ..
  164. "?submit=1&install=%s" % net:opkg_package()
  165. )
  166. end
  167. end
  168. p_switch = s:taboption("general", Button, "_switch")
  169. p_switch.title = translate("Really switch protocol?")
  170. p_switch.inputtitle = translate("Switch protocol")
  171. p_switch.inputstyle = "apply"
  172. local _, pr
  173. for _, pr in ipairs(nw:get_protocols()) do
  174. p:value(pr:proto(), pr:get_i18n())
  175. if pr:proto() ~= net:proto() then
  176. p_switch:depends("proto", pr:proto())
  177. end
  178. end
  179. auto = s:taboption("advanced", Flag, "auto", translate("Bring up on boot"))
  180. auto.default = (net:proto() == "none") and auto.disabled or auto.enabled
  181. delegate = s:taboption("advanced", Flag, "delegate", translate("Use builtin IPv6-management"))
  182. delegate.default = delegate.enabled
  183. if not net:is_virtual() then
  184. br = s:taboption("physical", Flag, "type", translate("Bridge interfaces"), translate("creates a bridge over specified interface(s)"))
  185. br.enabled = "bridge"
  186. br.rmempty = true
  187. br:depends("proto", "static")
  188. br:depends("proto", "dhcp")
  189. br:depends("proto", "none")
  190. stp = s:taboption("physical", Flag, "stp", translate("Enable <abbr title=\"Spanning Tree Protocol\">STP</abbr>"),
  191. translate("Enables the Spanning Tree Protocol on this bridge"))
  192. stp:depends("type", "bridge")
  193. stp.rmempty = true
  194. end
  195. if not net:is_floating() then
  196. ifname_single = s:taboption("physical", Value, "ifname_single", translate("Interface"))
  197. ifname_single.template = "cbi/network_ifacelist"
  198. ifname_single.widget = "radio"
  199. ifname_single.nobridges = true
  200. ifname_single.rmempty = false
  201. ifname_single.network = arg[1]
  202. ifname_single:depends("type", "")
  203. function ifname_single.cfgvalue(self, s)
  204. -- let the template figure out the related ifaces through the network model
  205. return nil
  206. end
  207. function ifname_single.write(self, s, val)
  208. local i
  209. local new_ifs = { }
  210. local old_ifs = { }
  211. for _, i in ipairs(net:get_interfaces() or { net:get_interface() }) do
  212. old_ifs[#old_ifs+1] = i:name()
  213. end
  214. for i in ut.imatch(val) do
  215. new_ifs[#new_ifs+1] = i
  216. -- if this is not a bridge, only assign first interface
  217. if self.option == "ifname_single" then
  218. break
  219. end
  220. end
  221. table.sort(old_ifs)
  222. table.sort(new_ifs)
  223. for i = 1, math.max(#old_ifs, #new_ifs) do
  224. if old_ifs[i] ~= new_ifs[i] then
  225. backup_ifnames()
  226. for i = 1, #old_ifs do
  227. net:del_interface(old_ifs[i])
  228. end
  229. for i = 1, #new_ifs do
  230. net:add_interface(new_ifs[i])
  231. end
  232. break
  233. end
  234. end
  235. end
  236. end
  237. if not net:is_virtual() then
  238. ifname_multi = s:taboption("physical", Value, "ifname_multi", translate("Interface"))
  239. ifname_multi.template = "cbi/network_ifacelist"
  240. ifname_multi.nobridges = true
  241. ifname_multi.rmempty = false
  242. ifname_multi.network = arg[1]
  243. ifname_multi.widget = "checkbox"
  244. ifname_multi:depends("type", "bridge")
  245. ifname_multi.cfgvalue = ifname_single.cfgvalue
  246. ifname_multi.write = ifname_single.write
  247. end
  248. if has_firewall then
  249. fwzone = s:taboption("firewall", Value, "_fwzone",
  250. translate("Create / Assign firewall-zone"),
  251. 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."))
  252. fwzone.template = "cbi/firewall_zonelist"
  253. fwzone.network = arg[1]
  254. fwzone.rmempty = false
  255. function fwzone.cfgvalue(self, section)
  256. self.iface = section
  257. local z = fw:get_zone_by_network(section)
  258. return z and z:name()
  259. end
  260. function fwzone.write(self, section, value)
  261. local zone = fw:get_zone(value)
  262. if not zone and value == '-' then
  263. value = m:formvalue(self:cbid(section) .. ".newzone")
  264. if value and #value > 0 then
  265. zone = fw:add_zone(value)
  266. else
  267. fw:del_network(section)
  268. end
  269. end
  270. if zone then
  271. fw:del_network(section)
  272. zone:add_network(section)
  273. end
  274. end
  275. end
  276. function p.write() end
  277. function p.remove() end
  278. function p.validate(self, value, section)
  279. if value == net:proto() then
  280. if not net:is_floating() and net:is_empty() then
  281. local ifn = ((br and (br:formvalue(section) == "bridge"))
  282. and ifname_multi:formvalue(section)
  283. or ifname_single:formvalue(section))
  284. for ifn in ut.imatch(ifn) do
  285. return value
  286. end
  287. return nil, translate("The selected protocol needs a device assigned")
  288. end
  289. end
  290. return value
  291. end
  292. local form, ferr = loadfile(
  293. ut.libpath() .. "/model/cbi/admin_network/proto_%s.lua" % net:proto()
  294. )
  295. if not form then
  296. s:taboption("general", DummyValue, "_error",
  297. translate("Missing protocol extension for proto %q" % net:proto())
  298. ).value = ferr
  299. else
  300. setfenv(form, getfenv(1))(m, s, net)
  301. end
  302. local _, field
  303. for _, field in ipairs(s.children) do
  304. if field ~= st and field ~= p and field ~= p_install and field ~= p_switch then
  305. if next(field.deps) then
  306. local _, dep
  307. for _, dep in ipairs(field.deps) do
  308. dep.proto = net:proto()
  309. end
  310. else
  311. field:depends("proto", net:proto())
  312. end
  313. end
  314. end
  315. --
  316. -- Display DNS settings if dnsmasq is available
  317. --
  318. if has_dnsmasq and net:proto() == "static" then
  319. m2 = Map("dhcp", "", "")
  320. local has_section = false
  321. m2.uci:foreach("dhcp", "dhcp", function(s)
  322. if s.interface == arg[1] then
  323. has_section = true
  324. return false
  325. end
  326. end)
  327. if not has_section and has_dnsmasq then
  328. s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
  329. s.anonymous = true
  330. s.cfgsections = function() return { "_enable" } end
  331. x = s:option(Button, "_enable")
  332. x.title = translate("No DHCP Server configured for this interface")
  333. x.inputtitle = translate("Setup DHCP Server")
  334. x.inputstyle = "apply"
  335. elseif has_section then
  336. s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
  337. s.addremove = false
  338. s.anonymous = true
  339. s:tab("general", translate("General Setup"))
  340. s:tab("advanced", translate("Advanced Settings"))
  341. s:tab("ipv6", translate("IPv6 Settings"))
  342. function s.filter(self, section)
  343. return m2.uci:get("dhcp", section, "interface") == arg[1]
  344. end
  345. local ignore = s:taboption("general", Flag, "ignore",
  346. translate("Ignore interface"),
  347. translate("Disable <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr> for " ..
  348. "this interface."))
  349. local start = s:taboption("general", Value, "start", translate("Start"),
  350. translate("Lowest leased address as offset from the network address."))
  351. start.optional = true
  352. start.datatype = "or(uinteger,ip4addr)"
  353. start.default = "100"
  354. local limit = s:taboption("general", Value, "limit", translate("Limit"),
  355. translate("Maximum number of leased addresses."))
  356. limit.optional = true
  357. limit.datatype = "uinteger"
  358. limit.default = "150"
  359. local ltime = s:taboption("general", Value, "leasetime", translate("Leasetime"),
  360. translate("Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>)."))
  361. ltime.rmempty = true
  362. ltime.default = "12h"
  363. local dd = s:taboption("advanced", Flag, "dynamicdhcp",
  364. translate("Dynamic <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr>"),
  365. translate("Dynamically allocate DHCP addresses for clients. If disabled, only " ..
  366. "clients having static leases will be served."))
  367. dd.default = dd.enabled
  368. s:taboption("advanced", Flag, "force", translate("Force"),
  369. translate("Force DHCP on this network even if another server is detected."))
  370. -- XXX: is this actually useful?
  371. --s:taboption("advanced", Value, "name", translate("Name"),
  372. -- translate("Define a name for this network."))
  373. mask = s:taboption("advanced", Value, "netmask",
  374. translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"),
  375. translate("Override the netmask sent to clients. Normally it is calculated " ..
  376. "from the subnet that is served."))
  377. mask.optional = true
  378. mask.datatype = "ip4addr"
  379. s:taboption("advanced", DynamicList, "dhcp_option", translate("DHCP-Options"),
  380. translate("Define additional DHCP options, for example \"<code>6,192.168.2.1," ..
  381. "192.168.2.2</code>\" which advertises different DNS servers to clients."))
  382. for i, n in ipairs(s.children) do
  383. if n ~= ignore then
  384. n:depends("ignore", "")
  385. end
  386. end
  387. o = s:taboption("ipv6", ListValue, "ra", translate("Router Advertisement-Service"))
  388. o:value("", translate("disabled"))
  389. o:value("server", translate("server mode"))
  390. o:value("relay", translate("relay mode"))
  391. o:value("hybrid", translate("hybrid mode"))
  392. o = s:taboption("ipv6", ListValue, "dhcpv6", translate("DHCPv6-Service"))
  393. o:value("", translate("disabled"))
  394. o:value("server", translate("server mode"))
  395. o:value("relay", translate("relay mode"))
  396. o:value("hybrid", translate("hybrid mode"))
  397. o = s:taboption("ipv6", ListValue, "ndp", translate("NDP-Proxy"))
  398. o:value("", translate("disabled"))
  399. o:value("relay", translate("relay mode"))
  400. o:value("hybrid", translate("hybrid mode"))
  401. o = s:taboption("ipv6", ListValue, "ra_management", translate("DHCPv6-Mode"),
  402. translate("Default is stateless + stateful"))
  403. o:value("0", translate("stateless"))
  404. o:value("1", translate("stateless + stateful"))
  405. o:value("2", translate("stateful-only"))
  406. o:depends("dhcpv6", "server")
  407. o:depends("dhcpv6", "hybrid")
  408. o.default = "1"
  409. o = s:taboption("ipv6", Flag, "ra_default", translate("Always announce default router"),
  410. translate("Announce as default router even if no public prefix is available."))
  411. o:depends("ra", "server")
  412. o:depends("ra", "hybrid")
  413. s:taboption("ipv6", DynamicList, "dns", translate("Announced DNS servers"))
  414. s:taboption("ipv6", DynamicList, "domain", translate("Announced DNS domains"))
  415. else
  416. m2 = nil
  417. end
  418. end
  419. return m, m2