modules/admin-full: also increase number of ports if cpu port is 8
[project/luci.git] / modules / admin-full / luasrc / model / cbi / admin_network / vlan.lua
1 --[[
2 LuCI - Lua Configuration Interface
3
4 Copyright 2008 Steven Barth <steven@midlink.org>
5 Copyright 2010-2011 Jo-Philipp Wich <xm@subsignal.org>
6
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
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 ]]--
14
15 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."))
16
17 local fs = require "nixio.fs"
18 local switches = { }
19
20 m.uci:foreach("network", "switch",
21 function(x)
22 local sid = x['.name']
23 local switch_name = x.name or sid
24 local has_vlan = nil
25 local has_learn = nil
26 local has_vlan4k = nil
27 local has_jumbo3 = nil
28 local min_vid = 0
29 local max_vid = 16
30 local num_vlans = 16
31 local cpu_port = tonumber(fs.readfile("/proc/switch/eth0/cpuport") or 5)
32 local num_ports = cpu_port + 1
33
34 local switch_title
35 local enable_vlan4k = false
36
37 -- Parse some common switch properties from swconfig help output.
38 local swc = io.popen("swconfig dev %q help 2>/dev/null" % switch_name)
39 if swc then
40
41 local is_port_attr = false
42 local is_vlan_attr = false
43
44 while true do
45 local line = swc:read("*l")
46 if not line then break end
47
48 if line:match("^%s+%-%-vlan") then
49 is_vlan_attr = true
50
51 elseif line:match("^%s+%-%-port") then
52 is_vlan_attr = false
53 is_port_attr = true
54
55 elseif line:match("cpu @") then
56 switch_title = line:match("^switch%d: %w+%((.-)%)")
57 num_ports, cpu_port, num_vlans =
58 line:match("ports: (%d+) %(cpu @ (%d+)%), vlans: (%d+)")
59
60 num_ports = tonumber(num_ports) or 6
61 num_vlans = tonumber(num_vlans) or 16
62 cpu_port = tonumber(cpu_port) or 5
63 min_vid = 1
64
65 elseif line:match(": pvid") or line:match(": tag") or line:match(": vid") then
66 if is_vlan_attr then has_vlan4k = line:match(": (%w+)") end
67
68 elseif line:match(": enable_vlan4k") then
69 enable_vlan4k = true
70
71 elseif line:match(": enable_vlan") then
72 has_vlan = "enable_vlan"
73
74 elseif line:match(": enable_learning") then
75 has_learn = "enable_learning"
76
77 elseif line:match(": max_length") then
78 has_jumbo3 = "max_length"
79 end
80 end
81
82 swc:close()
83 end
84
85
86 -- Switch properties
87 s = m:section(NamedSection, x['.name'], "switch",
88 switch_title and translatef("Switch %q (%s)", switch_name, switch_title)
89 or translatef("Switch %q", switch_name))
90
91 s.addremove = false
92
93 if has_vlan then
94 s:option(Flag, has_vlan, translate("Enable VLAN functionality"))
95 end
96
97 if has_learn then
98 x = s:option(Flag, has_learn, translate("Enable learning and aging"))
99 x.default = x.enabled
100 end
101
102 if has_jumbo3 then
103 x = s:option(Flag, has_jumbo3, translate("Enable Jumbo Frame passthrough"))
104 x.enabled = "3"
105 x.rmempty = true
106 end
107
108
109 -- VLAN table
110 s = m:section(TypedSection, "switch_vlan",
111 switch_title and translatef("VLANs on %q (%s)", switch_name, switch_title)
112 or translatef("VLANs on %q", switch_name))
113
114 s.template = "cbi/tblsection"
115 s.addremove = true
116 s.anonymous = true
117
118 -- Filter by switch
119 s.filter = function(self, section)
120 local device = m:get(section, "device")
121 return (device and device == switch_name)
122 end
123
124 -- Override cfgsections callback to enforce row ordering by vlan id.
125 s.cfgsections = function(self)
126 local osections = TypedSection.cfgsections(self)
127 local sections = { }
128 local section
129
130 for _, section in luci.util.spairs(
131 osections,
132 function(a, b)
133 return (tonumber(m:get(osections[a], has_vlan4k or "vlan")) or 9999)
134 < (tonumber(m:get(osections[b], has_vlan4k or "vlan")) or 9999)
135 end
136 ) do
137 sections[#sections+1] = section
138 end
139
140 return sections
141 end
142
143 -- When creating a new vlan, preset it with the highest found vid + 1.
144 s.create = function(self, section, origin)
145 -- Filter by switch
146 if m:get(origin, "device") ~= switch_name then
147 return
148 end
149
150 local sid = TypedSection.create(self, section)
151
152 local max_nr = 0
153 local max_id = 0
154
155 m.uci:foreach("network", "switch_vlan",
156 function(s)
157 if s.device == switch_name then
158 local nr = tonumber(s.vlan)
159 local id = has_vlan4k and tonumber(s[has_vlan4k])
160 if nr ~= nil and nr > max_nr then max_nr = nr end
161 if id ~= nil and id > max_id then max_id = id end
162 end
163 end)
164
165 m:set(sid, "device", switch_name)
166 m:set(sid, "vlan", max_nr + 1)
167
168 if has_vlan4k then
169 m:set(sid, has_vlan4k, max_id + 1)
170 end
171
172 return sid
173 end
174
175
176 local port_opts = { }
177 local untagged = { }
178
179 -- Parse current tagging state from the "ports" option.
180 local portvalue = function(self, section)
181 local pt
182 for pt in (m:get(section, "ports") or ""):gmatch("%w+") do
183 local pc, tu = pt:match("^(%d+)([tu]*)")
184 if pc == self.option then return (#tu > 0) and tu or "u" end
185 end
186 return ""
187 end
188
189 -- Validate port tagging. Ensure that a port is only untagged once,
190 -- bail out if not.
191 local portvalidate = function(self, value, section)
192 -- ensure that the ports appears untagged only once
193 if value == "u" then
194 if not untagged[self.option] then
195 untagged[self.option] = true
196 elseif min_vid > 0 or tonumber(self.option) ~= cpu_port then -- enable multiple untagged cpu ports due to weird broadcom default setup
197 return nil,
198 translatef("Port %d is untagged in multiple VLANs!", tonumber(self.option) + 1)
199 end
200 end
201 return value
202 end
203
204
205 local vid = s:option(Value, has_vlan4k or "vlan", "VLAN ID", "<div id='portstatus-%s'></div>" % switch_name)
206 local mx_vid = has_vlan4k and 4094 or (num_vlans - 1)
207
208 vid.rmempty = false
209 vid.forcewrite = true
210 vid.vlan_used = { }
211 vid.datatype = "and(uinteger,range("..min_vid..","..mx_vid.."))"
212
213 -- Validate user provided VLAN ID, make sure its within the bounds
214 -- allowed by the switch.
215 vid.validate = function(self, value, section)
216 local v = tonumber(value)
217 local m = has_vlan4k and 4094 or (num_vlans - 1)
218 if v ~= nil and v >= min_vid and v <= m then
219 if not self.vlan_used[v] then
220 self.vlan_used[v] = true
221 return value
222 else
223 return nil,
224 translatef("Invalid VLAN ID given! Only unique IDs are allowed")
225 end
226 else
227 return nil,
228 translatef("Invalid VLAN ID given! Only IDs between %d and %d are allowed.", min_vid, m)
229 end
230 end
231
232 -- When writing the "vid" or "vlan" option, serialize the port states
233 -- as well and write them as "ports" option to uci.
234 vid.write = function(self, section, value)
235 local o
236 local p = { }
237
238 for _, o in ipairs(port_opts) do
239 local v = o:formvalue(section)
240 if v == "t" then
241 p[#p+1] = o.option .. v
242 elseif v == "u" then
243 p[#p+1] = o.option
244 end
245 end
246
247 if enable_vlan4k then
248 m:set(sid, "enable_vlan4k", "1")
249 end
250
251 m:set(section, "ports", table.concat(p, " "))
252 return Value.write(self, section, value)
253 end
254
255 -- Fallback to "vlan" option if "vid" option is supported but unset.
256 vid.cfgvalue = function(self, section)
257 return m:get(section, has_vlan4k or "vlan")
258 or m:get(section, "vlan")
259 end
260
261 -- Build per-port off/untagged/tagged choice lists.
262 local pt
263 for pt = 0, num_ports - 1 do
264 local title
265 if pt == cpu_port then
266 title = translate("CPU")
267 else
268 title = translatef("Port %d", pt)
269 end
270
271 local po = s:option(ListValue, tostring(pt), title)
272
273 po:value("", translate("off"))
274 po:value("u", translate("untagged"))
275 po:value("t", translate("tagged"))
276
277 po.cfgvalue = portvalue
278 po.validate = portvalidate
279 po.write = function() end
280
281 port_opts[#port_opts+1] = po
282 end
283
284 switches[#switches+1] = switch_name
285 end
286 )
287
288 -- Switch status template
289 s = m:section(SimpleSection)
290 s.template = "admin_network/switch_status"
291 s.switches = switches
292
293 return m