2008-06-08 08:14:31 +00:00
--[[
LuCI - Lua Configuration Interface
Copyright 2008 Steven Barth < steven @ midlink.org >
2011-05-20 11:49:09 +00:00
Copyright 2010 - 2011 Jo - Philipp Wich < xm @ subsignal.org >
2008-06-08 08:14:31 +00:00
Licensed under the Apache License , Version 2.0 ( the " License " ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : // www.apache . org / licenses / LICENSE - 2.0
$ Id $
] ] --
2010-10-12 23:12:30 +00:00
2009-10-31 15:54:11 +00:00
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. " ) )
2008-04-11 19:03:30 +00:00
2010-03-06 19:13:03 +00:00
m.uci : foreach ( " network " , " switch " ,
function ( x )
2011-10-09 22:28:14 +00:00
local sid = x [ ' .name ' ]
local switch_name = x.name or sid
2011-05-20 11:49:09 +00:00
local has_vlan = nil
2011-10-09 22:28:14 +00:00
local has_learn = nil
2010-10-12 23:12:30 +00:00
local has_vlan4k = nil
local has_ptpvid = nil
2011-05-20 11:56:42 +00:00
local has_jumbo3 = nil
2011-05-19 23:51:50 +00:00
local min_vid = 0
2010-10-12 23:12:30 +00:00
local max_vid = 16
local num_vlans = 16
2011-05-20 11:49:09 +00:00
local num_ports = 6
2010-10-12 23:12:30 +00:00
local cpu_port = 5
2011-01-10 18:40:35 +00:00
local enable_vlan4k = false
2010-10-14 23:38:54 +00:00
-- Parse some common switch properties from swconfig help output.
2010-10-12 23:12:30 +00:00
local swc = io.popen ( " swconfig dev %q help 2>/dev/null " % switch_name )
if swc then
local is_port_attr = false
local is_vlan_attr = false
while true do
local line = swc : read ( " *l " )
if not line then break end
if line : match ( " ^%s+%-%-vlan " ) then
is_vlan_attr = true
elseif line : match ( " ^%s+%-%-port " ) then
is_vlan_attr = false
is_port_attr = true
elseif line : match ( " ^Switch %d+: " ) then
num_ports , cpu_port , num_vlans =
line : match ( " ports: (%d+) %(cpu @ (%d+)%), vlans: (%d+) " )
2011-07-17 05:46:54 +00:00
num_ports = tonumber ( num_ports ) or 6
num_vlans = tonumber ( num_vlans ) or 16
cpu_port = tonumber ( cpu_port ) or 5
2011-05-20 11:49:09 +00:00
min_vid = 1
2010-10-12 23:12:30 +00:00
2010-10-14 23:38:54 +00:00
elseif line : match ( " : pvid " ) or line : match ( " : tag " ) or line : match ( " : vid " ) then
if is_vlan_attr then has_vlan4k = line : match ( " : (%w+) " ) end
if is_port_attr then has_ptpvid = line : match ( " : (%w+) " ) end
2010-10-12 23:12:30 +00:00
2011-07-17 08:28:56 +00:00
elseif line : match ( " : enable_vlan4k " ) then
enable_vlan4k = true
2011-05-20 11:49:09 +00:00
elseif line : match ( " : enable_vlan " ) then
has_vlan = " enable_vlan "
elseif line : match ( " : enable_learning " ) then
has_learn = " enable_learning "
2011-05-20 11:56:42 +00:00
elseif line : match ( " : max_length " ) then
has_jumbo3 = " max_length "
2010-10-12 23:12:30 +00:00
end
end
swc : close ( )
end
2008-04-11 19:03:30 +00:00
2010-10-14 23:38:54 +00:00
-- The PVID options (if any) are added to this table so that
-- section create below can add the just created vlan to the
-- choice list of the PVID options...
local pvid_opts = { }
-- This function re-reads all existing vlan ids and populates
-- PVID options choice lists
local function populate_pvids ( )
local vlan_ids = { }
m.uci : foreach ( " network " , " switch_vlan " ,
function ( s )
2011-10-09 22:28:14 +00:00
if s.device == switch_name then
local vid = s [ has_vlan4k or " vlan " ] or s [ " vlan " ]
if vid ~= nil then
vlan_ids [ # vlan_ids + 1 ] = vid
end
2010-10-14 23:38:54 +00:00
end
end )
local opt , vid
for _ , opt in ipairs ( pvid_opts ) do
opt : reset_values ( )
opt : value ( " " , translate ( " none " ) )
for _ , vid in luci.util . vspairs ( vlan_ids ) do
opt : value ( vid , translatef ( " VLAN %d " , tonumber ( vid ) ) )
end
end
end
2010-03-06 19:13:03 +00:00
-- Switch properties
2010-10-14 23:38:54 +00:00
s = m : section ( NamedSection , x [ ' .name ' ] , " switch " , translatef ( " Switch %q " , switch_name ) )
2010-03-06 19:13:03 +00:00
s.addremove = false
2008-04-11 19:03:30 +00:00
2011-05-20 11:49:09 +00:00
if has_vlan then
2011-07-17 05:46:54 +00:00
s : option ( Flag , has_vlan , translate ( " Enable VLAN functionality " ) )
2011-05-20 11:49:09 +00:00
end
2010-03-06 19:13:03 +00:00
2011-01-10 18:40:35 +00:00
if enable_vlan4k then
s : option ( Flag , " enable_vlan4k " , translate ( " Enable 4K VLANs " ) )
end
2011-05-20 11:49:09 +00:00
if has_learn then
x = s : option ( Flag , has_learn , translate ( " Enable learning and aging " ) )
x.default = x.enabled
end
2011-05-20 11:56:42 +00:00
if has_jumbo3 then
x = s : option ( Flag , has_jumbo3 , translate ( " Enable Jumbo Frame passthrough " ) )
x.enabled = " 3 "
x.rmempty = true
end
2010-03-06 19:13:03 +00:00
-- VLAN table
2010-10-14 23:38:54 +00:00
s = m : section ( TypedSection , " switch_vlan " , translatef ( " VLANs on %q " , switch_name ) )
2010-03-06 19:13:03 +00:00
s.template = " cbi/tblsection "
s.addremove = true
2010-10-12 23:12:30 +00:00
s.anonymous = true
2010-03-06 19:13:03 +00:00
2011-10-09 22:28:14 +00:00
-- Filter by switch
s.filter = function ( self , section )
local device = m : get ( section , " device " )
return ( device and device == switch_name )
end
2010-10-14 23:38:54 +00:00
-- Override cfgsections callback to enforce row ordering by vlan id.
2010-03-06 19:13:03 +00:00
s.cfgsections = function ( self )
local osections = TypedSection.cfgsections ( self )
local sections = { }
2010-10-12 23:12:30 +00:00
local section
2010-03-06 19:13:03 +00:00
for _ , section in luci.util . spairs (
osections ,
function ( a , b )
2011-10-09 22:28:14 +00:00
return ( tonumber ( m : get ( osections [ a ] , has_vlan4k or " vlan " ) ) or 9999 )
< ( tonumber ( m : get ( osections [ b ] , has_vlan4k or " vlan " ) ) or 9999 )
2010-03-06 19:13:03 +00:00
end
) do
sections [ # sections + 1 ] = section
end
return sections
end
2010-10-14 23:38:54 +00:00
-- When creating a new vlan, preset it with the highest found vid + 1.
-- Repopulate the PVID choice lists afterwards.
2011-10-09 22:28:14 +00:00
s.create = function ( self , section , origin )
-- Filter by switch
if m : get ( origin , " device " ) ~= switch_name then
return
end
2010-10-14 00:24:01 +00:00
local sid = TypedSection.create ( self , section )
local max_nr = 0
local max_id = 0
m.uci : foreach ( " network " , " switch_vlan " ,
function ( s )
2011-10-09 22:28:14 +00:00
if s.device == switch_name then
local nr = tonumber ( s.vlan )
local id = has_vlan4k and tonumber ( s [ has_vlan4k ] )
if nr ~= nil and nr > max_nr then max_nr = nr end
if id ~= nil and id > max_id then max_id = id end
end
2010-10-14 00:24:01 +00:00
end )
2011-07-22 13:56:02 +00:00
m.uci : set ( " network " , sid , " device " , switch_name )
2010-10-14 00:24:01 +00:00
m.uci : set ( " network " , sid , " vlan " , max_nr + 1 )
if has_vlan4k then
m.uci : set ( " network " , sid , has_vlan4k , max_id + 1 )
end
2010-10-14 23:38:54 +00:00
-- add newly created vlan to the pvid choice list
populate_pvids ( )
2010-10-14 00:24:01 +00:00
return sid
end
2010-10-14 23:38:54 +00:00
-- Repopulate PVId choice lists if a vlan gets removed.
s.remove = function ( self , section )
local rv = TypedSection.remove ( self , section )
-- repopulate pvid choices
populate_pvids ( )
return rv
end
2010-10-14 00:24:01 +00:00
2010-10-12 23:12:30 +00:00
local port_opts = { }
local untagged = { }
2010-10-14 23:38:54 +00:00
-- Parse current tagging state from the "ports" option.
2010-10-12 23:12:30 +00:00
local portvalue = function ( self , section )
local pt
2011-10-09 22:28:14 +00:00
for pt in ( m : get ( section , " ports " ) or " " ) : gmatch ( " %w+ " ) do
2010-10-12 23:12:30 +00:00
local pc , tu = pt : match ( " ^(%d+)([tu]*) " )
if pc == self.option then return ( # tu > 0 ) and tu or " u " end
end
return " "
end
2010-10-14 23:38:54 +00:00
-- Validate port tagging. Ensure that a port is only untagged once,
-- bail out if not.
2010-10-12 23:12:30 +00:00
local portvalidate = function ( self , value , section )
2010-10-14 00:24:01 +00:00
-- ensure that the ports appears untagged only once
2010-10-12 23:12:30 +00:00
if value == " u " then
if not untagged [ self.option ] then
untagged [ self.option ] = true
2011-05-20 11:49:09 +00:00
elseif min_vid > 0 or tonumber ( self.option ) ~= cpu_port then -- enable multiple untagged cpu ports due to weird broadcom default setup
2010-10-12 23:12:30 +00:00
return nil ,
translatef ( " Port %d is untagged in multiple VLANs! " , tonumber ( self.option ) + 1 )
end
2010-03-06 19:13:03 +00:00
end
2010-10-12 23:12:30 +00:00
return value
2010-03-06 19:13:03 +00:00
end
2010-10-12 23:12:30 +00:00
local vid = s : option ( Value , has_vlan4k or " vlan " , " VLAN ID " )
2010-03-06 19:13:03 +00:00
2010-10-14 00:24:01 +00:00
vid.rmempty = false
2010-10-30 02:30:49 +00:00
vid.forcewrite = true
2010-03-06 19:13:03 +00:00
2010-10-14 23:38:54 +00:00
-- Validate user provided VLAN ID, make sure its within the bounds
-- allowed by the switch.
2010-10-12 23:12:30 +00:00
vid.validate = function ( self , value , section )
local v = tonumber ( value )
local m = has_vlan4k and 4094 or ( num_vlans - 1 )
2011-05-19 12:22:18 +00:00
if v ~= nil and v >= min_vid and v <= m then
2010-10-12 23:12:30 +00:00
return value
else
return nil ,
2011-05-19 12:22:18 +00:00
translatef ( " Invalid VLAN ID given! Only IDs between %d and %d are allowed. " , min_vid , m )
2010-10-12 23:12:30 +00:00
end
2010-03-06 19:13:03 +00:00
end
2010-10-14 23:38:54 +00:00
-- When writing the "vid" or "vlan" option, serialize the port states
-- as well and write them as "ports" option to uci.
2010-10-14 00:24:01 +00:00
vid.write = function ( self , section , value )
2010-10-12 23:12:30 +00:00
local o
local p = { }
for _ , o in ipairs ( port_opts ) do
local v = o : formvalue ( section )
if v == " t " then
p [ # p + 1 ] = o.option .. v
elseif v == " u " then
p [ # p + 1 ] = o.option
end
end
m.uci : set ( " network " , section , " ports " , table.concat ( p , " " ) )
2010-10-14 00:24:01 +00:00
return Value.write ( self , section , value )
2010-03-06 19:13:03 +00:00
end
2010-10-14 23:38:54 +00:00
-- Fallback to "vlan" option if "vid" option is supported but unset.
vid.cfgvalue = function ( self , section )
2011-10-09 22:28:14 +00:00
return m : get ( section , has_vlan4k or " vlan " )
or m : get ( section , " vlan " )
2010-10-14 23:38:54 +00:00
end
2010-10-12 23:12:30 +00:00
2010-10-14 23:38:54 +00:00
-- Build per-port off/untagged/tagged choice lists.
2010-10-12 23:12:30 +00:00
local pt
for pt = 0 , num_ports - 1 do
2010-10-14 00:24:01 +00:00
local po = s : option ( ListValue , tostring ( pt ) ,
2010-10-14 23:38:54 +00:00
( pt == cpu_port ) and translate ( " CPU " ) or translatef ( " Port %d " , ( pt + 1 ) ) )
2010-10-12 23:12:30 +00:00
po : value ( " " , translate ( " off " ) )
po : value ( " u " % pt , translate ( " untagged " ) )
po : value ( " t " % pt , translate ( " tagged " ) )
po.cfgvalue = portvalue
po.validate = portvalidate
2010-10-30 02:30:49 +00:00
po.write = function ( ) end
2010-10-12 23:12:30 +00:00
port_opts [ # port_opts + 1 ] = po
end
2010-10-14 23:38:54 +00:00
-- Does this switch support PVIDs?
if has_ptpvid then
-- Spawn a "virtual" section. We just attach it to the global
-- switch section here, the overrides below take care of writing
-- the actual values to the correct uci sections.
s = m : section ( TypedSection , " switch " ,
translatef ( " Port PVIDs on %q " , switch_name ) ,
translate ( " Port <abbr title= \" Primary VLAN IDs \" >PVIDs</abbr> specify " ..
2010-11-17 01:57:53 +00:00
" the default VLAN ID added to received untagged frames. " ) )
2010-10-14 23:38:54 +00:00
s.template = " cbi/tblsection "
s.addremove = false
s.anonymous = true
2011-10-09 22:28:14 +00:00
-- Filter by switch
function s . filter ( self , section )
return ( m : get ( section , " name " ) == switch_name )
end
2010-10-14 23:38:54 +00:00
-- Build port list, store pointers to the option objects in the
-- pvid_opts array so that other callbacks can repopulate their
-- choice lists.
local pt
for pt = 0 , num_ports - 1 do
local po = s : option ( ListValue , tostring ( pt ) ,
( pt == cpu_port ) and translate ( " CPU " ) or translatef ( " Port %d " , ( pt + 1 ) ) )
-- When cbi queries the current config value for this post,
-- lookup the associated switch_port section (if any) and
-- return its "pvid" or "vlan" option value.
po.cfgvalue = function ( self , section )
local val
m.uci : foreach ( " network " , " switch_port " ,
function ( s )
if s.port == self.option then
val = s [ has_ptpvid ]
return false
end
end )
return val
end
-- On write, find the actual switch_port section associated
-- to this port and set the value there. Create a new
-- switch_port section for this port if there is none yet.
po.write = function ( self , section , value )
local found = false
m.uci : foreach ( " network " , " switch_port " ,
function ( s )
if s.port == self.option then
m.uci : set ( " network " , s [ ' .name ' ] , has_ptpvid , value )
found = true
return false
end
end )
if not found then
m.uci : section ( " network " , " switch_port " , nil , {
[ " port " ] = self.option ,
[ has_ptpvid ] = value
} )
end
end
-- If the user cleared the PVID value on this port, find
-- the associated switch_port section and clear it.
-- If the section does not contain any other unrelated
-- options (like led or blinkrate) then remove it completely,
-- else just clear out the "pvid" option.
po.remove = function ( self , section )
m.uci : foreach ( " network " , " switch_port " ,
function ( s )
if s.port == self.option then
local k , found
local empty = true
for k , _ in pairs ( s ) do
if k : sub ( 1 , 1 ) ~= " . " and k ~= " port " and k ~= has_ptpvid then
empty = false
break
end
end
if empty then
m.uci : delete ( " network " , s [ ' .name ' ] )
else
m.uci : delete ( " network " , s [ ' .name ' ] , has_ptpvid )
end
return false
end
end )
end
-- The referenced VLAN might just have been removed, simply
-- return "" (none) in this case to avoid triggering a
-- validation error.
po.validate = function ( ... )
return ListValue.validate ( ... ) or " "
end
pvid_opts [ # pvid_opts + 1 ] = po
end
populate_pvids ( )
end
2010-03-06 19:13:03 +00:00
end
)
return m