linux bridge - mac & vlan forward

linux bridge - mac & vlan forward

https://www.jianshu.com/p/cd1b713b798d

 

这篇文档主要介绍一下bridge的vlan功能如何使用和生效。

如果bridge要支持vlan filter,需要满足如下条件
a. 打开kernel编译选项:CONFIG_BRIDGE_VLAN_FILTERING
b. 打开 vlan enable,比如打开网桥br1的vlan filter功能: echo 1 > /sys/class/net/br1/bridge/vlan_filtering

使能vlan filter后,只能通过bridge命令查看端口的vlan和fdb转发表

root@node2:~# bridge vlan 
port    vlan ids

br1  1 PVID Egress Untagged

vetha    1 PVID Egress Untagged

vethx    1 PVID Egress Untagged

root@node2:~# bridge fdb list br br1
33:33:00:00:00:01 dev br1 self permanent
66:e6:6f:a8:d4:97 dev vetha master br1 permanent
66:e6:6f:a8:d4:97 dev vetha vlan 10 master br1 permanent
66:e6:6f:a8:d4:97 dev vetha vlan 1 master br1 permanent
33:33:00:00:00:01 dev vetha self permanent
01:00:5e:00:00:01 dev vetha self permanent
12:27:96:8c:f4:58 dev vethx vlan 10 master br1 permanent
12:27:96:8c:f4:58 dev vethx master br1 permanent
12:27:96:8c:f4:58 dev vethx vlan 1 master br1 permanent
33:33:00:00:00:01 dev vethx self permanent
01:00:5e:00:00:01 dev vethx self permanent

给网桥和端口添加vlan的区别

#给端口添加vlan时,可指定master或者不指定,kernel 端会取出vetha的master设备(即网桥),
#调用网桥的 ndo_bridge_setlink 给端口添加vlan
bridge vlan add vid 10 dev vetha untagged pvid master
bridge vlan add vid 10 dev vetha untagged pvid

#给网桥添加vlan时,必须指定self,kernel端会调用网桥的ndo_bridge_setlink给网桥添加vlan
bridge vlan add vid 13 dev br1 untagged pvid self

关于两个参数: untagged pvid

untagged: 如果指定了此参数,则报文从此端口发出时,vlan会被剥掉。如果不指定,则报文会携带vlan发出去。
pvid:如果指定了此参数,则此端口收到不带vlan报文时,则会给报文添加pvid。如果不指定,则会给报文添加默认pvid 1。如果连pvid都没有,则收到不带vlan报文时,会被drop掉

接收报文处理

如果vlan filter功能没使能,则始终允许报文通过。
如果vlan filter功能使能了,需要根据报文是否携带vlan进行不同处理:
  如果报文带vlan,则判断此vlan是否在vlan_bitmap中,如果存在,则返回true,如果不存在,则返回flase,表示不允许此报文通过。
  如果报文不带vlan,将pvid赋给skb(如果pvid也不存在,则drop此报文),然后判断此vlan是否在vlan_bitmap中,如果存在,则返回true,如果不存在,则返回flase,表示不允许此报文通过。

发送报文处理

使能vlan filter功能后,报文在网桥内部转发过程中始终携带vlan,
如果要转发出端口时,会判断出端口是否允许此vlan的报文通过。
如果允许报文从此端口发出去,再根据untagged判断是否需要将vlan去掉。

实践部分

#创建网桥br1
brctl addbr br1
#使用网桥的vlan filter功能
echo 1 > /sys/class/net/br1/bridge/vlan_filtering
#添加两个namespace
ip netns add test1
ip netns add test2

#创建一对veth端口: vetha和vethb
ip link add vetha type veth peer vethb
#将vethb添加到ns test1,并设置ip 1.1.1.10
ip link set dev vethb netns test1
ip netns exec test1 ip link set dev vethb up
ip netns exec test1 ip address add dev vethb 1.1.1.10/24
将vetha添加到bridge br1
ip link set dev vetha up
brctl addif br1 vetha

#再创建一对veth端口: vethx和vethy
ip link add vethx type veth peer vethy
#将vethx添加到ns test2,并设置ip 1.1.1.11
ip link set dev vethy netns test2
ip netns exec test2 ip link set dev vethy up
ip netns exec test2 ip address add dev vethy 1.1.1.11/24
将vethx添加到bridge br1
ip link set dev vethx up
brctl addif br1 vethx

场景1 默认情况下,端口和网桥都有一个默认vlan 1,并且是pvid,和untagged模式。报文可以互通ping通

root@node2:~# bridge vlan
port    vlan ids
br1      1 Egress Untagged

vetha    1 PVID Egress Untagged

vethx    1 PVID Egress Untagged

root@node2:~# ip netns exec test1 ping 1.1.1.11
PING 1.1.1.11 (1.1.1.11) 56(84) bytes of data.
64 bytes from 1.1.1.11: icmp_seq=1 ttl=64 time=0.142 ms
^C
--- 1.1.1.11 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.142/0.142/0.142/0.000 ms

场景2 去掉端口vetha的pvid和untagged参数,因为vetha端口没有了pvid,所以端口vetha收到报文后就被drop掉了

bridge vlan add vid 1 dev vetha
root@node2:~# bridge vlan
port    vlan ids
br1      1 Egress Untagged

vetha    1

vethx    1 PVID Egress Untagged

root@node2:~# ip netns exec test1 ping 1.1.1.11
PING 1.1.1.11 (1.1.1.11) 56(84) bytes of data.
^C
--- 1.1.1.11 ping statistics ---
8 packets transmitted, 0 received, 100% packet loss, time 7151ms

场景3 只去掉端口 vethx 的 untagged 参数,报文从vethx发出去时,报文还携带vlan

root@node2:~# bridge vlan add vid 1 dev vetha pvid
root@node2:~# bridge vlan
port    vlan ids
br1      1 Egress Untagged

vetha    1 PVID Egress Untagged

vethx    1 PVID

root@node2:~# ip netns exec test1 ping 1.1.1.11 -c1
PING 1.1.1.11 (1.1.1.11) 56(84) bytes of data.
^C
--- 1.1.1.11 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

#在test2 ns抓包,可看到报文携带vlan 1
root@node2:~# ip netns exec test2 tcpdump -vne -i vethy
tcpdump: listening on vethy, link-type EN10MB (Ethernet), capture size 262144 bytes
^C21:00:11.558978 4e:b4:a4:4e:a7:96 > ff:ff:ff:ff:ff:ff, ethertype 802.1Q (0x8100), length 46: vlan 1, p 0, ethertype ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 1.1.1.11 tell 1.1.1.10, length 28
21:00:12.568679 4e:b4:a4:4e:a7:96 > ff:ff:ff:ff:ff:ff, ethertype 802.1Q (0x8100), length 46: vlan 1, p 0, ethertype ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 1.1.1.11 tell 1.1.1.10, length 28
21:00:13.592686 4e:b4:a4:4e:a7:96 > ff:ff:ff:ff:ff:ff, ethertype 802.1Q (0x8100), length 46: vlan 1, p 0, ethertype ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 1.1.1.11 tell 1.1.1.10, length 28

场景4 将默认vlan 1删除,创建新的vlan 10

bridge vlan del vid 1 dev vetha
bridge vlan del vid 1 dev vethx

bridge vlan add vid 10 dev vetha pvid untagged
bridge vlan add vid 10 dev vethx pvid untagged
root@node2:~# bridge vlan
port    vlan ids
br1      1 Egress Untagged

vetha    10 PVID Egress Untagged

vethx    10 PVID Egress Untagged

//互相ping是可以通的,可以通过删除vethx 的Untagged标签,验证不通的情况
root@node2:~# ip netns exec test1 ping 1.1.1.11
PING 1.1.1.11 (1.1.1.11) 56(84) bytes of data.
64 bytes from 1.1.1.11: icmp_seq=1 ttl=64 time=0.139 ms
^C
--- 1.1.1.11 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.139/0.139/0.139/0.000 ms

vlan相关代码初始化

创建网桥设备时,会调用 br_dev_init->br_vlan_init,设置 vlan_proto 和默认pvid,并将pvid和网桥mac添加到fdb中。
int br_vlan_init(struct net_bridge *br)
{
    br->vlan_proto = htons(ETH_P_8021Q);
    br->default_pvid = 1;
    return br_vlan_add(br, 1, BRIDGE_VLAN_INFO_PVID | BRIDGE_VLAN_INFO_UNTAGGED);
}

将接口添加到网桥上时,会调用 br_add_if->nbp_vlan_init,将网桥的pvid和接口mac地址添加到fdb中。
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;
}

添加vlan流程

通过bridge命令给端口添加vlan时
bridge vlan add vid 1 dev vetha pvid

命令行端代码

static int vlan_modify(int cmd, int argc, char **argv)
    struct {
        struct nlmsghdr n;
        struct ifinfomsg    ifm;
        char            buf[1024];
    } req = {
        .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
        .n.nlmsg_flags = NLM_F_REQUEST,
        .n.nlmsg_type = cmd,
        .ifm.ifi_family = PF_BRIDGE,
    };
    #如果指定了 self
    flags |= BRIDGE_FLAGS_SELF;
    #如果指定了 master
    flags |= BRIDGE_FLAGS_MASTER;
    #如果指定了vlan范围
    vid = atoi(*argv);
    vid_end = atoi(p);
    vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN;
    #如果指定了 pvid
    vinfo.flags |= BRIDGE_VLAN_INFO_PVID;
    #如果指定了 untagged
    vinfo.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
    afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);

    if (flags)
        addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, flags);
    add_vlan_info_range(&req.n, sizeof(req), vid, vid_end, vinfo.flags);

kernel端代码流程

static int rtnl_bridge_setlink(struct sk_buff *skb, struct nlmsghdr *nlh)
    //根据 ifi_index 获取 dev
    dev = __dev_get_by_index(net, ifm->ifi_index);
    //获取 flags
    br_spec = nlmsg_find_attr(nlh, sizeof(struct ifinfomsg), IFLA_AF_SPEC);
    if (br_spec) {
        nla_for_each_nested(attr, br_spec, rem) {
            //获取 flags
            if (nla_type(attr) == IFLA_BRIDGE_FLAGS) {
                if (nla_len(attr) < sizeof(flags))
                    return -EINVAL;
                have_flags = true;
                flags = nla_get_u16(attr);
                break;
    //如果flags为空或者flags包含标志BRIDGE_FLAGS_MASTER,则使用网桥设备的ndo_bridge_setlink
    //这个流程主要是给端口添加vlan
    if (!flags || (flags & BRIDGE_FLAGS_MASTER)) {
        struct net_device *br_dev = netdev_master_upper_dev_get(dev);

        if (!br_dev || !br_dev->netdev_ops->ndo_bridge_setlink) {
            err = -EOPNOTSUPP;
            goto out;
        }
        //调用 ndo_bridge_setlink,对于网桥来说,就是 br_setlink
        err = br_dev->netdev_ops->ndo_bridge_setlink(dev, nlh);
        if (err)
            goto out;

        flags &= ~BRIDGE_FLAGS_MASTER;
    }
    //如果flags指定了BRIDGE_FLAGS_SELF,则使用dev本身的ndo_bridge_setlink,
    //但是支持 ndo_bridge_setlink 的dev比较少,从代码看,只有bridge和ixgbe支持。
    //这个flag主要是为了给网桥添加vlan
    if ((flags & BRIDGE_FLAGS_SELF)) {
        if (!dev->netdev_ops->ndo_bridge_setlink)
            err = -EOPNOTSUPP;
        else
            err = dev->netdev_ops->ndo_bridge_setlink(dev, nlh);

        if (!err)
            flags &= ~BRIDGE_FLAGS_SELF;
    }
    
int br_setlink(struct net_device *dev, struct nlmsghdr *nlh)
    afspec = nlmsg_find_attr(nlh, sizeof(struct ifinfomsg), IFLA_AF_SPEC);
    if (afspec) {
        br_afspec((struct net_bridge *)netdev_priv(dev), p, afspec, RTM_SETLINK);
            switch (cmd) {
            case RTM_SETLINK:
                //在端口上添加vlan
                if (p) {
                    err = nbp_vlan_add(p, vinfo->vid, vinfo->flags);
                    if (err)
                        break;
                    //如果指定了master,也要将vlan添加到网桥上(但是从iproute2代码看,没有设置BRIDGE_VLAN_INFO_MASTER)
                    if (vinfo->flags & BRIDGE_VLAN_INFO_MASTER)
                        err = br_vlan_add(p->br, vinfo->vid,
                                  vinfo->flags);
                } else
                    //在网桥上添加vlan
                    err = br_vlan_add(br, vinfo->vid, vinfo->flags);
            }
    }

#给端口添加vlan流程
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;
}

#给网桥添加vlan流程
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;
}

不管给网桥还是端口添加vlan,都会调用此函数,做了如下几个事情:
如果指定了pvid,则保存到struct net_port_vlans->pvid,
如果指定了untagged,则保存到struct net_port_vlans->untagged_bitmap,
都会将vid保存到struct net_port_vlans->vlan_bitmap,
最后将vid和端口mac添加到fdb转发表中。
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.
         * This ensures tagged traffic enters the bridge when
         * promiscuous mode is disabled by br_manage_promisc().
         */
        err = vlan_vid_add(dev, br->vlan_proto, 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, br->vlan_proto, vid);
    return err;
}

网桥收到报文时的处理

//从端口接收的报文会进入网桥处理
int br_handle_frame_finish(struct sk_buff *skb)
    if (!br_allowed_ingress(p->br, nbp_get_vlan_info(p), skb, &vid))
        goto out;

//从网桥发出去的报文也会进入网桥处理
netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
    if (!br_allowed_ingress(br, br_get_vlan_info(br), skb, &vid))
        goto out;
        
//这个函数主要判断是否允许接收报文继续处理。
//如果vlan filter功能没使能,则返回true,即始终允许报文通过。
//如果vlan filter功能使能了,需要根据报文是否携带vlan进行处理
//如果报文带vlan,则判断此vlan是否在vlan_bitmap中,如果存在,则返回true,如果不存在,则返回flase,表示不允许此报文通过。
//如果报文不带vlan,将pvid赋给skb(如果pvid也不存在,则drop此报文),然后判断此vlan是否在vlan_bitmap中,如果存在,则返回true,
//如果不存在,则返回flase,表示不允许此报文通过。
bool br_allowed_ingress(struct net_bridge *br, struct net_port_vlans *v,
            struct sk_buff *skb, u16 *vid)
{
    bool tagged;
    __be16 proto;

    /* If VLAN filtering is disabled on the bridge, all packets are
     * permitted.
     */
    //如果没有使能vlan filter功能,则返回true,即不根据vlan转发,只根据mac转发
    if (!br->vlan_enabled) {
        BR_INPUT_SKB_CB(skb)->vlan_filtered = false;
        return true;
    }

    /* If there are no vlan in the permitted list, all packets are
     * rejected.
     */
    //如果使能了vlan filter,但是没有配置vlan,则drop掉此报文
    if (!v)
        goto drop;
    //设置标志位,表明后面流程需要对此报文做vlan处理
    BR_INPUT_SKB_CB(skb)->vlan_filtered = true;
    //取出 vlan protocol 0x0800
    proto = br->vlan_proto;

    /* 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.
     */
    //对于从端口接收的带vlan的报文,会在协议栈入口函数__netif_receive_skb_core会调用skb_vlan_untag剥掉vlan,
    //并设置VLAN_TAG_PRESENT,在这里就不用再次调用 skb_vlan_untag
    //对于从网桥发送的带vlan的报文(br_dev_xmit),会在这里调用skb_vlan_untag剥掉vlan,并设置VLAN_TAG_PRESENT
    if (unlikely(!vlan_tx_tag_present(skb) &&
             skb->protocol == proto)) {
        skb = skb_vlan_untag(skb);
        if (unlikely(!skb))
            return false;
    }

    if (!br_vlan_get_tag(skb, vid)) {
        //带vlan报文
        /* Tagged frame */
        if (skb->vlan_proto != proto) {
            /* Protocol-mismatch, empty out vlan_tci for new tag */
            skb_push(skb, ETH_HLEN);
            skb = vlan_insert_tag_set_proto(skb, skb->vlan_proto,
                            vlan_tx_tag_get(skb));
            if (unlikely(!skb))
                return false;

            skb_pull(skb, ETH_HLEN);
            skb_reset_mac_len(skb);
            *vid = 0;
            tagged = false;
        } else {
            tagged = true;
        }
    } else {
        //不带vlan报文
        /* Untagged frame */
        tagged = false;
    }
    //如果报文不带vlan,则将pvid赋给skb,如果pvid也不存在,则drop此报文
    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)
            goto drop;

        /* PVID is set on this port.  Any untagged or priority-tagged
         * ingress frame is considered to belong to this vlan.
         */
        *vid = pvid;
        if (likely(!tagged))
            /* Untagged Frame. */
            __vlan_hwaccel_put_tag(skb, proto, 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;
    }
    //最后判断此vid是否在端口或者网桥的vlan_bitmap中
    /* Frame had a valid vlan tag.  See if vlan is allowed */
    if (test_bit(*vid, v->vlan_bitmap))
        return true;
drop:
    kfree_skb(skb);
    return false;
}

vlan报文发送流程

前面接收流程可知,报文在网桥内部转发过程中始终携带vlan,如果要转发出端口时,
会调用 should_deliver --> br_allowed_egress,判断出端口是否允许此vlan的报文通过。
/* Called under RCU. */
bool br_allowed_egress(struct net_bridge *br,
               const struct net_port_vlans *v,
               const struct sk_buff *skb)
{
    u16 vid;
    //如果skb中的vlan_filtered为true,说明使能了vlan filter,返回true,即不允许报文通过。
    /* If this packet was not filtered at input, let it pass */
    if (!BR_INPUT_SKB_CB(skb)->vlan_filtered)
        return true;
    //没有配置vlan,返回false,即不允许报文通过。
    if (!v)
        return false;
    //获取vid,判断是否在出端口的vlan_bitmap中,如果在,则返回true,即不允许报文通过。
    br_vlan_get_tag(skb, &vid);
    if (test_bit(vid, v->vlan_bitmap))
        return true;

    return false;
}

如果允许报文从此端口发出去,再调用 br_handle_vlan 判断是否需要将vlan去掉
br_forward --> __br_forward --> br_handle_vlan
struct sk_buff *br_handle_vlan(struct net_bridge *br,
                   const struct net_port_vlans *pv,
                   struct sk_buff *skb)
{
    u16 vid;

    /* If this packet was not filtered at input, let it pass */
    if (!BR_INPUT_SKB_CB(skb)->vlan_filtered)
        goto out;

    /* 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;
        }
    }

    /* 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.
     */
    //从skb获取vid,判断此vid是否在 untagged_bitmap 中,如果在,则将skb->vlan_tci设置为0
    br_vlan_get_tag(skb, &vid);
    if (test_bit(vid, pv->untagged_bitmap))
        skb->vlan_tci = 0;

out:
    return skb;
}

发送报文时,如果有vlan,并且硬件不支持自动添加vlan,则需要将vlan添加到报文中
__dev_queue_xmit
    //支持tc enqueue的设备
    if (q->enqueue) {
        rc = __dev_xmit_skb(skb, q, dev, txq);
            sch_direct_xmit(skb, q, dev, txq, root_lock, true)
                validate_xmit_skb_list(skb, dev);
                    for (; skb != NULL; skb = next) {
                        validate_xmit_skb(skb, dev);
        goto out;
    //不支持tc的设备,一般是虚拟设备
    if (dev->flags & IFF_UP) {
        validate_xmit_skb(skb, dev);

static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev)
    validate_xmit_vlan(skb, features);
        if (vlan_tx_tag_present(skb) &&
        !vlan_hw_offload_capable(features, skb->vlan_proto))
        skb = __vlan_hwaccel_push_inside(skb);
        return skb;

static inline struct sk_buff *__vlan_hwaccel_push_inside(struct sk_buff *skb)
{
    //给报文添加vlan头
    skb = vlan_insert_tag_set_proto(skb, skb->vlan_proto, vlan_tx_tag_get(skb));
    if (likely(skb))
        skb->vlan_tci = 0;
    return skb;
}

=========== End
 

 

posted @ 2021-01-09 21:12  lsgxeva  阅读(702)  评论(0编辑  收藏  举报