If a bridge is not running STP, then it has no way to detect a cycle in the network. But if it is not running STP and some other machine or device is running STP, then if STP BPDU's get forwarded to it can detect the cycle. This is how the old 2.4 and early 2.6 code worked. Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org> Signed-off-by: David S. Miller <davem@davemloft.net>
171 lines
4 KiB
C
171 lines
4 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 && p->state != BR_STATE_DISABLED)
|
|
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)
|
|
{
|
|
const u16 *a = (const u16 *) dest;
|
|
static const u16 *const b = (const u16 *const ) br_group_address;
|
|
static const u16 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;
|
|
}
|