vlan.lua 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. -- Copyright 2008 Steven Barth <steven@midlink.org>
  2. -- Copyright 2010-2011 Jo-Philipp Wich <jow@openwrt.org>
  3. -- Licensed to the public under the Apache License 2.0.
  4. m = Map("network", translate("Switch"), translate("The network ports on this device can be combined to several <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s in which computers can communicate directly with each other. <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s are often used to separate different network segments. Often there is by default one Uplink port for a connection to the next greater network like the internet and other ports for a local network."))
  5. local fs = require "nixio.fs"
  6. local switches = { }
  7. m.uci:foreach("network", "switch",
  8. function(x)
  9. local sid = x['.name']
  10. local switch_name = x.name or sid
  11. local has_vlan = nil
  12. local has_learn = nil
  13. local has_vlan4k = nil
  14. local has_jumbo3 = nil
  15. local has_mirror = nil
  16. local min_vid = 0
  17. local max_vid = 16
  18. local num_vlans = 16
  19. local cpu_port = tonumber(fs.readfile("/proc/switch/eth0/cpuport") or 5)
  20. local num_ports = cpu_port + 1
  21. local switch_title
  22. local enable_vlan4k = false
  23. -- Parse some common switch properties from swconfig help output.
  24. local swc = io.popen("swconfig dev %q help 2>/dev/null" % switch_name)
  25. if swc then
  26. local is_port_attr = false
  27. local is_vlan_attr = false
  28. while true do
  29. local line = swc:read("*l")
  30. if not line then break end
  31. if line:match("^%s+%-%-vlan") then
  32. is_vlan_attr = true
  33. elseif line:match("^%s+%-%-port") then
  34. is_vlan_attr = false
  35. is_port_attr = true
  36. elseif line:match("cpu @") then
  37. switch_title = line:match("^switch%d: %w+%((.-)%)")
  38. num_ports, cpu_port, num_vlans =
  39. line:match("ports: (%d+) %(cpu @ (%d+)%), vlans: (%d+)")
  40. num_ports = tonumber(num_ports) or 6
  41. num_vlans = tonumber(num_vlans) or 16
  42. cpu_port = tonumber(cpu_port) or 5
  43. min_vid = 1
  44. elseif line:match(": pvid") or line:match(": tag") or line:match(": vid") then
  45. if is_vlan_attr then has_vlan4k = line:match(": (%w+)") end
  46. elseif line:match(": enable_vlan4k") then
  47. enable_vlan4k = true
  48. elseif line:match(": enable_vlan") then
  49. has_vlan = "enable_vlan"
  50. elseif line:match(": enable_learning") then
  51. has_learn = "enable_learning"
  52. elseif line:match(": enable_mirror_rx") then
  53. has_mirror = "enable_mirror_rx"
  54. elseif line:match(": max_length") then
  55. has_jumbo3 = "max_length"
  56. end
  57. end
  58. swc:close()
  59. end
  60. -- Switch properties
  61. s = m:section(NamedSection, x['.name'], "switch",
  62. switch_title and translatef("Switch %q (%s)", switch_name, switch_title)
  63. or translatef("Switch %q", switch_name))
  64. s.addremove = false
  65. if has_vlan then
  66. s:option(Flag, has_vlan, translate("Enable VLAN functionality"))
  67. end
  68. if has_learn then
  69. x = s:option(Flag, has_learn, translate("Enable learning and aging"))
  70. x.default = x.enabled
  71. end
  72. if has_jumbo3 then
  73. x = s:option(Flag, has_jumbo3, translate("Enable Jumbo Frame passthrough"))
  74. x.enabled = "3"
  75. x.rmempty = true
  76. end
  77. -- Does this switch support port mirroring?
  78. if has_mirror then
  79. s:option(Flag, "enable_mirror_rx", translate("Enable mirroring of incoming packets"))
  80. s:option(Flag, "enable_mirror_tx", translate("Enable mirroring of outgoing packets"))
  81. local sp = s:option(ListValue, "mirror_source_port", translate("Mirror source port"))
  82. local mp = s:option(ListValue, "mirror_monitor_port", translate("Mirror monitor port"))
  83. local pt
  84. for pt = 0, num_ports - 1 do
  85. local name
  86. name = (pt == cpu_port) and translate("CPU") or translatef("Port %d", pt)
  87. sp:value(pt, name)
  88. mp:value(pt, name)
  89. end
  90. end
  91. -- VLAN table
  92. s = m:section(TypedSection, "switch_vlan",
  93. switch_title and translatef("VLANs on %q (%s)", switch_name, switch_title)
  94. or translatef("VLANs on %q", switch_name))
  95. s.template = "cbi/tblsection"
  96. s.addremove = true
  97. s.anonymous = true
  98. -- Filter by switch
  99. s.filter = function(self, section)
  100. local device = m:get(section, "device")
  101. return (device and device == switch_name)
  102. end
  103. -- Override cfgsections callback to enforce row ordering by vlan id.
  104. s.cfgsections = function(self)
  105. local osections = TypedSection.cfgsections(self)
  106. local sections = { }
  107. local section
  108. for _, section in luci.util.spairs(
  109. osections,
  110. function(a, b)
  111. return (tonumber(m:get(osections[a], has_vlan4k or "vlan")) or 9999)
  112. < (tonumber(m:get(osections[b], has_vlan4k or "vlan")) or 9999)
  113. end
  114. ) do
  115. sections[#sections+1] = section
  116. end
  117. return sections
  118. end
  119. -- When creating a new vlan, preset it with the highest found vid + 1.
  120. s.create = function(self, section, origin)
  121. -- Filter by switch
  122. if m:get(origin, "device") ~= switch_name then
  123. return
  124. end
  125. local sid = TypedSection.create(self, section)
  126. local max_nr = 0
  127. local max_id = 0
  128. m.uci:foreach("network", "switch_vlan",
  129. function(s)
  130. if s.device == switch_name then
  131. local nr = tonumber(s.vlan)
  132. local id = has_vlan4k and tonumber(s[has_vlan4k])
  133. if nr ~= nil and nr > max_nr then max_nr = nr end
  134. if id ~= nil and id > max_id then max_id = id end
  135. end
  136. end)
  137. m:set(sid, "device", switch_name)
  138. m:set(sid, "vlan", max_nr + 1)
  139. if has_vlan4k then
  140. m:set(sid, has_vlan4k, max_id + 1)
  141. end
  142. return sid
  143. end
  144. local port_opts = { }
  145. local untagged = { }
  146. -- Parse current tagging state from the "ports" option.
  147. local portvalue = function(self, section)
  148. local pt
  149. for pt in (m:get(section, "ports") or ""):gmatch("%w+") do
  150. local pc, tu = pt:match("^(%d+)([tu]*)")
  151. if pc == self.option then return (#tu > 0) and tu or "u" end
  152. end
  153. return ""
  154. end
  155. -- Validate port tagging. Ensure that a port is only untagged once,
  156. -- bail out if not.
  157. local portvalidate = function(self, value, section)
  158. -- ensure that the ports appears untagged only once
  159. if value == "u" then
  160. if not untagged[self.option] then
  161. untagged[self.option] = true
  162. elseif min_vid > 0 or tonumber(self.option) ~= cpu_port then -- enable multiple untagged cpu ports due to weird broadcom default setup
  163. return nil,
  164. translatef("Port %d is untagged in multiple VLANs!", tonumber(self.option) + 1)
  165. end
  166. end
  167. return value
  168. end
  169. local vid = s:option(Value, has_vlan4k or "vlan", "VLAN ID", "<div id='portstatus-%s'></div>" % switch_name)
  170. local mx_vid = has_vlan4k and 4094 or (num_vlans - 1)
  171. vid.rmempty = false
  172. vid.forcewrite = true
  173. vid.vlan_used = { }
  174. vid.datatype = "and(uinteger,range("..min_vid..","..mx_vid.."))"
  175. -- Validate user provided VLAN ID, make sure its within the bounds
  176. -- allowed by the switch.
  177. vid.validate = function(self, value, section)
  178. local v = tonumber(value)
  179. local m = has_vlan4k and 4094 or (num_vlans - 1)
  180. if v ~= nil and v >= min_vid and v <= m then
  181. if not self.vlan_used[v] then
  182. self.vlan_used[v] = true
  183. return value
  184. else
  185. return nil,
  186. translatef("Invalid VLAN ID given! Only unique IDs are allowed")
  187. end
  188. else
  189. return nil,
  190. translatef("Invalid VLAN ID given! Only IDs between %d and %d are allowed.", min_vid, m)
  191. end
  192. end
  193. -- When writing the "vid" or "vlan" option, serialize the port states
  194. -- as well and write them as "ports" option to uci.
  195. vid.write = function(self, section, value)
  196. local o
  197. local p = { }
  198. for _, o in ipairs(port_opts) do
  199. local v = o:formvalue(section)
  200. if v == "t" then
  201. p[#p+1] = o.option .. v
  202. elseif v == "u" then
  203. p[#p+1] = o.option
  204. end
  205. end
  206. if enable_vlan4k then
  207. m:set(sid, "enable_vlan4k", "1")
  208. end
  209. m:set(section, "ports", table.concat(p, " "))
  210. return Value.write(self, section, value)
  211. end
  212. -- Fallback to "vlan" option if "vid" option is supported but unset.
  213. vid.cfgvalue = function(self, section)
  214. return m:get(section, has_vlan4k or "vlan")
  215. or m:get(section, "vlan")
  216. end
  217. -- Build per-port off/untagged/tagged choice lists.
  218. local pt
  219. for pt = 0, num_ports - 1 do
  220. local title
  221. if pt == cpu_port then
  222. title = translate("CPU")
  223. else
  224. title = translatef("Port %d", pt)
  225. end
  226. local po = s:option(ListValue, tostring(pt), title)
  227. po:value("", translate("off"))
  228. po:value("u", translate("untagged"))
  229. po:value("t", translate("tagged"))
  230. po.cfgvalue = portvalue
  231. po.validate = portvalidate
  232. po.write = function() end
  233. port_opts[#port_opts+1] = po
  234. end
  235. switches[#switches+1] = switch_name
  236. end
  237. )
  238. -- Switch status template
  239. s = m:section(SimpleSection)
  240. s.template = "admin_network/switch_status"
  241. s.switches = switches
  242. return m