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