Globally reduce copyright headers
[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 <xm@subsignal.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                         local pt
111                         for pt = 0, num_ports - 1 do
112                                 local name
113
114                                 name = (pt == cpu_port) and translate("CPU") or translatef("Port %d", pt)
115
116                                 sp:value(pt, name)
117                                 mp:value(pt, name)
118                         end
119                 end
120
121                 -- VLAN table
122                 s = m:section(TypedSection, "switch_vlan",
123                         switch_title and translatef("VLANs on %q (%s)", switch_name, switch_title)
124                                                   or translatef("VLANs on %q", switch_name))
125
126                 s.template = "cbi/tblsection"
127                 s.addremove = true
128                 s.anonymous = true
129
130                 -- Filter by switch
131                 s.filter = function(self, section)
132                         local device = m:get(section, "device")
133                         return (device and device == switch_name)
134                 end
135
136                 -- Override cfgsections callback to enforce row ordering by vlan id.
137                 s.cfgsections = function(self)
138                         local osections = TypedSection.cfgsections(self)
139                         local sections = { }
140                         local section
141
142                         for _, section in luci.util.spairs(
143                                 osections,
144                                 function(a, b)
145                                         return (tonumber(m:get(osections[a], has_vlan4k or "vlan")) or 9999)
146                                                 <  (tonumber(m:get(osections[b], has_vlan4k or "vlan")) or 9999)
147                                 end
148                         ) do
149                                 sections[#sections+1] = section
150                         end
151
152                         return sections
153                 end
154
155                 -- When creating a new vlan, preset it with the highest found vid + 1.
156                 s.create = function(self, section, origin)
157                         -- Filter by switch
158                         if m:get(origin, "device") ~= switch_name then
159                                 return
160                         end
161
162                         local sid = TypedSection.create(self, section)
163
164                         local max_nr = 0
165                         local max_id = 0
166
167                         m.uci:foreach("network", "switch_vlan",
168                                 function(s)
169                                         if s.device == switch_name then
170                                                 local nr = tonumber(s.vlan)
171                                                 local id = has_vlan4k and tonumber(s[has_vlan4k])
172                                                 if nr ~= nil and nr > max_nr then max_nr = nr end
173                                                 if id ~= nil and id > max_id then max_id = id end
174                                         end
175                                 end)
176
177                         m:set(sid, "device", switch_name)
178                         m:set(sid, "vlan", max_nr + 1)
179
180                         if has_vlan4k then
181                                 m:set(sid, has_vlan4k, max_id + 1)
182                         end
183
184                         return sid
185                 end
186
187
188                 local port_opts = { }
189                 local untagged  = { }
190
191                 -- Parse current tagging state from the "ports" option.
192                 local portvalue = function(self, section)
193                         local pt
194                         for pt in (m:get(section, "ports") or ""):gmatch("%w+") do
195                                 local pc, tu = pt:match("^(%d+)([tu]*)")
196                                 if pc == self.option then return (#tu > 0) and tu or "u" end
197                         end
198                         return ""
199                 end
200
201                 -- Validate port tagging. Ensure that a port is only untagged once,
202                 -- bail out if not.
203                 local portvalidate = function(self, value, section)
204                         -- ensure that the ports appears untagged only once
205                         if value == "u" then
206                                 if not untagged[self.option] then
207                                         untagged[self.option] = true
208                                 elseif min_vid > 0 or tonumber(self.option) ~= cpu_port then -- enable multiple untagged cpu ports due to weird broadcom default setup
209                                         return nil,
210                                                 translatef("Port %d is untagged in multiple VLANs!", tonumber(self.option) + 1)
211                                 end
212                         end
213                         return value
214                 end
215
216
217                 local vid = s:option(Value, has_vlan4k or "vlan", "VLAN ID", "<div id='portstatus-%s'></div>" % switch_name)
218                 local mx_vid = has_vlan4k and 4094 or (num_vlans - 1)
219
220                 vid.rmempty = false
221                 vid.forcewrite = true
222                 vid.vlan_used = { }
223                 vid.datatype = "and(uinteger,range("..min_vid..","..mx_vid.."))"
224
225                 -- Validate user provided VLAN ID, make sure its within the bounds
226                 -- allowed by the switch.
227                 vid.validate = function(self, value, section)
228                         local v = tonumber(value)
229                         local m = has_vlan4k and 4094 or (num_vlans - 1)
230                         if v ~= nil and v >= min_vid and v <= m then
231                                 if not self.vlan_used[v] then
232                                         self.vlan_used[v] = true
233                                         return value
234                                 else
235                                         return nil,
236                                                 translatef("Invalid VLAN ID given! Only unique IDs are allowed")
237                                 end
238                         else
239                                 return nil,
240                                         translatef("Invalid VLAN ID given! Only IDs between %d and %d are allowed.", min_vid, m)
241                         end
242                 end
243
244                 -- When writing the "vid" or "vlan" option, serialize the port states
245                 -- as well and write them as "ports" option to uci.
246                 vid.write = function(self, section, value)
247                         local o
248                         local p = { }
249
250                         for _, o in ipairs(port_opts) do
251                                 local v = o:formvalue(section)
252                                 if v == "t" then
253                                         p[#p+1] = o.option .. v
254                                 elseif v == "u" then
255                                         p[#p+1] = o.option
256                                 end
257                         end
258
259                         if enable_vlan4k then
260                                 m:set(sid, "enable_vlan4k", "1")
261                         end
262
263                         m:set(section, "ports", table.concat(p, " "))
264                         return Value.write(self, section, value)
265                 end
266
267                 -- Fallback to "vlan" option if "vid" option is supported but unset.
268                 vid.cfgvalue = function(self, section)
269                         return m:get(section, has_vlan4k or "vlan")
270                                 or m:get(section, "vlan")
271                 end
272
273                 -- Build per-port off/untagged/tagged choice lists.
274                 local pt
275                 for pt = 0, num_ports - 1 do
276                         local title
277                         if pt == cpu_port then
278                                 title = translate("CPU")
279                         else
280                                 title = translatef("Port %d", pt)
281                         end
282
283                         local po = s:option(ListValue, tostring(pt), title)
284
285                         po:value("",  translate("off"))
286                         po:value("u", translate("untagged"))
287                         po:value("t", translate("tagged"))
288
289                         po.cfgvalue = portvalue
290                         po.validate = portvalidate
291                         po.write    = function() end
292
293                         port_opts[#port_opts+1] = po
294                 end
295
296                 switches[#switches+1] = switch_name
297         end
298 )
299
300 -- Switch status template
301 s = m:section(SimpleSection)
302 s.template = "admin_network/switch_status"
303 s.switches = switches
304
305 return m