Open vSwitch 行为匹配的实现

 一、Datapath 模块的行为匹配

        在 Open vSwitch 的数据包转发流程中,存在快速路径和慢速路径两种模式,如下图所示:

        其中,快速路径直接在 Datapath 模块完成行为匹配,将数据包转发出去。而慢速路径的数据包无法在 Datapath 模块完全处理,需要通过 upcall 调用将数据包交给用户空间的 vswitchd 守护进程,但是最终守护进程会将处理后的数据包和流表发回 Datapath 模块,然后再次进行行为匹配和数据包的转发。

        对于快速路径而言,行为匹配发生在 Datapath 模块接收和检查筛选之后,即在数据包处理 ovs_dp_process_packet() 函数中调用:

void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key) {
    ......

	error = ovs_execute_actions(dp, skb, sf_acts, key);

    ......
}

        对于慢速路径而言,行为匹配发生在流表下发到 Datapath 模块和数据包通过 reinject 发回之后,即在数据包接收和执行 ovs_packet_cmd_execute() 函数中调用:

static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info) {
    ......

	err = ovs_execute_actions(dp, packet, sf_acts, &flow->key);

    ......
}

        相应的调用关系如下图所示:

        也就是说,无论是快速路径还是慢速路径,最终都会由 Datapath 模块进行行为匹配,只不过不同的路径需要走不同的流程,会有不同的函数实现。

二、行为匹配 ovs_execute_actions()

        函数 ovs_execute_actions() 是 Datapath 模块的核心部分,主要负责行为的匹配和执行,存储在 ovs-main/datapath/actions.c 文件中:

/* Execute a list of actions against 'skb'. */
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;
}

        函数的第一个输入参数 struct datapath *dp 表示 datapath 对象(代表一个 Open vSwitch 实例在内核空间的实现),第二个输入参数 struct sk_buff *skb 表示要处理的数据包,第三个输入参数 const struct sw_flow_actions *acts 表示要执行的动作,第四个输入参数 struct sw_flow_key *key 表示数据包的流关键字信息。

        函数首先维护一个当前 CPU 核心的 exec_actions_level 计数器,并进行一些预处理。然后调用 do_execute_actions(dp, skb, key, acts->actions, acts->actions_len) 函数来执行动作列表,并通过判断 exec_actions_level 计数器的值来限制递归次数。最后如果 exec_actions_level 计数器的值为 1 即表示最外层调用,则调用 process_deferred_actions(dp) 函数来处理延迟的动作。

Tips:这里的 exec_actions_level 计数器主要用于递归限制
        Open vSwitch 的 Datapath 模块支持递归执行,以处理嵌套的动作。但为了防止无限递归,代码设置了 OVS_RECURSION_LIMIT 的限制。如果递归深度超过该限制,则会打印警告日志,释放数据包,并返回相应错误码 -ENETDOWN。

Tips:延迟动作处理

        数据包的某些行为在匹配完成后,可能需要在后续处理过程中执行而不是立即执行(比如转发行为)。对于 Datapath 模块而言,这些行为会被推迟到最外层调用时统一处理,由 process_deferred_actions() 函数负责处理这些延迟的动作,该函数存储在 ovs-main/datapath/actions.c 文件中:

static void process_deferred_actions(struct datapath *dp) {
	struct action_fifo *fifo = this_cpu_ptr(action_fifos);

	/* Do not touch the FIFO in case there is no deferred actions. */
	if (action_fifo_is_empty(fifo))
		return;

	/* Finishing executing all deferred actions. */
	do {
		struct deferred_action *da = action_fifo_get(fifo);
		struct sk_buff *skb = da->skb;
		struct sw_flow_key *key = &da->pkt_key;
		const struct nlattr *actions = da->actions;
		int actions_len = da->actions_len;

		if (actions)
			do_execute_actions(dp, skb, key, actions, actions_len);
		else
			ovs_dp_process_packet(skb, key);
	} while (!action_fifo_is_empty(fifo));

	/* Reset FIFO for the next packet.  */
	action_fifo_init(fifo);
}

三、行为执行 do_execute_actions()

        函数 do_execute_actions() 的主要作用是针对不同的数据包类型执行相应的行为,存储在 ovs-main/datapath/actions.c 文件中:

/* Execute a list of actions against 'skb'. */
static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, struct sw_flow_key *key, const struct nlattr *attr, int len) {
	const struct nlattr *a;
	int rem;

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

		switch (nla_type(a)) {
    		case OVS_ACTION_ATTR_OUTPUT: {
			    int port = nla_get_u32(a);
			    struct sk_buff *clone;

			    /* Every output action needs a separate clone of 'skb', In case the output action is the last action, cloning can be avoided. */
			    if (nla_is_last(a, rem)) {
				    do_output(dp, skb, port, key);
				    /* 'skb' has been used for output. */
				    return 0;
			    }

			    clone = skb_clone(skb, GFP_ATOMIC);
			    if (clone)
				    do_output(dp, clone, port, key);
			    OVS_CB(skb)->cutlen = 0;
			    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: {
			    bool last = nla_is_last(a, rem);

			    err = execute_recirc(dp, skb, key, a, last);
			    if (last) {
				    /* 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: {
			    bool last = nla_is_last(a, rem);

			    err = sample(dp, skb, key, a, last);
			    if (last)
				    return err;

			    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;

		    case OVS_ACTION_ATTR_CT_CLEAR:
			    err = ovs_ct_clear(skb, key);
			    break;

		    case OVS_ACTION_ATTR_PUSH_ETH:
			    err = push_eth(skb, key, nla_data(a));
			    break;

		    case OVS_ACTION_ATTR_POP_ETH:
			    err = pop_eth(skb, key);
			    break;

		    case OVS_ACTION_ATTR_PUSH_NSH: {
			    u8 buffer[NSH_HDR_MAX_LEN];
			    struct nshhdr *nh = (struct nshhdr *)buffer;

			    err = nsh_hdr_from_nlattr(nla_data(a), nh, NSH_HDR_MAX_LEN);
			    if (unlikely(err))
				    break;
			    err = push_nsh(skb, key, nh);
			    break;
		    }

		    case OVS_ACTION_ATTR_POP_NSH:
			    err = pop_nsh(skb, key);
			    break;

		    case OVS_ACTION_ATTR_METER:
			    if (ovs_meter_execute(dp, skb, key, nla_get_u32(a))) {
				    consume_skb(skb);
				    return 0;
			    }
                break;

		    case OVS_ACTION_ATTR_CLONE: {
			    bool last = nla_is_last(a, rem);

			    err = clone(dp, skb, key, a, last);
			    if (last)
				    return err;
			    break;
		    }

		    case OVS_ACTION_ATTR_CHECK_PKT_LEN: {
                bool last = nla_is_last(a, rem);

                err = execute_check_pkt_len(dp, skb, key, a, last);
                if (last)
                    return err;

                    break;
            }
		}

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

	consume_skb(skb);
	return 0;
}

        函数的第一个输入参数 struct datapath *dp 表示 datapath 对象(代表一个 Open vSwitch 实例在内核空间的实现),第二个输入参数 struct sk_buff *skb 表示要处理的数据包,第三个输入参数 struct sw_flow_key *key 表示数据包的流关键字信息,第四个输入参数 const struct nlattr *attr 表示要执行的操作列表,第五个输入参数 int len 表示操作列表的长度。

        函数使用 for 循环遍历操作列表,对于每个操作而言,它都会根据 nla_type(a) 类型执行相应的行为。支持的操作类型包括:

  • OVS_ACTION_ATTR_OUTPUT 将数据包发送到指定端口
  • OVS_ACTION_ATTR_TRUNC 截断数据包长度
  • OVS_ACTION_ATTR_USERSPACE 将数据包发送到用户空间
  • OVS_ACTION_ATTR_HASH 计算数据包的哈希值
  • OVS_ACTION_ATTR_PUSH_MPLS 为数据包添加 MPLS 头部
  • OVS_ACTION_ATTR_POP_MPLS 从数据包中删除 MPLS 头部
  • OVS_ACTION_ATTR_PUSH_VLAN 为数据包添加 VLAN 头部
  • OVS_ACTION_ATTR_POP_VLAN 从数据包中删除 VLAN 头部
  • OVS_ACTION_ATTR_RECIRC 重新循环处理数据包
  • OVS_ACTION_ATTR_SET 设置数据包的特定字段
  • OVS_ACTION_ATTR_SET_MASKED 使用掩码设置数据包的特定字段
  • OVS_ACTION_ATTR_SAMPLE 对数据包进行采样操作
  • OVS_ACTION_ATTR_CT 执行连接跟踪操作
  • OVS_ACTION_ATTR_CT_CLEAR 清除数据包的连接跟踪状态
  • OVS_ACTION_ATTR_PUSH_ETH 为数据包添加以太网头部
  • OVS_ACTION_ATTR_POP_ETH 从数据包中删除以太网头部
  • OVS_ACTION_ATTR_PUSH_NSH 为数据包添加 NSH (网络服务头) 头部
  • OVS_ACTION_ATTR_POP_NSH 从数据包中删除 NSH 头部
  • OVS_ACTION_ATTR_METER 执行数据包计量操作
  • OVS_ACTION_ATTR_CLONE 克隆数据包
  • OVS_ACTION_ATTR_CHECK_PKT_LEN 检查数据包长度

        对于每个操作而言,如果执行过程中发生错误,则直接释放数据包并返回错误代码。如果所有操作都执行成功,则最终释放原始数据包。 

总结:

        在 Open vSwitch 的 Datapath 模块中,主要通过 ovs_execute_actions() 函数进行相应行为的匹配和执行,这是数据包在 Datapath 模块中必须执行的步骤。

        由于本人水平有限,以上内容如有不足之处欢迎大家指正(评论区/私信均可)。

参考资料:

Open vSwitch 官网

Open vSwitch 源代码 GitHub

Open vSwitch 数据包接收的实现-优快云博客

Open vSwitch 的 reinject 数据包发回-优快云博客

Open vSwitch v2.17.10 LTS 源代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值