OVS源碼--datapath包處理流程(八)

首先每個packet都會被datapath/vport.c : line 481的ovs_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;
}

原文鏈接:https://yq.aliyun.com/articles/626800

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章