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