2013-02-13 16:00:09 +04:00
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/netdevice.h>
|
|
|
|
#include <linux/rtnetlink.h>
|
|
|
|
#include <linux/slab.h>
|
2015-06-13 03:39:50 +03:00
|
|
|
#include <net/switchdev.h>
|
2013-02-13 16:00:09 +04:00
|
|
|
|
|
|
|
#include "br_private.h"
|
|
|
|
|
2013-02-13 16:00:15 +04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-02-13 16:00:20 +04:00
|
|
|
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);
|
2014-09-13 00:26:17 +04:00
|
|
|
else
|
|
|
|
__vlan_delete_pvid(v, vid);
|
2013-02-13 16:00:20 +04:00
|
|
|
|
|
|
|
if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
|
|
|
|
set_bit(vid, v->untagged_bitmap);
|
2014-09-13 00:26:17 +04:00
|
|
|
else
|
|
|
|
clear_bit(vid, v->untagged_bitmap);
|
2013-02-13 16:00:20 +04:00
|
|
|
}
|
|
|
|
|
2015-06-13 03:39:50 +03:00
|
|
|
static int __vlan_vid_add(struct net_device *dev, struct net_bridge *br,
|
|
|
|
u16 vid, u16 flags)
|
|
|
|
{
|
|
|
|
const struct net_device_ops *ops = dev->netdev_ops;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* If driver uses VLAN ndo ops, use 8021q to install vid
|
|
|
|
* on device, otherwise try switchdev ops to install vid.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (ops->ndo_vlan_rx_add_vid) {
|
|
|
|
err = vlan_vid_add(dev, br->vlan_proto, vid);
|
|
|
|
} else {
|
|
|
|
struct switchdev_obj vlan_obj = {
|
|
|
|
.id = SWITCHDEV_OBJ_PORT_VLAN,
|
|
|
|
.u.vlan = {
|
|
|
|
.flags = flags,
|
2015-06-22 10:27:16 +03:00
|
|
|
.vid_begin = vid,
|
2015-06-13 03:39:50 +03:00
|
|
|
.vid_end = vid,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
err = switchdev_port_obj_add(dev, &vlan_obj);
|
|
|
|
if (err == -EOPNOTSUPP)
|
|
|
|
err = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2013-02-13 16:00:15 +04:00
|
|
|
static int __vlan_add(struct net_port_vlans *v, u16 vid, u16 flags)
|
2013-02-13 16:00:09 +04:00
|
|
|
{
|
2013-02-13 16:00:19 +04:00
|
|
|
struct net_bridge_port *p = NULL;
|
|
|
|
struct net_bridge *br;
|
|
|
|
struct net_device *dev;
|
2013-02-13 16:00:09 +04:00
|
|
|
int err;
|
|
|
|
|
2013-02-13 16:00:15 +04:00
|
|
|
if (test_bit(vid, v->vlan_bitmap)) {
|
2013-02-13 16:00:20 +04:00
|
|
|
__vlan_add_flags(v, vid, flags);
|
2013-02-13 16:00:15 +04:00
|
|
|
return 0;
|
|
|
|
}
|
2013-02-13 16:00:09 +04:00
|
|
|
|
2013-10-16 12:07:13 +04:00
|
|
|
if (v->port_idx) {
|
|
|
|
p = v->parent.port;
|
|
|
|
br = p->br;
|
|
|
|
dev = p->dev;
|
|
|
|
} else {
|
|
|
|
br = v->parent.br;
|
|
|
|
dev = br->dev;
|
|
|
|
}
|
|
|
|
|
2013-11-13 12:26:12 +04:00
|
|
|
if (p) {
|
2013-10-16 12:07:13 +04:00
|
|
|
/* Add VLAN to the device filter if it is supported.
|
2014-08-05 10:57:15 +04:00
|
|
|
* This ensures tagged traffic enters the bridge when
|
|
|
|
* promiscuous mode is disabled by br_manage_promisc().
|
2013-10-16 12:07:13 +04:00
|
|
|
*/
|
2015-06-13 03:39:50 +03:00
|
|
|
err = __vlan_vid_add(dev, br, vid, flags);
|
2013-10-16 12:07:13 +04:00
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
}
|
2013-02-13 16:00:19 +04:00
|
|
|
|
2013-10-16 12:07:13 +04:00
|
|
|
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;
|
2013-02-13 16:00:09 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
set_bit(vid, v->vlan_bitmap);
|
2013-02-13 16:00:13 +04:00
|
|
|
v->num_vlans++;
|
2013-02-13 16:00:20 +04:00
|
|
|
__vlan_add_flags(v, vid, flags);
|
2013-02-13 16:00:15 +04:00
|
|
|
|
2013-02-13 16:00:09 +04:00
|
|
|
return 0;
|
2013-02-13 16:00:19 +04:00
|
|
|
|
|
|
|
out_filt:
|
2013-11-13 12:26:12 +04:00
|
|
|
if (p)
|
2014-06-10 15:59:23 +04:00
|
|
|
vlan_vid_del(dev, br->vlan_proto, vid);
|
2013-02-13 16:00:19 +04:00
|
|
|
return err;
|
2013-02-13 16:00:09 +04:00
|
|
|
}
|
|
|
|
|
2015-06-13 03:39:50 +03:00
|
|
|
static void __vlan_vid_del(struct net_device *dev, struct net_bridge *br,
|
|
|
|
u16 vid)
|
|
|
|
{
|
|
|
|
const struct net_device_ops *ops = dev->netdev_ops;
|
|
|
|
|
|
|
|
/* If driver uses VLAN ndo ops, use 8021q to delete vid
|
|
|
|
* on device, otherwise try switchdev ops to delete vid.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (ops->ndo_vlan_rx_kill_vid) {
|
|
|
|
vlan_vid_del(dev, br->vlan_proto, vid);
|
|
|
|
} else {
|
|
|
|
struct switchdev_obj vlan_obj = {
|
|
|
|
.id = SWITCHDEV_OBJ_PORT_VLAN,
|
|
|
|
.u.vlan = {
|
2015-06-22 10:27:16 +03:00
|
|
|
.vid_begin = vid,
|
2015-06-13 03:39:50 +03:00
|
|
|
.vid_end = vid,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
switchdev_port_obj_del(dev, &vlan_obj);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-13 16:00:09 +04:00
|
|
|
static int __vlan_del(struct net_port_vlans *v, u16 vid)
|
|
|
|
{
|
|
|
|
if (!test_bit(vid, v->vlan_bitmap))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2013-02-13 16:00:15 +04:00
|
|
|
__vlan_delete_pvid(v, vid);
|
2013-02-13 16:00:20 +04:00
|
|
|
clear_bit(vid, v->untagged_bitmap);
|
2013-02-13 16:00:15 +04:00
|
|
|
|
2014-06-10 15:59:23 +04:00
|
|
|
if (v->port_idx) {
|
|
|
|
struct net_bridge_port *p = v->parent.port;
|
2015-06-13 03:39:50 +03:00
|
|
|
__vlan_vid_del(p->dev, p->br, vid);
|
2014-06-10 15:59:23 +04:00
|
|
|
}
|
2013-02-13 16:00:09 +04:00
|
|
|
|
|
|
|
clear_bit(vid, v->vlan_bitmap);
|
2013-02-13 16:00:13 +04:00
|
|
|
v->num_vlans--;
|
2013-08-20 12:10:18 +04:00
|
|
|
if (bitmap_empty(v->vlan_bitmap, VLAN_N_VID)) {
|
2013-02-13 16:00:09 +04:00
|
|
|
if (v->port_idx)
|
2014-03-23 23:11:13 +04:00
|
|
|
RCU_INIT_POINTER(v->parent.port->vlan_info, NULL);
|
2013-02-13 16:00:09 +04:00
|
|
|
else
|
2014-03-23 23:11:13 +04:00
|
|
|
RCU_INIT_POINTER(v->parent.br->vlan_info, NULL);
|
2013-02-13 16:00:09 +04:00
|
|
|
kfree_rcu(v, rcu);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __vlan_flush(struct net_port_vlans *v)
|
|
|
|
{
|
2013-02-13 16:00:15 +04:00
|
|
|
smp_wmb();
|
|
|
|
v->pvid = 0;
|
2013-08-20 12:10:18 +04:00
|
|
|
bitmap_zero(v->vlan_bitmap, VLAN_N_VID);
|
2013-02-13 16:00:09 +04:00
|
|
|
if (v->port_idx)
|
2014-03-23 23:11:13 +04:00
|
|
|
RCU_INIT_POINTER(v->parent.port->vlan_info, NULL);
|
2013-02-13 16:00:09 +04:00
|
|
|
else
|
2014-03-23 23:11:13 +04:00
|
|
|
RCU_INIT_POINTER(v->parent.br->vlan_info, NULL);
|
2013-02-13 16:00:09 +04:00
|
|
|
kfree_rcu(v, rcu);
|
|
|
|
}
|
|
|
|
|
2013-02-13 16:00:14 +04:00
|
|
|
struct sk_buff *br_handle_vlan(struct net_bridge *br,
|
|
|
|
const struct net_port_vlans *pv,
|
|
|
|
struct sk_buff *skb)
|
2013-02-13 16:00:10 +04:00
|
|
|
{
|
|
|
|
u16 vid;
|
|
|
|
|
2014-09-13 00:26:16 +04:00
|
|
|
/* If this packet was not filtered at input, let it pass */
|
|
|
|
if (!BR_INPUT_SKB_CB(skb)->vlan_filtered)
|
2013-02-13 16:00:14 +04:00
|
|
|
goto out;
|
|
|
|
|
2014-03-28 05:51:18 +04:00
|
|
|
/* Vlan filter table must be configured at this point. The
|
|
|
|
* only exception is the bridge is set in promisc mode and the
|
|
|
|
* packet is destined for the bridge device. In this case
|
|
|
|
* pass the packet as is.
|
|
|
|
*/
|
|
|
|
if (!pv) {
|
|
|
|
if ((br->dev->flags & IFF_PROMISC) && skb->dev == br->dev) {
|
|
|
|
goto out;
|
|
|
|
} else {
|
|
|
|
kfree_skb(skb);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-13 16:00:14 +04:00
|
|
|
/* At this point, we know that the frame was filtered and contains
|
2013-02-13 16:00:20 +04:00
|
|
|
* a valid vlan id. If the vlan id is set in the untagged bitmap,
|
2013-12-16 17:32:46 +04:00
|
|
|
* send untagged; otherwise, send tagged.
|
2013-02-13 16:00:14 +04:00
|
|
|
*/
|
|
|
|
br_vlan_get_tag(skb, &vid);
|
2013-02-13 16:00:20 +04:00
|
|
|
if (test_bit(vid, pv->untagged_bitmap))
|
2014-03-27 16:46:56 +04:00
|
|
|
skb->vlan_tci = 0;
|
2013-02-13 16:00:14 +04:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2014-06-10 15:59:23 +04:00
|
|
|
bool tagged;
|
|
|
|
__be16 proto;
|
2013-10-16 12:07:14 +04:00
|
|
|
|
2013-02-13 16:00:10 +04:00
|
|
|
/* If VLAN filtering is disabled on the bridge, all packets are
|
|
|
|
* permitted.
|
|
|
|
*/
|
2014-09-13 00:26:16 +04:00
|
|
|
if (!br->vlan_enabled) {
|
|
|
|
BR_INPUT_SKB_CB(skb)->vlan_filtered = false;
|
2013-02-13 16:00:10 +04:00
|
|
|
return true;
|
2014-09-13 00:26:16 +04:00
|
|
|
}
|
2013-02-13 16:00:10 +04:00
|
|
|
|
|
|
|
/* If there are no vlan in the permitted list, all packets are
|
|
|
|
* rejected.
|
|
|
|
*/
|
|
|
|
if (!v)
|
2014-04-09 12:00:30 +04:00
|
|
|
goto drop;
|
2013-02-13 16:00:10 +04:00
|
|
|
|
2014-09-13 00:26:16 +04:00
|
|
|
BR_INPUT_SKB_CB(skb)->vlan_filtered = true;
|
2014-06-10 15:59:23 +04:00
|
|
|
proto = br->vlan_proto;
|
|
|
|
|
2014-03-27 16:46:55 +04:00
|
|
|
/* If vlan tx offload is disabled on bridge device and frame was
|
|
|
|
* sent from vlan device on the bridge device, it does not have
|
|
|
|
* HW accelerated vlan tag.
|
|
|
|
*/
|
2015-01-13 19:13:44 +03:00
|
|
|
if (unlikely(!skb_vlan_tag_present(skb) &&
|
2014-06-10 15:59:23 +04:00
|
|
|
skb->protocol == proto)) {
|
net: Always untag vlan-tagged traffic on input.
Currently the functionality to untag traffic on input resides
as part of the vlan module and is build only when VLAN support
is enabled in the kernel. When VLAN is disabled, the function
vlan_untag() turns into a stub and doesn't really untag the
packets. This seems to create an interesting interaction
between VMs supporting checksum offloading and some network drivers.
There are some drivers that do not allow the user to change
tx-vlan-offload feature of the driver. These drivers also seem
to assume that any VLAN-tagged traffic they transmit will
have the vlan information in the vlan_tci and not in the vlan
header already in the skb. When transmitting skbs that already
have tagged data with partial checksum set, the checksum doesn't
appear to be updated correctly by the card thus resulting in a
failure to establish TCP connections.
The following is a packet trace taken on the receiver where a
sender is a VM with a VLAN configued. The host VM is running on
doest not have VLAN support and the outging interface on the
host is tg3:
10:12:43.503055 52:54:00:ae:42:3f > 28:d2:44:7d:c2:de, ethertype 802.1Q
(0x8100), length 78: vlan 100, p 0, ethertype IPv4, (tos 0x0, ttl 64, id 27243,
offset 0, flags [DF], proto TCP (6), length 60)
10.0.100.1.58545 > 10.0.100.10.ircu-2: Flags [S], cksum 0xdc39 (incorrect
-> 0x48d9), seq 1069378582, win 29200, options [mss 1460,sackOK,TS val
4294837885 ecr 0,nop,wscale 7], length 0
10:12:44.505556 52:54:00:ae:42:3f > 28:d2:44:7d:c2:de, ethertype 802.1Q
(0x8100), length 78: vlan 100, p 0, ethertype IPv4, (tos 0x0, ttl 64, id 27244,
offset 0, flags [DF], proto TCP (6), length 60)
10.0.100.1.58545 > 10.0.100.10.ircu-2: Flags [S], cksum 0xdc39 (incorrect
-> 0x44ee), seq 1069378582, win 29200, options [mss 1460,sackOK,TS val
4294838888 ecr 0,nop,wscale 7], length 0
This connection finally times out.
I've only access to the TG3 hardware in this configuration thus have
only tested this with TG3 driver. There are a lot of other drivers
that do not permit user changes to vlan acceleration features, and
I don't know if they all suffere from a similar issue.
The patch attempt to fix this another way. It moves the vlan header
stipping code out of the vlan module and always builds it into the
kernel network core. This way, even if vlan is not supported on
a virtualizatoin host, the virtual machines running on top of such
host will still work with VLANs enabled.
CC: Patrick McHardy <kaber@trash.net>
CC: Nithin Nayak Sujir <nsujir@broadcom.com>
CC: Michael Chan <mchan@broadcom.com>
CC: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: Vladislav Yasevich <vyasevic@redhat.com>
Acked-by: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: David S. Miller <davem@davemloft.net>
2014-08-08 22:42:13 +04:00
|
|
|
skb = skb_vlan_untag(skb);
|
2014-03-27 16:46:55 +04:00
|
|
|
if (unlikely(!skb))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-06-10 15:59:23 +04:00
|
|
|
if (!br_vlan_get_tag(skb, vid)) {
|
|
|
|
/* Tagged frame */
|
|
|
|
if (skb->vlan_proto != proto) {
|
|
|
|
/* Protocol-mismatch, empty out vlan_tci for new tag */
|
|
|
|
skb_push(skb, ETH_HLEN);
|
2014-11-19 16:04:58 +03:00
|
|
|
skb = vlan_insert_tag_set_proto(skb, skb->vlan_proto,
|
2015-01-13 19:13:44 +03:00
|
|
|
skb_vlan_tag_get(skb));
|
2014-06-10 15:59:23 +04:00
|
|
|
if (unlikely(!skb))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
skb_pull(skb, ETH_HLEN);
|
|
|
|
skb_reset_mac_len(skb);
|
|
|
|
*vid = 0;
|
|
|
|
tagged = false;
|
|
|
|
} else {
|
|
|
|
tagged = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Untagged frame */
|
|
|
|
tagged = false;
|
|
|
|
}
|
|
|
|
|
2013-10-16 12:07:14 +04:00
|
|
|
if (!*vid) {
|
2013-02-13 16:00:14 +04:00
|
|
|
u16 pvid = br_get_pvid(v);
|
|
|
|
|
2013-10-16 12:07:14 +04:00
|
|
|
/* 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.
|
2013-02-13 16:00:14 +04:00
|
|
|
*/
|
2014-10-03 19:29:17 +04:00
|
|
|
if (!pvid)
|
2014-04-09 12:00:30 +04:00
|
|
|
goto drop;
|
2013-02-13 16:00:14 +04:00
|
|
|
|
2013-10-16 12:07:14 +04:00
|
|
|
/* PVID is set on this port. Any untagged or priority-tagged
|
|
|
|
* ingress frame is considered to belong to this vlan.
|
2013-02-13 16:00:14 +04:00
|
|
|
*/
|
2013-10-16 12:07:16 +04:00
|
|
|
*vid = pvid;
|
2014-06-10 15:59:23 +04:00
|
|
|
if (likely(!tagged))
|
2013-10-16 12:07:14 +04:00
|
|
|
/* Untagged Frame. */
|
2014-06-10 15:59:23 +04:00
|
|
|
__vlan_hwaccel_put_tag(skb, proto, pvid);
|
2013-10-16 12:07:14 +04:00
|
|
|
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;
|
|
|
|
|
2013-02-13 16:00:14 +04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Frame had a valid vlan tag. See if vlan is allowed */
|
|
|
|
if (test_bit(*vid, v->vlan_bitmap))
|
2013-02-13 16:00:10 +04:00
|
|
|
return true;
|
2014-04-09 12:00:30 +04:00
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
2013-02-13 16:00:10 +04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-02-13 16:00:11 +04:00
|
|
|
/* Called under RCU. */
|
|
|
|
bool br_allowed_egress(struct net_bridge *br,
|
|
|
|
const struct net_port_vlans *v,
|
|
|
|
const struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
u16 vid;
|
|
|
|
|
2014-09-13 00:26:16 +04:00
|
|
|
/* If this packet was not filtered at input, let it pass */
|
|
|
|
if (!BR_INPUT_SKB_CB(skb)->vlan_filtered)
|
2013-02-13 16:00:11 +04:00
|
|
|
return true;
|
|
|
|
|
|
|
|
if (!v)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
br_vlan_get_tag(skb, &vid);
|
|
|
|
if (test_bit(vid, v->vlan_bitmap))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-05-26 10:15:53 +04:00
|
|
|
/* Called under RCU */
|
|
|
|
bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid)
|
|
|
|
{
|
|
|
|
struct net_bridge *br = p->br;
|
|
|
|
struct net_port_vlans *v;
|
|
|
|
|
2014-09-13 00:26:16 +04:00
|
|
|
/* If filtering was disabled at input, let it pass. */
|
2014-09-15 23:24:26 +04:00
|
|
|
if (!br->vlan_enabled)
|
2014-05-26 10:15:53 +04:00
|
|
|
return true;
|
|
|
|
|
|
|
|
v = rcu_dereference(p->vlan_info);
|
|
|
|
if (!v)
|
|
|
|
return false;
|
|
|
|
|
2014-06-10 15:59:23 +04:00
|
|
|
if (!br_vlan_get_tag(skb, vid) && skb->vlan_proto != br->vlan_proto)
|
|
|
|
*vid = 0;
|
|
|
|
|
2014-05-26 10:15:53 +04:00
|
|
|
if (!*vid) {
|
|
|
|
*vid = br_get_pvid(v);
|
2014-10-03 19:29:17 +04:00
|
|
|
if (!*vid)
|
2014-05-26 10:15:53 +04:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (test_bit(*vid, v->vlan_bitmap))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-10-16 12:07:13 +04:00
|
|
|
/* Must be protected by RTNL.
|
|
|
|
* Must be called with vid in range from 1 to 4094 inclusive.
|
|
|
|
*/
|
2013-02-13 16:00:15 +04:00
|
|
|
int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags)
|
2013-02-13 16:00:09 +04:00
|
|
|
{
|
|
|
|
struct net_port_vlans *pv = NULL;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
ASSERT_RTNL();
|
|
|
|
|
|
|
|
pv = rtnl_dereference(br->vlan_info);
|
|
|
|
if (pv)
|
2013-02-13 16:00:15 +04:00
|
|
|
return __vlan_add(pv, vid, flags);
|
2013-02-13 16:00:09 +04:00
|
|
|
|
|
|
|
/* Create port vlan infomration
|
|
|
|
*/
|
|
|
|
pv = kzalloc(sizeof(*pv), GFP_KERNEL);
|
|
|
|
if (!pv)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
pv->parent.br = br;
|
2013-02-13 16:00:15 +04:00
|
|
|
err = __vlan_add(pv, vid, flags);
|
2013-02-13 16:00:09 +04:00
|
|
|
if (err)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
rcu_assign_pointer(br->vlan_info, pv);
|
|
|
|
return 0;
|
|
|
|
out:
|
|
|
|
kfree(pv);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2013-10-16 12:07:13 +04:00
|
|
|
/* Must be protected by RTNL.
|
|
|
|
* Must be called with vid in range from 1 to 4094 inclusive.
|
|
|
|
*/
|
2013-02-13 16:00:09 +04:00
|
|
|
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;
|
|
|
|
|
2014-02-07 11:48:25 +04:00
|
|
|
br_fdb_find_delete_local(br, NULL, br->dev->dev_addr, vid);
|
2013-02-13 16:00:19 +04:00
|
|
|
|
2013-02-13 16:00:09 +04:00
|
|
|
__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);
|
|
|
|
}
|
|
|
|
|
bridge: Fix the way to check if a local fdb entry can be deleted
We should take into account the followings when deleting a local fdb
entry.
- nbp_vlan_find() can be used only when vid != 0 to check if an entry is
deletable, because a fdb entry with vid 0 can exist at any time while
nbp_vlan_find() always return false with vid 0.
Example of problematic case:
ip link set eth0 address 12:34:56:78:90:ab
ip link set eth1 address 12:34:56:78:90:ab
brctl addif br0 eth0
brctl addif br0 eth1
ip link set eth0 address aa:bb:cc:dd:ee:ff
Then, the fdb entry 12:34:56:78:90:ab will be deleted even though the
bridge port eth1 still has that address.
- The port to which the bridge device is attached might needs a local entry
if its mac address is set manually.
Example of problematic case:
ip link set eth0 address 12:34:56:78:90:ab
brctl addif br0 eth0
ip link set br0 address 12:34:56:78:90:ab
ip link set eth0 address aa:bb:cc:dd:ee:ff
Then, the fdb still must have the entry 12:34:56:78:90:ab, but it will be
deleted.
We can use br->dev->addr_assign_type to check if the address is manually
set or not, but I propose another approach.
Since we delete and insert local entries whenever changing mac address
of the bridge device, we can change dst of the entry to NULL regardless of
addr_assign_type when deleting an entry associated with a certain port,
and if it is found to be unnecessary later, then delete it.
That is, if changing mac address of a port, the entry might be changed
to its dst being NULL first, but is eventually deleted when recalculating
and changing bridge id.
This approach is especially useful when we want to share the code with
deleting vlan in which the bridge device might want such an entry regardless
of addr_assign_type, and makes things easy because we don't have to consider
if mac address of the bridge device will be changed or not at the time we
delete a local entry of a port, which means fdb code will not be bothered
even if the bridge id calculating logic is changed in the future.
Also, this change reduces inconsistent state, where frames whose dst is the
mac address of the bridge, can't reach the bridge because of premature fdb
entry deletion. This change reduces the possibility that the bridge device
replies unreachable mac address to arp requests, which could occur during
the short window between calling del_nbp() and br_stp_recalculate_bridge_id()
in br_del_if(). This will effective after br_fdb_delete_by_port() starts to
use the same code by following patch.
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>
2014-02-07 11:48:22 +04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-06-10 15:59:25 +04:00
|
|
|
/* Must be protected by RTNL. */
|
|
|
|
static void recalculate_group_addr(struct net_bridge *br)
|
|
|
|
{
|
|
|
|
if (br->group_addr_set)
|
|
|
|
return;
|
|
|
|
|
|
|
|
spin_lock_bh(&br->lock);
|
|
|
|
if (!br->vlan_enabled || br->vlan_proto == htons(ETH_P_8021Q)) {
|
|
|
|
/* Bridge Group Address */
|
|
|
|
br->group_addr[5] = 0x00;
|
|
|
|
} else { /* vlan_enabled && ETH_P_8021AD */
|
|
|
|
/* Provider Bridge Group Address */
|
|
|
|
br->group_addr[5] = 0x08;
|
|
|
|
}
|
|
|
|
spin_unlock_bh(&br->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Must be protected by RTNL. */
|
|
|
|
void br_recalculate_fwd_mask(struct net_bridge *br)
|
|
|
|
{
|
|
|
|
if (!br->vlan_enabled || br->vlan_proto == htons(ETH_P_8021Q))
|
|
|
|
br->group_fwd_mask_required = BR_GROUPFWD_DEFAULT;
|
|
|
|
else /* vlan_enabled && ETH_P_8021AD */
|
|
|
|
br->group_fwd_mask_required = BR_GROUPFWD_8021AD &
|
|
|
|
~(1u << br->group_addr[5]);
|
|
|
|
}
|
|
|
|
|
2015-08-07 19:40:45 +03:00
|
|
|
int __br_vlan_filter_toggle(struct net_bridge *br, unsigned long val)
|
2013-02-13 16:00:09 +04:00
|
|
|
{
|
|
|
|
if (br->vlan_enabled == val)
|
2015-08-07 19:40:45 +03:00
|
|
|
return 0;
|
2013-02-13 16:00:09 +04:00
|
|
|
|
|
|
|
br->vlan_enabled = val;
|
2014-05-16 17:59:20 +04:00
|
|
|
br_manage_promisc(br);
|
2014-06-10 15:59:25 +04:00
|
|
|
recalculate_group_addr(br);
|
|
|
|
br_recalculate_fwd_mask(br);
|
2013-02-13 16:00:09 +04:00
|
|
|
|
2015-08-07 19:40:45 +03:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int br_vlan_filter_toggle(struct net_bridge *br, unsigned long val)
|
|
|
|
{
|
|
|
|
if (!rtnl_trylock())
|
|
|
|
return restart_syscall();
|
|
|
|
|
|
|
|
__br_vlan_filter_toggle(br, val);
|
2013-02-13 16:00:09 +04:00
|
|
|
rtnl_unlock();
|
2015-08-07 19:40:45 +03:00
|
|
|
|
2013-02-13 16:00:09 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-08-27 09:32:26 +03:00
|
|
|
int __br_vlan_set_proto(struct net_bridge *br, __be16 proto)
|
2014-06-10 15:59:25 +04:00
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
struct net_bridge_port *p;
|
|
|
|
struct net_port_vlans *pv;
|
2015-08-27 09:32:26 +03:00
|
|
|
__be16 oldproto;
|
2014-06-10 15:59:25 +04:00
|
|
|
u16 vid, errvid;
|
|
|
|
|
|
|
|
if (br->vlan_proto == proto)
|
2015-08-27 09:32:26 +03:00
|
|
|
return 0;
|
2014-06-10 15:59:25 +04:00
|
|
|
|
|
|
|
/* Add VLANs for the new proto to the device filter. */
|
|
|
|
list_for_each_entry(p, &br->port_list, list) {
|
|
|
|
pv = rtnl_dereference(p->vlan_info);
|
|
|
|
if (!pv)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for_each_set_bit(vid, pv->vlan_bitmap, VLAN_N_VID) {
|
|
|
|
err = vlan_vid_add(p->dev, proto, vid);
|
|
|
|
if (err)
|
|
|
|
goto err_filt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
oldproto = br->vlan_proto;
|
|
|
|
br->vlan_proto = proto;
|
|
|
|
|
|
|
|
recalculate_group_addr(br);
|
|
|
|
br_recalculate_fwd_mask(br);
|
|
|
|
|
|
|
|
/* Delete VLANs for the old proto from the device filter. */
|
|
|
|
list_for_each_entry(p, &br->port_list, list) {
|
|
|
|
pv = rtnl_dereference(p->vlan_info);
|
|
|
|
if (!pv)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for_each_set_bit(vid, pv->vlan_bitmap, VLAN_N_VID)
|
|
|
|
vlan_vid_del(p->dev, oldproto, vid);
|
|
|
|
}
|
|
|
|
|
2015-08-27 09:32:26 +03:00
|
|
|
return 0;
|
2014-06-10 15:59:25 +04:00
|
|
|
|
|
|
|
err_filt:
|
|
|
|
errvid = vid;
|
|
|
|
for_each_set_bit(vid, pv->vlan_bitmap, errvid)
|
|
|
|
vlan_vid_del(p->dev, proto, vid);
|
|
|
|
|
|
|
|
list_for_each_entry_continue_reverse(p, &br->port_list, list) {
|
|
|
|
pv = rtnl_dereference(p->vlan_info);
|
|
|
|
if (!pv)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for_each_set_bit(vid, pv->vlan_bitmap, VLAN_N_VID)
|
|
|
|
vlan_vid_del(p->dev, proto, vid);
|
|
|
|
}
|
|
|
|
|
2015-08-27 09:32:26 +03:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
int br_vlan_set_proto(struct net_bridge *br, unsigned long val)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (val != ETH_P_8021Q && val != ETH_P_8021AD)
|
|
|
|
return -EPROTONOSUPPORT;
|
|
|
|
|
|
|
|
if (!rtnl_trylock())
|
|
|
|
return restart_syscall();
|
|
|
|
|
|
|
|
err = __br_vlan_set_proto(br, htons(val));
|
|
|
|
rtnl_unlock();
|
|
|
|
|
|
|
|
return err;
|
2014-06-10 15:59:25 +04:00
|
|
|
}
|
|
|
|
|
2014-10-03 19:29:18 +04:00
|
|
|
static bool vlan_default_pvid(struct net_port_vlans *pv, u16 vid)
|
|
|
|
{
|
|
|
|
return pv && vid == pv->pvid && test_bit(vid, pv->untagged_bitmap);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void br_vlan_disable_default_pvid(struct net_bridge *br)
|
|
|
|
{
|
|
|
|
struct net_bridge_port *p;
|
|
|
|
u16 pvid = br->default_pvid;
|
|
|
|
|
|
|
|
/* Disable default_pvid on all ports where it is still
|
|
|
|
* configured.
|
|
|
|
*/
|
|
|
|
if (vlan_default_pvid(br_get_vlan_info(br), pvid))
|
|
|
|
br_vlan_delete(br, pvid);
|
|
|
|
|
|
|
|
list_for_each_entry(p, &br->port_list, list) {
|
|
|
|
if (vlan_default_pvid(nbp_get_vlan_info(p), pvid))
|
|
|
|
nbp_vlan_delete(p, pvid);
|
|
|
|
}
|
|
|
|
|
|
|
|
br->default_pvid = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid)
|
|
|
|
{
|
|
|
|
struct net_bridge_port *p;
|
|
|
|
u16 old_pvid;
|
|
|
|
int err = 0;
|
|
|
|
unsigned long *changed;
|
|
|
|
|
|
|
|
changed = kcalloc(BITS_TO_LONGS(BR_MAX_PORTS), sizeof(unsigned long),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!changed)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
old_pvid = br->default_pvid;
|
|
|
|
|
|
|
|
/* Update default_pvid config only if we do not conflict with
|
|
|
|
* user configuration.
|
|
|
|
*/
|
|
|
|
if ((!old_pvid || vlan_default_pvid(br_get_vlan_info(br), old_pvid)) &&
|
|
|
|
!br_vlan_find(br, pvid)) {
|
|
|
|
err = br_vlan_add(br, pvid,
|
|
|
|
BRIDGE_VLAN_INFO_PVID |
|
|
|
|
BRIDGE_VLAN_INFO_UNTAGGED);
|
|
|
|
if (err)
|
|
|
|
goto out;
|
|
|
|
br_vlan_delete(br, old_pvid);
|
|
|
|
set_bit(0, changed);
|
|
|
|
}
|
|
|
|
|
|
|
|
list_for_each_entry(p, &br->port_list, list) {
|
|
|
|
/* Update default_pvid config only if we do not conflict with
|
|
|
|
* user configuration.
|
|
|
|
*/
|
|
|
|
if ((old_pvid &&
|
|
|
|
!vlan_default_pvid(nbp_get_vlan_info(p), old_pvid)) ||
|
|
|
|
nbp_vlan_find(p, pvid))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
err = nbp_vlan_add(p, pvid,
|
|
|
|
BRIDGE_VLAN_INFO_PVID |
|
|
|
|
BRIDGE_VLAN_INFO_UNTAGGED);
|
|
|
|
if (err)
|
|
|
|
goto err_port;
|
|
|
|
nbp_vlan_delete(p, old_pvid);
|
|
|
|
set_bit(p->port_no, changed);
|
|
|
|
}
|
|
|
|
|
|
|
|
br->default_pvid = pvid;
|
|
|
|
|
|
|
|
out:
|
|
|
|
kfree(changed);
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err_port:
|
|
|
|
list_for_each_entry_continue_reverse(p, &br->port_list, list) {
|
|
|
|
if (!test_bit(p->port_no, changed))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (old_pvid)
|
|
|
|
nbp_vlan_add(p, old_pvid,
|
|
|
|
BRIDGE_VLAN_INFO_PVID |
|
|
|
|
BRIDGE_VLAN_INFO_UNTAGGED);
|
|
|
|
nbp_vlan_delete(p, pvid);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (test_bit(0, changed)) {
|
|
|
|
if (old_pvid)
|
|
|
|
br_vlan_add(br, old_pvid,
|
|
|
|
BRIDGE_VLAN_INFO_PVID |
|
|
|
|
BRIDGE_VLAN_INFO_UNTAGGED);
|
|
|
|
br_vlan_delete(br, pvid);
|
|
|
|
}
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2014-10-03 19:29:16 +04:00
|
|
|
int br_vlan_set_default_pvid(struct net_bridge *br, unsigned long val)
|
|
|
|
{
|
|
|
|
u16 pvid = val;
|
|
|
|
int err = 0;
|
|
|
|
|
2014-10-03 19:29:18 +04:00
|
|
|
if (val >= VLAN_VID_MASK)
|
2014-10-03 19:29:16 +04:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (!rtnl_trylock())
|
|
|
|
return restart_syscall();
|
|
|
|
|
|
|
|
if (pvid == br->default_pvid)
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
/* Only allow default pvid change when filtering is disabled */
|
|
|
|
if (br->vlan_enabled) {
|
|
|
|
pr_info_once("Please disable vlan filtering to change default_pvid\n");
|
|
|
|
err = -EPERM;
|
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
|
2014-10-03 19:29:18 +04:00
|
|
|
if (!pvid)
|
|
|
|
br_vlan_disable_default_pvid(br);
|
|
|
|
else
|
|
|
|
err = __br_vlan_set_default_pvid(br, pvid);
|
2014-10-03 19:29:16 +04:00
|
|
|
|
|
|
|
unlock:
|
|
|
|
rtnl_unlock();
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2014-10-03 19:29:18 +04:00
|
|
|
int br_vlan_init(struct net_bridge *br)
|
2014-06-10 15:59:23 +04:00
|
|
|
{
|
|
|
|
br->vlan_proto = htons(ETH_P_8021Q);
|
2014-10-03 19:29:16 +04:00
|
|
|
br->default_pvid = 1;
|
2014-10-03 19:29:18 +04:00
|
|
|
return br_vlan_add(br, 1,
|
|
|
|
BRIDGE_VLAN_INFO_PVID | BRIDGE_VLAN_INFO_UNTAGGED);
|
2014-06-10 15:59:23 +04:00
|
|
|
}
|
|
|
|
|
2013-10-16 12:07:13 +04:00
|
|
|
/* Must be protected by RTNL.
|
|
|
|
* Must be called with vid in range from 1 to 4094 inclusive.
|
|
|
|
*/
|
2013-02-13 16:00:15 +04:00
|
|
|
int nbp_vlan_add(struct net_bridge_port *port, u16 vid, u16 flags)
|
2013-02-13 16:00:09 +04:00
|
|
|
{
|
|
|
|
struct net_port_vlans *pv = NULL;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
ASSERT_RTNL();
|
|
|
|
|
|
|
|
pv = rtnl_dereference(port->vlan_info);
|
|
|
|
if (pv)
|
2013-02-13 16:00:15 +04:00
|
|
|
return __vlan_add(pv, vid, flags);
|
2013-02-13 16:00:09 +04:00
|
|
|
|
|
|
|
/* 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;
|
2013-02-13 16:00:15 +04:00
|
|
|
err = __vlan_add(pv, vid, flags);
|
2013-02-13 16:00:09 +04:00
|
|
|
if (err)
|
|
|
|
goto clean_up;
|
|
|
|
|
|
|
|
rcu_assign_pointer(port->vlan_info, pv);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
clean_up:
|
|
|
|
kfree(pv);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2013-10-16 12:07:13 +04:00
|
|
|
/* Must be protected by RTNL.
|
|
|
|
* Must be called with vid in range from 1 to 4094 inclusive.
|
|
|
|
*/
|
2013-02-13 16:00:09 +04:00
|
|
|
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;
|
|
|
|
|
2014-02-07 11:48:25 +04:00
|
|
|
br_fdb_find_delete_local(port->br, port, port->dev->dev_addr, vid);
|
2015-06-23 15:28:16 +03:00
|
|
|
br_fdb_delete_by_port(port->br, port, vid, 0);
|
2013-02-13 16:00:19 +04:00
|
|
|
|
2013-02-13 16:00:09 +04:00
|
|
|
return __vlan_del(pv, vid);
|
|
|
|
}
|
|
|
|
|
|
|
|
void nbp_vlan_flush(struct net_bridge_port *port)
|
|
|
|
{
|
|
|
|
struct net_port_vlans *pv;
|
2013-11-13 12:26:13 +04:00
|
|
|
u16 vid;
|
2013-02-13 16:00:09 +04:00
|
|
|
|
|
|
|
ASSERT_RTNL();
|
|
|
|
|
|
|
|
pv = rtnl_dereference(port->vlan_info);
|
|
|
|
if (!pv)
|
|
|
|
return;
|
|
|
|
|
2013-11-13 12:26:13 +04:00
|
|
|
for_each_set_bit(vid, pv->vlan_bitmap, VLAN_N_VID)
|
2014-06-10 15:59:23 +04:00
|
|
|
vlan_vid_del(port->dev, port->br->vlan_proto, vid);
|
2013-11-13 12:26:13 +04:00
|
|
|
|
2013-02-13 16:00:09 +04:00
|
|
|
__vlan_flush(pv);
|
|
|
|
}
|
2013-02-13 16:00:19 +04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2014-10-03 19:29:18 +04:00
|
|
|
|
|
|
|
int nbp_vlan_init(struct net_bridge_port *p)
|
|
|
|
{
|
|
|
|
return p->br->default_pvid ?
|
|
|
|
nbp_vlan_add(p, p->br->default_pvid,
|
|
|
|
BRIDGE_VLAN_INFO_PVID |
|
|
|
|
BRIDGE_VLAN_INFO_UNTAGGED) :
|
|
|
|
0;
|
|
|
|
}
|