2005-04-17 02:20:36 +04:00
/*
* Spanning tree protocol ; interface code
* Linux ethernet bridge
*
* Authors :
* Lennert Buytenhek < buytenh @ gnu . org >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*/
# include <linux/kernel.h>
2005-10-26 02:04:59 +04:00
# include <linux/etherdevice.h>
2006-05-26 03:00:12 +04:00
# include <linux/rtnetlink.h>
2005-04-17 02:20:36 +04:00
# include "br_private.h"
# include "br_private_stp.h"
/* Port id is composed of priority and port number.
* NB : least significant bits of priority are dropped to
* make room for more ports .
*/
static inline port_id br_make_port_id ( __u8 priority , __u16 port_no )
{
2007-02-09 17:24:35 +03:00
return ( ( u16 ) priority < < BR_PORT_BITS )
2005-04-17 02:20:36 +04:00
| ( port_no & ( ( 1 < < BR_PORT_BITS ) - 1 ) ) ;
}
/* called under bridge lock */
void br_init_port ( struct net_bridge_port * p )
{
p - > port_id = br_make_port_id ( p - > priority , p - > port_no ) ;
br_become_designated_port ( p ) ;
p - > state = BR_STATE_BLOCKING ;
p - > topology_change_ack = 0 ;
p - > config_pending = 0 ;
}
/* called under bridge lock */
void br_stp_enable_bridge ( struct net_bridge * br )
{
struct net_bridge_port * p ;
spin_lock_bh ( & br - > lock ) ;
mod_timer ( & br - > hello_timer , jiffies + br - > hello_time ) ;
mod_timer ( & br - > gc_timer , jiffies + HZ / 10 ) ;
2007-02-09 17:24:35 +03:00
2005-04-17 02:20:36 +04:00
br_config_bpdu_generation ( br ) ;
list_for_each_entry ( p , & br - > port_list , list ) {
if ( ( p - > dev - > flags & IFF_UP ) & & netif_carrier_ok ( p - > dev ) )
br_stp_enable_port ( p ) ;
}
spin_unlock_bh ( & br - > lock ) ;
}
/* NO locks held */
void br_stp_disable_bridge ( struct net_bridge * br )
{
struct net_bridge_port * p ;
2006-02-15 12:47:48 +03:00
spin_lock_bh ( & br - > lock ) ;
2005-04-17 02:20:36 +04:00
list_for_each_entry ( p , & br - > port_list , list ) {
if ( p - > state ! = BR_STATE_DISABLED )
br_stp_disable_port ( p ) ;
}
br - > topology_change = 0 ;
br - > topology_change_detected = 0 ;
2006-02-15 12:47:48 +03:00
spin_unlock_bh ( & br - > lock ) ;
2005-04-17 02:20:36 +04:00
del_timer_sync ( & br - > hello_timer ) ;
del_timer_sync ( & br - > topology_change_timer ) ;
del_timer_sync ( & br - > tcn_timer ) ;
del_timer_sync ( & br - > gc_timer ) ;
}
/* called under bridge lock */
void br_stp_enable_port ( struct net_bridge_port * p )
{
br_init_port ( p ) ;
br_port_state_selection ( p - > br ) ;
2010-05-10 13:31:09 +04:00
br_log_state ( p ) ;
2005-04-17 02:20:36 +04:00
}
/* called under bridge lock */
void br_stp_disable_port ( struct net_bridge_port * p )
{
2010-05-10 13:31:09 +04:00
struct net_bridge * br = p - > br ;
2005-04-17 02:20:36 +04:00
int wasroot ;
2010-05-10 13:31:09 +04:00
br_log_state ( p ) ;
2005-04-17 02:20:36 +04:00
wasroot = br_is_root_bridge ( br ) ;
br_become_designated_port ( p ) ;
p - > state = BR_STATE_DISABLED ;
p - > topology_change_ack = 0 ;
p - > config_pending = 0 ;
del_timer ( & p - > message_age_timer ) ;
del_timer ( & p - > forward_delay_timer ) ;
del_timer ( & p - > hold_timer ) ;
2006-10-13 01:45:38 +04:00
br_fdb_delete_by_port ( br , p , 0 ) ;
2010-02-28 11:49:38 +03:00
br_multicast_disable_port ( p ) ;
2006-10-13 01:45:38 +04:00
2005-04-17 02:20:36 +04:00
br_configuration_update ( br ) ;
br_port_state_selection ( br ) ;
if ( br_is_root_bridge ( br ) & & ! wasroot )
br_become_root_bridge ( br ) ;
}
2007-03-22 00:22:44 +03:00
static void br_stp_start ( struct net_bridge * br )
{
int r ;
char * argv [ ] = { BR_STP_PROG , br - > dev - > name , " start " , NULL } ;
char * envp [ ] = { NULL } ;
2007-07-18 05:37:03 +04:00
r = call_usermodehelper ( BR_STP_PROG , argv , envp , UMH_WAIT_PROC ) ;
2007-03-22 00:22:44 +03:00
if ( r = = 0 ) {
br - > stp_enabled = BR_USER_STP ;
2010-05-10 13:31:09 +04:00
br_debug ( br , " userspace STP started \n " ) ;
2007-03-22 00:22:44 +03:00
} else {
br - > stp_enabled = BR_KERNEL_STP ;
2010-05-10 13:31:09 +04:00
br_debug ( br , " using kernel STP \n " ) ;
2007-03-22 00:22:44 +03:00
/* To start timers on any ports left in blocking */
spin_lock_bh ( & br - > lock ) ;
br_port_state_selection ( br ) ;
spin_unlock_bh ( & br - > lock ) ;
}
}
static void br_stp_stop ( struct net_bridge * br )
{
int r ;
char * argv [ ] = { BR_STP_PROG , br - > dev - > name , " stop " , NULL } ;
char * envp [ ] = { NULL } ;
if ( br - > stp_enabled = = BR_USER_STP ) {
r = call_usermodehelper ( BR_STP_PROG , argv , envp , 1 ) ;
2010-05-10 13:31:09 +04:00
br_info ( br , " userspace STP stopped, return code %d \n " , r ) ;
2007-03-22 00:22:44 +03:00
/* To start timers on any ports left in blocking */
spin_lock_bh ( & br - > lock ) ;
br_port_state_selection ( br ) ;
spin_unlock_bh ( & br - > lock ) ;
}
br - > stp_enabled = BR_NO_STP ;
}
void br_stp_set_enabled ( struct net_bridge * br , unsigned long val )
{
ASSERT_RTNL ( ) ;
if ( val ) {
if ( br - > stp_enabled = = BR_NO_STP )
br_stp_start ( br ) ;
} else {
if ( br - > stp_enabled ! = BR_NO_STP )
br_stp_stop ( br ) ;
}
}
2005-04-17 02:20:36 +04:00
/* called under bridge lock */
2005-12-22 05:51:49 +03:00
void br_stp_change_bridge_id ( struct net_bridge * br , const unsigned char * addr )
2005-04-17 02:20:36 +04:00
{
2007-04-17 23:31:24 +04:00
/* should be aligned on 2 bytes for compare_ether_addr() */
unsigned short oldaddr_aligned [ ETH_ALEN > > 1 ] ;
unsigned char * oldaddr = ( unsigned char * ) oldaddr_aligned ;
2005-04-17 02:20:36 +04:00
struct net_bridge_port * p ;
int wasroot ;
wasroot = br_is_root_bridge ( br ) ;
memcpy ( oldaddr , br - > bridge_id . addr , ETH_ALEN ) ;
memcpy ( br - > bridge_id . addr , addr , ETH_ALEN ) ;
memcpy ( br - > dev - > dev_addr , addr , ETH_ALEN ) ;
list_for_each_entry ( p , & br - > port_list , list ) {
2005-10-26 02:04:59 +04:00
if ( ! compare_ether_addr ( p - > designated_bridge . addr , oldaddr ) )
2005-04-17 02:20:36 +04:00
memcpy ( p - > designated_bridge . addr , addr , ETH_ALEN ) ;
2005-10-26 02:04:59 +04:00
if ( ! compare_ether_addr ( p - > designated_root . addr , oldaddr ) )
2005-04-17 02:20:36 +04:00
memcpy ( p - > designated_root . addr , addr , ETH_ALEN ) ;
}
br_configuration_update ( br ) ;
br_port_state_selection ( br ) ;
if ( br_is_root_bridge ( br ) & & ! wasroot )
br_become_root_bridge ( br ) ;
}
2007-04-17 23:31:24 +04:00
/* should be aligned on 2 bytes for compare_ether_addr() */
static const unsigned short br_mac_zero_aligned [ ETH_ALEN > > 1 ] ;
2005-04-17 02:20:36 +04:00
/* called under bridge lock */
void br_stp_recalculate_bridge_id ( struct net_bridge * br )
{
2007-04-17 23:31:24 +04:00
const unsigned char * br_mac_zero =
( const unsigned char * ) br_mac_zero_aligned ;
2005-04-17 02:20:36 +04:00
const unsigned char * addr = br_mac_zero ;
struct net_bridge_port * p ;
2008-06-18 03:10:06 +04:00
/* user has chosen a value so keep it */
if ( br - > flags & BR_SET_MAC_ADDR )
return ;
2005-04-17 02:20:36 +04:00
list_for_each_entry ( p , & br - > port_list , list ) {
if ( addr = = br_mac_zero | |
2006-01-04 01:35:54 +03:00
memcmp ( p - > dev - > dev_addr , addr , ETH_ALEN ) < 0 )
2005-04-17 02:20:36 +04:00
addr = p - > dev - > dev_addr ;
}
2005-10-26 02:04:59 +04:00
if ( compare_ether_addr ( br - > bridge_id . addr , addr ) )
2005-04-17 02:20:36 +04:00
br_stp_change_bridge_id ( br , addr ) ;
}
/* called under bridge lock */
void br_stp_set_bridge_priority ( struct net_bridge * br , u16 newprio )
{
struct net_bridge_port * p ;
int wasroot ;
wasroot = br_is_root_bridge ( br ) ;
list_for_each_entry ( p , & br - > port_list , list ) {
if ( p - > state ! = BR_STATE_DISABLED & &
br_is_designated_port ( p ) ) {
p - > designated_bridge . prio [ 0 ] = ( newprio > > 8 ) & 0xFF ;
p - > designated_bridge . prio [ 1 ] = newprio & 0xFF ;
}
}
br - > bridge_id . prio [ 0 ] = ( newprio > > 8 ) & 0xFF ;
br - > bridge_id . prio [ 1 ] = newprio & 0xFF ;
br_configuration_update ( br ) ;
br_port_state_selection ( br ) ;
if ( br_is_root_bridge ( br ) & & ! wasroot )
br_become_root_bridge ( br ) ;
}
/* called under bridge lock */
void br_stp_set_port_priority ( struct net_bridge_port * p , u8 newprio )
{
port_id new_port_id = br_make_port_id ( newprio , p - > port_no ) ;
if ( br_is_designated_port ( p ) )
p - > designated_port = new_port_id ;
p - > port_id = new_port_id ;
p - > priority = newprio ;
if ( ! memcmp ( & p - > br - > bridge_id , & p - > designated_bridge , 8 ) & &
p - > port_id < p - > designated_port ) {
br_become_designated_port ( p ) ;
br_port_state_selection ( p - > br ) ;
}
}
/* called under bridge lock */
void br_stp_set_path_cost ( struct net_bridge_port * p , u32 path_cost )
{
p - > path_cost = path_cost ;
br_configuration_update ( p - > br ) ;
br_port_state_selection ( p - > br ) ;
}
ssize_t br_show_bridge_id ( char * buf , const struct bridge_id * id )
{
return sprintf ( buf , " %.2x%.2x.%.2x%.2x%.2x%.2x%.2x%.2x \n " ,
id - > prio [ 0 ] , id - > prio [ 1 ] ,
id - > addr [ 0 ] , id - > addr [ 1 ] , id - > addr [ 2 ] ,
id - > addr [ 3 ] , id - > addr [ 4 ] , id - > addr [ 5 ] ) ;
}