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