summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--net/bridge/Kconfig1
-rw-r--r--net/bridge/br.c12
-rw-r--r--net/bridge/br_input.c32
-rw-r--r--net/bridge/br_private.h3
-rw-r--r--net/bridge/br_stp_bpdu.c51
5 files changed, 71 insertions, 28 deletions
diff --git a/net/bridge/Kconfig b/net/bridge/Kconfig
index db23d59746c..12265aff709 100644
--- a/net/bridge/Kconfig
+++ b/net/bridge/Kconfig
@@ -4,6 +4,7 @@
config BRIDGE
tristate "802.1d Ethernet Bridging"
+ select LLC
---help---
If you say Y here, then your Linux box will be able to act as an
Ethernet bridge, which means that the different Ethernet segments it
diff --git a/net/bridge/br.c b/net/bridge/br.c
index 188cc1ac49e..22d806cf40c 100644
--- a/net/bridge/br.c
+++ b/net/bridge/br.c
@@ -19,13 +19,23 @@
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/init.h>
+#include <linux/llc.h>
+#include <net/llc.h>
#include "br_private.h"
int (*br_should_route_hook) (struct sk_buff **pskb) = NULL;
+static struct llc_sap *br_stp_sap;
+
static int __init br_init(void)
{
+ br_stp_sap = llc_sap_open(LLC_SAP_BSPAN, br_stp_rcv);
+ if (!br_stp_sap) {
+ printk(KERN_ERR "bridge: can't register sap for STP\n");
+ return -EBUSY;
+ }
+
br_fdb_init();
#ifdef CONFIG_BRIDGE_NETFILTER
@@ -45,6 +55,8 @@ static int __init br_init(void)
static void __exit br_deinit(void)
{
+ llc_sap_close(br_stp_sap);
+
#ifdef CONFIG_BRIDGE_NETFILTER
br_netfilter_fini();
#endif
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index 6e223723cc8..dad40948975 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -94,6 +94,25 @@ drop:
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)
+{
+ return memcmp(dest, bridge_ula, 5) == 0 && (dest[5] & 0xf0) == 0;
+}
+
/*
* Called via br_handle_frame_hook.
* Return 0 if *pskb should be processed furthur
@@ -111,15 +130,10 @@ int br_handle_frame(struct net_bridge_port *p, struct sk_buff **pskb)
if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
goto err;
- if (p->br->stp_enabled &&
- !memcmp(dest, bridge_ula, 5) &&
- !(dest[5] & 0xF0)) {
- if (!dest[5]) {
- NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,
- NULL, br_stp_handle_bpdu);
- return 1;
- }
- goto err;
+ if (unlikely(is_link_local(dest))) {
+ skb->pkt_type = PACKET_HOST;
+ return NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,
+ NULL, br_handle_local_finish) != 0;
}
if (p->state == BR_STATE_FORWARDING || p->state == BR_STATE_LEARNING) {
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 8f10e09f251..3bc9ad48347 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -217,7 +217,8 @@ extern void br_stp_set_path_cost(struct net_bridge_port *p,
extern ssize_t br_show_bridge_id(char *buf, const struct bridge_id *id);
/* br_stp_bpdu.c */
-extern int br_stp_handle_bpdu(struct sk_buff *skb);
+extern int br_stp_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt, struct net_device *orig_dev);
/* br_stp_timer.c */
extern void br_stp_timer_init(struct net_bridge *br);
diff --git a/net/bridge/br_stp_bpdu.c b/net/bridge/br_stp_bpdu.c
index c3da01cc6a6..a99e90e2095 100644
--- a/net/bridge/br_stp_bpdu.c
+++ b/net/bridge/br_stp_bpdu.c
@@ -15,6 +15,9 @@
#include <linux/kernel.h>
#include <linux/netfilter_bridge.h>
+#include <linux/etherdevice.h>
+#include <linux/llc.h>
+#include <net/llc_pdu.h>
#include "br_private.h"
#include "br_private_stp.h"
@@ -130,42 +133,54 @@ void br_send_tcn_bpdu(struct net_bridge_port *p)
br_send_bpdu(p, buf, 7);
}
-static const unsigned char header[6] = {0x42, 0x42, 0x03, 0x00, 0x00, 0x00};
-
-/* NO locks, but rcu_read_lock (preempt_disabled) */
-int br_stp_handle_bpdu(struct sk_buff *skb)
+/*
+ * 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)
{
- struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);
+ 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);
struct net_bridge *br;
- unsigned char *buf;
+ const unsigned char *buf;
if (!p)
goto err;
- br = p->br;
- spin_lock(&br->lock);
+ if (pdu->ssap != LLC_SAP_BSPAN
+ || pdu->dsap != LLC_SAP_BSPAN
+ || pdu->ctrl_1 != LLC_PDU_TYPE_U)
+ goto err;
- if (p->state == BR_STATE_DISABLED || !(br->dev->flags & IFF_UP))
- goto out;
+ 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;
- /* insert into forwarding database after filtering to avoid spoofing */
- br_fdb_update(br, p, eth_hdr(skb)->h_source);
+ br = p->br;
+ spin_lock(&br->lock);
- if (!br->stp_enabled)
+ if (p->state == BR_STATE_DISABLED
+ || !br->stp_enabled
+ || !(br->dev->flags & IFF_UP))
goto out;
- /* need at least the 802 and STP headers */
- if (!pskb_may_pull(skb, sizeof(header)+1) ||
- memcmp(skb->data, header, sizeof(header)))
+ if (compare_ether_addr(dest, bridge_ula) != 0)
goto out;
- buf = skb_pull(skb, sizeof(header));
+ buf = skb_pull(skb, 3);
if (buf[0] == BPDU_TYPE_CONFIG) {
struct br_config_bpdu bpdu;
if (!pskb_may_pull(skb, 32))
- goto out;
+ goto out;
buf = skb->data;
bpdu.topology_change = (buf[1] & 0x01) ? 1 : 0;