424bb9c97c
Vlan codes unconditionally delete local fdb entries. We should consider the possibility that other ports have the same address and vlan. Example of problematic case: ip link set eth0 address 12:34:56:78:90:ab ip link set eth1 address aa:bb:cc:dd:ee:ff brctl addif br0 eth0 brctl addif br0 eth1 # br0 will have mac address 12:34:56:78:90:ab bridge vlan add dev eth0 vid 10 bridge vlan add dev eth1 vid 10 bridge vlan add dev br0 vid 10 self We will have fdb entry such that f->dst == eth0, f->vlan_id == 10 and f->addr == 12:34:56:78:90:ab at this time. Next, delete eth0 vlan 10. bridge vlan del dev eth0 vid 10 In this case, we still need the entry for br0, but it will be deleted. Note that br0 needs the entry even though its mac address is not set manually. To delete the entry with proper condition checking, fdb_delete_local() is suitable to use. Signed-off-by: Toshiaki Makita <makita.toshiaki@lab.ntt.co.jp> Acked-by: Vlad Yasevich <vyasevic@redhat.com> Signed-off-by: David S. Miller <davem@davemloft.net>
419 lines
8.0 KiB
C
419 lines
8.0 KiB
C
#include <linux/kernel.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "br_private.h"
|
|
|
|
static void __vlan_add_pvid(struct net_port_vlans *v, u16 vid)
|
|
{
|
|
if (v->pvid == vid)
|
|
return;
|
|
|
|
smp_wmb();
|
|
v->pvid = vid;
|
|
}
|
|
|
|
static void __vlan_delete_pvid(struct net_port_vlans *v, u16 vid)
|
|
{
|
|
if (v->pvid != vid)
|
|
return;
|
|
|
|
smp_wmb();
|
|
v->pvid = 0;
|
|
}
|
|
|
|
static void __vlan_add_flags(struct net_port_vlans *v, u16 vid, u16 flags)
|
|
{
|
|
if (flags & BRIDGE_VLAN_INFO_PVID)
|
|
__vlan_add_pvid(v, vid);
|
|
|
|
if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
|
|
set_bit(vid, v->untagged_bitmap);
|
|
}
|
|
|
|
static int __vlan_add(struct net_port_vlans *v, u16 vid, u16 flags)
|
|
{
|
|
struct net_bridge_port *p = NULL;
|
|
struct net_bridge *br;
|
|
struct net_device *dev;
|
|
int err;
|
|
|
|
if (test_bit(vid, v->vlan_bitmap)) {
|
|
__vlan_add_flags(v, vid, flags);
|
|
return 0;
|
|
}
|
|
|
|
if (v->port_idx) {
|
|
p = v->parent.port;
|
|
br = p->br;
|
|
dev = p->dev;
|
|
} else {
|
|
br = v->parent.br;
|
|
dev = br->dev;
|
|
}
|
|
|
|
if (p) {
|
|
/* Add VLAN to the device filter if it is supported.
|
|
* Stricly speaking, this is not necessary now, since
|
|
* devices are made promiscuous by the bridge, but if
|
|
* that ever changes this code will allow tagged
|
|
* traffic to enter the bridge.
|
|
*/
|
|
err = vlan_vid_add(dev, htons(ETH_P_8021Q), vid);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
err = br_fdb_insert(br, p, dev->dev_addr, vid);
|
|
if (err) {
|
|
br_err(br, "failed insert local address into bridge "
|
|
"forwarding table\n");
|
|
goto out_filt;
|
|
}
|
|
|
|
set_bit(vid, v->vlan_bitmap);
|
|
v->num_vlans++;
|
|
__vlan_add_flags(v, vid, flags);
|
|
|
|
return 0;
|
|
|
|
out_filt:
|
|
if (p)
|
|
vlan_vid_del(dev, htons(ETH_P_8021Q), vid);
|
|
return err;
|
|
}
|
|
|
|
static int __vlan_del(struct net_port_vlans *v, u16 vid)
|
|
{
|
|
if (!test_bit(vid, v->vlan_bitmap))
|
|
return -EINVAL;
|
|
|
|
__vlan_delete_pvid(v, vid);
|
|
clear_bit(vid, v->untagged_bitmap);
|
|
|
|
if (v->port_idx)
|
|
vlan_vid_del(v->parent.port->dev, htons(ETH_P_8021Q), vid);
|
|
|
|
clear_bit(vid, v->vlan_bitmap);
|
|
v->num_vlans--;
|
|
if (bitmap_empty(v->vlan_bitmap, VLAN_N_VID)) {
|
|
if (v->port_idx)
|
|
rcu_assign_pointer(v->parent.port->vlan_info, NULL);
|
|
else
|
|
rcu_assign_pointer(v->parent.br->vlan_info, NULL);
|
|
kfree_rcu(v, rcu);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void __vlan_flush(struct net_port_vlans *v)
|
|
{
|
|
smp_wmb();
|
|
v->pvid = 0;
|
|
bitmap_zero(v->vlan_bitmap, VLAN_N_VID);
|
|
if (v->port_idx)
|
|
rcu_assign_pointer(v->parent.port->vlan_info, NULL);
|
|
else
|
|
rcu_assign_pointer(v->parent.br->vlan_info, NULL);
|
|
kfree_rcu(v, rcu);
|
|
}
|
|
|
|
/* Strip the tag from the packet. Will return skb with tci set 0. */
|
|
static struct sk_buff *br_vlan_untag(struct sk_buff *skb)
|
|
{
|
|
if (skb->protocol != htons(ETH_P_8021Q)) {
|
|
skb->vlan_tci = 0;
|
|
return skb;
|
|
}
|
|
|
|
skb->vlan_tci = 0;
|
|
skb = vlan_untag(skb);
|
|
if (skb)
|
|
skb->vlan_tci = 0;
|
|
|
|
return skb;
|
|
}
|
|
|
|
struct sk_buff *br_handle_vlan(struct net_bridge *br,
|
|
const struct net_port_vlans *pv,
|
|
struct sk_buff *skb)
|
|
{
|
|
u16 vid;
|
|
|
|
if (!br->vlan_enabled)
|
|
goto out;
|
|
|
|
/* At this point, we know that the frame was filtered and contains
|
|
* a valid vlan id. If the vlan id is set in the untagged bitmap,
|
|
* send untagged; otherwise, send tagged.
|
|
*/
|
|
br_vlan_get_tag(skb, &vid);
|
|
if (test_bit(vid, pv->untagged_bitmap))
|
|
skb = br_vlan_untag(skb);
|
|
|
|
out:
|
|
return skb;
|
|
}
|
|
|
|
/* Called under RCU */
|
|
bool br_allowed_ingress(struct net_bridge *br, struct net_port_vlans *v,
|
|
struct sk_buff *skb, u16 *vid)
|
|
{
|
|
int err;
|
|
|
|
/* If VLAN filtering is disabled on the bridge, all packets are
|
|
* permitted.
|
|
*/
|
|
if (!br->vlan_enabled)
|
|
return true;
|
|
|
|
/* If there are no vlan in the permitted list, all packets are
|
|
* rejected.
|
|
*/
|
|
if (!v)
|
|
return false;
|
|
|
|
err = br_vlan_get_tag(skb, vid);
|
|
if (!*vid) {
|
|
u16 pvid = br_get_pvid(v);
|
|
|
|
/* Frame had a tag with VID 0 or did not have a tag.
|
|
* See if pvid is set on this port. That tells us which
|
|
* vlan untagged or priority-tagged traffic belongs to.
|
|
*/
|
|
if (pvid == VLAN_N_VID)
|
|
return false;
|
|
|
|
/* PVID is set on this port. Any untagged or priority-tagged
|
|
* ingress frame is considered to belong to this vlan.
|
|
*/
|
|
*vid = pvid;
|
|
if (likely(err))
|
|
/* Untagged Frame. */
|
|
__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), pvid);
|
|
else
|
|
/* Priority-tagged Frame.
|
|
* At this point, We know that skb->vlan_tci had
|
|
* VLAN_TAG_PRESENT bit and its VID field was 0x000.
|
|
* We update only VID field and preserve PCP field.
|
|
*/
|
|
skb->vlan_tci |= pvid;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Frame had a valid vlan tag. See if vlan is allowed */
|
|
if (test_bit(*vid, v->vlan_bitmap))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Called under RCU. */
|
|
bool br_allowed_egress(struct net_bridge *br,
|
|
const struct net_port_vlans *v,
|
|
const struct sk_buff *skb)
|
|
{
|
|
u16 vid;
|
|
|
|
if (!br->vlan_enabled)
|
|
return true;
|
|
|
|
if (!v)
|
|
return false;
|
|
|
|
br_vlan_get_tag(skb, &vid);
|
|
if (test_bit(vid, v->vlan_bitmap))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Must be protected by RTNL.
|
|
* Must be called with vid in range from 1 to 4094 inclusive.
|
|
*/
|
|
int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags)
|
|
{
|
|
struct net_port_vlans *pv = NULL;
|
|
int err;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
pv = rtnl_dereference(br->vlan_info);
|
|
if (pv)
|
|
return __vlan_add(pv, vid, flags);
|
|
|
|
/* Create port vlan infomration
|
|
*/
|
|
pv = kzalloc(sizeof(*pv), GFP_KERNEL);
|
|
if (!pv)
|
|
return -ENOMEM;
|
|
|
|
pv->parent.br = br;
|
|
err = __vlan_add(pv, vid, flags);
|
|
if (err)
|
|
goto out;
|
|
|
|
rcu_assign_pointer(br->vlan_info, pv);
|
|
return 0;
|
|
out:
|
|
kfree(pv);
|
|
return err;
|
|
}
|
|
|
|
/* Must be protected by RTNL.
|
|
* Must be called with vid in range from 1 to 4094 inclusive.
|
|
*/
|
|
int br_vlan_delete(struct net_bridge *br, u16 vid)
|
|
{
|
|
struct net_port_vlans *pv;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
pv = rtnl_dereference(br->vlan_info);
|
|
if (!pv)
|
|
return -EINVAL;
|
|
|
|
br_fdb_find_delete_local(br, NULL, br->dev->dev_addr, vid);
|
|
|
|
__vlan_del(pv, vid);
|
|
return 0;
|
|
}
|
|
|
|
void br_vlan_flush(struct net_bridge *br)
|
|
{
|
|
struct net_port_vlans *pv;
|
|
|
|
ASSERT_RTNL();
|
|
pv = rtnl_dereference(br->vlan_info);
|
|
if (!pv)
|
|
return;
|
|
|
|
__vlan_flush(pv);
|
|
}
|
|
|
|
bool br_vlan_find(struct net_bridge *br, u16 vid)
|
|
{
|
|
struct net_port_vlans *pv;
|
|
bool found = false;
|
|
|
|
rcu_read_lock();
|
|
pv = rcu_dereference(br->vlan_info);
|
|
|
|
if (!pv)
|
|
goto out;
|
|
|
|
if (test_bit(vid, pv->vlan_bitmap))
|
|
found = true;
|
|
|
|
out:
|
|
rcu_read_unlock();
|
|
return found;
|
|
}
|
|
|
|
int br_vlan_filter_toggle(struct net_bridge *br, unsigned long val)
|
|
{
|
|
if (!rtnl_trylock())
|
|
return restart_syscall();
|
|
|
|
if (br->vlan_enabled == val)
|
|
goto unlock;
|
|
|
|
br->vlan_enabled = val;
|
|
|
|
unlock:
|
|
rtnl_unlock();
|
|
return 0;
|
|
}
|
|
|
|
/* Must be protected by RTNL.
|
|
* Must be called with vid in range from 1 to 4094 inclusive.
|
|
*/
|
|
int nbp_vlan_add(struct net_bridge_port *port, u16 vid, u16 flags)
|
|
{
|
|
struct net_port_vlans *pv = NULL;
|
|
int err;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
pv = rtnl_dereference(port->vlan_info);
|
|
if (pv)
|
|
return __vlan_add(pv, vid, flags);
|
|
|
|
/* Create port vlan infomration
|
|
*/
|
|
pv = kzalloc(sizeof(*pv), GFP_KERNEL);
|
|
if (!pv) {
|
|
err = -ENOMEM;
|
|
goto clean_up;
|
|
}
|
|
|
|
pv->port_idx = port->port_no;
|
|
pv->parent.port = port;
|
|
err = __vlan_add(pv, vid, flags);
|
|
if (err)
|
|
goto clean_up;
|
|
|
|
rcu_assign_pointer(port->vlan_info, pv);
|
|
return 0;
|
|
|
|
clean_up:
|
|
kfree(pv);
|
|
return err;
|
|
}
|
|
|
|
/* Must be protected by RTNL.
|
|
* Must be called with vid in range from 1 to 4094 inclusive.
|
|
*/
|
|
int nbp_vlan_delete(struct net_bridge_port *port, u16 vid)
|
|
{
|
|
struct net_port_vlans *pv;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
pv = rtnl_dereference(port->vlan_info);
|
|
if (!pv)
|
|
return -EINVAL;
|
|
|
|
br_fdb_find_delete_local(port->br, port, port->dev->dev_addr, vid);
|
|
|
|
return __vlan_del(pv, vid);
|
|
}
|
|
|
|
void nbp_vlan_flush(struct net_bridge_port *port)
|
|
{
|
|
struct net_port_vlans *pv;
|
|
u16 vid;
|
|
|
|
ASSERT_RTNL();
|
|
|
|
pv = rtnl_dereference(port->vlan_info);
|
|
if (!pv)
|
|
return;
|
|
|
|
for_each_set_bit(vid, pv->vlan_bitmap, VLAN_N_VID)
|
|
vlan_vid_del(port->dev, htons(ETH_P_8021Q), vid);
|
|
|
|
__vlan_flush(pv);
|
|
}
|
|
|
|
bool nbp_vlan_find(struct net_bridge_port *port, u16 vid)
|
|
{
|
|
struct net_port_vlans *pv;
|
|
bool found = false;
|
|
|
|
rcu_read_lock();
|
|
pv = rcu_dereference(port->vlan_info);
|
|
|
|
if (!pv)
|
|
goto out;
|
|
|
|
if (test_bit(vid, pv->vlan_bitmap))
|
|
found = true;
|
|
|
|
out:
|
|
rcu_read_unlock();
|
|
return found;
|
|
}
|