Open vSwitch 中 upcall 消息的类型

一、upcall 调用的流程

        在 Open vSwitch 的数据包转发流程中,如果数据包在内核空间无法完全处理(比如匹配不到流表项),就会发生 upcall 调用,将数据包从内核空间的 Datapath 模块传输至用户空间的 ovs-vswitchd 守护进程进行处理。由于 upcall 调用经过了内核态到用户态的转发和 Open vSwitch 在用户空间的分层设计,所以 upcall 消息需要经过层层封装、解析和处理才能最终到达 vswitchd 守护进程。这里将详细讨论在 upcall 调用过程的不同阶段中,消息类型的变化。

二、内核空间中的 upcall 消息类型

        内核空间对应的 upcall 结构体主要用于 Netlink 消息的封装,定义在 ovs-main/datapath/linux/compat/include/linux/openvswitch.h 头文件中:

enum ovs_packet_cmd {
	OVS_PACKET_CMD_UNSPEC,

	/* Kernel-to-user notifications. */
	OVS_PACKET_CMD_MISS,    /* Flow table miss. */
	OVS_PACKET_CMD_ACTION,  /* OVS_ACTION_ATTR_USERSPACE action. */

	/* Userspace commands. */
	OVS_PACKET_CMD_EXECUTE  /* Apply actions to a packet. */
};

        这时的 ovs_packet_cmd 对于 upcall 调用而言,只有 OVS_PACKET_CMD_MISS 和 OVS_PACKET_CMD_ACTION 这两种主要类型,分别对应内核模块 Datapath 匹配不到流表项和需要执行一些特定动作的情况。

        也就是说,当内核模块 Datapath 匹配不到流表项时,会产生 MISS 类型的 upcall 消息,相应代码存储在 ovs-main/datapath/datapath.c 文件中:

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

	if (unlikely(!flow)) {
		......

		upcall.cmd = OVS_PACKET_CMD_MISS;

        ......

        error = ovs_dp_upcall(dp, skb, key, &upcall, 0);

        ......
	}

    ......
}

        而相应的,ACTION 类型的处理函数存储在 ovs-main/datapath/actions.c 文件中:

static int output_userspace(struct datapath *dp, struct sk_buff *skb, struct sw_flow_key *key, const struct nlattr *attr, const struct nlattr *actions, int actions_len, uint32_t cutlen) {
    ......

	upcall.cmd = OVS_PACKET_CMD_ACTION;

    ......

	err = ovs_dp_upcall(dp, skb, key, &upcall, cutlen);
    
    ......
}

二、用户空间中的 upcall 消息类型

        由于 Open vSwitch 在用户空间的分层设计,用户空间需要对 upcall 消息结构进行重新整合。

(1)dpif 中的 upcall 消息类型

        这里 dpif 对应的 upcall 结构体主要用于接收来自内核的消息,定义在 ovs-main/lib/dpif.h 头文件中:

enum dpif_upcall_type {
    DPIF_UC_MISS,               /* Miss in flow table. */
    DPIF_UC_ACTION,             /* OVS_ACTION_ATTR_USERSPACE action. */
    DPIF_N_UC_TYPES
};

        可以看到这里的 DPIF_UC_MISS 和 DPIF_UC_ACTION 和内核空间中 upcall 消息类型是一一对应的关系。由此可见,此时是接收后直接存储,不需要进行额外关于类型的处理。

(2)ofproto 中的 upcall 消息类型

        这里 ofproto 对应的 upcall 结构体主要为了便于 upcall 消息处理函数 process_upcall() 对不同类型 upcall 消息的处理,存储在 ovs-main/ofproto/ofproto-dpif-upcall.c 文件中:

enum upcall_type {
    BAD_UPCALL,                 /* Some kind of bug somewhere. */
    MISS_UPCALL,                /* A flow miss.  */
    SLOW_PATH_UPCALL,           /* Slow path upcall.  */
    SFLOW_UPCALL,               /* sFlow sample. */
    FLOW_SAMPLE_UPCALL,         /* Per-flow sampling. */
    IPFIX_UPCALL,               /* Per-bridge sampling. */
    CONTROLLER_UPCALL           /* Destined for the controller. */
};

此时相应的 process_upcall() 处理流程如下图所示:

(3)用户空间的 upcall 消息类型转换

        用户空间的 upcall 消息类型转换主要发生在 dpif 和 ofproto 之间,将 dpif 中简单粗略的 upcall 消息类型进行细化,存储在 ovs-main/ofproto/ofproto-dpif-upcall.c 文件中:

static enum upcall_type classify_upcall(enum dpif_upcall_type type, const struct nlattr *userdata, struct user_action_cookie *cookie) {
    /* First look at the upcall type. */
    switch (type) {
    case DPIF_UC_ACTION:
        break;

    case DPIF_UC_MISS:
        return MISS_UPCALL;

    case DPIF_N_UC_TYPES:
    default:
        VLOG_WARN_RL(&rl, "upcall has unexpected type %"PRIu32, type);
        return BAD_UPCALL;
    }

    /* "action" upcalls need a closer look. */
    if (!userdata) {
        VLOG_WARN_RL(&rl, "action upcall missing cookie");
        return BAD_UPCALL;
    }

    size_t userdata_len = nl_attr_get_size(userdata);
    if (userdata_len != sizeof *cookie) {
        VLOG_WARN_RL(&rl, "action upcall cookie has unexpected size %"PRIuSIZE,
                     userdata_len);
        return BAD_UPCALL;
    }
    memcpy(cookie, nl_attr_get(userdata), sizeof *cookie);
    if (cookie->type == USER_ACTION_COOKIE_SFLOW) {
        return SFLOW_UPCALL;
    } else if (cookie->type == USER_ACTION_COOKIE_SLOW_PATH) {
        return SLOW_PATH_UPCALL;
    } else if (cookie->type == USER_ACTION_COOKIE_FLOW_SAMPLE) {
        return FLOW_SAMPLE_UPCALL;
    } else if (cookie->type == USER_ACTION_COOKIE_IPFIX) {
        return IPFIX_UPCALL;
    } else if (cookie->type == USER_ACTION_COOKIE_CONTROLLER) {
        return CONTROLLER_UPCALL;
    } else {
        VLOG_WARN_RL(&rl, "invalid user cookie of type %"PRIu16
                     " and size %"PRIuSIZE, cookie->type, userdata_len);
        return BAD_UPCALL;
    }
}

        函数通过 case 检查 dpif_upcall_type 类型:如果是 DPIF_UC_MISS,则返回 MISS_UPCALL;如果是 DPIF_N_UC_TYPES 或其他未知类型,则返回 BAD_UPCALL;如果是 DPIF_UC_ACTION 则根据 userdata 和 user_action_cookie 进行更详细的和分类。

        这里的 user_action_cookie 定义在 ovs-main/lib/odp-util.h 头文件中:

/* user_action_cookie is passed as argument to OVS_ACTION_ATTR_USERSPACE. */
struct user_action_cookie {
    uint16_t type;              /* enum user_action_cookie_type. */
    ofp_port_t ofp_in_port;     /* OpenFlow in port, or OFPP_NONE. */
    struct uuid ofproto_uuid;   /* UUID of ofproto-dpif. */

    union {
        struct {
            /* USER_ACTION_COOKIE_SFLOW. */
            ovs_be16 vlan_tci;      /* Destination VLAN TCI. */
            uint32_t output;        /* SFL_FLOW_SAMPLE_TYPE 'output' value. */
        } sflow;

        struct {
            /* USER_ACTION_COOKIE_SLOW_PATH. */
            uint16_t unused;
            uint32_t reason;        /* enum slow_path_reason. */
        } slow_path;

        struct {
            /* USER_ACTION_COOKIE_FLOW_SAMPLE. */
            uint16_t probability;   /* Sampling probability. */
            uint32_t collector_set_id; /* ID of IPFIX collector set. */
            uint32_t obs_domain_id; /* Observation Domain ID. */
            uint32_t obs_point_id;  /* Observation Point ID. */
            odp_port_t output_odp_port; /* The output odp port. */
            enum nx_action_sample_direction direction;
        } flow_sample;

        struct {
            /* USER_ACTION_COOKIE_IPFIX. */
            odp_port_t output_odp_port; /* The output odp port. */
        } ipfix;

        struct {
            /* USER_ACTION_COOKIE_CONTROLLER. */
            uint8_t dont_send;      /* Don't send the packet to controller. */
            uint8_t continuation;   /* Send packet-in as a continuation. */
            uint16_t reason;
            uint32_t recirc_id;
            ovs_32aligned_be64 rule_cookie;
            uint16_t controller_id;
            uint16_t max_len;
        } controller;
    };
};
BUILD_ASSERT_DECL(sizeof(struct user_action_cookie) == 48);

总结:

        通过上面的分析可以得到以下结论:

(1)对于 upcall 调用中的消息类型而言,只有 MISS 和 ACTION 这两种主要类型。

(2)当内核空间的 Datapath 模块匹配不到流表项时,产生的一定是 MISS 类型的 upcall 消息,并且在后续的过程中如果没有出现错误,则一直将会是 MISS 类型的消息,只是名称不同。

(3)当内核空间的 Datapath 模块判断出数据包需要传输至 vswitchd 守护进程进行进一步处理时,会产生 ACTION 类型的 upcall 消息,并将配套信息通过 OVS_ACTION_ATTR_USERSPACE 也传输到用户空间。用户空间会根据这些信息将 ACTION 类型的 upcall 消息进行更细致的划分后,再进行处理。

        此外,还有一点需要注意:我们在数据包的处理流程中提到过快速路径慢速路径的概念,但是这和 upcall 调用过程在名称上并不是完全匹配。

        这里的慢速路径对应的 upcall 类型其实是 MISS 类型,而 upcall 消息在用户空间也存在 SLOW_PATH 的 upcall 类型。虽然在进行最终处理的时候二者被归为一类,并且处理的流程区别不大,但认真分辨下来还是有细微差别的,在特定场景下需要注意区分。

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

参考资料:

Open vSwitch 官网

Open vSwitch 源代码 GitHub

Open vSwitch 数据包处理流程-优快云博客

Open vSwitch 的 upcall 调用(内核空间部分)-优快云博客

Open vSwitch 的 upcall 调用(用户空间部分)-优快云博客

Open vSwitch 守护进程的 upcall 处理-优快云博客

Open vSwitch v2.17.10 LTS 源代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值