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