modules/admin-full: don't bother with 4k vlan / no 4k vlan nuisances, simply enable...
[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