br_stp_bpdu.c 5.78 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
/*
 *	Spanning tree protocol; BPDU handling
 *	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/netfilter_bridge.h>
16
17
#include <linux/etherdevice.h>
#include <linux/llc.h>
18
#include <linux/slab.h>
19
#include <linux/pkt_sched.h>
20
#include <net/net_namespace.h>
21
#include <net/llc.h>
22
#include <net/llc_pdu.h>
Patrick McHardy's avatar
Patrick McHardy committed
23
#include <net/stp.h>
24
#include <asm/unaligned.h>
Linus Torvalds's avatar
Linus Torvalds committed
25
26
27
28

#include "br_private.h"
#include "br_private_stp.h"

29
#define STP_HZ		256
Linus Torvalds's avatar
Linus Torvalds committed
30

31
32
#define LLC_RESERVE sizeof(struct llc_pdu_un)

33
34
35
36
37
static int br_send_bpdu_finish(struct sock *sk, struct sk_buff *skb)
{
	return dev_queue_xmit(skb);
}

38
static void br_send_bpdu(struct net_bridge_port *p,
39
			 const unsigned char *data, int length)
Linus Torvalds's avatar
Linus Torvalds committed
40
41
42
{
	struct sk_buff *skb;

43
44
	skb = dev_alloc_skb(length+LLC_RESERVE);
	if (!skb)
Linus Torvalds's avatar
Linus Torvalds committed
45
46
		return;

47
	skb->dev = p->dev;
Linus Torvalds's avatar
Linus Torvalds committed
48
	skb->protocol = htons(ETH_P_802_2);
49
	skb->priority = TC_PRIO_CONTROL;
50
51
52
53
54
55
56
57
58

	skb_reserve(skb, LLC_RESERVE);
	memcpy(__skb_put(skb, length), data, length);

	llc_pdu_header_init(skb, LLC_PDU_TYPE_U, LLC_SAP_BSPAN,
			    LLC_SAP_BSPAN, LLC_PDU_CMD);
	llc_pdu_init_as_ui_cmd(skb);

	llc_mac_hdr_init(skb, p->dev->dev_addr, p->br->group_addr);
Linus Torvalds's avatar
Linus Torvalds committed
59

60
61
	skb_reset_mac_header(skb);

62
63
	NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, NULL, skb,
		NULL, skb->dev,
64
		br_send_bpdu_finish);
Linus Torvalds's avatar
Linus Torvalds committed
65
66
}

67
static inline void br_set_ticks(unsigned char *dest, int j)
Linus Torvalds's avatar
Linus Torvalds committed
68
{
69
	unsigned long ticks = (STP_HZ * j)/ HZ;
Linus Torvalds's avatar
Linus Torvalds committed
70

71
	put_unaligned_be16(ticks, dest);
Linus Torvalds's avatar
Linus Torvalds committed
72
73
}

74
static inline int br_get_ticks(const unsigned char *src)
Linus Torvalds's avatar
Linus Torvalds committed
75
{
76
	unsigned long ticks = get_unaligned_be16(src);
77

78
	return DIV_ROUND_UP(ticks * HZ, STP_HZ);
Linus Torvalds's avatar
Linus Torvalds committed
79
80
81
82
83
}

/* called under bridge lock */
void br_send_config_bpdu(struct net_bridge_port *p, struct br_config_bpdu *bpdu)
{
84
85
	unsigned char buf[35];

86
87
88
	if (p->br->stp_enabled != BR_KERNEL_STP)
		return;

89
90
91
92
93
	buf[0] = 0;
	buf[1] = 0;
	buf[2] = 0;
	buf[3] = BPDU_TYPE_CONFIG;
	buf[4] = (bpdu->topology_change ? 0x01 : 0) |
Linus Torvalds's avatar
Linus Torvalds committed
94
		(bpdu->topology_change_ack ? 0x80 : 0);
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
	buf[5] = bpdu->root.prio[0];
	buf[6] = bpdu->root.prio[1];
	buf[7] = bpdu->root.addr[0];
	buf[8] = bpdu->root.addr[1];
	buf[9] = bpdu->root.addr[2];
	buf[10] = bpdu->root.addr[3];
	buf[11] = bpdu->root.addr[4];
	buf[12] = bpdu->root.addr[5];
	buf[13] = (bpdu->root_path_cost >> 24) & 0xFF;
	buf[14] = (bpdu->root_path_cost >> 16) & 0xFF;
	buf[15] = (bpdu->root_path_cost >> 8) & 0xFF;
	buf[16] = bpdu->root_path_cost & 0xFF;
	buf[17] = bpdu->bridge_id.prio[0];
	buf[18] = bpdu->bridge_id.prio[1];
	buf[19] = bpdu->bridge_id.addr[0];
	buf[20] = bpdu->bridge_id.addr[1];
	buf[21] = bpdu->bridge_id.addr[2];
	buf[22] = bpdu->bridge_id.addr[3];
	buf[23] = bpdu->bridge_id.addr[4];
	buf[24] = bpdu->bridge_id.addr[5];
	buf[25] = (bpdu->port_id >> 8) & 0xFF;
	buf[26] = bpdu->port_id & 0xFF;

	br_set_ticks(buf+27, bpdu->message_age);
	br_set_ticks(buf+29, bpdu->max_age);
	br_set_ticks(buf+31, bpdu->hello_time);
	br_set_ticks(buf+33, bpdu->forward_delay);

	br_send_bpdu(p, buf, 35);
Linus Torvalds's avatar
Linus Torvalds committed
124
125
126
127
128
}

/* called under bridge lock */
void br_send_tcn_bpdu(struct net_bridge_port *p)
{
129
130
	unsigned char buf[4];

131
132
133
	if (p->br->stp_enabled != BR_KERNEL_STP)
		return;

134
135
136
137
	buf[0] = 0;
	buf[1] = 0;
	buf[2] = 0;
	buf[3] = BPDU_TYPE_TCN;
138
	br_send_bpdu(p, buf, 4);
Linus Torvalds's avatar
Linus Torvalds committed
139
140
}

141
142
143
/*
 * Called from llc.
 *
144
 * NO locks, but rcu_read_lock
145
 */
Patrick McHardy's avatar
Patrick McHardy committed
146
147
void br_stp_rcv(const struct stp_proto *proto, struct sk_buff *skb,
		struct net_device *dev)
Linus Torvalds's avatar
Linus Torvalds committed
148
{
149
	const unsigned char *dest = eth_hdr(skb)->h_dest;
150
	struct net_bridge_port *p;
151
	struct net_bridge *br;
152
	const unsigned char *buf;
Linus Torvalds's avatar
Linus Torvalds committed
153

154
155
156
157
158
159
160
	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;
161

162
	p = br_port_get_check_rcu(dev);
163
164
165
	if (!p)
		goto err;

166
167
	br = p->br;
	spin_lock(&br->lock);
168

169
170
171
172
173
174
175
	if (br->stp_enabled != BR_KERNEL_STP)
		goto out;

	if (!(br->dev->flags & IFF_UP))
		goto out;

	if (p->state == BR_STATE_DISABLED)
176
		goto out;
177

178
	if (!ether_addr_equal(dest, br->group_addr))
179
		goto out;
Linus Torvalds's avatar
Linus Torvalds committed
180

181
182
183
184
185
186
187
	if (p->flags & BR_BPDU_GUARD) {
		br_notice(br, "BPDU received on blocked port %u(%s)\n",
			  (unsigned int) p->port_no, p->dev->name);
		br_stp_disable_port(p);
		goto out;
	}

188
	buf = skb_pull(skb, 3);
Linus Torvalds's avatar
Linus Torvalds committed
189
190
191
192
193

	if (buf[0] == BPDU_TYPE_CONFIG) {
		struct br_config_bpdu bpdu;

		if (!pskb_may_pull(skb, 32))
194
			goto out;
Linus Torvalds's avatar
Linus Torvalds committed
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227

		buf = skb->data;
		bpdu.topology_change = (buf[1] & 0x01) ? 1 : 0;
		bpdu.topology_change_ack = (buf[1] & 0x80) ? 1 : 0;

		bpdu.root.prio[0] = buf[2];
		bpdu.root.prio[1] = buf[3];
		bpdu.root.addr[0] = buf[4];
		bpdu.root.addr[1] = buf[5];
		bpdu.root.addr[2] = buf[6];
		bpdu.root.addr[3] = buf[7];
		bpdu.root.addr[4] = buf[8];
		bpdu.root.addr[5] = buf[9];
		bpdu.root_path_cost =
			(buf[10] << 24) |
			(buf[11] << 16) |
			(buf[12] << 8) |
			buf[13];
		bpdu.bridge_id.prio[0] = buf[14];
		bpdu.bridge_id.prio[1] = buf[15];
		bpdu.bridge_id.addr[0] = buf[16];
		bpdu.bridge_id.addr[1] = buf[17];
		bpdu.bridge_id.addr[2] = buf[18];
		bpdu.bridge_id.addr[3] = buf[19];
		bpdu.bridge_id.addr[4] = buf[20];
		bpdu.bridge_id.addr[5] = buf[21];
		bpdu.port_id = (buf[22] << 8) | buf[23];

		bpdu.message_age = br_get_ticks(buf+24);
		bpdu.max_age = br_get_ticks(buf+26);
		bpdu.hello_time = br_get_ticks(buf+28);
		bpdu.forward_delay = br_get_ticks(buf+30);

228
229
230
231
232
233
234
235
236
237
238
		if (bpdu.message_age > bpdu.max_age) {
			if (net_ratelimit())
				br_notice(p->br,
					  "port %u config from %pM"
					  " (message_age %ul > max_age %ul)\n",
					  p->port_no,
					  eth_hdr(skb)->h_source,
					  bpdu.message_age, bpdu.max_age);
			goto out;
		}

Linus Torvalds's avatar
Linus Torvalds committed
239
		br_received_config_bpdu(p, &bpdu);
stephen hemminger's avatar
stephen hemminger committed
240
	} else if (buf[0] == BPDU_TYPE_TCN) {
Linus Torvalds's avatar
Linus Torvalds committed
241
242
243
		br_received_tcn_bpdu(p);
	}
 out:
244
	spin_unlock(&br->lock);
Linus Torvalds's avatar
Linus Torvalds committed
245
246
247
 err:
	kfree_skb(skb);
}