2005-04-17 02:20:36 +04:00
/*
* Spanning tree protocol ; BPDU handling
* Linux ethernet bridge
*
* Authors :
* Lennert Buytenhek < buytenh @ gnu . org >
*
* $ Id : br_stp_bpdu . c , v 1.3 2001 / 11 / 10 02 : 35 : 25 davem Exp $
*
* 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>
# include <linux/netfilter_bridge.h>
2006-03-21 09:59:06 +03:00
# include <linux/etherdevice.h>
# include <linux/llc.h>
2006-03-21 09:59:49 +03:00
# include <net/llc.h>
2006-03-21 09:59:06 +03:00
# include <net/llc_pdu.h>
2006-03-24 08:39:47 +03:00
# include <asm/unaligned.h>
2005-04-17 02:20:36 +04:00
# include "br_private.h"
# include "br_private_stp.h"
2006-03-21 09:58:49 +03:00
# define STP_HZ 256
2005-04-17 02:20:36 +04:00
2006-03-21 09:59:49 +03:00
# define LLC_RESERVE sizeof(struct llc_pdu_un)
static void br_send_bpdu ( struct net_bridge_port * p ,
2007-02-09 17:24:35 +03:00
const unsigned char * data , int length )
2005-04-17 02:20:36 +04:00
{
struct sk_buff * skb ;
if ( ! p - > br - > stp_enabled )
return ;
2006-03-21 09:59:49 +03:00
skb = dev_alloc_skb ( length + LLC_RESERVE ) ;
if ( ! skb )
2005-04-17 02:20:36 +04:00
return ;
2006-03-21 09:59:49 +03:00
skb - > dev = p - > dev ;
2005-04-17 02:20:36 +04:00
skb - > protocol = htons ( ETH_P_802_2 ) ;
2006-03-21 09:59:49 +03:00
skb_reserve ( skb , LLC_RESERVE ) ;
memcpy ( __skb_put ( skb , length ) , data , length ) ;
llc_pdu_header_init ( skb , LLC_PDU_TYPE_U , LLC_SAP_BSPAN ,
LLC_SAP_BSPAN , LLC_PDU_CMD ) ;
llc_pdu_init_as_ui_cmd ( skb ) ;
llc_mac_hdr_init ( skb , p - > dev - > dev_addr , p - > br - > group_addr ) ;
2005-04-17 02:20:36 +04:00
NF_HOOK ( PF_BRIDGE , NF_BR_LOCAL_OUT , skb , NULL , skb - > dev ,
dev_queue_xmit ) ;
}
2006-03-21 09:58:49 +03:00
static inline void br_set_ticks ( unsigned char * dest , int j )
2005-04-17 02:20:36 +04:00
{
2006-03-21 09:58:49 +03:00
unsigned long ticks = ( STP_HZ * j ) / HZ ;
2005-04-17 02:20:36 +04:00
2006-03-24 08:39:47 +03:00
put_unaligned ( htons ( ticks ) , ( __be16 * ) dest ) ;
2005-04-17 02:20:36 +04:00
}
2006-03-21 09:58:49 +03:00
static inline int br_get_ticks ( const unsigned char * src )
2005-04-17 02:20:36 +04:00
{
2006-03-24 08:39:47 +03:00
unsigned long ticks = ntohs ( get_unaligned ( ( __be16 * ) src ) ) ;
2006-03-21 09:58:49 +03:00
return ( ticks * HZ + STP_HZ - 1 ) / STP_HZ ;
2005-04-17 02:20:36 +04:00
}
/* called under bridge lock */
void br_send_config_bpdu ( struct net_bridge_port * p , struct br_config_bpdu * bpdu )
{
2006-03-21 09:59:49 +03:00
unsigned char buf [ 35 ] ;
buf [ 0 ] = 0 ;
buf [ 1 ] = 0 ;
buf [ 2 ] = 0 ;
buf [ 3 ] = BPDU_TYPE_CONFIG ;
buf [ 4 ] = ( bpdu - > topology_change ? 0x01 : 0 ) |
2005-04-17 02:20:36 +04:00
( bpdu - > topology_change_ack ? 0x80 : 0 ) ;
2006-03-21 09:59:49 +03:00
buf [ 5 ] = bpdu - > root . prio [ 0 ] ;
buf [ 6 ] = bpdu - > root . prio [ 1 ] ;
buf [ 7 ] = bpdu - > root . addr [ 0 ] ;
buf [ 8 ] = bpdu - > root . addr [ 1 ] ;
buf [ 9 ] = bpdu - > root . addr [ 2 ] ;
buf [ 10 ] = bpdu - > root . addr [ 3 ] ;
buf [ 11 ] = bpdu - > root . addr [ 4 ] ;
buf [ 12 ] = bpdu - > root . addr [ 5 ] ;
buf [ 13 ] = ( bpdu - > root_path_cost > > 24 ) & 0xFF ;
buf [ 14 ] = ( bpdu - > root_path_cost > > 16 ) & 0xFF ;
buf [ 15 ] = ( bpdu - > root_path_cost > > 8 ) & 0xFF ;
buf [ 16 ] = bpdu - > root_path_cost & 0xFF ;
buf [ 17 ] = bpdu - > bridge_id . prio [ 0 ] ;
buf [ 18 ] = bpdu - > bridge_id . prio [ 1 ] ;
buf [ 19 ] = bpdu - > bridge_id . addr [ 0 ] ;
buf [ 20 ] = bpdu - > bridge_id . addr [ 1 ] ;
buf [ 21 ] = bpdu - > bridge_id . addr [ 2 ] ;
buf [ 22 ] = bpdu - > bridge_id . addr [ 3 ] ;
buf [ 23 ] = bpdu - > bridge_id . addr [ 4 ] ;
buf [ 24 ] = bpdu - > bridge_id . addr [ 5 ] ;
buf [ 25 ] = ( bpdu - > port_id > > 8 ) & 0xFF ;
buf [ 26 ] = bpdu - > port_id & 0xFF ;
br_set_ticks ( buf + 27 , bpdu - > message_age ) ;
br_set_ticks ( buf + 29 , bpdu - > max_age ) ;
br_set_ticks ( buf + 31 , bpdu - > hello_time ) ;
br_set_ticks ( buf + 33 , bpdu - > forward_delay ) ;
br_send_bpdu ( p , buf , 35 ) ;
2005-04-17 02:20:36 +04:00
}
/* called under bridge lock */
void br_send_tcn_bpdu ( struct net_bridge_port * p )
{
2006-03-21 09:59:49 +03:00
unsigned char buf [ 4 ] ;
buf [ 0 ] = 0 ;
buf [ 1 ] = 0 ;
buf [ 2 ] = 0 ;
buf [ 3 ] = BPDU_TYPE_TCN ;
2006-09-14 07:12:40 +04:00
br_send_bpdu ( p , buf , 4 ) ;
2005-04-17 02:20:36 +04:00
}
2006-03-21 09:59:06 +03:00
/*
* Called from llc .
*
* NO locks , but rcu_read_lock ( preempt_disabled )
*/
int br_stp_rcv ( struct sk_buff * skb , struct net_device * dev ,
struct packet_type * pt , struct net_device * orig_dev )
2005-04-17 02:20:36 +04:00
{
2006-03-21 09:59:06 +03:00
const struct llc_pdu_un * pdu = llc_pdu_un_hdr ( skb ) ;
const unsigned char * dest = eth_hdr ( skb ) - > h_dest ;
struct net_bridge_port * p = rcu_dereference ( dev - > br_port ) ;
2006-02-10 04:08:52 +03:00
struct net_bridge * br ;
2006-03-21 09:59:06 +03:00
const unsigned char * buf ;
2005-04-17 02:20:36 +04:00
2006-02-10 04:08:52 +03:00
if ( ! p )
goto err ;
2006-03-21 09:59:06 +03:00
if ( pdu - > ssap ! = LLC_SAP_BSPAN
| | pdu - > dsap ! = LLC_SAP_BSPAN
| | pdu - > ctrl_1 ! = LLC_PDU_TYPE_U )
goto err ;
2006-02-10 04:08:52 +03:00
2006-03-21 09:59:06 +03:00
if ( ! pskb_may_pull ( skb , 4 ) )
goto err ;
/* compare of protocol id and version */
buf = skb - > data ;
if ( buf [ 0 ] ! = 0 | | buf [ 1 ] ! = 0 | | buf [ 2 ] ! = 0 )
goto err ;
2006-02-10 04:08:52 +03:00
2006-03-21 09:59:06 +03:00
br = p - > br ;
spin_lock ( & br - > lock ) ;
2006-02-10 04:08:52 +03:00
2006-03-21 09:59:06 +03:00
if ( p - > state = = BR_STATE_DISABLED
| | ! br - > stp_enabled
| | ! ( br - > dev - > flags & IFF_UP ) )
2006-02-10 04:08:52 +03:00
goto out ;
2005-05-30 01:15:55 +04:00
2006-03-21 09:59:21 +03:00
if ( compare_ether_addr ( dest , br - > group_addr ) ! = 0 )
2006-02-10 04:08:52 +03:00
goto out ;
2005-04-17 02:20:36 +04:00
2006-03-21 09:59:06 +03:00
buf = skb_pull ( skb , 3 ) ;
2005-04-17 02:20:36 +04:00
if ( buf [ 0 ] = = BPDU_TYPE_CONFIG ) {
struct br_config_bpdu bpdu ;
if ( ! pskb_may_pull ( skb , 32 ) )
2006-03-21 09:59:06 +03:00
goto out ;
2005-04-17 02:20:36 +04:00
buf = skb - > data ;
bpdu . topology_change = ( buf [ 1 ] & 0x01 ) ? 1 : 0 ;
bpdu . topology_change_ack = ( buf [ 1 ] & 0x80 ) ? 1 : 0 ;
bpdu . root . prio [ 0 ] = buf [ 2 ] ;
bpdu . root . prio [ 1 ] = buf [ 3 ] ;
bpdu . root . addr [ 0 ] = buf [ 4 ] ;
bpdu . root . addr [ 1 ] = buf [ 5 ] ;
bpdu . root . addr [ 2 ] = buf [ 6 ] ;
bpdu . root . addr [ 3 ] = buf [ 7 ] ;
bpdu . root . addr [ 4 ] = buf [ 8 ] ;
bpdu . root . addr [ 5 ] = buf [ 9 ] ;
bpdu . root_path_cost =
( buf [ 10 ] < < 24 ) |
( buf [ 11 ] < < 16 ) |
( buf [ 12 ] < < 8 ) |
buf [ 13 ] ;
bpdu . bridge_id . prio [ 0 ] = buf [ 14 ] ;
bpdu . bridge_id . prio [ 1 ] = buf [ 15 ] ;
bpdu . bridge_id . addr [ 0 ] = buf [ 16 ] ;
bpdu . bridge_id . addr [ 1 ] = buf [ 17 ] ;
bpdu . bridge_id . addr [ 2 ] = buf [ 18 ] ;
bpdu . bridge_id . addr [ 3 ] = buf [ 19 ] ;
bpdu . bridge_id . addr [ 4 ] = buf [ 20 ] ;
bpdu . bridge_id . addr [ 5 ] = buf [ 21 ] ;
bpdu . port_id = ( buf [ 22 ] < < 8 ) | buf [ 23 ] ;
bpdu . message_age = br_get_ticks ( buf + 24 ) ;
bpdu . max_age = br_get_ticks ( buf + 26 ) ;
bpdu . hello_time = br_get_ticks ( buf + 28 ) ;
bpdu . forward_delay = br_get_ticks ( buf + 30 ) ;
br_received_config_bpdu ( p , & bpdu ) ;
}
else if ( buf [ 0 ] = = BPDU_TYPE_TCN ) {
br_received_tcn_bpdu ( p ) ;
}
out :
2006-02-10 04:08:52 +03:00
spin_unlock ( & br - > lock ) ;
2005-04-17 02:20:36 +04:00
err :
kfree_skb ( skb ) ;
return 0 ;
}