1 /*
2 * Forwarding database
3 * Linux ethernet bridge
4 *
5 * Authors:
6 * Lennert Buytenhek <buytenh@gnu.org>
7 *
8 * $Id: br_fdb.c,v 1.5.2.1 2002/01/17 00:59:01 davem Exp $
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version
13 * 2 of the License, or (at your option) any later version.
14 */
15
16 #include <linux/kernel.h>
17 #include <linux/spinlock.h>
18 #include <linux/if_bridge.h>
19 #include <asm/atomic.h>
20 #include <asm/uaccess.h>
21 #include "br_private.h"
22
__timeout(struct net_bridge * br)23 static __inline__ unsigned long __timeout(struct net_bridge *br)
24 {
25 unsigned long timeout;
26
27 timeout = jiffies - br->ageing_time;
28 if (br->topology_change)
29 timeout = jiffies - br->forward_delay;
30
31 return timeout;
32 }
33
has_expired(struct net_bridge * br,struct net_bridge_fdb_entry * fdb)34 static __inline__ int has_expired(struct net_bridge *br,
35 struct net_bridge_fdb_entry *fdb)
36 {
37 if (!fdb->is_static &&
38 time_before_eq(fdb->ageing_timer, __timeout(br)))
39 return 1;
40
41 return 0;
42 }
43
copy_fdb(struct __fdb_entry * ent,struct net_bridge_fdb_entry * f)44 static __inline__ void copy_fdb(struct __fdb_entry *ent, struct net_bridge_fdb_entry *f)
45 {
46 memset(ent, 0, sizeof(struct __fdb_entry));
47 memcpy(ent->mac_addr, f->addr.addr, ETH_ALEN);
48 ent->port_no = f->dst?f->dst->port_no:0;
49 ent->is_local = f->is_local;
50 ent->ageing_timer_value = 0;
51 if (!f->is_static)
52 ent->ageing_timer_value = jiffies - f->ageing_timer;
53 }
54
br_mac_hash(unsigned char * mac)55 static __inline__ int br_mac_hash(unsigned char *mac)
56 {
57 unsigned long x;
58
59 x = mac[0];
60 x = (x << 2) ^ mac[1];
61 x = (x << 2) ^ mac[2];
62 x = (x << 2) ^ mac[3];
63 x = (x << 2) ^ mac[4];
64 x = (x << 2) ^ mac[5];
65
66 x ^= x >> 8;
67
68 return x & (BR_HASH_SIZE - 1);
69 }
70
__hash_link(struct net_bridge * br,struct net_bridge_fdb_entry * ent,int hash)71 static __inline__ void __hash_link(struct net_bridge *br,
72 struct net_bridge_fdb_entry *ent,
73 int hash)
74 {
75 ent->next_hash = br->hash[hash];
76 if (ent->next_hash != NULL)
77 ent->next_hash->pprev_hash = &ent->next_hash;
78 br->hash[hash] = ent;
79 ent->pprev_hash = &br->hash[hash];
80 }
81
__hash_unlink(struct net_bridge_fdb_entry * ent)82 static __inline__ void __hash_unlink(struct net_bridge_fdb_entry *ent)
83 {
84 *(ent->pprev_hash) = ent->next_hash;
85 if (ent->next_hash != NULL)
86 ent->next_hash->pprev_hash = ent->pprev_hash;
87 ent->next_hash = NULL;
88 ent->pprev_hash = NULL;
89 }
90
91
92
br_fdb_changeaddr(struct net_bridge_port * p,unsigned char * newaddr)93 void br_fdb_changeaddr(struct net_bridge_port *p, unsigned char *newaddr)
94 {
95 struct net_bridge *br;
96 int i;
97
98 br = p->br;
99 write_lock_bh(&br->hash_lock);
100 for (i=0;i<BR_HASH_SIZE;i++) {
101 struct net_bridge_fdb_entry *f;
102
103 f = br->hash[i];
104 while (f != NULL) {
105 if (f->dst == p && f->is_local) {
106 __hash_unlink(f);
107 memcpy(f->addr.addr, newaddr, ETH_ALEN);
108 __hash_link(br, f, br_mac_hash(newaddr));
109 write_unlock_bh(&br->hash_lock);
110 return;
111 }
112 f = f->next_hash;
113 }
114 }
115 write_unlock_bh(&br->hash_lock);
116 }
117
br_fdb_cleanup(struct net_bridge * br)118 void br_fdb_cleanup(struct net_bridge *br)
119 {
120 int i;
121 unsigned long timeout;
122
123 timeout = __timeout(br);
124
125 write_lock_bh(&br->hash_lock);
126 for (i=0;i<BR_HASH_SIZE;i++) {
127 struct net_bridge_fdb_entry *f;
128
129 f = br->hash[i];
130 while (f != NULL) {
131 struct net_bridge_fdb_entry *g;
132
133 g = f->next_hash;
134 if (!f->is_static &&
135 time_before_eq(f->ageing_timer, timeout)) {
136 __hash_unlink(f);
137 br_fdb_put(f);
138 }
139 f = g;
140 }
141 }
142 write_unlock_bh(&br->hash_lock);
143 }
144
br_fdb_delete_by_port(struct net_bridge * br,struct net_bridge_port * p)145 void br_fdb_delete_by_port(struct net_bridge *br, struct net_bridge_port *p)
146 {
147 int i;
148
149 write_lock_bh(&br->hash_lock);
150 for (i=0;i<BR_HASH_SIZE;i++) {
151 struct net_bridge_fdb_entry *f;
152
153 f = br->hash[i];
154 while (f != NULL) {
155 struct net_bridge_fdb_entry *g;
156
157 g = f->next_hash;
158 if (f->dst == p) {
159 __hash_unlink(f);
160 br_fdb_put(f);
161 }
162 f = g;
163 }
164 }
165 write_unlock_bh(&br->hash_lock);
166 }
167
br_fdb_get(struct net_bridge * br,unsigned char * addr)168 struct net_bridge_fdb_entry *br_fdb_get(struct net_bridge *br, unsigned char *addr)
169 {
170 struct net_bridge_fdb_entry *fdb;
171
172 read_lock_bh(&br->hash_lock);
173 fdb = br->hash[br_mac_hash(addr)];
174 while (fdb != NULL) {
175 if (!memcmp(fdb->addr.addr, addr, ETH_ALEN)) {
176 if (!has_expired(br, fdb)) {
177 atomic_inc(&fdb->use_count);
178 read_unlock_bh(&br->hash_lock);
179 return fdb;
180 }
181
182 read_unlock_bh(&br->hash_lock);
183 return NULL;
184 }
185
186 fdb = fdb->next_hash;
187 }
188
189 read_unlock_bh(&br->hash_lock);
190 return NULL;
191 }
192
br_fdb_put(struct net_bridge_fdb_entry * ent)193 void br_fdb_put(struct net_bridge_fdb_entry *ent)
194 {
195 if (atomic_dec_and_test(&ent->use_count))
196 kfree(ent);
197 }
198
br_fdb_get_entries(struct net_bridge * br,unsigned char * _buf,int maxnum,int offset)199 int br_fdb_get_entries(struct net_bridge *br,
200 unsigned char *_buf,
201 int maxnum,
202 int offset)
203 {
204 int i;
205 int num;
206 struct __fdb_entry *walk;
207
208 num = 0;
209 walk = (struct __fdb_entry *)_buf;
210
211 read_lock_bh(&br->hash_lock);
212 for (i=0;i<BR_HASH_SIZE;i++) {
213 struct net_bridge_fdb_entry *f;
214
215 f = br->hash[i];
216 while (f != NULL && num < maxnum) {
217 struct __fdb_entry ent;
218 int err;
219 struct net_bridge_fdb_entry *g;
220 struct net_bridge_fdb_entry **pp;
221
222 if (has_expired(br, f)) {
223 f = f->next_hash;
224 continue;
225 }
226
227 if (offset) {
228 offset--;
229 f = f->next_hash;
230 continue;
231 }
232
233 copy_fdb(&ent, f);
234
235 atomic_inc(&f->use_count);
236 read_unlock_bh(&br->hash_lock);
237 err = copy_to_user(walk, &ent, sizeof(struct __fdb_entry));
238 read_lock_bh(&br->hash_lock);
239
240 g = f->next_hash;
241 pp = f->pprev_hash;
242 br_fdb_put(f);
243
244 if (err)
245 goto out_fault;
246
247 if (g == NULL && pp == NULL)
248 goto out_disappeared;
249
250 num++;
251 walk++;
252
253 f = g;
254 }
255 }
256
257 out:
258 read_unlock_bh(&br->hash_lock);
259 return num;
260
261 out_disappeared:
262 num = -EAGAIN;
263 goto out;
264
265 out_fault:
266 num = -EFAULT;
267 goto out;
268 }
269
__fdb_possibly_replace(struct net_bridge_fdb_entry * fdb,struct net_bridge_port * source,int is_local)270 static __inline__ void __fdb_possibly_replace(struct net_bridge_fdb_entry *fdb,
271 struct net_bridge_port *source,
272 int is_local)
273 {
274 if (!fdb->is_static || is_local) {
275 fdb->dst = source;
276 fdb->is_local = is_local;
277 fdb->is_static = is_local;
278 fdb->ageing_timer = jiffies;
279 }
280 }
281
br_fdb_insert(struct net_bridge * br,struct net_bridge_port * source,unsigned char * addr,int is_local)282 void br_fdb_insert(struct net_bridge *br,
283 struct net_bridge_port *source,
284 unsigned char *addr,
285 int is_local)
286 {
287 struct net_bridge_fdb_entry *fdb;
288 int hash;
289
290 hash = br_mac_hash(addr);
291
292 write_lock_bh(&br->hash_lock);
293 fdb = br->hash[hash];
294 while (fdb != NULL) {
295 if (!memcmp(fdb->addr.addr, addr, ETH_ALEN)) {
296 /* attempt to update an entry for a local interface */
297 if (fdb->is_local) {
298 if (is_local)
299 printk(KERN_INFO "%s: attempt to add"
300 " interface with same source address.\n",
301 source->dev->name);
302 else if (net_ratelimit())
303 printk(KERN_WARNING "%s: received packet with "
304 " own address as source address\n",
305 source->dev->name);
306 goto out;
307 }
308
309 __fdb_possibly_replace(fdb, source, is_local);
310 goto out;
311 }
312
313 fdb = fdb->next_hash;
314 }
315
316 fdb = kmalloc(sizeof(*fdb), GFP_ATOMIC);
317 if (fdb == NULL)
318 goto out;
319
320 memcpy(fdb->addr.addr, addr, ETH_ALEN);
321 atomic_set(&fdb->use_count, 1);
322 fdb->dst = source;
323 fdb->is_local = is_local;
324 fdb->is_static = is_local;
325 fdb->ageing_timer = jiffies;
326
327 __hash_link(br, fdb, hash);
328
329 out:
330 write_unlock_bh(&br->hash_lock);
331 }
332