zoukankan      html  css  js  c++  java
  • linux bridge

    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
    ");
            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
     
  • 相关阅读:
    MVC异常过滤器
    文件分块传输
    UDP广播
    React 还是 Vue: 你应该选择哪一个Web前端框架?
    一个很好的XLSX的操作
    报表神器
    pycharm快敏捷键
    xlwt
    常用的列表和元祖
    HTML,css
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/14256473.html
Copyright © 2011-2022 走看看