美文网首页
Linux网络协议栈5--ovs收发包

Linux网络协议栈5--ovs收发包

作者: 苏苏林 | 来源:发表于2020-10-15 11:53 被阅读0次

ovs,全名openvswitch,是一个高质量的、多层虚拟交换机,相对于bridge的一些优势:

1)方便网络管理与监控。OVS 的引入,可以方便管理员对整套云环境中的网络状态和数据流量进行监控,比如可以分析网络中流淌的数据包是来自哪个 VM、哪个 OS 及哪个用户,这些都可以借助 OVS 提供的工具来达到。
2)加速数据包的寻路与转发。相比 Bridge 单纯的基于 MAC 地址学习的转发规则,OVS 引入流缓存的机制,可以加快数据包的转发效率。
3)基于 SDN 控制面与数据面分离的思想。上面两点其实都跟这一点有关,OVS 控制面负责流表的学习与下发,具体的转发动作则有数据面来完成。可扩展性强。
4)隧道协议支持。Bridge 只支持 VxLAN,OVS 支持 gre/vxlan/IPsec 等。
5)适用于 Xen、KVM、VirtualBox、VMware 等多种 Hypervisors。

不过这些年,openflow明显热度降低,SDN网络可以有很多实现方式,如segment routing技术,结合传统的mpls/bgp,也能很好的实现SDN,相比于基于openflow协议的SDN网络,设备厂商支持的更好,更稳定。

同bridge一样,在向ovs bridge中添加成员接口的时候,会在成员接口的dev->rx_handler 上挂载收包处理函数,如下,ovs_vport_add 中会根据加入接口类型的不同,调用接口对应的create函数,而所有create函数都会调用ovs_netdev_link,其中注册了netdev_frame_hook 作为ovs成员口的收包处理函数。
以vxlan_create为例,vxlan本身的创建和linux vxlan一样,核心函数也是vxlan_dev_configure,其次是创建ovs成员口的私有数据 vport,最后在ovs_netdev_link 函数中间vxlan和vport关联,以及挂载接口收报函数和私有数据(netdev_frame_hook,vport)

struct vport *ovs_vport_add(const struct vport_parms *parms)
{
    struct vport_ops *ops;
    struct vport *vport;

    ops = ovs_vport_lookup(parms);
    if (ops) {
        struct hlist_head *bucket;

        if (!try_module_get(ops->owner))
            return ERR_PTR(-EAFNOSUPPORT);

        vport = ops->create(parms);
        ......
}

struct vport *ovs_netdev_link(struct vport *vport, const char *name)
{
    ......
    err = netdev_rx_handler_register(vport->dev, netdev_frame_hook,
                     vport);
    ......
}
EXPORT_SYMBOL_GPL(ovs_netdev_link);
image.png

网卡收到包后,走到__netif_receive_skb_core后,剥完vlan找到vlan子接口(如果有的话),如果skb->dev是ovs成员口,就会走到netdev_frame_hook处理函数。

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
......
    // ovs挂载的 netdev_frame_hook 函数。
    rx_handler = rcu_dereference(skb->dev->rx_handler);
    if (rx_handler) {
        if (pt_prev) {
            ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = NULL;
        }
        switch (rx_handler(&skb)) {
        case RX_HANDLER_CONSUMED:  // 报文已经被消费,结束处理
            ret = NET_RX_SUCCESS;
            goto out;
        case RX_HANDLER_ANOTHER:  // skb->dev 被修改,重新走一次
            goto another_round;
        case RX_HANDLER_EXACT: /* 精确传递到ptype->dev == skb->dev */
            deliver_exact = true;
        case RX_HANDLER_PASS:
            break;
        default:
            BUG();
        }
    }

    ......
}

struct vport 是ovs成员端口的核心数据结构,它挂载在net_device 的rx_handler_data 上成员上,ovs模块中用的更多的是这个结构,但对外部模块不可见(私有数据结构)。

struct vport {
    struct net_device *dev;        
    struct datapath *dp;                // 对应一个bridge,ovs中可以添加多个bridge。
    struct vport_portids __rcu *upcall_portids;
    u16 port_no;

    struct hlist_node hash_node;
    struct hlist_node dp_hash_node;
    const struct vport_ops *ops;         // 对应不同接口类型的操作处理函数。

    struct list_head detach_list;
    struct rcu_head rcu;
};

netdev_frame_hook --> netdev_port_receive -->ovs_vport_receive-->ovs_dp_process_packet 流程。在进入 ovs_dp_process_packet 之前,从tun_info和 skb,提取了流的key信息,包含 tunnel、二层、三层、四层的报文头信息,为ovs dp匹配流表提供依据。

static void netdev_port_receive(struct sk_buff *skb)
{
    struct vport *vport;

    vport = ovs_netdev_get_vport(skb->dev);
    if (unlikely(!vport))
        goto error;

    if (unlikely(skb_warn_if_lro(skb)))
        goto error;

    skb = skb_share_check(skb, GFP_ATOMIC);
    if (unlikely(!skb))
        return;
    // ovs 是 switch,所以智能加二层口,向一些三层的tunnel口是无法加入的
    skb_push(skb, ETH_HLEN);
    skb_postpush_rcsum(skb, skb->data, ETH_HLEN);
    // 一些tunnel口,如vxlan、gre,会在skb的dst_entry中缓存tunnel key
    ovs_vport_receive(vport, skb, skb_tunnel_info(skb));
    return;
error:
    kfree_skb(skb);
}

int ovs_vport_receive(struct vport *vport, struct sk_buff *skb,
              const struct ip_tunnel_info *tun_info)
{
    struct sw_flow_key key;
    int error;

    OVS_CB(skb)->input_vport = vport;
    OVS_CB(skb)->mru = 0;
    OVS_CB(skb)->cutlen = 0;
    if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
        u32 mark;

        mark = skb->mark;
        skb_scrub_packet(skb, true);
        skb->mark = mark;
        tun_info = NULL;
    }

    /* Extract flow from 'skb' into 'key'. */
    // 这里从tun_info和 skb,提取了流的key信息,sw_flow_key 包含 tunnel、二层、三层、四层的报文头信息,为ovs dp匹配流表提供依据。
    error = ovs_flow_key_extract(tun_info, skb, &key);
    if (unlikely(error)) {
        kfree_skb(skb);
        return error;
    }
    ovs_dp_process_packet(skb, &key);
    return 0;
}

ovs_dp_process_packet 函数会根据报文key match流表:
1、如果没找到,走upcall处理,调用 queue_userspace_packet 将报文各层协议头 (OVS_PACKET_ATTR_KEY )、skb本身数据(OVS_PACKET_ATTR_PACKET)信息上送用户态(upcall.cmd=OVS_PACKET_CMD_MISS),用户态会有线程监听消息,在udpif_start_threads中创建了处理upcall的线程,处理handler = udpif_upcall_handler,udpif_upcall_handler通过fd poll的方式等待触发,如果有upcall上送,则进入recv_upcalls的处理函数中
1)从Device接收Packet交给事先注册的event handler进行处理;
2)接收Packet后识别是否是unknown packet,是则交由upcall处理;
3)vswitchd对unknown packet找到flow rule进行处理,其中最重要的调用是通过rule_dpif_lookup_from_table查找到匹配的流表规则,进而生成actions
rule_dpif_lookup_from_table又会通过流表的级联一个个顺序查找,每单个流表都会调用rule_dpif_lookup_in_table;
4)经过process_upcall流程后,已经为upcall的流生成对应的缓存流表信息了,缓存流表的key信息以及actions动作保存在struct upcall里,接下去就是要将缓存流表put到datapath了,通知内核态ovs处理put(对应OVS_FLOW_CMD_NEW)及execute(OVS_PACKET_CMD_EXECUTE)流程;
https://www.codenong.com/cs109398201/

2、存在匹配的流表,根据流表的action,执行相应的操作。action 类型很多,但最终一般需要是output action,从一个接口发送出去。

void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
{
    const struct vport *p = OVS_CB(skb)->input_vport;
    struct datapath *dp = p->dp;
    struct sw_flow *flow;
    struct sw_flow_actions *sf_acts;
    struct dp_stats_percpu *stats;
    u64 *stats_counter;
    u32 n_mask_hit;

    stats = this_cpu_ptr(dp->stats_percpu);

    /* Look up flow. */
    // 流表查询,根据前面从报文提取的信息
    flow = ovs_flow_tbl_lookup_stats(&dp->table, key, &n_mask_hit);
    if (unlikely(!flow)) {
        struct dp_upcall_info upcall;
        int error;
        // 未查找到流表,做upcall 处理
        memset(&upcall, 0, sizeof(upcall));
        upcall.cmd = OVS_PACKET_CMD_MISS;
        upcall.portid = ovs_vport_find_upcall_portid(p, skb);
        upcall.mru = OVS_CB(skb)->mru;
        error = ovs_dp_upcall(dp, skb, key, &upcall, 0);
        if (unlikely(error))
            kfree_skb(skb);
        else
            consume_skb(skb);
        stats_counter = &stats->n_missed;
        goto out;
    }
    // 查找到了流表,根据流表的action,执行相应的操作。
    ovs_flow_stats_update(flow, key->tp.flags, skb);
    sf_acts = rcu_dereference(flow->sf_acts);
    ovs_execute_actions(dp, skb, sf_acts, key);

    stats_counter = &stats->n_hit;

out:
    /* Update datapath statistics. */
    u64_stats_update_begin(&stats->syncp);
    (*stats_counter)++;
    stats->n_mask_hit += n_mask_hit;
    u64_stats_update_end(&stats->syncp);
}

int ovs_execute_actions(struct datapath *dp, struct sk_buff *skb,
            const struct sw_flow_actions *acts,
            struct sw_flow_key *key)
{
    int err, level;

    level = __this_cpu_inc_return(exec_actions_level);
    if (unlikely(level > OVS_RECURSION_LIMIT)) {
        net_crit_ratelimited("ovs: recursion limit reached on datapath %s, probable configuration error\n",
                     ovs_dp_name(dp));
        kfree_skb(skb);
        err = -ENETDOWN;
        goto out;
    }

    OVS_CB(skb)->acts_origlen = acts->orig_len;
    err = do_execute_actions(dp, skb, key,
                 acts->actions, acts->actions_len);

    if (level == 1)
        process_deferred_actions(dp);

out:
    __this_cpu_dec(exec_actions_level);
    return err;
}

// action 类型很多,但最终一般需要是output action,从一个接口发送出去
static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
                  struct sw_flow_key *key,
                  const struct nlattr *attr, int len)
{
    /* Every output action needs a separate clone of 'skb', but the common
     * case is just a single output action, so that doing a clone and
     * then freeing the original skbuff is wasteful.  So the following code
     * is slightly obscure just to avoid that.
     */
    int prev_port = -1;
    const struct nlattr *a;
    int rem;

    for (a = attr, rem = len; rem > 0;
         a = nla_next(a, &rem)) {
        int err = 0;

        if (unlikely(prev_port != -1)) {
            struct sk_buff *out_skb = skb_clone(skb, GFP_ATOMIC);

            if (out_skb)
                do_output(dp, out_skb, prev_port, key);

            OVS_CB(skb)->cutlen = 0;
            prev_port = -1;
        }

        switch (nla_type(a)) {
        case OVS_ACTION_ATTR_OUTPUT:
            prev_port = nla_get_u32(a);
            break;

        case OVS_ACTION_ATTR_TRUNC: {
            struct ovs_action_trunc *trunc = nla_data(a);

            if (skb->len > trunc->max_len)
                OVS_CB(skb)->cutlen = skb->len - trunc->max_len;
            break;
        }

        case OVS_ACTION_ATTR_USERSPACE:
            output_userspace(dp, skb, key, a, attr,
                             len, OVS_CB(skb)->cutlen);
            OVS_CB(skb)->cutlen = 0;
            break;

        case OVS_ACTION_ATTR_HASH:
            execute_hash(skb, key, a);
            break;

        case OVS_ACTION_ATTR_PUSH_MPLS:
            err = push_mpls(skb, key, nla_data(a));
            break;

        case OVS_ACTION_ATTR_POP_MPLS:
            err = pop_mpls(skb, key, nla_get_be16(a));
            break;

        case OVS_ACTION_ATTR_PUSH_VLAN:
            err = push_vlan(skb, key, nla_data(a));
            break;

        case OVS_ACTION_ATTR_POP_VLAN:
            err = pop_vlan(skb, key);
            break;

        case OVS_ACTION_ATTR_RECIRC:
            err = execute_recirc(dp, skb, key, a, rem);
            if (nla_is_last(a, rem)) {
                /* If this is the last action, the skb has
                 * been consumed or freed.
                 * Return immediately.
                 */
                return err;
            }
            break;

        case OVS_ACTION_ATTR_SET:
            err = execute_set_action(skb, key, nla_data(a));
            break;

        case OVS_ACTION_ATTR_SET_MASKED:
        case OVS_ACTION_ATTR_SET_TO_MASKED:
            err = execute_masked_set_action(skb, key, nla_data(a));
            break;

        case OVS_ACTION_ATTR_SAMPLE:
            err = sample(dp, skb, key, a, attr, len);
            break;

        case OVS_ACTION_ATTR_CT:
            if (!is_flow_key_valid(key)) {
                err = ovs_flow_key_update(skb, key);
                if (err)
                    return err;
            }

            err = ovs_ct_execute(ovs_dp_get_net(dp), skb, key,
                         nla_data(a));

            /* Hide stolen IP fragments from user space. */
            if (err)
                return err == -EINPROGRESS ? 0 : err;
            break;
        }

        if (unlikely(err)) {
            kfree_skb(skb);
            return err;
        }
    }

    if (prev_port != -1)
        do_output(dp, skb, prev_port, key);
    else
        consume_skb(skb);

    return 0;
}

接口的output操作,流程是 do_output -->ovs_vport_send --> vport 注册的send函数,都是直接调用的 dev_queue_xmit 函数,因为ovs进来的是二层包,流表只是对报头做了修改,再转发出去,不需要走协议栈。

相关文章

  • Linux网络协议栈5--ovs收发包

    ovs,全名openvswitch,是一个高质量的、多层虚拟交换机,相对于bridge的一些优势: 1)方便网络管...

  • docker网络基础

    网络的命名空间 linux在网络栈中引入网络命名空间,从而支持网络协议栈的多个实例。这些独立的协议栈被隔离到不同的...

  • Linux网络协议栈4--bridge收发包

    bridge 是linux上的虚拟交换机,具有交换机的功能。 网卡收到包后,走到__netif_receive_s...

  • Linux网络协议栈7--ipsec收发包流程

    IPSec收包解封流程 流程路径:ip_rcv() --> ip_rcv_finish() --> ip_loca...

  • K8S原理简介及环境搭建

    一、原理简介 名词解释 1、网络的命名空间:Linux在网络栈中引入网络命名空间,将独立的网络协议栈隔离到不同的命...

  • 网络协议、端口和Socket

    1、网络协议分层 网络层次可划分为五层因特网协议栈和七层因特网协议栈。 1.1 五层因特网协议栈 因特网协议栈共有...

  • 用户态协议栈的实现

    协议栈,指的是TCP/IP协议栈。linux系统中,协议栈是内核实现的。 Client发送数据给server,数据...

  • CentOS系统启动流程你懂否

    一、Linux内核的组成 相关概念:Linux系统的组成部分:内核+根文件系统内核:进程管理、内存管理、网络协议栈...

  • linux.network 网络协议栈

    https://blog.csdn.net/zxorange321/article/details/75676063

  • 协议栈的内部结构

    什么是协议栈? 如果说网卡是连接网络的硬件,那么协议栈就是连接网络的软件。 协议栈包括什么? 主要包括TCP,UD...

网友评论

      本文标题:Linux网络协议栈5--ovs收发包

      本文链接:https://www.haomeiwen.com/subject/nrtgpktx.html