744ff661e3868bf93fc3e17e3cce9a5fb17823e5
[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 enable_vlan4k then
96 s:option(Flag, "enable_vlan4k", translate("Enable 4K VLANs"))
97 end
98
99 if has_learn then
100 x = s:option(Flag, has_learn, translate("Enable learning and aging"))
101 x.default = x.enabled
102 end
103
104 if has_jumbo3 then
105 x = s:option(Flag, has_jumbo3, translate("Enable Jumbo Frame passthrough"))
106 x.enabled = "3"
107 x.rmempty = true
108 end
109
110
111 -- VLAN table
112 s = m:section(TypedSection, "switch_vlan", translatef("VLANs on %q", switch_name))
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.uci:set("network", sid, "device", switch_name)
165 m.uci:set("network", sid, "vlan", max_nr + 1)
166
167 if has_vlan4k then
168 m.uci:set("network", 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")
205
206 vid.rmempty = false
207 vid.forcewrite = true
208 vid.vlan_used = { }
209
210 -- Validate user provided VLAN ID, make sure its within the bounds
211 -- allowed by the switch.
212 vid.validate = function(self, value, section)
213 local v = tonumber(value)
214 local m = has_vlan4k and 4094 or (num_vlans - 1)
215 if v ~= nil and v >= min_vid and v <= m then
216 if not self.vlan_used[v] then
217 self.vlan_used[v] = true
218 return value
219 else
220 return nil,
221 translatef("Invalid VLAN ID given! Only unique IDs are allowed")
222 end
223 else
224 return nil,
225 translatef("Invalid VLAN ID given! Only IDs between %d and %d are allowed.", min_vid, m)
226 end
227 end
228
229 -- When writing the "vid" or "vlan" option, serialize the port states
230 -- as well and write them as "ports" option to uci.
231 vid.write = function(self, section, value)
232 local o
233 local p = { }
234
235 for _, o in ipairs(port_opts) do
236 local v = o:formvalue(section)
237 if v == "t" then
238 p[#p+1] = o.option .. v
239 elseif v == "u" then
240 p[#p+1] = o.option
241 end
242 end
243
244 m.uci:set("network", section, "ports", table.concat(p, " "))
245 return Value.write(self, section, value)
246 end
247
248 -- Fallback to "vlan" option if "vid" option is supported but unset.
249 vid.cfgvalue = function(self, section)
250 return m:get(section, has_vlan4k or "vlan")
251 or m:get(section, "vlan")
252 end
253
254 -- Build per-port off/untagged/tagged choice lists.
255 local pt
256 local off = 1
257 for pt = 0, num_ports - 1 do
258 local title
259 if pt == cpu_port then
260 off = 0
261 title = translate("CPU")
262 else
263 title = translatef("Port %d", pt + off)
264 end
265
266 local po = s:option(ListValue, tostring(pt), title)
267
268 po:value("", translate("off"))
269 po:value("u", translate("untagged"))
270 po:value("t", translate("tagged"))
271
272 po.cfgvalue = portvalue
273 po.validate = portvalidate
274 po.write = function() end
275
276 port_opts[#port_opts+1] = po
277 end
278 end
279 )
280
281 return m