X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=net%2F8021q%2Fvlan.c;h=2b7390e377b3f3a7d6a9e92fb2cbea73d8b6d401;hb=45f902178022439795a21e14f886b8ccb49a75d2;hp=3678f0719934e65397452f56b86a385e34ab050c;hpb=c17d8874f9959070552fddf1b4e1d73c0c144c0f;p=safe%2Fjmp%2Flinux-2.6 diff --git a/net/8021q/vlan.c b/net/8021q/vlan.c index 3678f07..2b7390e 100644 --- a/net/8021q/vlan.c +++ b/net/8021q/vlan.c @@ -3,7 +3,7 @@ * Ethernet-type device handling. * * Authors: Ben Greear - * Please send support related email to: vlan@scry.wanfear.com + * Please send support related email to: netdev@vger.kernel.org * VLAN Home Page: http://www.candelatech.com/~greear/vlan.html * * Fixes: @@ -18,19 +18,20 @@ * 2 of the License, or (at your option) any later version. */ -#include /* for copy_from_user */ #include #include #include #include -#include -#include -#include #include +#include #include #include #include #include +#include +#include +#include +#include #include #include "vlan.h" @@ -40,133 +41,37 @@ /* Global VLAN variables */ +int vlan_net_id; + /* Our listing of VLAN group(s) */ static struct hlist_head vlan_group_hash[VLAN_GRP_HASH_SIZE]; -#define vlan_grp_hashfn(IDX) ((((IDX) >> VLAN_GRP_HASH_SHIFT) ^ (IDX)) & VLAN_GRP_HASH_MASK) - -static char vlan_fullname[] = "802.1Q VLAN Support"; -static char vlan_version[] = DRV_VERSION; -static char vlan_copyright[] = "Ben Greear "; -static char vlan_buggyright[] = "David S. Miller "; - -static int vlan_device_event(struct notifier_block *, unsigned long, void *); -static int vlan_ioctl_handler(void __user *); -static int unregister_vlan_dev(struct net_device *, unsigned short ); - -static struct notifier_block vlan_notifier_block = { - .notifier_call = vlan_device_event, -}; - -/* These may be changed at run-time through IOCTLs */ -/* Determines interface naming scheme. */ -unsigned short vlan_name_type = VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD; +const char vlan_fullname[] = "802.1Q VLAN Support"; +const char vlan_version[] = DRV_VERSION; +static const char vlan_copyright[] = "Ben Greear "; +static const char vlan_buggyright[] = "David S. Miller "; -static struct packet_type vlan_packet_type = { - .type = __constant_htons(ETH_P_8021Q), +static struct packet_type vlan_packet_type __read_mostly = { + .type = cpu_to_be16(ETH_P_8021Q), .func = vlan_skb_recv, /* VLAN receive method */ }; /* End of global variables definitions. */ -/* - * Function vlan_proto_init (pro) - * - * Initialize VLAN protocol layer, - * - */ -static int __init vlan_proto_init(void) +static inline unsigned int vlan_grp_hashfn(unsigned int idx) { - int err; - - printk(VLAN_INF "%s v%s %s\n", - vlan_fullname, vlan_version, vlan_copyright); - printk(VLAN_INF "All bugs added by %s\n", - vlan_buggyright); - - /* proc file system initialization */ - err = vlan_proc_init(); - if (err < 0) { - printk(KERN_ERR - "%s %s: can't create entry in proc filesystem!\n", - __FUNCTION__, VLAN_NAME); - return err; - } - - dev_add_pack(&vlan_packet_type); - - /* Register us to receive netdevice events */ - err = register_netdevice_notifier(&vlan_notifier_block); - if (err < 0) { - dev_remove_pack(&vlan_packet_type); - vlan_proc_cleanup(); - return err; - } - - vlan_ioctl_set(vlan_ioctl_handler); - - return 0; -} - -/* Cleanup all vlan devices - * Note: devices that have been registered that but not - * brought up will exist but have no module ref count. - */ -static void __exit vlan_cleanup_devices(void) -{ - struct net_device *dev, *nxt; - - rtnl_lock(); - for_each_netdev_safe(dev, nxt) { - if (dev->priv_flags & IFF_802_1Q_VLAN) { - unregister_vlan_dev(VLAN_DEV_INFO(dev)->real_dev, - VLAN_DEV_INFO(dev)->vlan_id); - - unregister_netdevice(dev); - } - } - rtnl_unlock(); + return ((idx >> VLAN_GRP_HASH_SHIFT) ^ idx) & VLAN_GRP_HASH_MASK; } -/* - * Module 'remove' entry point. - * o delete /proc/net/router directory and static entries. - */ -static void __exit vlan_cleanup_module(void) -{ - int i; - - vlan_ioctl_set(NULL); - - /* Un-register us from receiving netdevice events */ - unregister_netdevice_notifier(&vlan_notifier_block); - - dev_remove_pack(&vlan_packet_type); - vlan_cleanup_devices(); - - /* This table must be empty if there are no module - * references left. - */ - for (i = 0; i < VLAN_GRP_HASH_SIZE; i++) { - BUG_ON(!hlist_empty(&vlan_group_hash[i])); - } - vlan_proc_cleanup(); - - synchronize_net(); -} - -module_init(vlan_proto_init); -module_exit(vlan_cleanup_module); - /* Must be invoked with RCU read lock (no preempt) */ -static struct vlan_group *__vlan_find_group(int real_dev_ifindex) +static struct vlan_group *__vlan_find_group(struct net_device *real_dev) { struct vlan_group *grp; struct hlist_node *n; - int hash = vlan_grp_hashfn(real_dev_ifindex); + int hash = vlan_grp_hashfn(real_dev->ifindex); hlist_for_each_entry_rcu(grp, n, &vlan_group_hash[hash], hlist) { - if (grp->real_dev_ifindex == real_dev_ifindex) + if (grp->real_dev == real_dev) return grp; } @@ -177,13 +82,12 @@ static struct vlan_group *__vlan_find_group(int real_dev_ifindex) * * Must be invoked with RCU read lock (no preempt) */ -struct net_device *__find_vlan_dev(struct net_device *real_dev, - unsigned short VID) +struct net_device *__find_vlan_dev(struct net_device *real_dev, u16 vlan_id) { - struct vlan_group *grp = __vlan_find_group(real_dev->ifindex); + struct vlan_group *grp = __vlan_find_group(real_dev); if (grp) - return vlan_group_get_device(grp, VID); + return vlan_group_get_device(grp, vlan_id); return NULL; } @@ -192,134 +96,95 @@ static void vlan_group_free(struct vlan_group *grp) { int i; - for (i=0; i < VLAN_GROUP_ARRAY_SPLIT_PARTS; i++) + for (i = 0; i < VLAN_GROUP_ARRAY_SPLIT_PARTS; i++) kfree(grp->vlan_devices_arrays[i]); kfree(grp); } -static void vlan_rcu_free(struct rcu_head *rcu) -{ - vlan_group_free(container_of(rcu, struct vlan_group, rcu)); -} - - -/* This returns 0 if everything went fine. - * It will return 1 if the group was killed as a result. - * A negative return indicates failure. - * - * The RTNL lock must be held. - */ -static int unregister_vlan_dev(struct net_device *real_dev, - unsigned short vlan_id) +static struct vlan_group *vlan_group_alloc(struct net_device *real_dev) { - struct net_device *dev = NULL; - int real_dev_ifindex = real_dev->ifindex; struct vlan_group *grp; - int i, ret; -#ifdef VLAN_DEBUG - printk(VLAN_DBG "%s: VID: %i\n", __FUNCTION__, vlan_id); -#endif - - /* sanity check */ - if (vlan_id >= VLAN_VID_MASK) - return -EINVAL; - - ASSERT_RTNL(); - grp = __vlan_find_group(real_dev_ifindex); + grp = kzalloc(sizeof(struct vlan_group), GFP_KERNEL); + if (!grp) + return NULL; - ret = 0; + grp->real_dev = real_dev; + hlist_add_head_rcu(&grp->hlist, + &vlan_group_hash[vlan_grp_hashfn(real_dev->ifindex)]); + return grp; +} - if (grp) { - dev = vlan_group_get_device(grp, vlan_id); - if (dev) { - /* Remove proc entry */ - vlan_proc_rem_dev(dev); +static int vlan_group_prealloc_vid(struct vlan_group *vg, u16 vlan_id) +{ + struct net_device **array; + unsigned int size; - /* Take it out of our own structures, but be sure to - * interlock with HW accelerating devices or SW vlan - * input packet processing. - */ - if (real_dev->features & NETIF_F_HW_VLAN_FILTER) - real_dev->vlan_rx_kill_vid(real_dev, vlan_id); + ASSERT_RTNL(); - vlan_group_set_device(grp, vlan_id, NULL); - synchronize_net(); + array = vg->vlan_devices_arrays[vlan_id / VLAN_GROUP_ARRAY_PART_LEN]; + if (array != NULL) + return 0; + size = sizeof(struct net_device *) * VLAN_GROUP_ARRAY_PART_LEN; + array = kzalloc(size, GFP_KERNEL); + if (array == NULL) + return -ENOBUFS; - /* Caller unregisters (and if necessary, puts) - * VLAN device, but we get rid of the reference to - * real_dev here. - */ - dev_put(real_dev); + vg->vlan_devices_arrays[vlan_id / VLAN_GROUP_ARRAY_PART_LEN] = array; + return 0; +} - /* If the group is now empty, kill off the - * group. - */ - for (i = 0; i < VLAN_VID_MASK; i++) - if (vlan_group_get_device(grp, i)) - break; +static void vlan_rcu_free(struct rcu_head *rcu) +{ + vlan_group_free(container_of(rcu, struct vlan_group, rcu)); +} - if (i == VLAN_VID_MASK) { - if (real_dev->features & NETIF_F_HW_VLAN_RX) - real_dev->vlan_rx_register(real_dev, NULL); +void unregister_vlan_dev(struct net_device *dev) +{ + struct vlan_dev_info *vlan = vlan_dev_info(dev); + struct net_device *real_dev = vlan->real_dev; + const struct net_device_ops *ops = real_dev->netdev_ops; + struct vlan_group *grp; + u16 vlan_id = vlan->vlan_id; - hlist_del_rcu(&grp->hlist); + ASSERT_RTNL(); - /* Free the group, after all cpu's are done. */ - call_rcu(&grp->rcu, vlan_rcu_free); + grp = __vlan_find_group(real_dev); + BUG_ON(!grp); - grp = NULL; - ret = 1; - } - } - } + /* Take it out of our own structures, but be sure to interlock with + * HW accelerating devices or SW vlan input packet processing. + */ + if (real_dev->features & NETIF_F_HW_VLAN_FILTER) + ops->ndo_vlan_rx_kill_vid(real_dev, vlan_id); - return ret; -} + vlan_group_set_device(grp, vlan_id, NULL); + grp->nr_vlans--; -static int unregister_vlan_device(struct net_device *dev) -{ - int ret; + synchronize_net(); - ret = unregister_vlan_dev(VLAN_DEV_INFO(dev)->real_dev, - VLAN_DEV_INFO(dev)->vlan_id); unregister_netdevice(dev); - if (ret == 1) - ret = 0; - return ret; -} + /* If the group is now empty, kill off the group. */ + if (grp->nr_vlans == 0) { + vlan_gvrp_uninit_applicant(real_dev); -static void vlan_setup(struct net_device *new_dev) -{ - SET_MODULE_OWNER(new_dev); + if (real_dev->features & NETIF_F_HW_VLAN_RX) + ops->ndo_vlan_rx_register(real_dev, NULL); - /* new_dev->ifindex = 0; it will be set when added to - * the global list. - * iflink is set as well. - */ - new_dev->get_stats = vlan_dev_get_stats; + hlist_del_rcu(&grp->hlist); - /* Make this thing known as a VLAN device */ - new_dev->priv_flags |= IFF_802_1Q_VLAN; + /* Free the group, after all cpu's are done. */ + call_rcu(&grp->rcu, vlan_rcu_free); + } - /* Set us up to have no queue, as the underlying Hardware device - * can do all the queueing we could want. - */ - new_dev->tx_queue_len = 0; - - /* set up method calls */ - new_dev->change_mtu = vlan_dev_change_mtu; - new_dev->open = vlan_dev_open; - new_dev->stop = vlan_dev_stop; - new_dev->set_mac_address = vlan_dev_set_mac_address; - new_dev->set_multicast_list = vlan_dev_set_multicast_list; - new_dev->destructor = free_netdev; - new_dev->do_ioctl = vlan_dev_ioctl; + /* Get rid of the vlan's reference to real_dev */ + dev_put(real_dev); } -static void vlan_transfer_operstate(const struct net_device *dev, struct net_device *vlandev) +static void vlan_transfer_operstate(const struct net_device *dev, + struct net_device *vlandev) { /* Have to respect userspace enforced dormant state * of real device, also must allow supplicant running @@ -339,234 +204,236 @@ static void vlan_transfer_operstate(const struct net_device *dev, struct net_dev } } -/* - * vlan network devices have devices nesting below it, and are a special - * "super class" of normal network devices; split their locks off into a - * separate class since they always nest. - */ -static struct lock_class_key vlan_netdev_xmit_lock_key; - - -/* Attach a VLAN device to a mac address (ie Ethernet Card). - * Returns the device that was created, or NULL if there was - * an error of some kind. - */ -static struct net_device *register_vlan_device(struct net_device *real_dev, - unsigned short VLAN_ID) +int vlan_check_real_dev(struct net_device *real_dev, u16 vlan_id) { - struct vlan_group *grp; - struct net_device *new_dev; - char name[IFNAMSIZ]; - int i; - -#ifdef VLAN_DEBUG - printk(VLAN_DBG "%s: if_name -:%s:- vid: %i\n", - __FUNCTION__, eth_IF_name, VLAN_ID); -#endif - - if (VLAN_ID >= VLAN_VID_MASK) - goto out_ret_null; + const char *name = real_dev->name; + const struct net_device_ops *ops = real_dev->netdev_ops; if (real_dev->features & NETIF_F_VLAN_CHALLENGED) { - printk(VLAN_DBG "%s: VLANs not supported on %s.\n", - __FUNCTION__, real_dev->name); - goto out_ret_null; + pr_info("8021q: VLANs not supported on %s\n", name); + return -EOPNOTSUPP; } - if ((real_dev->features & NETIF_F_HW_VLAN_RX) && - !real_dev->vlan_rx_register) { - printk(VLAN_DBG "%s: Device %s has buggy VLAN hw accel.\n", - __FUNCTION__, real_dev->name); - goto out_ret_null; + if ((real_dev->features & NETIF_F_HW_VLAN_RX) && !ops->ndo_vlan_rx_register) { + pr_info("8021q: device %s has buggy VLAN hw accel\n", name); + return -EOPNOTSUPP; } if ((real_dev->features & NETIF_F_HW_VLAN_FILTER) && - (!real_dev->vlan_rx_add_vid || !real_dev->vlan_rx_kill_vid)) { - printk(VLAN_DBG "%s: Device %s has buggy VLAN hw accel.\n", - __FUNCTION__, real_dev->name); - goto out_ret_null; + (!ops->ndo_vlan_rx_add_vid || !ops->ndo_vlan_rx_kill_vid)) { + pr_info("8021q: Device %s has buggy VLAN hw accel\n", name); + return -EOPNOTSUPP; } /* The real device must be up and operating in order to * assosciate a VLAN device with it. */ if (!(real_dev->flags & IFF_UP)) - goto out_ret_null; + return -ENETDOWN; - if (__find_vlan_dev(real_dev, VLAN_ID) != NULL) { - /* was already registered. */ - printk(VLAN_DBG "%s: ALREADY had VLAN registered\n", __FUNCTION__); - goto out_ret_null; + if (__find_vlan_dev(real_dev, vlan_id) != NULL) + return -EEXIST; + + return 0; +} + +int register_vlan_dev(struct net_device *dev) +{ + struct vlan_dev_info *vlan = vlan_dev_info(dev); + struct net_device *real_dev = vlan->real_dev; + const struct net_device_ops *ops = real_dev->netdev_ops; + u16 vlan_id = vlan->vlan_id; + struct vlan_group *grp, *ngrp = NULL; + int err; + + grp = __vlan_find_group(real_dev); + if (!grp) { + ngrp = grp = vlan_group_alloc(real_dev); + if (!grp) + return -ENOBUFS; + err = vlan_gvrp_init_applicant(real_dev); + if (err < 0) + goto out_free_group; } + err = vlan_group_prealloc_vid(grp, vlan_id); + if (err < 0) + goto out_uninit_applicant; + + err = register_netdevice(dev); + if (err < 0) + goto out_uninit_applicant; + + /* Account for reference in struct vlan_dev_info */ + dev_hold(real_dev); + + vlan_transfer_operstate(real_dev, dev); + linkwatch_fire_event(dev); /* _MUST_ call rfc2863_policy() */ + + /* So, got the sucker initialized, now lets place + * it into our local structure. + */ + vlan_group_set_device(grp, vlan_id, dev); + grp->nr_vlans++; + + if (ngrp && real_dev->features & NETIF_F_HW_VLAN_RX) + ops->ndo_vlan_rx_register(real_dev, ngrp); + if (real_dev->features & NETIF_F_HW_VLAN_FILTER) + ops->ndo_vlan_rx_add_vid(real_dev, vlan_id); + + return 0; + +out_uninit_applicant: + if (ngrp) + vlan_gvrp_uninit_applicant(real_dev); +out_free_group: + if (ngrp) + vlan_group_free(ngrp); + return err; +} + +/* Attach a VLAN device to a mac address (ie Ethernet Card). + * Returns 0 if the device was created or a negative error code otherwise. + */ +static int register_vlan_device(struct net_device *real_dev, u16 vlan_id) +{ + struct net_device *new_dev; + struct net *net = dev_net(real_dev); + struct vlan_net *vn = net_generic(net, vlan_net_id); + char name[IFNAMSIZ]; + int err; + + if (vlan_id >= VLAN_VID_MASK) + return -ERANGE; + + err = vlan_check_real_dev(real_dev, vlan_id); + if (err < 0) + return err; + /* Gotta set up the fields for the device. */ -#ifdef VLAN_DEBUG - printk(VLAN_DBG "About to allocate name, vlan_name_type: %i\n", - vlan_name_type); -#endif - switch (vlan_name_type) { + switch (vn->name_type) { case VLAN_NAME_TYPE_RAW_PLUS_VID: /* name will look like: eth1.0005 */ - snprintf(name, IFNAMSIZ, "%s.%.4i", real_dev->name, VLAN_ID); + snprintf(name, IFNAMSIZ, "%s.%.4i", real_dev->name, vlan_id); break; case VLAN_NAME_TYPE_PLUS_VID_NO_PAD: /* Put our vlan.VID in the name. * Name will look like: vlan5 */ - snprintf(name, IFNAMSIZ, "vlan%i", VLAN_ID); + snprintf(name, IFNAMSIZ, "vlan%i", vlan_id); break; case VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD: /* Put our vlan.VID in the name. * Name will look like: eth0.5 */ - snprintf(name, IFNAMSIZ, "%s.%i", real_dev->name, VLAN_ID); + snprintf(name, IFNAMSIZ, "%s.%i", real_dev->name, vlan_id); break; case VLAN_NAME_TYPE_PLUS_VID: /* Put our vlan.VID in the name. * Name will look like: vlan0005 */ default: - snprintf(name, IFNAMSIZ, "vlan%.4i", VLAN_ID); + snprintf(name, IFNAMSIZ, "vlan%.4i", vlan_id); } new_dev = alloc_netdev(sizeof(struct vlan_dev_info), name, vlan_setup); if (new_dev == NULL) - goto out_ret_null; - -#ifdef VLAN_DEBUG - printk(VLAN_DBG "Allocated new name -:%s:-\n", new_dev->name); -#endif - /* IFF_BROADCAST|IFF_MULTICAST; ??? */ - new_dev->flags = real_dev->flags; - new_dev->flags &= ~IFF_UP; - - new_dev->state = (real_dev->state & ((1<<__LINK_STATE_NOCARRIER) | - (1<<__LINK_STATE_DORMANT))) | - (1<<__LINK_STATE_PRESENT); + return -ENOBUFS; + dev_net_set(new_dev, net); /* need 4 bytes for extra VLAN header info, * hope the underlying device can handle it. */ new_dev->mtu = real_dev->mtu; - /* TODO: maybe just assign it to be ETHERNET? */ - new_dev->type = real_dev->type; - - new_dev->hard_header_len = real_dev->hard_header_len; - if (!(real_dev->features & NETIF_F_HW_VLAN_TX)) { - /* Regular ethernet + 4 bytes (18 total). */ - new_dev->hard_header_len += VLAN_HLEN; - } - - VLAN_MEM_DBG("new_dev->priv malloc, addr: %p size: %i\n", - new_dev->priv, - sizeof(struct vlan_dev_info)); - - memcpy(new_dev->broadcast, real_dev->broadcast, real_dev->addr_len); - memcpy(new_dev->dev_addr, real_dev->dev_addr, real_dev->addr_len); - new_dev->addr_len = real_dev->addr_len; - - if (real_dev->features & NETIF_F_HW_VLAN_TX) { - new_dev->hard_header = real_dev->hard_header; - new_dev->hard_start_xmit = vlan_dev_hwaccel_hard_start_xmit; - new_dev->rebuild_header = real_dev->rebuild_header; - } else { - new_dev->hard_header = vlan_dev_hard_header; - new_dev->hard_start_xmit = vlan_dev_hard_start_xmit; - new_dev->rebuild_header = vlan_dev_rebuild_header; - } - new_dev->hard_header_parse = real_dev->hard_header_parse; - - VLAN_DEV_INFO(new_dev)->vlan_id = VLAN_ID; /* 1 through VLAN_VID_MASK */ - VLAN_DEV_INFO(new_dev)->real_dev = real_dev; - VLAN_DEV_INFO(new_dev)->dent = NULL; - VLAN_DEV_INFO(new_dev)->flags = 1; + vlan_dev_info(new_dev)->vlan_id = vlan_id; + vlan_dev_info(new_dev)->real_dev = real_dev; + vlan_dev_info(new_dev)->dent = NULL; + vlan_dev_info(new_dev)->flags = VLAN_FLAG_REORDER_HDR; -#ifdef VLAN_DEBUG - printk(VLAN_DBG "About to go find the group for idx: %i\n", - real_dev->ifindex); -#endif - - if (register_netdevice(new_dev)) + new_dev->rtnl_link_ops = &vlan_link_ops; + err = register_vlan_dev(new_dev); + if (err < 0) goto out_free_newdev; - lockdep_set_class(&new_dev->_xmit_lock, &vlan_netdev_xmit_lock_key); - - new_dev->iflink = real_dev->ifindex; - vlan_transfer_operstate(real_dev, new_dev); - linkwatch_fire_event(new_dev); /* _MUST_ call rfc2863_policy() */ - - /* So, got the sucker initialized, now lets place - * it into our local structure. - */ - grp = __vlan_find_group(real_dev->ifindex); - - /* Note, we are running under the RTNL semaphore - * so it cannot "appear" on us. - */ - if (!grp) { /* need to add a new group */ - grp = kzalloc(sizeof(struct vlan_group), GFP_KERNEL); - if (!grp) - goto out_free_unregister; - - for (i=0; i < VLAN_GROUP_ARRAY_SPLIT_PARTS; i++) { - grp->vlan_devices_arrays[i] = kzalloc( - sizeof(struct net_device *)*VLAN_GROUP_ARRAY_PART_LEN, - GFP_KERNEL); - - if (!grp->vlan_devices_arrays[i]) - goto out_free_arrays; - } - - /* printk(KERN_ALERT "VLAN REGISTER: Allocated new group.\n"); */ - grp->real_dev_ifindex = real_dev->ifindex; + return 0; - hlist_add_head_rcu(&grp->hlist, - &vlan_group_hash[vlan_grp_hashfn(real_dev->ifindex)]); +out_free_newdev: + free_netdev(new_dev); + return err; +} - if (real_dev->features & NETIF_F_HW_VLAN_RX) - real_dev->vlan_rx_register(real_dev, grp); - } +static void vlan_sync_address(struct net_device *dev, + struct net_device *vlandev) +{ + struct vlan_dev_info *vlan = vlan_dev_info(vlandev); - vlan_group_set_device(grp, VLAN_ID, new_dev); + /* May be called without an actual change */ + if (!compare_ether_addr(vlan->real_dev_addr, dev->dev_addr)) + return; - if (vlan_proc_add_dev(new_dev)<0)/* create it's proc entry */ - printk(KERN_WARNING "VLAN: failed to add proc entry for %s\n", - new_dev->name); + /* vlan address was different from the old address and is equal to + * the new address */ + if (compare_ether_addr(vlandev->dev_addr, vlan->real_dev_addr) && + !compare_ether_addr(vlandev->dev_addr, dev->dev_addr)) + dev_unicast_delete(dev, vlandev->dev_addr, ETH_ALEN); - if (real_dev->features & NETIF_F_HW_VLAN_FILTER) - real_dev->vlan_rx_add_vid(real_dev, VLAN_ID); + /* vlan address was equal to the old address and is different from + * the new address */ + if (!compare_ether_addr(vlandev->dev_addr, vlan->real_dev_addr) && + compare_ether_addr(vlandev->dev_addr, dev->dev_addr)) + dev_unicast_add(dev, vlandev->dev_addr, ETH_ALEN); - /* Account for reference in struct vlan_dev_info */ - dev_hold(real_dev); -#ifdef VLAN_DEBUG - printk(VLAN_DBG "Allocated new device successfully, returning.\n"); -#endif - return new_dev; + memcpy(vlan->real_dev_addr, dev->dev_addr, ETH_ALEN); +} -out_free_arrays: - vlan_group_free(grp); +static void vlan_transfer_features(struct net_device *dev, + struct net_device *vlandev) +{ + unsigned long old_features = vlandev->features; -out_free_unregister: - unregister_netdev(new_dev); - goto out_ret_null; + vlandev->features &= ~dev->vlan_features; + vlandev->features |= dev->features & dev->vlan_features; + vlandev->gso_max_size = dev->gso_max_size; -out_free_newdev: - free_netdev(new_dev); + if (old_features != vlandev->features) + netdev_features_change(vlandev); +} -out_ret_null: - return NULL; +static void __vlan_device_event(struct net_device *dev, unsigned long event) +{ + switch (event) { + case NETDEV_CHANGENAME: + vlan_proc_rem_dev(dev); + if (vlan_proc_add_dev(dev) < 0) + pr_warning("8021q: failed to change proc name for %s\n", + dev->name); + break; + case NETDEV_REGISTER: + if (vlan_proc_add_dev(dev) < 0) + pr_warning("8021q: failed to add proc entry for %s\n", + dev->name); + break; + case NETDEV_UNREGISTER: + vlan_proc_rem_dev(dev); + break; + } } -static int vlan_device_event(struct notifier_block *unused, unsigned long event, void *ptr) +static int vlan_device_event(struct notifier_block *unused, unsigned long event, + void *ptr) { struct net_device *dev = ptr; - struct vlan_group *grp = __vlan_find_group(dev->ifindex); + struct vlan_group *grp; int i, flgs; struct net_device *vlandev; + if (is_vlan_dev(dev)) + __vlan_device_event(dev, event); + + grp = __vlan_find_group(dev); if (!grp) goto out; @@ -586,6 +453,33 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event, } break; + case NETDEV_CHANGEADDR: + /* Adjust unicast filters on underlying device */ + for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) { + vlandev = vlan_group_get_device(grp, i); + if (!vlandev) + continue; + + flgs = vlandev->flags; + if (!(flgs & IFF_UP)) + continue; + + vlan_sync_address(dev, vlandev); + } + break; + + case NETDEV_FEAT_CHANGE: + /* Propagate device features to underlying device */ + for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) { + vlandev = vlan_group_get_device(grp, i); + if (!vlandev) + continue; + + vlan_transfer_features(dev, vlandev); + } + + break; + case NETDEV_DOWN: /* Put all VLANs for this dev in the down state too. */ for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) { @@ -619,20 +513,16 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event, case NETDEV_UNREGISTER: /* Delete all VLANs for this dev. */ for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) { - int ret; - vlandev = vlan_group_get_device(grp, i); if (!vlandev) continue; - ret = unregister_vlan_dev(dev, - VLAN_DEV_INFO(vlandev)->vlan_id); + /* unregistration of last vlan destroys group, abort + * afterwards */ + if (grp->nr_vlans == 1) + i = VLAN_GROUP_ARRAY_LEN; - unregister_netdevice(vlandev); - - /* Group was destroyed? */ - if (ret == 1) - break; + unregister_vlan_dev(vlandev); } break; } @@ -641,15 +531,18 @@ out: return NOTIFY_DONE; } +static struct notifier_block vlan_notifier_block __read_mostly = { + .notifier_call = vlan_device_event, +}; + /* * VLAN IOCTL handler. * o execute requested action or pass command to the device driver * arg is really a struct vlan_ioctl_args __user *. */ -static int vlan_ioctl_handler(void __user *arg) +static int vlan_ioctl_handler(struct net *net, void __user *arg) { int err; - unsigned short vid = 0; struct vlan_ioctl_args args; struct net_device *dev = NULL; @@ -660,10 +553,6 @@ static int vlan_ioctl_handler(void __user *arg) args.device1[23] = 0; args.u.device2[23] = 0; -#ifdef VLAN_DEBUG - printk(VLAN_DBG "%s: args.cmd: %x\n", __FUNCTION__, args.cmd); -#endif - rtnl_lock(); switch (args.cmd) { @@ -675,13 +564,12 @@ static int vlan_ioctl_handler(void __user *arg) case GET_VLAN_REALDEV_NAME_CMD: case GET_VLAN_VID_CMD: err = -ENODEV; - dev = __dev_get_by_name(args.device1); + dev = __dev_get_by_name(net, args.device1); if (!dev) goto out; err = -EINVAL; - if (args.cmd != ADD_VLAN_CMD && - !(dev->priv_flags & IFF_802_1Q_VLAN)) + if (args.cmd != ADD_VLAN_CMD && !is_vlan_dev(dev)) goto out; } @@ -693,6 +581,7 @@ static int vlan_ioctl_handler(void __user *arg) vlan_dev_set_ingress_priority(dev, args.u.skb_priority, args.vlan_qos); + err = 0; break; case SET_VLAN_EGRESS_PRIORITY_CMD: @@ -708,18 +597,21 @@ static int vlan_ioctl_handler(void __user *arg) err = -EPERM; if (!capable(CAP_NET_ADMIN)) break; - err = vlan_dev_set_vlan_flag(dev, - args.u.flag, - args.vlan_qos); + err = vlan_dev_change_flags(dev, + args.vlan_qos ? args.u.flag : 0, + args.u.flag); break; case SET_VLAN_NAME_TYPE_CMD: err = -EPERM; if (!capable(CAP_NET_ADMIN)) - return -EPERM; + break; if ((args.u.name_type >= 0) && (args.u.name_type < VLAN_NAME_TYPE_HIGHEST)) { - vlan_name_type = args.u.name_type; + struct vlan_net *vn; + + vn = net_generic(net, vlan_net_id); + vn->name_type = args.u.name_type; err = 0; } else { err = -EINVAL; @@ -730,62 +622,35 @@ static int vlan_ioctl_handler(void __user *arg) err = -EPERM; if (!capable(CAP_NET_ADMIN)) break; - if (register_vlan_device(dev, args.u.VID)) { - err = 0; - } else { - err = -EINVAL; - } + err = register_vlan_device(dev, args.u.VID); break; case DEL_VLAN_CMD: err = -EPERM; if (!capable(CAP_NET_ADMIN)) break; - err = unregister_vlan_device(dev); + unregister_vlan_dev(dev); + err = 0; break; - case GET_VLAN_INGRESS_PRIORITY_CMD: - /* TODO: Implement - err = vlan_dev_get_ingress_priority(args); - if (copy_to_user((void*)arg, &args, - sizeof(struct vlan_ioctl_args))) { - err = -EFAULT; - } - */ - err = -EINVAL; - break; - case GET_VLAN_EGRESS_PRIORITY_CMD: - /* TODO: Implement - err = vlan_dev_get_egress_priority(args.device1, &(args.args); - if (copy_to_user((void*)arg, &args, - sizeof(struct vlan_ioctl_args))) { - err = -EFAULT; - } - */ - err = -EINVAL; - break; case GET_VLAN_REALDEV_NAME_CMD: + err = 0; vlan_dev_get_realdev_name(dev, args.u.device2); if (copy_to_user(arg, &args, - sizeof(struct vlan_ioctl_args))) { + sizeof(struct vlan_ioctl_args))) err = -EFAULT; - } break; case GET_VLAN_VID_CMD: - vlan_dev_get_vid(dev, &vid); - args.u.VID = vid; + err = 0; + args.u.VID = vlan_dev_vlan_id(dev); if (copy_to_user(arg, &args, - sizeof(struct vlan_ioctl_args))) { + sizeof(struct vlan_ioctl_args))) err = -EFAULT; - } break; default: - /* pass on to underlying device instead?? */ - printk(VLAN_DBG "%s: Unknown VLAN CMD: %x \n", - __FUNCTION__, args.cmd); - err = -EINVAL; + err = -EOPNOTSUPP; break; } out: @@ -793,5 +658,111 @@ out: return err; } +static int vlan_init_net(struct net *net) +{ + int err; + struct vlan_net *vn; + + err = -ENOMEM; + vn = kzalloc(sizeof(struct vlan_net), GFP_KERNEL); + if (vn == NULL) + goto err_alloc; + + err = net_assign_generic(net, vlan_net_id, vn); + if (err < 0) + goto err_assign; + + vn->name_type = VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD; + + err = vlan_proc_init(net); + if (err < 0) + goto err_proc; + + return 0; + +err_proc: + /* nothing */ +err_assign: + kfree(vn); +err_alloc: + return err; +} + +static void vlan_exit_net(struct net *net) +{ + struct vlan_net *vn; + + vn = net_generic(net, vlan_net_id); + rtnl_kill_links(net, &vlan_link_ops); + vlan_proc_cleanup(net); + kfree(vn); +} + +static struct pernet_operations vlan_net_ops = { + .init = vlan_init_net, + .exit = vlan_exit_net, +}; + +static int __init vlan_proto_init(void) +{ + int err; + + pr_info("%s v%s %s\n", vlan_fullname, vlan_version, vlan_copyright); + pr_info("All bugs added by %s\n", vlan_buggyright); + + err = register_pernet_gen_device(&vlan_net_id, &vlan_net_ops); + if (err < 0) + goto err0; + + err = register_netdevice_notifier(&vlan_notifier_block); + if (err < 0) + goto err2; + + err = vlan_gvrp_init(); + if (err < 0) + goto err3; + + err = vlan_netlink_init(); + if (err < 0) + goto err4; + + dev_add_pack(&vlan_packet_type); + vlan_ioctl_set(vlan_ioctl_handler); + return 0; + +err4: + vlan_gvrp_uninit(); +err3: + unregister_netdevice_notifier(&vlan_notifier_block); +err2: + unregister_pernet_gen_device(vlan_net_id, &vlan_net_ops); +err0: + return err; +} + +static void __exit vlan_cleanup_module(void) +{ + unsigned int i; + + vlan_ioctl_set(NULL); + vlan_netlink_fini(); + + unregister_netdevice_notifier(&vlan_notifier_block); + + dev_remove_pack(&vlan_packet_type); + + /* This table must be empty if there are no module references left. */ + for (i = 0; i < VLAN_GRP_HASH_SIZE; i++) + BUG_ON(!hlist_empty(&vlan_group_hash[i])); + + unregister_pernet_gen_device(vlan_net_id, &vlan_net_ops); + synchronize_net(); + + vlan_gvrp_uninit(); +} + +module_init(vlan_proto_init); +module_exit(vlan_cleanup_module); + MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION);