forked from Minki/linux
df1c0b8468
This patch fixes some packet leakage in bridge. The bridging code was allowing forward table entries to be generated even if a device was being blocked. The fix is to not add forwarding database entries unless the port is active. The bug arose as part of the conversion to processing STP frames through normal receive path (in 2.6.17). Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org> Acked-by: John W. Linville <linville@tuxdriver.com> Signed-off-by: David S. Miller <davem@davemloft.net>
170 lines
4.0 KiB
C
170 lines
4.0 KiB
C
/*
|
|
* Handle incoming frames
|
|
* Linux ethernet bridge
|
|
*
|
|
* Authors:
|
|
* Lennert Buytenhek <buytenh@gnu.org>
|
|
*
|
|
* $Id: br_input.c,v 1.10 2001/12/24 04:50:20 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/netdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/netfilter_bridge.h>
|
|
#include "br_private.h"
|
|
|
|
/* Bridge group multicast address 802.1d (pg 51). */
|
|
const u8 br_group_address[ETH_ALEN] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 };
|
|
|
|
static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)
|
|
{
|
|
struct net_device *indev;
|
|
|
|
br->statistics.rx_packets++;
|
|
br->statistics.rx_bytes += skb->len;
|
|
|
|
indev = skb->dev;
|
|
skb->dev = br->dev;
|
|
|
|
NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,
|
|
netif_receive_skb);
|
|
}
|
|
|
|
/* 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;
|
|
struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);
|
|
struct net_bridge *br;
|
|
struct net_bridge_fdb_entry *dst;
|
|
int passedup = 0;
|
|
|
|
if (!p || p->state == BR_STATE_DISABLED)
|
|
goto drop;
|
|
|
|
/* insert into forwarding database after filtering to avoid spoofing */
|
|
br = p->br;
|
|
br_fdb_update(br, p, eth_hdr(skb)->h_source);
|
|
|
|
if (p->state == BR_STATE_LEARNING)
|
|
goto drop;
|
|
|
|
if (br->dev->flags & IFF_PROMISC) {
|
|
struct sk_buff *skb2;
|
|
|
|
skb2 = skb_clone(skb, GFP_ATOMIC);
|
|
if (skb2 != NULL) {
|
|
passedup = 1;
|
|
br_pass_frame_up(br, skb2);
|
|
}
|
|
}
|
|
|
|
if (is_multicast_ether_addr(dest)) {
|
|
br->statistics.multicast++;
|
|
br_flood_forward(br, skb, !passedup);
|
|
if (!passedup)
|
|
br_pass_frame_up(br, skb);
|
|
goto out;
|
|
}
|
|
|
|
dst = __br_fdb_get(br, dest);
|
|
if (dst != NULL && dst->is_local) {
|
|
if (!passedup)
|
|
br_pass_frame_up(br, skb);
|
|
else
|
|
kfree_skb(skb);
|
|
goto out;
|
|
}
|
|
|
|
if (dst != NULL) {
|
|
br_forward(dst->dst, skb);
|
|
goto out;
|
|
}
|
|
|
|
br_flood_forward(br, skb, 0);
|
|
|
|
out:
|
|
return 0;
|
|
drop:
|
|
kfree_skb(skb);
|
|
goto out;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
if (p)
|
|
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:0X
|
|
*/
|
|
static inline int is_link_local(const unsigned char *dest)
|
|
{
|
|
__be16 *a = (__be16 *)dest;
|
|
static const __be16 *b = (const __be16 *)br_group_address;
|
|
static const __be16 m = __constant_cpu_to_be16(0xfff0);
|
|
|
|
return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | ((a[2] ^ b[2]) & m)) == 0;
|
|
}
|
|
|
|
/*
|
|
* Called via br_handle_frame_hook.
|
|
* Return NULL if skb is handled
|
|
* note: already called with rcu_read_lock (preempt_disabled)
|
|
*/
|
|
struct sk_buff *br_handle_frame(struct net_bridge_port *p, struct sk_buff *skb)
|
|
{
|
|
const unsigned char *dest = eth_hdr(skb)->h_dest;
|
|
|
|
if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
|
|
goto drop;
|
|
|
|
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;
|
|
|
|
/* Process STP BPDU's through normal netif_receive_skb() path */
|
|
if (p->br->stp_enabled != BR_NO_STP) {
|
|
if (NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,
|
|
NULL, br_handle_local_finish))
|
|
return NULL;
|
|
else
|
|
return skb;
|
|
}
|
|
}
|
|
|
|
switch (p->state) {
|
|
case BR_STATE_FORWARDING:
|
|
|
|
if (br_should_route_hook) {
|
|
if (br_should_route_hook(&skb))
|
|
return skb;
|
|
dest = eth_hdr(skb)->h_dest;
|
|
}
|
|
/* fall through */
|
|
case BR_STATE_LEARNING:
|
|
if (!compare_ether_addr(p->br->dev->dev_addr, dest))
|
|
skb->pkt_type = PACKET_HOST;
|
|
|
|
NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
|
|
br_handle_frame_finish);
|
|
break;
|
|
default:
|
|
drop:
|
|
kfree_skb(skb);
|
|
}
|
|
return NULL;
|
|
}
|