// SPDX-License-Identifier: GPL-2.0-only
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <linux/slab.h>
#include <net/switchdev.h>
#include "br_private.h"
#include "br_private_tunnel.h"
static void nbp_vlan_set_vlan_dev_state(struct net_bridge_port *p, u16 vid);
static inline int br_vlan_cmp(struct rhashtable_compare_arg *arg,
const void *ptr)
{
const struct net_bridge_vlan *vle = ptr;
u16 vid = *(u16 *)arg->key;
return vle->vid != vid;
}
static const struct rhashtable_params br_vlan_rht_params = {
.head_offset = offsetof(struct net_bridge_vlan, vnode),
.key_offset = offsetof(struct net_bridge_vlan, vid),
.key_len = sizeof(u16),
.nelem_hint = 3,
.max_size = VLAN_N_VID,
.obj_cmpfn = br_vlan_cmp,
.automatic_shrinking = true,
};
static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid)
{
return rhashtable_lookup_fast(tbl, &vid, br_vlan_rht_params);
}
static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg, u16 vid)
{
if (vg->pvid == vid)
return false;
smp_wmb();
vg->pvid = vid;
return true;
}
static bool __vlan_delete_pvid(struct net_bridge_vlan_group *vg, u16 vid)
{
if (vg->pvid != vid)
return false;
smp_wmb();
vg->pvid = 0;
return true;
}
/* return true if anything changed, false otherwise */
static bool __vlan_add_flags(struct net_bridge_vlan *v, u16 flags)
{
struct net_bridge_vlan_group *vg;
u16 old_flags = v->flags;
bool ret;
if (br_vlan_is_master(v))
vg = br_vlan_group(v->br);
else
vg = nbp_vlan_group(v->port);
if (flags & BRIDGE_VLAN_INFO_PVID)
ret = __vlan_add_pvid(vg, v->vid);
else
ret = __vlan_delete_pvid(vg, v->vid);
if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
v->flags |= BRIDGE_VLAN_INFO_UNTAGGED;
else
v->flags &= ~BRIDGE_VLAN_INFO_UNTAGGED;
return ret || !!(old_flags ^ v->flags);
}
static int __vlan_vid_add(struct net_device *dev, struct net_bridge *br,
struct net_bridge_vlan *v, u16 flags,
struct netlink_ext_ack *extack)
{
int err;
/* Try switchdev op first. In case it is not supported, fallback to
* 8021q add.
*/
err = br_switchdev_port_vlan_add(dev, v->vid, flags, extack);
if (err == -EOPNOTSUPP)
return vlan_vid_add(dev, br->vlan_proto, v->vid);
v->priv_flags |= BR_VLFLAG_ADDED_BY_SWITCHDEV;
return err;
}
static void __vlan_add_list(struct net_bridge_vlan *v)
{
struct net_bridge_vlan_group *vg;
struct list_head *headp, *hpos;
struct net_bridge_vlan *vent;
if (br_vlan_is_master(v))
vg = br_vlan_group(v->br);
else
vg = nbp_vlan_group(v->port);
headp = &vg->vlan_list;
list_for_each_prev(hpos, headp) {
vent = list_entry(hpos, struct net_bridge_vlan, vlist);
if (v->vid < vent->vid)
continue;
else
break;
}
list_add_rcu(&v->vlist, hpos);
}
static void __vlan_del_list(struct net_bridge_vlan *v)
{
list_del_rcu(&v->vlist);
}
static int __vlan_vid_del(struct net_device *dev, struct net_bridge *br,
const struct net_bridge_vlan *v)
{
int err;
/* Try switchdev op first. In case it is not supported, fallback to
* 8021q del.
*/
err = br_switchdev_port_vlan_del(dev, v->vid);
if (!(v->priv_flags & BR_VLFLAG_ADDED_BY_SWITCHDEV))
vlan_vid_del(dev, br->vlan_proto, v->vid);
return err == -EOPNOTSUPP ? 0 : err;
}
/* Returns a master vlan, if it didn't exist it gets created. In all cases a
* a reference is taken to the master vlan before returning.
*/
static struct net_bridge_vlan *
br_vlan_get_master(struct net_bridge *br, u16 vid,
struct netlink_ext_ack *extack)
{
struct net_bridge_vlan_group *vg;
struct net_bridge_vlan *masterv;
vg = br_vlan_group(br);
masterv = br_vlan_find(vg, vid);
if (!masterv) {
bool changed;
/* missing global ctx, create it now */
if (br_vlan_add(br, vid, 0, &changed, extack))
return NULL;
masterv = br_vlan_find(vg, vid);
if (WARN_ON(!masterv))
return NULL;
refcount_set(&masterv->refcnt, 1);
return masterv;
}
refcount_inc(&masterv->refcnt);
return masterv;
}
static void br_master_vlan_rcu_free(struct rcu_head *rcu)
{
struct net_bridge_vlan *v;
v = container_of(rcu, struct net_bridge_vlan, rcu);
WARN_ON(!br_vlan_is_master(v));
free_percpu(v->stats);
v->stats = NULL;
kfree(v);
}
static void br_vlan_put_master(struct net_bridge_vlan *masterv)
{
struct net_bridge_vlan_group *vg;
if (!br_vlan_is_master(masterv))
return;
vg = br_vlan_group(masterv->br);
if (refcount_dec_and_test(&masterv->refcnt)) {
rhashtable_remove_fast(&vg->vlan_hash,
&masterv->vnode, br_vlan_rht_params);
__vlan_del_list(masterv);
call_rcu(&masterv->rcu, br_master_vlan_rcu_free);
}
}
static void nbp_vlan_rcu_free(struct rcu_head *rcu)
{
struct net_bridge_vlan *v;
v = container_of(rcu, struct net_bridge_vlan, rcu);
WARN_ON(br_vlan_is_master(v));
/* if we had per-port stats configured then free them here */
if (v->priv_flags & BR_VLFLAG_PER_PORT_STATS)
free_percpu(v->stats);
v->stats = NULL;
kfree(v);
}
/* This is the shared VLAN add function which works for both ports and bridge
* devices. There are four possible calls to this function in terms of the
* vlan entry type:
* 1. vlan is being added on a port (no master flags, global entry exists)
* 2. vlan is being added on a bridge (both master and brentry flags)
* 3. vlan is being added on a port, but a global entry didn't exist which
* is being created right now (master flag set, brentry flag unset), the
* global entry is used for global per-vlan features, but not for filtering
* 4. same as 3 but with both master and brentry flags set so the entry
* will be used for filtering in both the port and the bridge
*/
static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
struct netlink_ext_ack *extack)
{
struct net_bridge_vlan *masterv = NULL;
struct net_bridge_port *p = NULL;
struct net_bridge_vlan_group *vg;
struct net_device *dev;
struct net_bridge *br;
int err;
if (br_vlan_is_master(v)) {
br = v->br;
dev = br->dev;
vg = br_vlan_group(br);
} else {
p = v->port;
br = p->br;
dev = p->dev;
vg = nbp_vlan_group(p);
}
if (p) {
/* Add VLAN to the device filter if it is supported.
* This ensures tagged traffic enters the bridge when
* promiscuous mode is disabled by br_manage_promisc().
*/
err = __vlan_vid_add(dev, br, v, flags, extack);
if (err)
goto out;
/* need to work on the master vlan too */
if (flags & BRIDGE_VLAN_INFO_MASTER) {
bool changed;
err = br_vlan_add(br, v->vid,
flags | BRIDGE_VLAN_INFO_BRENTRY,
&changed, extack);
if (err)
goto out_filt;
}
masterv = br_vlan_get_master(br, v->vid, extack);
if (!masterv)
goto out_filt;
v->brvlan = masterv;
if (br_opt_get(br, BROPT_VLAN_STATS_PER_PORT)) {
v->stats = netdev_alloc_pcpu_stats(struct br_vlan_stats);
if (!v->stats) {
err = -ENOMEM;
goto out_filt;
}
v->priv_flags |= BR_VLFLAG_PER_PORT_STATS;
} else {
v->stats = masterv->stats;
}
} else {
err = br_switchdev_port_vlan_add(dev, v->vid, flags, extack);
if (err && err != -EOPNOTSUPP)
goto out;
}
/* Add the dev mac and count the vlan only if it's usable */
if (br_vlan_should_use(v)) {
err = br_fdb_insert(br, p, dev->dev_addr, v->vid);
if (err) {
br_err(br, "failed insert local address into bridge forwarding table\n");
goto out_filt;
}
vg->num_vlans++;
}
err = rhashtable_lookup_insert_fast(&vg->vlan_hash, &v->vnode,
br_vlan_rht_params);
if (err)
goto out_fdb_insert;
__vlan_add_list(v);
__vlan_add_flags(v, flags);
if (p)
nbp_vlan_set_vlan_dev_state(p, v->vid);
out:
return err;
out_fdb_insert:
if (br_vlan_should_use(v)) {
br_fdb_find_delete_local(br, p, dev->dev_addr, v->vid);
vg->num_vlans--;
}
out_filt:
if (p) {
__vlan_vid_del(dev, br, v);
if (masterv) {
if (v->stats && masterv->stats != v->stats)
free_percpu(v->stats);
v->stats = NULL;
br_vlan_put_master(masterv);
v->brvlan = NULL;
}
} else {
br_switchdev_port_vlan_del(dev, v->vid);
}
goto out;
}
static int __vlan_del(struct net_bridge_vlan *v)
{
struct net_bridge_vlan *masterv = v;
struct net_bridge_vlan_group *vg;
st
|