Commit 5b9ea6e0 authored by Jiri Pirko's avatar Jiri Pirko Committed by David S. Miller

vlan: introduce vid list with reference counting

This allows to keep track of vids needed to be in rx vlan filters of
devices even if they are used in bond/team etc.

vlan_info as well as vlan_group previously was, is allocated when first
vid is added and dealocated whan last vid is deleted.

vlan_group definition is moved to private header.
Signed-off-by: default avatarJiri Pirko <jpirko@redhat.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 87002b03
...@@ -74,22 +74,7 @@ static inline struct vlan_ethhdr *vlan_eth_hdr(const struct sk_buff *skb) ...@@ -74,22 +74,7 @@ static inline struct vlan_ethhdr *vlan_eth_hdr(const struct sk_buff *skb)
/* found in socket.c */ /* found in socket.c */
extern void vlan_ioctl_set(int (*hook)(struct net *, void __user *)); extern void vlan_ioctl_set(int (*hook)(struct net *, void __user *));
/* if this changes, algorithm will have to be reworked because this struct vlan_info;
* depends on completely exhausting the VLAN identifier space. Thus
* it gives constant time look-up, but in many cases it wastes memory.
*/
#define VLAN_GROUP_ARRAY_SPLIT_PARTS 8
#define VLAN_GROUP_ARRAY_PART_LEN (VLAN_N_VID/VLAN_GROUP_ARRAY_SPLIT_PARTS)
struct vlan_group {
struct net_device *real_dev; /* The ethernet(like) device
* the vlan is attached to.
*/
unsigned int nr_vlans;
struct hlist_node hlist; /* linked list */
struct net_device **vlan_devices_arrays[VLAN_GROUP_ARRAY_SPLIT_PARTS];
struct rcu_head rcu;
};
static inline int is_vlan_dev(struct net_device *dev) static inline int is_vlan_dev(struct net_device *dev)
{ {
......
...@@ -55,7 +55,6 @@ ...@@ -55,7 +55,6 @@
#include <linux/netdev_features.h> #include <linux/netdev_features.h>
struct vlan_group;
struct netpoll_info; struct netpoll_info;
struct phy_device; struct phy_device;
/* 802.11 specific */ /* 802.11 specific */
...@@ -1096,7 +1095,7 @@ struct net_device { ...@@ -1096,7 +1095,7 @@ struct net_device {
/* Protocol specific pointers */ /* Protocol specific pointers */
#if IS_ENABLED(CONFIG_VLAN_8021Q) #if IS_ENABLED(CONFIG_VLAN_8021Q)
struct vlan_group __rcu *vlgrp; /* VLAN group */ struct vlan_info __rcu *vlan_info; /* VLAN info */
#endif #endif
#if IS_ENABLED(CONFIG_NET_DSA) #if IS_ENABLED(CONFIG_NET_DSA)
struct dsa_switch_tree *dsa_ptr; /* dsa specific data */ struct dsa_switch_tree *dsa_ptr; /* dsa specific data */
......
...@@ -51,27 +51,6 @@ const char vlan_version[] = DRV_VERSION; ...@@ -51,27 +51,6 @@ const char vlan_version[] = DRV_VERSION;
/* End of global variables definitions. */ /* End of global variables definitions. */
static void vlan_group_free(struct vlan_group *grp)
{
int i;
for (i = 0; i < VLAN_GROUP_ARRAY_SPLIT_PARTS; i++)
kfree(grp->vlan_devices_arrays[i]);
kfree(grp);
}
static struct vlan_group *vlan_group_alloc(struct net_device *real_dev)
{
struct vlan_group *grp;
grp = kzalloc(sizeof(struct vlan_group), GFP_KERNEL);
if (!grp)
return NULL;
grp->real_dev = real_dev;
return grp;
}
static int vlan_group_prealloc_vid(struct vlan_group *vg, u16 vlan_id) static int vlan_group_prealloc_vid(struct vlan_group *vg, u16 vlan_id)
{ {
struct net_device **array; struct net_device **array;
...@@ -92,22 +71,20 @@ static int vlan_group_prealloc_vid(struct vlan_group *vg, u16 vlan_id) ...@@ -92,22 +71,20 @@ static int vlan_group_prealloc_vid(struct vlan_group *vg, u16 vlan_id)
return 0; return 0;
} }
static void vlan_rcu_free(struct rcu_head *rcu)
{
vlan_group_free(container_of(rcu, struct vlan_group, rcu));
}
void unregister_vlan_dev(struct net_device *dev, struct list_head *head) void unregister_vlan_dev(struct net_device *dev, struct list_head *head)
{ {
struct vlan_dev_priv *vlan = vlan_dev_priv(dev); struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
struct net_device *real_dev = vlan->real_dev; struct net_device *real_dev = vlan->real_dev;
struct vlan_info *vlan_info;
struct vlan_group *grp; struct vlan_group *grp;
u16 vlan_id = vlan->vlan_id; u16 vlan_id = vlan->vlan_id;
ASSERT_RTNL(); ASSERT_RTNL();
grp = rtnl_dereference(real_dev->vlgrp); vlan_info = rtnl_dereference(real_dev->vlan_info);
BUG_ON(!grp); BUG_ON(!vlan_info);
grp = &vlan_info->grp;
/* Take it out of our own structures, but be sure to interlock with /* Take it out of our own structures, but be sure to interlock with
* HW accelerating devices or SW vlan input packet processing if * HW accelerating devices or SW vlan input packet processing if
...@@ -116,7 +93,7 @@ void unregister_vlan_dev(struct net_device *dev, struct list_head *head) ...@@ -116,7 +93,7 @@ void unregister_vlan_dev(struct net_device *dev, struct list_head *head)
if (vlan_id) if (vlan_id)
vlan_vid_del(real_dev, vlan_id); vlan_vid_del(real_dev, vlan_id);
grp->nr_vlans--; grp->nr_vlan_devs--;
if (vlan->flags & VLAN_FLAG_GVRP) if (vlan->flags & VLAN_FLAG_GVRP)
vlan_gvrp_request_leave(dev); vlan_gvrp_request_leave(dev);
...@@ -128,16 +105,9 @@ void unregister_vlan_dev(struct net_device *dev, struct list_head *head) ...@@ -128,16 +105,9 @@ void unregister_vlan_dev(struct net_device *dev, struct list_head *head)
*/ */
unregister_netdevice_queue(dev, head); unregister_netdevice_queue(dev, head);
/* If the group is now empty, kill off the group. */ if (grp->nr_vlan_devs == 0)
if (grp->nr_vlans == 0) {
vlan_gvrp_uninit_applicant(real_dev); vlan_gvrp_uninit_applicant(real_dev);
RCU_INIT_POINTER(real_dev->vlgrp, NULL);
/* Free the group, after all cpu's are done. */
call_rcu(&grp->rcu, vlan_rcu_free);
}
/* Get rid of the vlan's reference to real_dev */ /* Get rid of the vlan's reference to real_dev */
dev_put(real_dev); dev_put(real_dev);
} }
...@@ -169,17 +139,23 @@ int register_vlan_dev(struct net_device *dev) ...@@ -169,17 +139,23 @@ int register_vlan_dev(struct net_device *dev)
struct vlan_dev_priv *vlan = vlan_dev_priv(dev); struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
struct net_device *real_dev = vlan->real_dev; struct net_device *real_dev = vlan->real_dev;
u16 vlan_id = vlan->vlan_id; u16 vlan_id = vlan->vlan_id;
struct vlan_group *grp, *ngrp = NULL; struct vlan_info *vlan_info;
struct vlan_group *grp;
int err; int err;
grp = rtnl_dereference(real_dev->vlgrp); err = vlan_vid_add(real_dev, vlan_id);
if (!grp) { if (err)
ngrp = grp = vlan_group_alloc(real_dev); return err;
if (!grp)
return -ENOBUFS; vlan_info = rtnl_dereference(real_dev->vlan_info);
/* vlan_info should be there now. vlan_vid_add took care of it */
BUG_ON(!vlan_info);
grp = &vlan_info->grp;
if (grp->nr_vlan_devs == 0) {
err = vlan_gvrp_init_applicant(real_dev); err = vlan_gvrp_init_applicant(real_dev);
if (err < 0) if (err < 0)
goto out_free_group; goto out_vid_del;
} }
err = vlan_group_prealloc_vid(grp, vlan_id); err = vlan_group_prealloc_vid(grp, vlan_id);
...@@ -200,23 +176,15 @@ int register_vlan_dev(struct net_device *dev) ...@@ -200,23 +176,15 @@ int register_vlan_dev(struct net_device *dev)
* it into our local structure. * it into our local structure.
*/ */
vlan_group_set_device(grp, vlan_id, dev); vlan_group_set_device(grp, vlan_id, dev);
grp->nr_vlans++; grp->nr_vlan_devs++;
if (ngrp) {
rcu_assign_pointer(real_dev->vlgrp, ngrp);
}
vlan_vid_add(real_dev, vlan_id);
return 0; return 0;
out_uninit_applicant: out_uninit_applicant:
if (ngrp) if (grp->nr_vlan_devs == 0)
vlan_gvrp_uninit_applicant(real_dev); vlan_gvrp_uninit_applicant(real_dev);
out_free_group: out_vid_del:
if (ngrp) { vlan_vid_del(real_dev, vlan_id);
/* Free the group, after all cpu's are done. */
call_rcu(&ngrp->rcu, vlan_rcu_free);
}
return err; return err;
} }
...@@ -357,6 +325,7 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event, ...@@ -357,6 +325,7 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event,
{ {
struct net_device *dev = ptr; struct net_device *dev = ptr;
struct vlan_group *grp; struct vlan_group *grp;
struct vlan_info *vlan_info;
int i, flgs; int i, flgs;
struct net_device *vlandev; struct net_device *vlandev;
struct vlan_dev_priv *vlan; struct vlan_dev_priv *vlan;
...@@ -372,9 +341,10 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event, ...@@ -372,9 +341,10 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event,
vlan_vid_add(dev, 0); vlan_vid_add(dev, 0);
} }
grp = rtnl_dereference(dev->vlgrp); vlan_info = rtnl_dereference(dev->vlan_info);
if (!grp) if (!vlan_info)
goto out; goto out;
grp = &vlan_info->grp;
/* It is OK that we do not hold the group lock right now, /* It is OK that we do not hold the group lock right now,
* as we run under the RTNL lock. * as we run under the RTNL lock.
...@@ -478,9 +448,9 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event, ...@@ -478,9 +448,9 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event,
if (!vlandev) if (!vlandev)
continue; continue;
/* unregistration of last vlan destroys group, abort /* removal of last vid destroys vlan_info, abort
* afterwards */ * afterwards */
if (grp->nr_vlans == 1) if (vlan_info->nr_vids == 1)
i = VLAN_N_VID; i = VLAN_N_VID;
unregister_vlan_dev(vlandev, &list); unregister_vlan_dev(vlandev, &list);
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
#include <linux/if_vlan.h> #include <linux/if_vlan.h>
#include <linux/u64_stats_sync.h> #include <linux/u64_stats_sync.h>
#include <linux/list.h>
/** /**
...@@ -74,6 +75,29 @@ static inline struct vlan_dev_priv *vlan_dev_priv(const struct net_device *dev) ...@@ -74,6 +75,29 @@ static inline struct vlan_dev_priv *vlan_dev_priv(const struct net_device *dev)
return netdev_priv(dev); return netdev_priv(dev);
} }
/* if this changes, algorithm will have to be reworked because this
* depends on completely exhausting the VLAN identifier space. Thus
* it gives constant time look-up, but in many cases it wastes memory.
*/
#define VLAN_GROUP_ARRAY_SPLIT_PARTS 8
#define VLAN_GROUP_ARRAY_PART_LEN (VLAN_N_VID/VLAN_GROUP_ARRAY_SPLIT_PARTS)
struct vlan_group {
unsigned int nr_vlan_devs;
struct hlist_node hlist; /* linked list */
struct net_device **vlan_devices_arrays[VLAN_GROUP_ARRAY_SPLIT_PARTS];
};
struct vlan_info {
struct net_device *real_dev; /* The ethernet(like) device
* the vlan is attached to.
*/
struct vlan_group grp;
struct list_head vid_list;
unsigned int nr_vids;
struct rcu_head rcu;
};
static inline struct net_device *vlan_group_get_device(struct vlan_group *vg, static inline struct net_device *vlan_group_get_device(struct vlan_group *vg,
u16 vlan_id) u16 vlan_id)
{ {
...@@ -97,10 +121,10 @@ static inline void vlan_group_set_device(struct vlan_group *vg, ...@@ -97,10 +121,10 @@ static inline void vlan_group_set_device(struct vlan_group *vg,
static inline struct net_device *vlan_find_dev(struct net_device *real_dev, static inline struct net_device *vlan_find_dev(struct net_device *real_dev,
u16 vlan_id) u16 vlan_id)
{ {
struct vlan_group *grp = rcu_dereference_rtnl(real_dev->vlgrp); struct vlan_info *vlan_info = rcu_dereference_rtnl(real_dev->vlan_info);
if (grp) if (vlan_info)
return vlan_group_get_device(grp, vlan_id); return vlan_group_get_device(&vlan_info->grp, vlan_id);
return NULL; return NULL;
} }
......
...@@ -71,10 +71,10 @@ bool vlan_do_receive(struct sk_buff **skbp, bool last_handler) ...@@ -71,10 +71,10 @@ bool vlan_do_receive(struct sk_buff **skbp, bool last_handler)
struct net_device *__vlan_find_dev_deep(struct net_device *real_dev, struct net_device *__vlan_find_dev_deep(struct net_device *real_dev,
u16 vlan_id) u16 vlan_id)
{ {
struct vlan_group *grp = rcu_dereference_rtnl(real_dev->vlgrp); struct vlan_info *vlan_info = rcu_dereference_rtnl(real_dev->vlan_info);
if (grp) { if (vlan_info) {
return vlan_group_get_device(grp, vlan_id); return vlan_group_get_device(&vlan_info->grp, vlan_id);
} else { } else {
/* /*
* Bonding slaves do not have grp assigned to themselves. * Bonding slaves do not have grp assigned to themselves.
...@@ -147,25 +147,177 @@ struct sk_buff *vlan_untag(struct sk_buff *skb) ...@@ -147,25 +147,177 @@ struct sk_buff *vlan_untag(struct sk_buff *skb)
return NULL; return NULL;
} }
int vlan_vid_add(struct net_device *dev, unsigned short vid)
/*
* vlan info and vid list
*/
static void vlan_group_free(struct vlan_group *grp)
{
int i;
for (i = 0; i < VLAN_GROUP_ARRAY_SPLIT_PARTS; i++)
kfree(grp->vlan_devices_arrays[i]);
}
static void vlan_info_free(struct vlan_info *vlan_info)
{
vlan_group_free(&vlan_info->grp);
kfree(vlan_info);
}
static void vlan_info_rcu_free(struct rcu_head *rcu)
{
vlan_info_free(container_of(rcu, struct vlan_info, rcu));
}
static struct vlan_info *vlan_info_alloc(struct net_device *dev)
{
struct vlan_info *vlan_info;
vlan_info = kzalloc(sizeof(struct vlan_info), GFP_KERNEL);
if (!vlan_info)
return NULL;
vlan_info->real_dev = dev;
INIT_LIST_HEAD(&vlan_info->vid_list);
return vlan_info;
}
struct vlan_vid_info {
struct list_head list;
unsigned short vid;
int refcount;
};
static struct vlan_vid_info *vlan_vid_info_get(struct vlan_info *vlan_info,
unsigned short vid)
{
struct vlan_vid_info *vid_info;
list_for_each_entry(vid_info, &vlan_info->vid_list, list) {
if (vid_info->vid == vid)
return vid_info;
}
return NULL;
}
static struct vlan_vid_info *vlan_vid_info_alloc(unsigned short vid)
{
struct vlan_vid_info *vid_info;
vid_info = kzalloc(sizeof(struct vlan_vid_info), GFP_KERNEL);
if (!vid_info)
return NULL;
vid_info->vid = vid;
return vid_info;
}
static int __vlan_vid_add(struct vlan_info *vlan_info, unsigned short vid,
struct vlan_vid_info **pvid_info)
{ {
struct net_device *dev = vlan_info->real_dev;
const struct net_device_ops *ops = dev->netdev_ops; const struct net_device_ops *ops = dev->netdev_ops;
struct vlan_vid_info *vid_info;
int err;
vid_info = vlan_vid_info_alloc(vid);
if (!vid_info)
return -ENOMEM;
if ((dev->features & NETIF_F_HW_VLAN_FILTER) && if ((dev->features & NETIF_F_HW_VLAN_FILTER) &&
ops->ndo_vlan_rx_add_vid) { ops->ndo_vlan_rx_add_vid) {
return ops->ndo_vlan_rx_add_vid(dev, vid); err = ops->ndo_vlan_rx_add_vid(dev, vid);
if (err) {
kfree(vid_info);
return err;
}
} }
list_add(&vid_info->list, &vlan_info->vid_list);
vlan_info->nr_vids++;
*pvid_info = vid_info;
return 0; return 0;
} }
int vlan_vid_add(struct net_device *dev, unsigned short vid)
{
struct vlan_info *vlan_info;
struct vlan_vid_info *vid_info;
bool vlan_info_created = false;
int err;
ASSERT_RTNL();
vlan_info = rtnl_dereference(dev->vlan_info);
if (!vlan_info) {
vlan_info = vlan_info_alloc(dev);
if (!vlan_info)
return -ENOMEM;
vlan_info_created = true;
}
vid_info = vlan_vid_info_get(vlan_info, vid);
if (!vid_info) {
err = __vlan_vid_add(vlan_info, vid, &vid_info);
if (err)
goto out_free_vlan_info;
}
vid_info->refcount++;
if (vlan_info_created)
rcu_assign_pointer(dev->vlan_info, vlan_info);
return 0;
out_free_vlan_info:
if (vlan_info_created)
kfree(vlan_info);
return err;
}
EXPORT_SYMBOL(vlan_vid_add); EXPORT_SYMBOL(vlan_vid_add);
void vlan_vid_del(struct net_device *dev, unsigned short vid) static void __vlan_vid_del(struct vlan_info *vlan_info,
struct vlan_vid_info *vid_info)
{ {
struct net_device *dev = vlan_info->real_dev;
const struct net_device_ops *ops = dev->netdev_ops; const struct net_device_ops *ops = dev->netdev_ops;
unsigned short vid = vid_info->vid;
int err;
if ((dev->features & NETIF_F_HW_VLAN_FILTER) && if ((dev->features & NETIF_F_HW_VLAN_FILTER) &&
ops->ndo_vlan_rx_kill_vid) { ops->ndo_vlan_rx_kill_vid) {
ops->ndo_vlan_rx_kill_vid(dev, vid); err = ops->ndo_vlan_rx_kill_vid(dev, vid);
if (err) {
pr_warn("failed to kill vid %d for device %s\n",
vid, dev->name);
}
}
list_del(&vid_info->list);
kfree(vid_info);
vlan_info->nr_vids--;
}
void vlan_vid_del(struct net_device *dev, unsigned short vid)
{
struct vlan_info *vlan_info;
struct vlan_vid_info *vid_info;
ASSERT_RTNL();
vlan_info = rtnl_dereference(dev->vlan_info);
if (!vlan_info)
return;
vid_info = vlan_vid_info_get(vlan_info, vid);
if (!vid_info)
return;
vid_info->refcount--;
if (vid_info->refcount == 0) {
__vlan_vid_del(vlan_info, vid_info);
if (vlan_info->nr_vids == 0) {
RCU_INIT_POINTER(dev->vlan_info, NULL);
call_rcu(&vlan_info->rcu, vlan_info_rcu_free);
}
} }
} }
EXPORT_SYMBOL(vlan_vid_del); EXPORT_SYMBOL(vlan_vid_del);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment