Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * Linux NET3: Multicast List maintenance. | |
3 | * | |
4 | * Authors: | |
5 | * Tim Kordas <tjk@nostromo.eeap.cwru.edu> | |
6 | * Richard Underwood <richard@wuzz.demon.co.uk> | |
7 | * | |
8 | * Stir fried together from the IP multicast and CAP patches above | |
9 | * Alan Cox <Alan.Cox@linux.org> | |
10 | * | |
11 | * Fixes: | |
12 | * Alan Cox : Update the device on a real delete | |
13 | * rather than any time but... | |
14 | * Alan Cox : IFF_ALLMULTI support. | |
15 | * Alan Cox : New format set_multicast_list() calls. | |
16 | * Gleb Natapov : Remove dev_mc_lock. | |
17 | * | |
18 | * This program is free software; you can redistribute it and/or | |
19 | * modify it under the terms of the GNU General Public License | |
20 | * as published by the Free Software Foundation; either version | |
21 | * 2 of the License, or (at your option) any later version. | |
22 | */ | |
23 | ||
bf0d5249 | 24 | #include <linux/module.h> |
1da177e4 LT |
25 | #include <asm/uaccess.h> |
26 | #include <asm/system.h> | |
27 | #include <linux/bitops.h> | |
28 | #include <linux/types.h> | |
29 | #include <linux/kernel.h> | |
30 | #include <linux/sched.h> | |
31 | #include <linux/string.h> | |
32 | #include <linux/mm.h> | |
33 | #include <linux/socket.h> | |
34 | #include <linux/sockios.h> | |
35 | #include <linux/in.h> | |
36 | #include <linux/errno.h> | |
37 | #include <linux/interrupt.h> | |
38 | #include <linux/if_ether.h> | |
39 | #include <linux/inet.h> | |
40 | #include <linux/netdevice.h> | |
41 | #include <linux/etherdevice.h> | |
42 | #include <linux/proc_fs.h> | |
43 | #include <linux/seq_file.h> | |
44 | #include <linux/init.h> | |
45 | #include <net/ip.h> | |
46 | #include <net/route.h> | |
47 | #include <linux/skbuff.h> | |
48 | #include <net/sock.h> | |
49 | #include <net/arp.h> | |
50 | ||
51 | ||
52 | /* | |
53 | * Device multicast list maintenance. | |
54 | * | |
55 | * This is used both by IP and by the user level maintenance functions. | |
56 | * Unlike BSD we maintain a usage count on a given multicast address so | |
57 | * that a casual user application can add/delete multicasts used by | |
58 | * protocols without doing damage to the protocols when it deletes the | |
59 | * entries. It also helps IP as it tracks overlapping maps. | |
60 | * | |
61 | * Device mc lists are changed by bh at least if IPv6 is enabled, | |
62 | * so that it must be bh protected. | |
63 | * | |
932ff279 | 64 | * We block accesses to device mc filters with netif_tx_lock. |
1da177e4 LT |
65 | */ |
66 | ||
67 | /* | |
68 | * Update the multicast list into the physical NIC controller. | |
69 | */ | |
70 | ||
71 | static void __dev_mc_upload(struct net_device *dev) | |
72 | { | |
73 | /* Don't do anything till we up the interface | |
74 | * [dev_open will call this function so the list will | |
75 | * stay sane] | |
76 | */ | |
77 | ||
78 | if (!(dev->flags&IFF_UP)) | |
79 | return; | |
80 | ||
81 | /* | |
82 | * Devices with no set multicast or which have been | |
83 | * detached don't get set. | |
84 | */ | |
85 | ||
86 | if (dev->set_multicast_list == NULL || | |
87 | !netif_device_present(dev)) | |
88 | return; | |
89 | ||
90 | dev->set_multicast_list(dev); | |
91 | } | |
92 | ||
93 | void dev_mc_upload(struct net_device *dev) | |
94 | { | |
932ff279 | 95 | netif_tx_lock_bh(dev); |
1da177e4 | 96 | __dev_mc_upload(dev); |
932ff279 | 97 | netif_tx_unlock_bh(dev); |
1da177e4 LT |
98 | } |
99 | ||
100 | /* | |
101 | * Delete a device level multicast | |
102 | */ | |
103 | ||
104 | int dev_mc_delete(struct net_device *dev, void *addr, int alen, int glbl) | |
105 | { | |
106 | int err = 0; | |
107 | struct dev_mc_list *dmi, **dmip; | |
108 | ||
932ff279 | 109 | netif_tx_lock_bh(dev); |
1da177e4 LT |
110 | |
111 | for (dmip = &dev->mc_list; (dmi = *dmip) != NULL; dmip = &dmi->next) { | |
112 | /* | |
113 | * Find the entry we want to delete. The device could | |
114 | * have variable length entries so check these too. | |
115 | */ | |
116 | if (memcmp(dmi->dmi_addr, addr, dmi->dmi_addrlen) == 0 && | |
117 | alen == dmi->dmi_addrlen) { | |
118 | if (glbl) { | |
119 | int old_glbl = dmi->dmi_gusers; | |
120 | dmi->dmi_gusers = 0; | |
121 | if (old_glbl == 0) | |
122 | break; | |
123 | } | |
124 | if (--dmi->dmi_users) | |
125 | goto done; | |
126 | ||
127 | /* | |
128 | * Last user. So delete the entry. | |
129 | */ | |
130 | *dmip = dmi->next; | |
131 | dev->mc_count--; | |
132 | ||
133 | kfree(dmi); | |
134 | ||
135 | /* | |
136 | * We have altered the list, so the card | |
137 | * loaded filter is now wrong. Fix it | |
138 | */ | |
139 | __dev_mc_upload(dev); | |
140 | ||
932ff279 | 141 | netif_tx_unlock_bh(dev); |
1da177e4 LT |
142 | return 0; |
143 | } | |
144 | } | |
145 | err = -ENOENT; | |
146 | done: | |
932ff279 | 147 | netif_tx_unlock_bh(dev); |
1da177e4 LT |
148 | return err; |
149 | } | |
150 | ||
151 | /* | |
152 | * Add a device level multicast | |
153 | */ | |
154 | ||
155 | int dev_mc_add(struct net_device *dev, void *addr, int alen, int glbl) | |
156 | { | |
157 | int err = 0; | |
158 | struct dev_mc_list *dmi, *dmi1; | |
159 | ||
8b3a7005 | 160 | dmi1 = kmalloc(sizeof(*dmi), GFP_ATOMIC); |
1da177e4 | 161 | |
932ff279 | 162 | netif_tx_lock_bh(dev); |
1da177e4 LT |
163 | for (dmi = dev->mc_list; dmi != NULL; dmi = dmi->next) { |
164 | if (memcmp(dmi->dmi_addr, addr, dmi->dmi_addrlen) == 0 && | |
165 | dmi->dmi_addrlen == alen) { | |
166 | if (glbl) { | |
167 | int old_glbl = dmi->dmi_gusers; | |
168 | dmi->dmi_gusers = 1; | |
169 | if (old_glbl) | |
170 | goto done; | |
171 | } | |
172 | dmi->dmi_users++; | |
173 | goto done; | |
174 | } | |
175 | } | |
176 | ||
177 | if ((dmi = dmi1) == NULL) { | |
932ff279 | 178 | netif_tx_unlock_bh(dev); |
1da177e4 LT |
179 | return -ENOMEM; |
180 | } | |
181 | memcpy(dmi->dmi_addr, addr, alen); | |
182 | dmi->dmi_addrlen = alen; | |
183 | dmi->next = dev->mc_list; | |
184 | dmi->dmi_users = 1; | |
185 | dmi->dmi_gusers = glbl ? 1 : 0; | |
186 | dev->mc_list = dmi; | |
187 | dev->mc_count++; | |
188 | ||
189 | __dev_mc_upload(dev); | |
190 | ||
932ff279 | 191 | netif_tx_unlock_bh(dev); |
1da177e4 LT |
192 | return 0; |
193 | ||
194 | done: | |
932ff279 | 195 | netif_tx_unlock_bh(dev); |
a51482bd | 196 | kfree(dmi1); |
1da177e4 LT |
197 | return err; |
198 | } | |
199 | ||
200 | /* | |
201 | * Discard multicast list when a device is downed | |
202 | */ | |
203 | ||
204 | void dev_mc_discard(struct net_device *dev) | |
205 | { | |
932ff279 | 206 | netif_tx_lock_bh(dev); |
1da177e4 LT |
207 | |
208 | while (dev->mc_list != NULL) { | |
209 | struct dev_mc_list *tmp = dev->mc_list; | |
210 | dev->mc_list = tmp->next; | |
211 | if (tmp->dmi_users > tmp->dmi_gusers) | |
212 | printk("dev_mc_discard: multicast leakage! dmi_users=%d\n", tmp->dmi_users); | |
213 | kfree(tmp); | |
214 | } | |
215 | dev->mc_count = 0; | |
216 | ||
932ff279 | 217 | netif_tx_unlock_bh(dev); |
1da177e4 LT |
218 | } |
219 | ||
220 | #ifdef CONFIG_PROC_FS | |
221 | static void *dev_mc_seq_start(struct seq_file *seq, loff_t *pos) | |
222 | { | |
223 | struct net_device *dev; | |
224 | loff_t off = 0; | |
225 | ||
226 | read_lock(&dev_base_lock); | |
227 | for (dev = dev_base; dev; dev = dev->next) { | |
228 | if (off++ == *pos) | |
229 | return dev; | |
230 | } | |
231 | return NULL; | |
232 | } | |
233 | ||
234 | static void *dev_mc_seq_next(struct seq_file *seq, void *v, loff_t *pos) | |
235 | { | |
236 | struct net_device *dev = v; | |
237 | ++*pos; | |
238 | return dev->next; | |
239 | } | |
240 | ||
241 | static void dev_mc_seq_stop(struct seq_file *seq, void *v) | |
242 | { | |
243 | read_unlock(&dev_base_lock); | |
244 | } | |
245 | ||
246 | ||
247 | static int dev_mc_seq_show(struct seq_file *seq, void *v) | |
248 | { | |
249 | struct dev_mc_list *m; | |
250 | struct net_device *dev = v; | |
251 | ||
932ff279 | 252 | netif_tx_lock_bh(dev); |
1da177e4 LT |
253 | for (m = dev->mc_list; m; m = m->next) { |
254 | int i; | |
255 | ||
256 | seq_printf(seq, "%-4d %-15s %-5d %-5d ", dev->ifindex, | |
257 | dev->name, m->dmi_users, m->dmi_gusers); | |
258 | ||
259 | for (i = 0; i < m->dmi_addrlen; i++) | |
260 | seq_printf(seq, "%02x", m->dmi_addr[i]); | |
261 | ||
262 | seq_putc(seq, '\n'); | |
263 | } | |
932ff279 | 264 | netif_tx_unlock_bh(dev); |
1da177e4 LT |
265 | return 0; |
266 | } | |
267 | ||
268 | static struct seq_operations dev_mc_seq_ops = { | |
269 | .start = dev_mc_seq_start, | |
270 | .next = dev_mc_seq_next, | |
271 | .stop = dev_mc_seq_stop, | |
272 | .show = dev_mc_seq_show, | |
273 | }; | |
274 | ||
275 | static int dev_mc_seq_open(struct inode *inode, struct file *file) | |
276 | { | |
277 | return seq_open(file, &dev_mc_seq_ops); | |
278 | } | |
279 | ||
280 | static struct file_operations dev_mc_seq_fops = { | |
281 | .owner = THIS_MODULE, | |
282 | .open = dev_mc_seq_open, | |
283 | .read = seq_read, | |
284 | .llseek = seq_lseek, | |
285 | .release = seq_release, | |
286 | }; | |
287 | ||
288 | #endif | |
289 | ||
290 | void __init dev_mcast_init(void) | |
291 | { | |
292 | proc_net_fops_create("dev_mcast", 0, &dev_mc_seq_fops); | |
293 | } | |
294 | ||
295 | EXPORT_SYMBOL(dev_mc_add); | |
296 | EXPORT_SYMBOL(dev_mc_delete); | |
297 | EXPORT_SYMBOL(dev_mc_upload); |