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