br_input.c 3.95 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
 *	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"

22
23
/* Bridge group multicast address 802.1d (pg 51). */
const u8 br_group_address[ETH_ALEN] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 };
Linus Torvalds's avatar
Linus Torvalds committed
24
25
26
27
28
29
30
31
32
33
34
35

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,
36
		netif_receive_skb);
Linus Torvalds's avatar
Linus Torvalds committed
37
38
39
40
41
42
}

/* 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;
43
44
	struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);
	struct net_bridge *br;
Linus Torvalds's avatar
Linus Torvalds committed
45
46
47
	struct net_bridge_fdb_entry *dst;
	int passedup = 0;

48
49
50
	if (!p || p->state == BR_STATE_DISABLED)
		goto drop;

51
	/* insert into forwarding database after filtering to avoid spoofing */
52
53
	br = p->br;
	br_fdb_update(br, p, eth_hdr(skb)->h_source);
54

55
56
	if (p->state == BR_STATE_LEARNING)
		goto drop;
57

Linus Torvalds's avatar
Linus Torvalds committed
58
59
60
61
62
63
64
65
66
67
	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);
		}
	}

68
	if (is_multicast_ether_addr(dest)) {
69
		br->statistics.multicast++;
Linus Torvalds's avatar
Linus Torvalds committed
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
		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;
94
95
96
drop:
	kfree_skb(skb);
	goto out;
Linus Torvalds's avatar
Linus Torvalds committed
97
98
}

99
100
101
102
103
/* 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);

104
	if (p)
105
106
107
108
109
110
111
		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
 */
112
static inline int is_link_local(const unsigned char *dest)
113
{
Al Viro's avatar
Al Viro committed
114
115
116
	__be16 *a = (__be16 *)dest;
	static const __be16 *b = (const __be16 *)br_group_address;
	static const __be16 m = __constant_cpu_to_be16(0xfff0);
117
118

	return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | ((a[2] ^ b[2]) & m)) == 0;
119
120
}

Linus Torvalds's avatar
Linus Torvalds committed
121
122
/*
 * Called via br_handle_frame_hook.
123
 * Return NULL if skb is handled
124
 * note: already called with rcu_read_lock (preempt_disabled)
Linus Torvalds's avatar
Linus Torvalds committed
125
 */
126
struct sk_buff *br_handle_frame(struct net_bridge_port *p, struct sk_buff *skb)
Linus Torvalds's avatar
Linus Torvalds committed
127
128
129
130
{
	const unsigned char *dest = eth_hdr(skb)->h_dest;

	if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
131
		goto drop;
Linus Torvalds's avatar
Linus Torvalds committed
132

133
134
135
136
137
	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;

138
139
140
141
142
143
144
145
		/* 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;
		}
146
	}
Linus Torvalds's avatar
Linus Torvalds committed
147

148
149
150
	switch (p->state) {
	case BR_STATE_FORWARDING:

Linus Torvalds's avatar
Linus Torvalds committed
151
		if (br_should_route_hook) {
152
153
			if (br_should_route_hook(&skb))
				return skb;
Linus Torvalds's avatar
Linus Torvalds committed
154
155
			dest = eth_hdr(skb)->h_dest;
		}
156
157
		/* fall through */
	case BR_STATE_LEARNING:
158
		if (!compare_ether_addr(p->br->dev->dev_addr, dest))
Linus Torvalds's avatar
Linus Torvalds committed
159
160
161
162
			skb->pkt_type = PACKET_HOST;

		NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
			br_handle_frame_finish);
163
164
165
166
		break;
	default:
drop:
		kfree_skb(skb);
Linus Torvalds's avatar
Linus Torvalds committed
167
	}
168
	return NULL;
Linus Torvalds's avatar
Linus Torvalds committed
169
}