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