diff options
author | Patrick McHardy <kaber@trash.net> | 2007-07-14 21:55:06 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2007-07-14 21:55:06 -0400 |
commit | b863ceb7ddcea8c55fcf1d7b2ac591d50aa7ed53 (patch) | |
tree | a65d5e4be77666600c0005c5f4c9091df63a3a1b /drivers/net | |
parent | 56addd6eeeb4e11f5a0af7093ca078e0f29140e0 (diff) |
[NET]: Add macvlan driver
Add macvlan driver, which allows to create virtual ethernet devices
based on MAC address.
Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/Kconfig | 10 | ||||
-rw-r--r-- | drivers/net/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/macvlan.c | 496 |
3 files changed, 507 insertions, 0 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index ba314adf68b8..d17d64eb7065 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig | |||
@@ -82,6 +82,16 @@ config BONDING | |||
82 | To compile this driver as a module, choose M here: the module | 82 | To compile this driver as a module, choose M here: the module |
83 | will be called bonding. | 83 | will be called bonding. |
84 | 84 | ||
85 | config MACVLAN | ||
86 | tristate "MAC-VLAN support (EXPERIMENTAL)" | ||
87 | depends on EXPERIMENTAL | ||
88 | ---help--- | ||
89 | This allows one to create virtual interfaces that map packets to | ||
90 | or from specific MAC addresses to a particular interface. | ||
91 | |||
92 | To compile this driver as a module, choose M here: the module | ||
93 | will be called macvlan. | ||
94 | |||
85 | config EQUALIZER | 95 | config EQUALIZER |
86 | tristate "EQL (serial line load balancing) support" | 96 | tristate "EQL (serial line load balancing) support" |
87 | ---help--- | 97 | ---help--- |
diff --git a/drivers/net/Makefile b/drivers/net/Makefile index a2241e6e1457..c26b8674213c 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile | |||
@@ -128,6 +128,7 @@ obj-$(CONFIG_SLHC) += slhc.o | |||
128 | 128 | ||
129 | obj-$(CONFIG_DUMMY) += dummy.o | 129 | obj-$(CONFIG_DUMMY) += dummy.o |
130 | obj-$(CONFIG_IFB) += ifb.o | 130 | obj-$(CONFIG_IFB) += ifb.o |
131 | obj-$(CONFIG_MACVLAN) += macvlan.o | ||
131 | obj-$(CONFIG_DE600) += de600.o | 132 | obj-$(CONFIG_DE600) += de600.o |
132 | obj-$(CONFIG_DE620) += de620.o | 133 | obj-$(CONFIG_DE620) += de620.o |
133 | obj-$(CONFIG_LANCE) += lance.o | 134 | obj-$(CONFIG_LANCE) += lance.o |
diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c new file mode 100644 index 000000000000..dc74d006e01f --- /dev/null +++ b/drivers/net/macvlan.c | |||
@@ -0,0 +1,496 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2007 Patrick McHardy <kaber@trash.net> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License as | ||
6 | * published by the Free Software Foundation; either version 2 of | ||
7 | * the License, or (at your option) any later version. | ||
8 | * | ||
9 | * The code this is based on carried the following copyright notice: | ||
10 | * --- | ||
11 | * (C) Copyright 2001-2006 | ||
12 | * Alex Zeffertt, Cambridge Broadband Ltd, ajz@cambridgebroadband.com | ||
13 | * Re-worked by Ben Greear <greearb@candelatech.com> | ||
14 | * --- | ||
15 | */ | ||
16 | #include <linux/kernel.h> | ||
17 | #include <linux/types.h> | ||
18 | #include <linux/module.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/errno.h> | ||
21 | #include <linux/slab.h> | ||
22 | #include <linux/string.h> | ||
23 | #include <linux/list.h> | ||
24 | #include <linux/notifier.h> | ||
25 | #include <linux/netdevice.h> | ||
26 | #include <linux/etherdevice.h> | ||
27 | #include <linux/ethtool.h> | ||
28 | #include <linux/if_arp.h> | ||
29 | #include <linux/if_link.h> | ||
30 | #include <linux/if_macvlan.h> | ||
31 | #include <net/rtnetlink.h> | ||
32 | |||
33 | #define MACVLAN_HASH_SIZE (1 << BITS_PER_BYTE) | ||
34 | |||
35 | struct macvlan_port { | ||
36 | struct net_device *dev; | ||
37 | struct hlist_head vlan_hash[MACVLAN_HASH_SIZE]; | ||
38 | struct list_head vlans; | ||
39 | }; | ||
40 | |||
41 | struct macvlan_dev { | ||
42 | struct net_device *dev; | ||
43 | struct list_head list; | ||
44 | struct hlist_node hlist; | ||
45 | struct macvlan_port *port; | ||
46 | struct net_device *lowerdev; | ||
47 | }; | ||
48 | |||
49 | |||
50 | static struct macvlan_dev *macvlan_hash_lookup(const struct macvlan_port *port, | ||
51 | const unsigned char *addr) | ||
52 | { | ||
53 | struct macvlan_dev *vlan; | ||
54 | struct hlist_node *n; | ||
55 | |||
56 | hlist_for_each_entry_rcu(vlan, n, &port->vlan_hash[addr[5]], hlist) { | ||
57 | if (!compare_ether_addr(vlan->dev->dev_addr, addr)) | ||
58 | return vlan; | ||
59 | } | ||
60 | return NULL; | ||
61 | } | ||
62 | |||
63 | static void macvlan_broadcast(struct sk_buff *skb, | ||
64 | const struct macvlan_port *port) | ||
65 | { | ||
66 | const struct ethhdr *eth = eth_hdr(skb); | ||
67 | const struct macvlan_dev *vlan; | ||
68 | struct hlist_node *n; | ||
69 | struct net_device *dev; | ||
70 | struct sk_buff *nskb; | ||
71 | unsigned int i; | ||
72 | |||
73 | for (i = 0; i < MACVLAN_HASH_SIZE; i++) { | ||
74 | hlist_for_each_entry_rcu(vlan, n, &port->vlan_hash[i], hlist) { | ||
75 | dev = vlan->dev; | ||
76 | if (unlikely(!(dev->flags & IFF_UP))) | ||
77 | continue; | ||
78 | |||
79 | nskb = skb_clone(skb, GFP_ATOMIC); | ||
80 | if (nskb == NULL) { | ||
81 | dev->stats.rx_errors++; | ||
82 | dev->stats.rx_dropped++; | ||
83 | continue; | ||
84 | } | ||
85 | |||
86 | dev->stats.rx_bytes += skb->len + ETH_HLEN; | ||
87 | dev->stats.rx_packets++; | ||
88 | dev->stats.multicast++; | ||
89 | dev->last_rx = jiffies; | ||
90 | |||
91 | nskb->dev = dev; | ||
92 | if (!compare_ether_addr(eth->h_dest, dev->broadcast)) | ||
93 | nskb->pkt_type = PACKET_BROADCAST; | ||
94 | else | ||
95 | nskb->pkt_type = PACKET_MULTICAST; | ||
96 | |||
97 | netif_rx(nskb); | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | |||
102 | /* called under rcu_read_lock() from netif_receive_skb */ | ||
103 | static struct sk_buff *macvlan_handle_frame(struct sk_buff *skb) | ||
104 | { | ||
105 | const struct ethhdr *eth = eth_hdr(skb); | ||
106 | const struct macvlan_port *port; | ||
107 | const struct macvlan_dev *vlan; | ||
108 | struct net_device *dev; | ||
109 | |||
110 | port = rcu_dereference(skb->dev->macvlan_port); | ||
111 | if (port == NULL) | ||
112 | return skb; | ||
113 | |||
114 | if (is_multicast_ether_addr(eth->h_dest)) { | ||
115 | macvlan_broadcast(skb, port); | ||
116 | return skb; | ||
117 | } | ||
118 | |||
119 | vlan = macvlan_hash_lookup(port, eth->h_dest); | ||
120 | if (vlan == NULL) | ||
121 | return skb; | ||
122 | |||
123 | dev = vlan->dev; | ||
124 | if (unlikely(!(dev->flags & IFF_UP))) { | ||
125 | kfree_skb(skb); | ||
126 | return NULL; | ||
127 | } | ||
128 | |||
129 | skb = skb_share_check(skb, GFP_ATOMIC); | ||
130 | if (skb == NULL) { | ||
131 | dev->stats.rx_errors++; | ||
132 | dev->stats.rx_dropped++; | ||
133 | return NULL; | ||
134 | } | ||
135 | |||
136 | dev->stats.rx_bytes += skb->len + ETH_HLEN; | ||
137 | dev->stats.rx_packets++; | ||
138 | dev->last_rx = jiffies; | ||
139 | |||
140 | skb->dev = dev; | ||
141 | skb->pkt_type = PACKET_HOST; | ||
142 | |||
143 | netif_rx(skb); | ||
144 | return NULL; | ||
145 | } | ||
146 | |||
147 | static int macvlan_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) | ||
148 | { | ||
149 | const struct macvlan_dev *vlan = netdev_priv(dev); | ||
150 | unsigned int len = skb->len; | ||
151 | int ret; | ||
152 | |||
153 | skb->dev = vlan->lowerdev; | ||
154 | ret = dev_queue_xmit(skb); | ||
155 | |||
156 | if (likely(ret == NET_XMIT_SUCCESS)) { | ||
157 | dev->stats.tx_packets++; | ||
158 | dev->stats.tx_bytes += len; | ||
159 | } else { | ||
160 | dev->stats.tx_errors++; | ||
161 | dev->stats.tx_aborted_errors++; | ||
162 | } | ||
163 | return NETDEV_TX_OK; | ||
164 | } | ||
165 | |||
166 | static int macvlan_hard_header(struct sk_buff *skb, struct net_device *dev, | ||
167 | unsigned short type, void *daddr, void *saddr, | ||
168 | unsigned len) | ||
169 | { | ||
170 | const struct macvlan_dev *vlan = netdev_priv(dev); | ||
171 | struct net_device *lowerdev = vlan->lowerdev; | ||
172 | |||
173 | return lowerdev->hard_header(skb, lowerdev, type, daddr, | ||
174 | saddr ? : dev->dev_addr, len); | ||
175 | } | ||
176 | |||
177 | static int macvlan_open(struct net_device *dev) | ||
178 | { | ||
179 | struct macvlan_dev *vlan = netdev_priv(dev); | ||
180 | struct macvlan_port *port = vlan->port; | ||
181 | struct net_device *lowerdev = vlan->lowerdev; | ||
182 | int err; | ||
183 | |||
184 | err = dev_unicast_add(lowerdev, dev->dev_addr, ETH_ALEN); | ||
185 | if (err < 0) | ||
186 | return err; | ||
187 | if (dev->flags & IFF_ALLMULTI) | ||
188 | dev_set_allmulti(lowerdev, 1); | ||
189 | |||
190 | hlist_add_head_rcu(&vlan->hlist, &port->vlan_hash[dev->dev_addr[5]]); | ||
191 | return 0; | ||
192 | } | ||
193 | |||
194 | static int macvlan_stop(struct net_device *dev) | ||
195 | { | ||
196 | struct macvlan_dev *vlan = netdev_priv(dev); | ||
197 | struct net_device *lowerdev = vlan->lowerdev; | ||
198 | |||
199 | dev_mc_unsync(lowerdev, dev); | ||
200 | if (dev->flags & IFF_ALLMULTI) | ||
201 | dev_set_allmulti(lowerdev, -1); | ||
202 | |||
203 | dev_unicast_delete(lowerdev, dev->dev_addr, ETH_ALEN); | ||
204 | |||
205 | hlist_del_rcu(&vlan->hlist); | ||
206 | synchronize_rcu(); | ||
207 | return 0; | ||
208 | } | ||
209 | |||
210 | static void macvlan_change_rx_flags(struct net_device *dev, int change) | ||
211 | { | ||
212 | struct macvlan_dev *vlan = netdev_priv(dev); | ||
213 | struct net_device *lowerdev = vlan->lowerdev; | ||
214 | |||
215 | if (change & IFF_ALLMULTI) | ||
216 | dev_set_allmulti(lowerdev, dev->flags & IFF_ALLMULTI ? 1 : -1); | ||
217 | } | ||
218 | |||
219 | static void macvlan_set_multicast_list(struct net_device *dev) | ||
220 | { | ||
221 | struct macvlan_dev *vlan = netdev_priv(dev); | ||
222 | |||
223 | dev_mc_sync(vlan->lowerdev, dev); | ||
224 | } | ||
225 | |||
226 | static int macvlan_change_mtu(struct net_device *dev, int new_mtu) | ||
227 | { | ||
228 | struct macvlan_dev *vlan = netdev_priv(dev); | ||
229 | |||
230 | if (new_mtu < 68 || vlan->lowerdev->mtu < new_mtu) | ||
231 | return -EINVAL; | ||
232 | dev->mtu = new_mtu; | ||
233 | return 0; | ||
234 | } | ||
235 | |||
236 | /* | ||
237 | * macvlan network devices have devices nesting below it and are a special | ||
238 | * "super class" of normal network devices; split their locks off into a | ||
239 | * separate class since they always nest. | ||
240 | */ | ||
241 | static struct lock_class_key macvlan_netdev_xmit_lock_key; | ||
242 | |||
243 | #define MACVLAN_FEATURES \ | ||
244 | (NETIF_F_SG | NETIF_F_ALL_CSUM | NETIF_F_HIGHDMA | NETIF_F_FRAGLIST | \ | ||
245 | NETIF_F_GSO | NETIF_F_TSO | NETIF_F_UFO | NETIF_F_GSO_ROBUST | \ | ||
246 | NETIF_F_TSO_ECN | NETIF_F_TSO6) | ||
247 | |||
248 | #define MACVLAN_STATE_MASK \ | ||
249 | ((1<<__LINK_STATE_NOCARRIER) | (1<<__LINK_STATE_DORMANT)) | ||
250 | |||
251 | static int macvlan_init(struct net_device *dev) | ||
252 | { | ||
253 | struct macvlan_dev *vlan = netdev_priv(dev); | ||
254 | const struct net_device *lowerdev = vlan->lowerdev; | ||
255 | |||
256 | dev->state = (dev->state & ~MACVLAN_STATE_MASK) | | ||
257 | (lowerdev->state & MACVLAN_STATE_MASK); | ||
258 | dev->features = lowerdev->features & MACVLAN_FEATURES; | ||
259 | dev->iflink = lowerdev->ifindex; | ||
260 | |||
261 | lockdep_set_class(&dev->_xmit_lock, &macvlan_netdev_xmit_lock_key); | ||
262 | return 0; | ||
263 | } | ||
264 | |||
265 | static void macvlan_ethtool_get_drvinfo(struct net_device *dev, | ||
266 | struct ethtool_drvinfo *drvinfo) | ||
267 | { | ||
268 | snprintf(drvinfo->driver, 32, "macvlan"); | ||
269 | snprintf(drvinfo->version, 32, "0.1"); | ||
270 | } | ||
271 | |||
272 | static u32 macvlan_ethtool_get_rx_csum(struct net_device *dev) | ||
273 | { | ||
274 | const struct macvlan_dev *vlan = netdev_priv(dev); | ||
275 | struct net_device *lowerdev = vlan->lowerdev; | ||
276 | |||
277 | if (lowerdev->ethtool_ops->get_rx_csum == NULL) | ||
278 | return 0; | ||
279 | return lowerdev->ethtool_ops->get_rx_csum(lowerdev); | ||
280 | } | ||
281 | |||
282 | static const struct ethtool_ops macvlan_ethtool_ops = { | ||
283 | .get_link = ethtool_op_get_link, | ||
284 | .get_rx_csum = macvlan_ethtool_get_rx_csum, | ||
285 | .get_tx_csum = ethtool_op_get_tx_csum, | ||
286 | .get_tso = ethtool_op_get_tso, | ||
287 | .get_ufo = ethtool_op_get_ufo, | ||
288 | .get_sg = ethtool_op_get_sg, | ||
289 | .get_drvinfo = macvlan_ethtool_get_drvinfo, | ||
290 | }; | ||
291 | |||
292 | static void macvlan_setup(struct net_device *dev) | ||
293 | { | ||
294 | ether_setup(dev); | ||
295 | |||
296 | dev->init = macvlan_init; | ||
297 | dev->open = macvlan_open; | ||
298 | dev->stop = macvlan_stop; | ||
299 | dev->change_mtu = macvlan_change_mtu; | ||
300 | dev->change_rx_flags = macvlan_change_rx_flags; | ||
301 | dev->set_multicast_list = macvlan_set_multicast_list; | ||
302 | dev->hard_header = macvlan_hard_header; | ||
303 | dev->hard_start_xmit = macvlan_hard_start_xmit; | ||
304 | dev->destructor = free_netdev; | ||
305 | dev->ethtool_ops = &macvlan_ethtool_ops; | ||
306 | dev->tx_queue_len = 0; | ||
307 | } | ||
308 | |||
309 | static int macvlan_port_create(struct net_device *dev) | ||
310 | { | ||
311 | struct macvlan_port *port; | ||
312 | unsigned int i; | ||
313 | |||
314 | if (dev->type != ARPHRD_ETHER || dev->flags & IFF_LOOPBACK) | ||
315 | return -EINVAL; | ||
316 | |||
317 | port = kzalloc(sizeof(*port), GFP_KERNEL); | ||
318 | if (port == NULL) | ||
319 | return -ENOMEM; | ||
320 | |||
321 | port->dev = dev; | ||
322 | INIT_LIST_HEAD(&port->vlans); | ||
323 | for (i = 0; i < MACVLAN_HASH_SIZE; i++) | ||
324 | INIT_HLIST_HEAD(&port->vlan_hash[i]); | ||
325 | rcu_assign_pointer(dev->macvlan_port, port); | ||
326 | return 0; | ||
327 | } | ||
328 | |||
329 | static void macvlan_port_destroy(struct net_device *dev) | ||
330 | { | ||
331 | struct macvlan_port *port = dev->macvlan_port; | ||
332 | |||
333 | rcu_assign_pointer(dev->macvlan_port, NULL); | ||
334 | synchronize_rcu(); | ||
335 | kfree(port); | ||
336 | } | ||
337 | |||
338 | static void macvlan_transfer_operstate(struct net_device *dev) | ||
339 | { | ||
340 | struct macvlan_dev *vlan = netdev_priv(dev); | ||
341 | const struct net_device *lowerdev = vlan->lowerdev; | ||
342 | |||
343 | if (lowerdev->operstate == IF_OPER_DORMANT) | ||
344 | netif_dormant_on(dev); | ||
345 | else | ||
346 | netif_dormant_off(dev); | ||
347 | |||
348 | if (netif_carrier_ok(lowerdev)) { | ||
349 | if (!netif_carrier_ok(dev)) | ||
350 | netif_carrier_on(dev); | ||
351 | } else { | ||
352 | if (netif_carrier_ok(lowerdev)) | ||
353 | netif_carrier_off(dev); | ||
354 | } | ||
355 | } | ||
356 | |||
357 | static int macvlan_validate(struct nlattr *tb[], struct nlattr *data[]) | ||
358 | { | ||
359 | if (tb[IFLA_ADDRESS]) { | ||
360 | if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN) | ||
361 | return -EINVAL; | ||
362 | if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) | ||
363 | return -EADDRNOTAVAIL; | ||
364 | } | ||
365 | return 0; | ||
366 | } | ||
367 | |||
368 | static int macvlan_newlink(struct net_device *dev, | ||
369 | struct nlattr *tb[], struct nlattr *data[]) | ||
370 | { | ||
371 | struct macvlan_dev *vlan = netdev_priv(dev); | ||
372 | struct macvlan_port *port; | ||
373 | struct net_device *lowerdev; | ||
374 | int err; | ||
375 | |||
376 | if (!tb[IFLA_LINK]) | ||
377 | return -EINVAL; | ||
378 | |||
379 | lowerdev = __dev_get_by_index(nla_get_u32(tb[IFLA_LINK])); | ||
380 | if (lowerdev == NULL) | ||
381 | return -ENODEV; | ||
382 | |||
383 | if (!tb[IFLA_MTU]) | ||
384 | dev->mtu = lowerdev->mtu; | ||
385 | else if (dev->mtu > lowerdev->mtu) | ||
386 | return -EINVAL; | ||
387 | |||
388 | if (!tb[IFLA_ADDRESS]) | ||
389 | random_ether_addr(dev->dev_addr); | ||
390 | |||
391 | if (lowerdev->macvlan_port == NULL) { | ||
392 | err = macvlan_port_create(lowerdev); | ||
393 | if (err < 0) | ||
394 | return err; | ||
395 | } | ||
396 | port = lowerdev->macvlan_port; | ||
397 | |||
398 | vlan->lowerdev = lowerdev; | ||
399 | vlan->dev = dev; | ||
400 | vlan->port = port; | ||
401 | |||
402 | err = register_netdevice(dev); | ||
403 | if (err < 0) | ||
404 | return err; | ||
405 | |||
406 | list_add_tail(&vlan->list, &port->vlans); | ||
407 | macvlan_transfer_operstate(dev); | ||
408 | return 0; | ||
409 | } | ||
410 | |||
411 | static void macvlan_dellink(struct net_device *dev) | ||
412 | { | ||
413 | struct macvlan_dev *vlan = netdev_priv(dev); | ||
414 | struct macvlan_port *port = vlan->port; | ||
415 | |||
416 | list_del(&vlan->list); | ||
417 | unregister_netdevice(dev); | ||
418 | |||
419 | if (list_empty(&port->vlans)) | ||
420 | macvlan_port_destroy(dev); | ||
421 | } | ||
422 | |||
423 | static struct rtnl_link_ops macvlan_link_ops __read_mostly = { | ||
424 | .kind = "macvlan", | ||
425 | .priv_size = sizeof(struct macvlan_dev), | ||
426 | .setup = macvlan_setup, | ||
427 | .validate = macvlan_validate, | ||
428 | .newlink = macvlan_newlink, | ||
429 | .dellink = macvlan_dellink, | ||
430 | }; | ||
431 | |||
432 | static int macvlan_device_event(struct notifier_block *unused, | ||
433 | unsigned long event, void *ptr) | ||
434 | { | ||
435 | struct net_device *dev = ptr; | ||
436 | struct macvlan_dev *vlan, *next; | ||
437 | struct macvlan_port *port; | ||
438 | |||
439 | port = dev->macvlan_port; | ||
440 | if (port == NULL) | ||
441 | return NOTIFY_DONE; | ||
442 | |||
443 | switch (event) { | ||
444 | case NETDEV_CHANGE: | ||
445 | list_for_each_entry(vlan, &port->vlans, list) | ||
446 | macvlan_transfer_operstate(vlan->dev); | ||
447 | break; | ||
448 | case NETDEV_FEAT_CHANGE: | ||
449 | list_for_each_entry(vlan, &port->vlans, list) { | ||
450 | vlan->dev->features = dev->features & MACVLAN_FEATURES; | ||
451 | netdev_features_change(vlan->dev); | ||
452 | } | ||
453 | break; | ||
454 | case NETDEV_UNREGISTER: | ||
455 | list_for_each_entry_safe(vlan, next, &port->vlans, list) | ||
456 | macvlan_dellink(vlan->dev); | ||
457 | break; | ||
458 | } | ||
459 | return NOTIFY_DONE; | ||
460 | } | ||
461 | |||
462 | static struct notifier_block macvlan_notifier_block __read_mostly = { | ||
463 | .notifier_call = macvlan_device_event, | ||
464 | }; | ||
465 | |||
466 | static int __init macvlan_init_module(void) | ||
467 | { | ||
468 | int err; | ||
469 | |||
470 | register_netdevice_notifier(&macvlan_notifier_block); | ||
471 | macvlan_handle_frame_hook = macvlan_handle_frame; | ||
472 | |||
473 | err = rtnl_link_register(&macvlan_link_ops); | ||
474 | if (err < 0) | ||
475 | goto err1; | ||
476 | return 0; | ||
477 | err1: | ||
478 | macvlan_handle_frame_hook = macvlan_handle_frame; | ||
479 | unregister_netdevice_notifier(&macvlan_notifier_block); | ||
480 | return err; | ||
481 | } | ||
482 | |||
483 | static void __exit macvlan_cleanup_module(void) | ||
484 | { | ||
485 | rtnl_link_unregister(&macvlan_link_ops); | ||
486 | macvlan_handle_frame_hook = NULL; | ||
487 | unregister_netdevice_notifier(&macvlan_notifier_block); | ||
488 | } | ||
489 | |||
490 | module_init(macvlan_init_module); | ||
491 | module_exit(macvlan_cleanup_module); | ||
492 | |||
493 | MODULE_LICENSE("GPL"); | ||
494 | MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>"); | ||
495 | MODULE_DESCRIPTION("Driver for MAC address based VLANs"); | ||
496 | MODULE_ALIAS_RTNL_LINK("macvlan"); | ||