OVS源码分析-datapath流程

OVS源码分析-datapath流程

首先每个packet都会被datapath/vport.c : line 481ovs_vport_receive函数接受。这个是第一步。

int ovs_vport_receive(struct vport *vport, struct sk_buff *skb,
              const struct ip_tunnel_info *tun_info)
{
    struct sw_flow_key key;  //  packet包头信息的提取,便于后面进行流表的匹配
    int error;
    OVS_CB(skb)->input_vport = vport;  //  在skb_buff数据结构里,有个struct cb 用来存放临时信息的。
    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;
    }
    ovs_skb_init_inner_protocol(skb);
    skb_clear_ovs_gso_cb(skb);
    /* Extract flow from 'skb' into 'key'. */
    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;
}

然后由datapath/datapath.c中的ovs_dp_process_packet函数处理:


void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
{
    const struct vport *p = OVS_CB(skb)->input_vport;  //  获取此packet是从哪个vport进来的。
    struct datapath *dp = p->dp;  //  从struct vport 中获取vport所属的datapath
    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. */
    // 在datapath中查找匹配的流表项。
    flow = ovs_flow_tbl_lookup_stats(&dp->table, key, skb_get_hash(skb),
                     &n_mask_hit);
    //  如果查找失败,没有匹配的流表,则进行upcall,上报到用户态。                  
    if (unlikely(!flow)) {
        struct dp_upcall_info upcall;
        int error;
        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);
}

如果流表匹配成功,则执行流表相对应的action。函数是datapath/actions中的ovs_execute_actions函数对数据包执行action。代码如下:


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

在上面的函数中有个do_execute_actions函数,里面是针对OVS_ACTION_ATTR_*来执行相对应的action函数。最终如果从某个端口转发出去,则会执行do_output函数,通过调用datapath/vport.c/ovs_vport_send函数进行端口转发。这就是一个数据包如果在datapath中匹配到流表的大致流程。

如果没有匹配成功,则执行upcall,函数是datapath/ovs_dp_upcall函数,代码如下:

int ovs_dp_upcall(struct datapath *dp, struct sk_buff *skb,
          const struct sw_flow_key *key,
          const struct dp_upcall_info *upcall_info,
          uint32_t cutlen)
{
    struct dp_stats_percpu *stats;
    int err;
    // 判断pid是否为0,这个是用来NetLink通讯使用的,为0表示传给内核空间
    if (upcall_info->portid == 0) {
        err = -ENOTCONN;
        goto err;
    }
    if (!skb_is_gso(skb))
    // 如果不是gso数据包,则直接发送到用户队列,这个在之前有说明
        err = queue_userspace_packet(dp, skb, key, upcall_info, cutlen);
    else
    // 如果是gso数据包,则先处理gso协议,然后继续发送到用户队列
    // 函数里最终会把数据包发送到上面的函数中 queue_userspace_packet
        err = queue_gso_packets(dp, skb, key, upcall_info, cutlen);
    if (err)
        goto err;
    return 0;
err:
    stats = this_cpu_ptr(dp->stats_percpu);
    u64_stats_update_begin(&stats->syncp);
    stats->n_lost++;
    u64_stats_update_end(&stats->syncp);
    return err;
}

点击关注阿里云科技快讯,关注最新大新闻!还有机会获得精美礼品!!


OVS源码分析-datapath流程


OVS源码分析-datapath流程
上一篇:Docker部署迁移实战


下一篇:CUDA学习(十)