2005-04-17 02:20:36 +04:00
/*
* Handle incoming frames
* 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>
# include <linux/netdevice.h>
# include <linux/etherdevice.h>
# include <linux/netfilter_bridge.h>
# include "br_private.h"
2006-03-21 09:59:21 +03:00
/* Bridge group multicast address 802.1d (pg 51). */
const u8 br_group_address [ ETH_ALEN ] = { 0x01 , 0x80 , 0xc2 , 0x00 , 0x00 , 0x00 } ;
2005-04-17 02:20:36 +04:00
2010-02-27 22:41:40 +03:00
static int br_pass_frame_up ( struct sk_buff * skb )
2005-04-17 02:20:36 +04:00
{
2010-02-27 22:41:40 +03:00
struct net_device * indev , * brdev = BR_INPUT_SKB_CB ( skb ) - > brdev ;
2005-04-17 02:20:36 +04:00
2008-05-22 01:13:47 +04:00
brdev - > stats . rx_packets + + ;
brdev - > stats . rx_bytes + = skb - > len ;
2005-04-17 02:20:36 +04:00
indev = skb - > dev ;
2008-05-22 01:13:47 +04:00
skb - > dev = brdev ;
2005-04-17 02:20:36 +04:00
2010-02-27 22:41:40 +03:00
return NF_HOOK ( PF_BRIDGE , NF_BR_LOCAL_IN , skb , indev , NULL ,
netif_receive_skb ) ;
2005-04-17 02:20:36 +04:00
}
/* note: already called with rcu_read_lock (preempt_disabled) */
int br_handle_frame_finish ( struct sk_buff * skb )
{
const unsigned char * dest = eth_hdr ( skb ) - > h_dest ;
2006-02-10 04:08:52 +03:00
struct net_bridge_port * p = rcu_dereference ( skb - > dev - > br_port ) ;
struct net_bridge * br ;
2005-04-17 02:20:36 +04:00
struct net_bridge_fdb_entry * dst ;
2010-02-27 22:41:48 +03:00
struct net_bridge_mdb_entry * mdst ;
2007-09-17 03:20:48 +04:00
struct sk_buff * skb2 ;
2005-04-17 02:20:36 +04:00
2006-02-10 04:08:52 +03:00
if ( ! p | | p - > state = = BR_STATE_DISABLED )
goto drop ;
2005-05-30 01:15:55 +04:00
/* insert into forwarding database after filtering to avoid spoofing */
2006-02-10 04:08:52 +03:00
br = p - > br ;
br_fdb_update ( br , p , eth_hdr ( skb ) - > h_source ) ;
2005-05-30 01:15:55 +04:00
2010-02-27 22:41:48 +03:00
if ( is_multicast_ether_addr ( dest ) & &
br_multicast_rcv ( br , p , skb ) )
goto drop ;
2006-02-10 04:08:52 +03:00
if ( p - > state = = BR_STATE_LEARNING )
goto drop ;
2005-12-22 06:00:18 +03:00
2010-02-27 22:41:40 +03:00
BR_INPUT_SKB_CB ( skb ) - > brdev = br - > dev ;
2007-09-17 03:20:48 +04:00
/* The packet skb2 goes to the local host (NULL to skip). */
skb2 = NULL ;
2005-04-17 02:20:36 +04:00
2007-09-17 03:20:48 +04:00
if ( br - > dev - > flags & IFF_PROMISC )
skb2 = skb ;
dst = NULL ;
2005-04-17 02:20:36 +04:00
2006-01-07 00:05:58 +03:00
if ( is_multicast_ether_addr ( dest ) ) {
2010-02-27 22:41:48 +03:00
mdst = br_mdb_get ( br , skb ) ;
2010-03-16 00:51:18 +03:00
if ( mdst | | BR_INPUT_SKB_CB_MROUTERS_ONLY ( skb ) ) {
2010-02-27 22:41:48 +03:00
if ( ( mdst & & ! hlist_unhashed ( & mdst - > mglist ) ) | |
br_multicast_is_router ( br ) )
skb2 = skb ;
br_multicast_forward ( mdst , skb , skb2 ) ;
skb = NULL ;
if ( ! skb2 )
goto out ;
} else
skb2 = skb ;
2008-05-22 01:13:47 +04:00
br - > dev - > stats . multicast + + ;
2007-09-17 03:20:48 +04:00
} else if ( ( dst = __br_fdb_get ( br , dest ) ) & & dst - > is_local ) {
skb2 = skb ;
/* Do not forward the packet since it's local. */
skb = NULL ;
2005-04-17 02:20:36 +04:00
}
2007-09-17 03:20:48 +04:00
if ( skb ) {
if ( dst )
2010-03-16 10:26:22 +03:00
br_forward ( dst - > dst , skb , skb2 ) ;
2007-09-17 03:20:48 +04:00
else
2010-02-27 22:41:41 +03:00
br_flood_forward ( br , skb , skb2 ) ;
2007-09-17 03:20:48 +04:00
}
2005-04-17 02:20:36 +04:00
2010-02-27 22:41:39 +03:00
if ( skb2 )
2010-02-27 22:41:40 +03:00
return br_pass_frame_up ( skb2 ) ;
2010-02-27 22:41:39 +03:00
2005-04-17 02:20:36 +04:00
out :
return 0 ;
2006-02-10 04:08:52 +03:00
drop :
kfree_skb ( skb ) ;
goto out ;
2005-04-17 02:20:36 +04:00
}
2006-03-21 09:59:06 +03:00
/* note: already called with rcu_read_lock (preempt_disabled) */
static int br_handle_local_finish ( struct sk_buff * skb )
{
struct net_bridge_port * p = rcu_dereference ( skb - > dev - > br_port ) ;
2007-08-31 09:15:35 +04:00
if ( p )
2006-03-21 09:59:06 +03:00
br_fdb_update ( p - > br , p , eth_hdr ( skb ) - > h_source ) ;
return 0 ; /* process further */
}
/* Does address match the link local multicast address.
* 01 : 80 : c2 : 00 : 00 : 0 X
*/
2006-03-21 10:00:56 +03:00
static inline int is_link_local ( const unsigned char * dest )
2006-03-21 09:59:06 +03:00
{
2007-07-26 20:33:39 +04:00
__be16 * a = ( __be16 * ) dest ;
static const __be16 * b = ( const __be16 * ) br_group_address ;
2007-12-11 21:51:03 +03:00
static const __be16 m = cpu_to_be16 ( 0xfff0 ) ;
2007-03-13 02:25:32 +03:00
return ( ( a [ 0 ] ^ b [ 0 ] ) | ( a [ 1 ] ^ b [ 1 ] ) | ( ( a [ 2 ] ^ b [ 2 ] ) & m ) ) = = 0 ;
2006-03-21 09:59:06 +03:00
}
2005-04-17 02:20:36 +04:00
/*
* Called via br_handle_frame_hook .
2007-03-21 23:38:47 +03:00
* Return NULL if skb is handled
2007-02-09 17:24:35 +03:00
* note : already called with rcu_read_lock ( preempt_disabled )
2005-04-17 02:20:36 +04:00
*/
2007-03-21 23:38:47 +03:00
struct sk_buff * br_handle_frame ( struct net_bridge_port * p , struct sk_buff * skb )
2005-04-17 02:20:36 +04:00
{
const unsigned char * dest = eth_hdr ( skb ) - > h_dest ;
2007-11-29 15:58:58 +03:00
int ( * rhook ) ( struct sk_buff * skb ) ;
2005-04-17 02:20:36 +04:00
if ( ! is_valid_ether_addr ( eth_hdr ( skb ) - > h_source ) )
2007-03-21 23:42:06 +03:00
goto drop ;
2005-04-17 02:20:36 +04:00
2007-10-14 11:39:01 +04:00
skb = skb_share_check ( skb , GFP_ATOMIC ) ;
if ( ! skb )
return NULL ;
2007-04-26 09:05:55 +04:00
if ( unlikely ( is_link_local ( dest ) ) ) {
/* Pause frames shouldn't be passed up by driver anyway */
if ( skb - > protocol = = htons ( ETH_P_PAUSE ) )
goto drop ;
2009-05-15 10:10:13 +04:00
/* If STP is turned off, then forward */
if ( p - > br - > stp_enabled = = BR_NO_STP & & dest [ 5 ] = = 0 )
goto forward ;
2008-06-18 03:09:45 +04:00
if ( NF_HOOK ( PF_BRIDGE , NF_BR_LOCAL_IN , skb , skb - > dev ,
NULL , br_handle_local_finish ) )
return NULL ; /* frame consumed by filter */
else
return skb ; /* continue processing */
2007-04-26 09:05:55 +04:00
}
2005-04-17 02:20:36 +04:00
2009-05-15 10:10:13 +04:00
forward :
2007-03-21 23:42:06 +03:00
switch ( p - > state ) {
case BR_STATE_FORWARDING :
2007-11-29 15:58:58 +03:00
rhook = rcu_dereference ( br_should_route_hook ) ;
if ( rhook ! = NULL ) {
if ( rhook ( skb ) )
2007-03-21 23:38:47 +03:00
return skb ;
2005-04-17 02:20:36 +04:00
dest = eth_hdr ( skb ) - > h_dest ;
}
2007-03-21 23:42:06 +03:00
/* fall through */
case BR_STATE_LEARNING :
2005-10-26 02:04:59 +04:00
if ( ! compare_ether_addr ( p - > br - > dev - > dev_addr , dest ) )
2005-04-17 02:20:36 +04:00
skb - > pkt_type = PACKET_HOST ;
NF_HOOK ( PF_BRIDGE , NF_BR_PRE_ROUTING , skb , skb - > dev , NULL ,
br_handle_frame_finish ) ;
2007-03-21 23:42:06 +03:00
break ;
default :
drop :
kfree_skb ( skb ) ;
2005-04-17 02:20:36 +04:00
}
2007-03-21 23:38:47 +03:00
return NULL ;
2005-04-17 02:20:36 +04:00
}