2018-01-23 14:36:03 +00:00
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2010-2011 Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
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. " ) )
local fs = require " nixio.fs "
2018-05-25 12:36:55 +00:00
local ut = require " luci.util "
2018-01-23 14:36:03 +00:00
local nw = require " luci.model.network "
local switches = { }
nw.init ( m.uci )
local topologies = nw : get_switch_topologies ( ) or { }
local update_interfaces = function ( old_ifname , new_ifname )
local info = { }
m.uci : foreach ( " network " , " interface " , function ( section )
2019-05-28 19:51:29 +00:00
local old_ifnames = section.ifname
2018-01-23 14:36:03 +00:00
local new_ifnames = { }
local cur_ifname
local changed = false
for cur_ifname in luci.util . imatch ( old_ifnames ) do
if cur_ifname == old_ifname then
new_ifnames [ # new_ifnames + 1 ] = new_ifname
changed = true
else
new_ifnames [ # new_ifnames + 1 ] = cur_ifname
end
end
if changed then
m.uci : set ( " network " , section [ " .name " ] , " ifname " , table.concat ( new_ifnames , " " ) )
info [ # info + 1 ] = translatef ( " Interface %q device auto-migrated from %q to %q. " ,
section [ " .name " ] , old_ifname , new_ifname )
end
end )
if # info > 0 then
m.message = ( m.message and m.message .. " \n " or " " ) .. table.concat ( info , " \n " )
end
end
2019-05-28 19:51:29 +00:00
local vlan_already_created
2018-01-23 14:36:03 +00:00
m.uci : foreach ( " network " , " switch " ,
function ( x )
local sid = x [ ' .name ' ]
local switch_name = x.name or sid
local has_vlan = nil
local has_learn = nil
local has_vlan4k = nil
local has_jumbo3 = nil
local has_mirror = nil
local min_vid = 0
local max_vid = 16
local num_vlans = 16
local switch_title
local enable_vlan4k = false
local topo = topologies [ switch_name ]
if not topo then
m.message = translatef ( " Switch %q has an unknown topology - the VLAN settings might not be accurate. " , switch_name )
topo = {
ports = {
{ num = 0 , label = " Port 1 " } ,
{ num = 1 , label = " Port 2 " } ,
{ num = 2 , label = " Port 3 " } ,
{ num = 3 , label = " Port 4 " } ,
{ num = 4 , label = " Port 5 " } ,
{ num = 5 , label = " CPU (eth0) " , tagged = false }
}
}
end
-- Parse some common switch properties from swconfig help output.
2018-05-25 12:36:55 +00:00
local swc = io.popen ( " swconfig dev %s help 2>/dev/null " % ut.shellquote ( switch_name ) )
2018-01-23 14:36:03 +00:00
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 ( " cpu @ " ) then
switch_title = line : match ( " ^switch%d: %w+%((.-)%) " )
num_vlans = tonumber ( line : match ( " vlans: (%d+) " ) ) or 16
min_vid = 1
elseif line : match ( " : pvid " ) or line : match ( " : tag " ) or line : match ( " : vid " ) then
if is_vlan_attr then has_vlan4k = line : match ( " : (%w+) " ) end
elseif line : match ( " : enable_vlan4k " ) then
enable_vlan4k = true
elseif line : match ( " : enable_vlan " ) then
has_vlan = " enable_vlan "
elseif line : match ( " : enable_learning " ) then
has_learn = " enable_learning "
elseif line : match ( " : enable_mirror_rx " ) then
has_mirror = " enable_mirror_rx "
elseif line : match ( " : max_length " ) then
has_jumbo3 = " max_length "
end
end
swc : close ( )
end
-- Switch properties
s = m : section ( NamedSection , x [ ' .name ' ] , " switch " ,
switch_title and translatef ( " Switch %q (%s) " , switch_name , switch_title )
or translatef ( " Switch %q " , switch_name ) )
s.addremove = false
if has_vlan then
s : option ( Flag , has_vlan , translate ( " Enable VLAN functionality " ) )
end
if has_learn then
x = s : option ( Flag , has_learn , translate ( " Enable learning and aging " ) )
x.default = x.enabled
end
if has_jumbo3 then
x = s : option ( Flag , has_jumbo3 , translate ( " Enable Jumbo Frame passthrough " ) )
x.enabled = " 3 "
x.rmempty = true
end
-- Does this switch support port mirroring?
if has_mirror then
s : option ( Flag , " enable_mirror_rx " , translate ( " Enable mirroring of incoming packets " ) )
s : option ( Flag , " enable_mirror_tx " , translate ( " Enable mirroring of outgoing packets " ) )
local sp = s : option ( ListValue , " mirror_source_port " , translate ( " Mirror source port " ) )
local mp = s : option ( ListValue , " mirror_monitor_port " , translate ( " Mirror monitor port " ) )
sp : depends ( " enable_mirror_tx " , " 1 " )
sp : depends ( " enable_mirror_rx " , " 1 " )
mp : depends ( " enable_mirror_tx " , " 1 " )
mp : depends ( " enable_mirror_rx " , " 1 " )
local _ , pt
for _ , pt in ipairs ( topo.ports ) do
sp : value ( pt.num , pt.label )
mp : value ( pt.num , pt.label )
end
end
-- VLAN table
s = m : section ( TypedSection , " switch_vlan " ,
switch_title and translatef ( " VLANs on %q (%s) " , switch_name , switch_title )
or translatef ( " VLANs on %q " , switch_name ) )
s.template = " cbi/tblsection "
s.addremove = true
s.anonymous = true
-- Filter by switch
s.filter = function ( self , section )
local device = m : get ( section , " device " )
return ( device and device == switch_name )
end
-- Override cfgsections callback to enforce row ordering by vlan id.
s.cfgsections = function ( self )
local osections = TypedSection.cfgsections ( self )
local sections = { }
local section
for _ , section in luci.util . spairs (
osections ,
function ( a , b )
return ( tonumber ( m : get ( osections [ a ] , has_vlan4k or " vlan " ) ) or 9999 )
< ( tonumber ( m : get ( osections [ b ] , has_vlan4k or " vlan " ) ) or 9999 )
end
) do
sections [ # sections + 1 ] = section
end
return sections
end
-- When creating a new vlan, preset it with the highest found vid + 1.
s.create = function ( self , section , origin )
2019-05-28 19:51:29 +00:00
-- VLAN has already been created for another switch
if vlan_already_created then
return
-- VLAN add button was pressed in an empty VLAN section so only
-- accept the create event if our switch is without existing VLANs
elseif origin == " " then
local is_empty_switch = true
m.uci : foreach ( " network " , " switch_vlan " ,
function ( s )
if s.device == switch_name then
is_empty_switch = false
return false
end
end )
if not is_empty_switch then
return
end
-- VLAN was created for another switch
elseif m : get ( origin , " device " ) ~= switch_name then
2018-01-23 14:36:03 +00:00
return
end
local sid = TypedSection.create ( self , section )
local max_nr = 0
local max_id = 0
m.uci : foreach ( " network " , " switch_vlan " ,
function ( s )
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
end )
m : set ( sid , " device " , switch_name )
m : set ( sid , " vlan " , max_nr + 1 )
if has_vlan4k then
m : set ( sid , has_vlan4k , max_id + 1 )
end
2019-05-28 19:51:29 +00:00
vlan_already_created = true
2018-01-23 14:36:03 +00:00
return sid
end
local port_opts = { }
local untagged = { }
-- Parse current tagging state from the "ports" option.
local portvalue = function ( self , section )
local pt
for pt in ( m : get ( section , " ports " ) or " " ) : gmatch ( " %w+ " ) do
local pc , tu = pt : match ( " ^(%d+)([tu]*) " )
if pc == self.option then return ( # tu > 0 ) and tu or " u " end
end
return " "
end
-- Validate port tagging. Ensure that a port is only untagged once,
-- bail out if not.
local portvalidate = function ( self , value , section )
-- ensure that the ports appears untagged only once
if value == " u " then
if not untagged [ self.option ] then
untagged [ self.option ] = true
else
return nil ,
translatef ( " %s is untagged in multiple VLANs! " , self.title )
end
end
return value
end
2018-07-03 14:03:20 +00:00
local vid = s : option ( Value , has_vlan4k or " vlan " , " VLAN ID " )
2018-01-23 14:36:03 +00:00
local mx_vid = has_vlan4k and 4094 or ( num_vlans - 1 )
vid.rmempty = false
vid.forcewrite = true
vid.vlan_used = { }
vid.datatype = " and(uinteger,range( " .. min_vid .. " , " .. mx_vid .. " )) "
-- Validate user provided VLAN ID, make sure its within the bounds
-- allowed by the switch.
vid.validate = function ( self , value , section )
local v = tonumber ( value )
local m = has_vlan4k and 4094 or ( num_vlans - 1 )
if v ~= nil and v >= min_vid and v <= m then
if not self.vlan_used [ v ] then
self.vlan_used [ v ] = true
return value
else
return nil ,
translatef ( " Invalid VLAN ID given! Only unique IDs are allowed " )
end
else
return nil ,
translatef ( " Invalid VLAN ID given! Only IDs between %d and %d are allowed. " , min_vid , m )
end
end
-- When writing the "vid" or "vlan" option, serialize the port states
-- as well and write them as "ports" option to uci.
vid.write = function ( self , section , new_vid )
local o
local p = { }
for _ , o in ipairs ( port_opts ) do
local new_tag = o : formvalue ( section )
if new_tag == " t " then
p [ # p + 1 ] = o.option .. new_tag
elseif new_tag == " u " then
p [ # p + 1 ] = o.option
end
if o.info and o.info . device then
local old_tag = o : cfgvalue ( section )
local old_vid = self : cfgvalue ( section )
if old_tag ~= new_tag or old_vid ~= new_vid then
local old_ifname = ( old_tag == " u " ) and o.info . device
or " %s.%s " % { o.info . device , old_vid }
local new_ifname = ( new_tag == " u " ) and o.info . device
or " %s.%s " % { o.info . device , new_vid }
if old_ifname ~= new_ifname then
update_interfaces ( old_ifname , new_ifname )
end
end
end
end
if enable_vlan4k then
m : set ( sid , " enable_vlan4k " , " 1 " )
end
m : set ( section , " ports " , table.concat ( p , " " ) )
return Value.write ( self , section , new_vid )
end
-- Fallback to "vlan" option if "vid" option is supported but unset.
vid.cfgvalue = function ( self , section )
return m : get ( section , has_vlan4k or " vlan " )
or m : get ( section , " vlan " )
end
local _ , pt
for _ , pt in ipairs ( topo.ports ) do
2018-07-03 14:03:20 +00:00
local po = s : option ( ListValue , tostring ( pt.num ) , pt.label )
2018-01-23 14:36:03 +00:00
po : value ( " " , translate ( " off " ) )
if not pt.tagged then
po : value ( " u " , translate ( " untagged " ) )
end
po : value ( " t " , translate ( " tagged " ) )
po.cfgvalue = portvalue
po.validate = portvalidate
po.write = function ( ) end
po.info = pt
port_opts [ # port_opts + 1 ] = po
end
table.sort ( port_opts , function ( a , b ) return a.option < b.option end )
switches [ # switches + 1 ] = switch_name
end
)
-- Switch status template
s = m : section ( SimpleSection )
s.template = " admin_network/switch_status "
s.switches = switches
return m