ovs set_field/load/move action

ovs提供了修改报文内容的action,比如 set_field/load/move等,本文也重点分析这三个action及其源码实现。

从ovs-action截取这三个action的基本用法,如下

move action: src 和 dst 都必须是 field,将src的值赋值给dst。
   Syntax:
          move:src->dst

   Copies the named bits from field or subfield src to field or
   subfield dst. src and dst should fields or subfields in the
   syntax described under ``Field Specifications’’ above. The two
   fields or subfields must have the same width.

   Examples:
          •      move:reg0[0..5]->reg1[26..31] copies the six bits
                 numbered 0 through 5 in register 0 into bits 26
                 through 31 of register 1.
          •      move:reg0[0..15]->vlan_tci copies the least
                 significant 16 bits of register 0 into the VLAN TCI
                 field.

set_field action: dst 必须是field,value可以是任意值
    Syntax:
        set_field:value[/mask]->dst
        
    The set_field action takes value in the customary syntax
    for field dst, e.g. 00:11:22:33:44:55 for an Ethernet address,
    and dst as the field’s name. The optional mask allows part of a
    field to be set.

load action: dst 必须是field,value可以是任意值
    Syntax:
        load:value->dst
        
    The load action takes value as an integer value (in decimal or
    prefixed by 0x for hexadecimal) and dst as a field or subfield in
    the syntax described under ``Field Specifications’’ above.

The following all set the Ethernet source address to 00:11:22:33:44:55:
      •      set_field:00:11:22:33:44:55->eth_src
      •      load:0x001122334455->eth_src
      •      load:0x001122334455->OXM_OF_ETH_SRC[]

The following all set the multicast bit in the Ethernet destination address:
      •      set_field:01:00:00:00:00:00/01:00:00:00:00:00->eth_dst
      •      load:1->eth_dst[40]

load和set_field的作用基本上是等同的,都是将任意值赋值给field,或者赋值给field的某些位,后面分析源码时会看到他们用的是同一个结构体 struct ofpact_set_field。
move相比上面两个action,它只能将field的值赋值给另一个field,src和dst都必须是field。

这里需要解释下field,它表示报文字段,metadata等信息,在代码中使用 struct mf_field 表示,其中枚举类型 mf_field_id 表示 field 类型。

enum OVS_PACKED_ENUM mf_field_id {
    MFF_DP_HASH,
    MFF_RECIRC_ID,
    MFF_PACKET_TYPE,
    MFF_TUN_ID,
    MFF_METADATA,
    MFF_IN_PORT,
    ...
}

struct mf_field {
    /* Identification. */
    enum mf_field_id id;        /* MFF_*. */
    const char *name;           /* Name of this field, e.g. "eth_type". */
    const char *extra_name;     /* Alternate name, e.g. "dl_type", or NULL. */
    ....
}

有一个全局变量 mf_fields 用来保存所有的field

mf_fields
{{id = MFF_DP_HASH, name = 0x55555564854a "dp_hash", extra_name = 0x0, n_bytes = 4, n_bits = 32, variable_len = false,
    maskable = MFM_FULLY, string = MFS_HEXADECIMAL, prereqs = MFP_NONE, writable = false, mapped = false, usable_protocols_exact = 1004,
    usable_protocols_cidr = 1004, usable_protocols_bitwise = 1004, flow_be32ofs = -1}, 
{id = MFF_RECIRC_ID,
    name = 0x555555648540 "recirc_id", extra_name = 0x0, n_bytes = 4, n_bits = 32, variable_len = false, maskable = MFM_NONE,
    string = MFS_DECIMAL, prereqs = MFP_NONE, writable = false, mapped = false, usable_protocols_exact = 1004, usable_protocols_cidr = 0,
    usable_protocols_bitwise = 0, flow_be32ofs = -1},
    ...

这三个action的用法中都提到的field,必须能在全局变量mf_fields找到才能继续。

除了上面说的三个action,还有如下常用的action用来修改报文内容

mod_dl_src和mod_dl_dst: 修改报文源目的mac
mod_nw_src和mod_nw_dst: 修改报文源目的ip
mod_nw_tos: 修改ip头里的tos字段
mod_tp_src和mod_tp_dst: 修改TCP,UDP等四层报文的源目的端口号

源码分析

命令行解析

move action
解析move action后,保存到结构体 struct ofpact_reg_move

struct ofpact_reg_move {
    struct ofpact ofpact; //type为OFPACT_REG_MOVE
    struct mf_subfield src;
    struct mf_subfield dst;
};

parse_REG_MOVE
    struct ofpact_reg_move *move = ofpact_put_REG_MOVE(ofpacts);
    return nxm_parse_reg_move(move, arg);
        const char *full_s = s;
        char *error;
        //解析src field,subfield意思是可能只修改field的某些字段
        error = mf_parse_subfield__(&move->src, &s);
        if (error) {
            return error;
        }
        //必须包含 "->"
        if (strncmp(s, "->", 2)) {
            return xasprintf("%s: missing `->' following source", full_s);
        }
        s += 2;
        //解析 dst field
        error = mf_parse_subfield(&move->dst, s);
        if (error) {
            return error;
        }
        //src和dst field位宽必须一致
        if (move->src.n_bits != move->dst.n_bits) {
            return xasprintf("%s: source field is %d bits wide but destination is "
                             "%d bits wide", full_s,
                             move->src.n_bits, move->dst.n_bits);
        }
        return NULL;

set_field和load都使用struct ofpact_set_field保存action

struct ofpact_set_field {
    OFPACT_PADDED_MEMBERS(
        struct ofpact ofpact; //ofpact->type 为 OFPACT_SET_FIELD
        bool flow_has_vlan;   /* VLAN present at action validation time. */
        const struct mf_field *field; //目的 field
    );
    //要设置的值
    union mf_value value[];  /* Significant value bytes followed by
                              * significant mask bytes. */  
};

//解析 set_field
parse_SET_FIELD
    set_field_parse__(copy, port_map, ofpacts, usable_protocols);
        char *value;
        char *delim;
        char *key;
        const struct mf_field *mf;
        union mf_value sf_value, sf_mask;

        set_field_split_str(arg, &key, &value, &delim);
        mf = mf_from_name(key);
        delim[0] = '\0';
        mf_parse(mf, value, port_map, &sf_value, &sf_mask);
        mf_is_value_valid(mf, &sf_value)
        ofpact_put_set_field(ofpacts, mf, &sf_value, &sf_mask);
            struct ofpact_set_field *sf = ofpact_put_SET_FIELD(ofpacts);
            sf->field = field;

            /* Fill in the value and mask if given, otherwise put zeroes so that the
             * caller may fill in the value and mask itself. */
            if (value) {
                ofpbuf_put_uninit(ofpacts, 2 * field->n_bytes);
                sf = ofpacts->header;
                memcpy(sf->value, value, field->n_bytes);
                if (mask) {
                    memcpy(ofpact_set_field_mask(sf), mask, field->n_bytes);
                } else {
                    memset(ofpact_set_field_mask(sf), 0xff, field->n_bytes);
                }
            } else {
                ofpbuf_put_zeros(ofpacts, 2 * field->n_bytes);
                sf = ofpacts->header;
            }
            /* Update length. */
            ofpact_finish_SET_FIELD(ofpacts, &sf);

//解析 load
ofpacts_parse__
    if (!strcasecmp(key, "load"))
        parse_reg_load(value, ofpacts);
            struct mf_subfield dst;
            char *key, *value_str;
            union mf_value value;

            //解析出 key value,key 为 "->" 后面的字符串,value 为 "->" 前面的字符串
            set_field_split_str(arg, &key, &value_str, NULL);
            //根据 key 查找 field 或者 subfield。subfield 表示 field 的其中某些位。
            mf_parse_subfield(&dst, key);
            
            parse_int_string(value_str, (uint8_t *)&value, dst.field->n_bytes, &key);

            struct ofpact_set_field *sf = ofpact_put_reg_load(ofpacts, dst.field, NULL, NULL);

            bitwise_copy(&value, dst.field->n_bytes, 0, sf->value, dst.field->n_bytes, dst.ofs, dst.n_bits);
            bitwise_one(ofpact_set_field_mask(sf), dst.field->n_bytes, dst.ofs, dst.n_bits);

ovs-vswitchd接收openflow流表

ovs-vswitchd接收到openflow流表,插入指定的table中,这里就不做分析了。

报文匹配流表,执行action

数据流首包查找datapath流表失败后,在slowpath中查找openflow流表,匹配成功后,将openflow流表及其action转换成datapath流表,并对报文执行action操作。

慢速路径

xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
    //后面查找到openflow流表后,其action可能会修改 xin->flow,
    //所以先保存 xin->flow 到 ctx->base_flow。
    //调用 xlate_commit_actions 时,比较 xin->flow 和 ctx->base_flow 差异即可得出有哪些action
    struct flow *flow = &xin->flow;
    struct xlate_ctx ctx = {
        .base_flow = *flow, //保存原始flow。
    };
    //查找流表
    rule_dpif_lookup_from_table
    
    do_xlate_actions(ofpacts, ofpacts_len, &ctx);
        struct flow_wildcards *wc = ctx->wc;
        struct flow *flow = &ctx->xin->flow;
        const struct ofpact *a;

        OFPACT_FOR_EACH (a, ofpacts, ofpacts_len)
            const struct ofpact_set_field *set_field;
            const struct mf_field *mf;
            
            switch (a->type) {
            case OFPACT_REG_MOVE:
                xlate_ofpact_reg_move(ctx, ofpact_get_REG_MOVE(a));
                    mf_subfield_copy(&a->src, &a->dst, &ctx->xin->flow, ctx->wc);
                    xlate_report_subfield(ctx, &a->dst);
                break;
                
            case OFPACT_SET_FIELD:
                set_field = ofpact_get_SET_FIELD(a);
                mf = set_field->field;

                /* Set the field only if the packet actually has it. */
                //判断要设置的field在报文中是否存在。比如要修改mac地址,得保证报文有mac头
                if (mf_are_prereqs_ok(mf, flow, wc)) {
                    mf_mask_field_masked(mf, ofpact_set_field_mask(set_field), wc);
                    //将要修改的字段保存到 flow
                    mf_set_flow_value_masked(mf, set_field->value, ofpact_set_field_mask(set_field), flow);
                } else {
                    //如果不满足,则打印log即可
                    xlate_report(ctx, OFT_WARN,
                                 "unmet prerequisites for %s, set_field ignored",
                                 mf->name);

                }
                break;
            }

    /* Output only fully processed packets. */
    if (!ctx.freezing
        && xbridge->has_in_band
        && in_band_must_output_to_local_port(flow)
        && !actions_output_to_local_port(&ctx)) {
        //比较被openflow流表的action修改后的 flow 和报文原始 base_flow,
        //得出的差异就是openflow流表的action,需要
        //将其转换成 datapath action,并保存到 ctx->odp_actions。
        //后面流程会将原始 base_flow 和 ctx->odp_actions 添加到 datapath 流表中,
        //后续报文直接匹配 datapath 流表进行转发。
        compose_output_action(&ctx, OFPP_LOCAL, NULL);
            compose_output_action__(ctx, ofp_port, xr, true);
                /* Commit accumulated flow updates before output. */
                xlate_commit_actions(ctx);
                    commit_odp_actions(&ctx->xin->flow, &ctx->base_flow,
                                          ctx->odp_actions, ctx->wc,
                                          use_masked, ctx->pending_encap,
                                          ctx->encap_data);

比较被openflow流表的action修改后的 flow 和报文原始 base_flow,得出 openflow action,转换成 datapath action。对于有些报文需要返回 slow_path_reason,表示此报文需要slowpath做特殊处理。

enum slow_path_reason
commit_odp_actions(const struct flow *flow, struct flow *base,
                   struct ofpbuf *odp_actions, struct flow_wildcards *wc,
                   bool use_masked, bool pending_encap,
                   struct ofpbuf *encap_data)
{
    enum slow_path_reason slow1, slow2;
    bool mpls_done = false;

    commit_packet_type_change(flow, base, odp_actions, wc,
                              pending_encap, encap_data);
    commit_set_ether_action(flow, base, odp_actions, wc, use_masked);
    /* Make packet a non-MPLS packet before committing L3/4 actions,
     * which would otherwise do nothing. */
    if (eth_type_mpls(base->dl_type) && !eth_type_mpls(flow->dl_type)) {
        commit_mpls_action(flow, base, odp_actions);
        mpls_done = true;
    }
    commit_set_nsh_action(flow, base, odp_actions, wc, use_masked);
    slow1 = commit_set_nw_action(flow, base, odp_actions, wc, use_masked);
    commit_set_port_action(flow, base, odp_actions, wc, use_masked);
    slow2 = commit_set_icmp_action(flow, base, odp_actions, wc);
    if (!mpls_done) {
        commit_mpls_action(flow, base, odp_actions);
    }
    commit_vlan_action(flow, base, odp_actions, wc);
    commit_set_priority_action(flow, base, odp_actions, wc, use_masked);
    commit_set_pkt_mark_action(flow, base, odp_actions, wc, use_masked);

    return slow1 ? slow1 : slow2;
}

以修改 ether 头为例,如果需要修改 ether 头,则会添加 action OVS_ACTION_ATTR_SET,其中又会嵌套 OVS_KEY_ATTR_ETHERNET。

static void
commit_set_ether_action(const struct flow *flow, struct flow *base_flow,
                        struct ofpbuf *odp_actions,
                        struct flow_wildcards *wc,
                        bool use_masked)
{
    struct ovs_key_ethernet key, base, mask;

    if (flow->packet_type != htonl(PT_ETH)) {
        return;
    }

    get_ethernet_key(flow, &key);
    get_ethernet_key(base_flow, &base);
    get_ethernet_key(&wc->masks, &mask);

    if (commit(OVS_KEY_ATTR_ETHERNET, use_masked,
               &key, &base, &mask, sizeof key, odp_actions))
        commit_set_action(odp_actions, attr, key, size);
            size_t offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_SET);
            nl_msg_put_unspec(odp_actions, key_type, key, key_size);
            nl_msg_end_nested(odp_actions, offset);
   {
        put_ethernet_key(&base, base_flow);
        put_ethernet_key(&mask, &wc->masks);
    }
}

参考

https://man7.org/linux/man-pages/man7/ovs-fields.7.html
https://man7.org/linux/man-pages/man7/ovs-actions.7.html

你可能感兴趣的:(ovs set_field/load/move action)