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
     
  • 相关阅读:
    Vue 2.x windows环境下安装
    VSCODE官网下载缓慢或下载失败 解决办法
    angular cli 降级
    Win10 VS2019 设置 以管理员身份运行
    XSHELL 连接 阿里云ECS实例
    Chrome浏览器跨域设置
    DBeaver 执行 mysql 多条语句报错
    DBeaver 连接MySql 8.0 报错 Public Key Retrieval is not allowed
    DBeaver 连接MySql 8.0报错 Unable to load authentication plugin 'caching_sha2_password'
    Linux系统分区
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/14256473.html
Copyright © 2011-2022 走看看