#SDN--Open vSwitch 自定义用户态 action

引言

       在 SDN 领域中,Open vSwitch 的地位举足轻重,借助 SDN 可以完成许多传统网络难以解决的问题。而其中,对 Open vSwitch 实现定制 action 显得尤为重要。
       目前网络上相关资料非常有限,勉强找到两篇文章,都是基于 2.3 版本,都是很老旧的了,看起来很费解不说,还有很多错误在里面。
       鉴于上述原因,笔者特此记录基于 Open vSwitch 的一个 LTS 版本 2.13.3 进行自定义用户态 action 方法。


Open vSwitch 基础知识

       在实现自定义 action 之前,有必要先了解一下 Open vSwitch 的体系结构。

(1)、OVS 网络架构

       Open vSwitch 是一个虚拟交换机,支持 Open Flow 协议(也有一些硬件交换机支持
Open Flow),他们被远端的 Controller 通过 Open Flow 协议统一管理着,从而实现对接入的虚拟机(或设备)进行组网和互通,整体组网结构如下图:

(2)、OVS 内部架构

  • ovs-vswitchd:主要模块,实现 vswitch 的守候进程 daemon。
  • ovsdb-server: 轻量级数据库服务器,用于 ovs 的配置信息。
  • ovs-vsctl:通过和 ovsdb-server 通信,查询和更新 vswitch 的配置。
  • ovs-dpctl:用来配置 vswitch 内核模块的一个工具。
  • ovs-appctl:发送命令消息到 ovs 进程。
  • ovs-ofctl:查询和控制 OpenFlow 虚拟交换机的流表。
  • datapath:内核模块,根据流表匹配结果做相应处理。

(3)、OVS 代码架构

  • vswitchd:是 ovs 主要的用户态程序,它从 ovsdb-server 读取配置并发送到 ofproto 层,也从 ofproto 读取特定的状态和统计信息并发送到数据库。
  • ofproto:是 openflow 的接口层,负责和 Openflow controller 通信并通过ofproto_class 与 ofproto provider 底层交互。
  • ofproto-dpif:是 ofproto 接口类的具体实现。
  • netdev:是 ovs 系统的网络设备抽象(比如 linux 的 net_device 或交换机的 port),netdev_class 定义了 netdev-provider 的具体实现需要的接口,具体的平台实现需要支持这些统一的接口,从而完成 netdev 设备的创建、销毁、打开、关闭等一系列操作。
  • datapath 为 ovs 内核模块,负责执行数据处理,也就是把从接收端口收到的数据包在流表中进行匹配,并执行匹配到的动作。一个 datapath 可以对应多个 vport,一个vport 类似物理交换机的端口概念。一个 datapth 关联一个 flow table,一个 flow table包含多个条目,每个条目包括两个内容:一个 match/key 和一个 action。

(4)、数据流动


       一般的数据包在 Linux 网络协议中的流向为上图中的蓝色箭头流向:网卡 eth0 收到数据包后判断报文走向,如果是本地报文把数据传送到用户态,如果是转发报文根据选路(二层交换或三层路由)把报文送到另一个网卡如 eth1。当有 OVS 时,数据流向如红色所示:从网卡 eth0 收到报文后进入 ovs 的端口,根据 key 值进行流表匹配,如果匹配成功执行流表对应的 action;如果失败通过 upcall 送入用户态处理。

(5)、action 处理流程

       当一个流表到达 OVS 后,首先进入内核态进行查找匹配,因为在内核态进行的操作是最快速的。当在内核态找不到对应的 action 时,内核 datapath 采样的数据通过 netlink 发送到用户空间的 vswithd 进程,交给用户层处理,入口函数ovs_dp_upcall(),该函数调用 queue_userspace_packet()构造发往用户层的 skb,通过 netlink 通信机制发送到用户层,其中形成的主要数据格式如下:

       上交给用户层处理的动作称为 upcall,用户层采用多个线程处理内核发往用户层的 upcall 请求,入口函数为udpif_set_threads(),主要处理流程如下:

       笔者这里选择在用户态自定义 action 的原因是,内核态固然快速,但其编程难度大,可扩展性没有用户态强。


工具与文件准备

       1、首先下载 mininet 镜像文件 ,选择下载 mininet 版本为 2.3.0 系统版本为 Ubuntu18 的系统镜像文件。
       2、等待下载完成后,解压,导入至 VMware 中,建议使用 putty 连接 miniet 系统,便于后续操作。
       3、因为 OVS 2.13.3 要求 Linux 内核为 4.16.x 或 4.17.x,所以前往 Ubuntu 的 Kernel 站点 下载以下四个文件。

       4、下载到本地后,可以利用 pscp 工具传到 mininet 系统,传输完毕后,使用以下命令进行安装升级。

sudo dpkg -i *deb

       5、剩余操作请参考该博客 Ubuntu 如何更换内核
       6、升级完成后,运行以下命令查看内核版本是否为 4.17.0.

cat /proc/version


       7、参考博主以下两篇博客,将时区、系统编码以及 Vim 配置完毕。

              #Linux杂记–Ubuntu 将系统编码设置为UTF-8,时区设置为中国上海的方法
              #Linux杂记–配置vim常用环境变量以及如何让vim共享系统剪切板?

       8、前往 Open vSwitch 官网,下载最新 LTS 版本 2.13.3,传至 mininet 中,使用以下命令进行解压。

 tar -xzvf  openvswitch-2.13.3.tar.gz

       9、卸载预装的 openvswitch。

sudo apt-get remove openvswitch-common openvswitch-pki openvswitch-switch

自定义 action

       切换至 root 用户,打开 include/openvswitch/ofp-actions.h 文件,在 142 行,GOTO_TABLE 后面添加自己的 action 声明。

#define OFPACTS
	/* ... */
	OFPACT(GOTO_TABLE,      ofpact_goto_table,  ofpact, "goto_table") \
	OFPACT(TEST,        	ofpact_test,    	ofpact, "test")

       在 1105 行的结构体 struct ofpact_decap 后面为自己的 action 添加接收参数所用结构体。

/* ..., after "struct ofpact_decap { ... }" */

/* OFPACT_TEST.
 *
 * Used for OFPAT_TEST */
struct ofpact_test {
	OFPACT_PADDED_MEMBERS(
		struct ofpact ofpact;
		/* 注意,这里不能使用 char 数组或指针作为变量 */
		uint32_t p;
	);
	uint8_t data[];
};

       打开 lib/ofp-actions.c,在 356 行 NXAST_RAW_DECAP 后面为自己的 action 声明 action code。

enum ofp_raw_action_type {
	/* ... */

	/* NX1.3+(47): struct nx_action_decap, ... */
	NXAST_RAW_DECAP,

	/* OF1.0+(30): uint32_t. */
	OFPAT_RAW_TEST,

	/* ... */
}

       分别在 505、8078、8990 行后面添加自己的 action,其作用可参考文件中的函数声明。

/* Returns the ofpact following 'ofpact', except that if 'ofpact' contains
 * nested ofpacts it returns the first one. */
struct ofpact *
ofpact_next_flattened(const struct ofpact *ofpact)
{
	switch (ofpact->type) {
		/* ... */
		case OFPACT_TEST:
			return ofpact_next(ofpact);
	}
	/* ... */
}

/* ... */

enum ovs_instruction_type
ovs_instruction_type_from_ofpact_type(enum ofpact_type type,
                                      enum ofp_version version)
{
	switch (type) {
	/* ... */
	case OFPACT_TEST:
	default:
		return OVSINST_OFPIT11_APPLY_ACTIONS;
	/* ... */
	}
}

/* ... */

/* Returns true if 'action' outputs to 'port', false otherwise. */
static bool
ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
{
	switch (ofpact->type) {
	/* ... */
	case OFPACT_TEST:
	default:
		return false;
	}
}

       在 7779 行后面为自己的 action 添加宏定义。

/* The order in which actions in an action set get executed.  This is only for
 * the actions where only the last instance added is used. */
#define ACTION_SET_ORDER                        \
		/* ... */
    SLOT(OFPACT_DEC_NSH_TTL)					\
    SLOT(OFPACT_TEST)                           

       在 8788 行,get_ofpact_map 方法里分别为各个 openflow 版本添加自定义的 action。

static const struct ofpact_map *
get_ofpact_map(enum ofp_version version)
{
    /* OpenFlow 1.0 actions. */
    static const struct ofpact_map of10[] = {
		/* ... */
        { OFPACT_TEST, 30},
        { 0, -1 },
    };
	
    /* OpenFlow 1.1 actions. */
    static const struct ofpact_map of11[] = {
		/* ... */
        { OFPACT_TEST, 30},
        { 0, -1 },
    };
    
    /* OpenFlow 1.2, 1.3, and 1.4 actions. */
    static const struct ofpact_map of12[] = {
		/* ... */
        { OFPACT_TEST, 30},
        { 0, -1 },
    };
	/* ... */
}

       在 7637 行,GOTO_TABLE 后面为自己的 action 添加命令行以流表参数解析,格式化。

/* ..., immediately after format_GOTO_TABLE */

/* test instruction. */

static void
encode_TEST(const struct ofpact_test *test,
			enum ofp_version ofp_version OVS_UNUSED,
			struct ofpbuf *out)
{
	put_OFPAT_TEST(out, test->p);
}

static enum ofperr
decode_OFPAT_RAW_TEST(uint32_t p,
					  enum ofp_version ofp_version OVS_UNUSED,
					  struct ofpbuf *out)
{
	ofpact_put_TEST(out)->p = p;
	return 0;
}

static char * OVS_WARN_UNUSED_RESULT
parse_TEST(char *arg, const struct ofpact_parse_params *pp)
{
	uint32_t p;
	char *error;
	struct ofpact_test *test;
	
	error = str_to_u32(arg, &p);
	if (error) return error;
	
	test = ofpact_put_TEST(pp->ofpacts);
	test->p = p;
	
	return NULL;
}

static void
format_TEST(const struct ofpact_test *a,
			const struct ofpact_format_params *fp)
{
	/* Feel free to use e.g. colors.param,
	colors.end around parameter names */
	ds_put_format(fp->s, "%stest:%"PRIu32"%s", colors.param, a->p, colors.end);
}

static enum ofperr
check_TEST(const struct ofpact_test *a OVS_UNUSED,
		   const struct ofpact_check_params *cp OVS_UNUSED)
{
	/* My method needs no checking. Probably. */
	return 0;
}

       打开 ofproto/ofproto-dpif-xlate.c,分别在 5687、5988、6649 行后面添加自己的 action,其作用可参考文件中的函数声明。

/* Determine if an datapath action translated from the openflow action
 * can be reversed by another datapath action.
 *
 * Openflow actions that do not emit datapath actions are trivially
 * reversible. Reversiblity of other actions depends on nature of
 * action and their translation.  */
static bool
reversible_actions(const struct ofpact *ofpacts, size_t ofpacts_len)
{
	const struct ofpact *a;

	OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) {
		switch (a->type) {
		/*... */
		case OFPACT_TEST:
			return false;
		}
	}
	return true;
}

/* ... */

/* Copy actions 'a' through 'end' to ctx->frozen_actions, which will be
 * executed after thawing.  Inserts an UNROLL_XLATE action, if none is already
 * present, before any action that may depend on the current table ID or flow
 * cookie. */
static void
freeze_unroll_actions(const struct ofpact *a, const struct ofpact *end,
                      struct xlate_ctx *ctx)
{
	/* ... */
	switch (a->type) {
		case OFPACT_TEST:
			/* These may not generate PACKET INs. */
			break;
	}
}

/* ... */

static void
recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx)
{
	/* ... */
	switch (a->type) {
	case OFPACT_TEST:
	default:
		break;
	}
}

       到此为止,自定义的 action 已经添加进 OVS 了,但我们还没有去实现 action,接下来就是最后一步,实现 action。在 7106 行, do_xlate_actions 函数中添加自己的 action。

static void
compose_test_action(uint32_t p)
{
    VLOG_INFO("compose_test_action: the parameter is %u", p);
}

static void
do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
                 struct xlate_ctx *ctx, bool is_last_action,
                 bool group_bucket_action)
{
	/* ... */
	switch (a->type) {
	/* ... */
	case OFPACT_TEST:
		// 对于用户态 action,这句话很重要!!!只有加了这句话,才可以拿得到数据包。
		// 数据包位于 ctx->xin->packet 中,相关操作函数以及结构体定义见 lib/dp-packet.h
		ctx->xout->slow |= SLOW_ACTION;
		compose_test_action(ofpact_get_TEST(a)->p);
		break;
	}
}

验证自定义 action

       大功告成!接下来就是编译了,依次执行以下命令:

bash boot.sh
bash configure --prefix=/usr/local --with-linux=/lib/modules/`uname -r`/build CFLAGS="-g -O2 -march=native" EXTRA_CFLAGS="-g -O2 -march=native" --enable-Werror
make
make install
make modules_install
rmmod openvswitch

config_file="/etc/depmod.d/openvswitch.conf"
rm -f $config_file
for module in datapath/linux/*.ko;
do
    modname="$(basename ${module})"
    echo "override ${modname%.ko} * extra" >> "$config_file"
    echo "override ${modname%.ko} * weak-updates" >> "$config_file"
done
depmod -a

       最后就是启动 OVS,执行以下命令:

export PATH=$PATH:/usr/local/share/openvswitch/scripts
ovs-ctl start
export PATH=$PATH:/usr/local/share/openvswitch/scripts
ovs-ctl --no-ovs-vswitchd start
export PATH=$PATH:/usr/local/share/openvswitch/scripts
ovs-ctl --no--ovsdb-server start
mkdir -p /usr/local/etc/openvswitch
ovsdb-tool create /usr/local/etc/openvswitch/conf.db vswitchd/vswitch.ovsschema
mkdir -p /usr/local/var/run/openvswitch
ovsdb-server --remote=punix:/usr/local/var/run/openvswitch/db.sock --remote=db:Open_vSwitch,Open_vSwitch,manager_options --private-key=db:Open_vSwitch,SSL,private_key --certificate=db:Open_vSwitch,SSL,certificate --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert --pidfile --detach --log-file
ovs-vsctl --no-wait init
ovs-vswitchd --pidfile --detach --log-file

       查看 OVS 版本是否正确:

ovs-vsctl show

       清空日志文件并开启日志输出功能。

cat /dev/null > /var/log/syslog
ovs-appctl vlog/set ANY:syslog:info

       最后的最后,就是进 mininet 检验是否添加成功。运行 mn 命令,启动 mininet 后,运行以下命令可以查看交换机所支持的所有 actions,可以看到,我们自定义的 test 已经在里面了。

sh ovs-ofctl show s1


       为 s1 添加流表。

sh ovs-ofctl add-flow s1 in_port="s1-eth1",actions=test:10,output:"s1-eth2"
sh ovs-ofctl add-flow s1 in_port="s1-eth2",actions=test:20,output:"s1-eth1"

       用 h1 ping h2。

h1 ping h2

       查看日志文件内容是否正确。

sh cat /var/log/syslog | grep compose_test_action


写在最后

       每次开机都需要运行以下脚本,启动 OVS 及其日志功能:

export PATH=$PATH:/usr/local/share/openvswitch/scripts
ovs-ctl start
export PATH=$PATH:/usr/local/share/openvswitch/scripts
ovs-ctl --no-ovs-vswitchd start
export PATH=$PATH:/usr/local/share/openvswitch/scripts
ovs-ctl --no--ovsdb-server start
mkdir -p /usr/local/etc/openvswitch
ovsdb-tool create /usr/local/etc/openvswitch/conf.db vswitchd/vswitch.ovsschema
mkdir -p /usr/local/var/run/openvswitch
ovsdb-server --remote=punix:/usr/local/var/run/openvswitch/db.sock --remote=db:Open_vSwitch,Open_vSwitch,manager_options --private-key=db:Open_vSwitch,SSL,private_key --certificate=db:Open_vSwitch,SSL,certificate --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert --pidfile --detach --log-file
ovs-vsctl --no-wait init
ovs-vswitchd --pidfile --detach --log-file

cat /dev/null > /var/log/syslog
ovs-appctl vlog/set ofproto_dpif_xlate:syslog:info

       如果需要添加源文件,建议在 lib 目录下新增自己的 .c 和 .h 文件,并修改 lib/automake.mk 文件:在 lib_libopenvswitch_la_SOURCES 后面按照格式添加上自己的源文件。
       如果 action 修改了 ip 包体的长度,那么就要连着修改 ip 包头的 content length 和首部校验,修改时必须注意将 本机字节顺序转为网络字节顺序即大端模式,否则将会出现不可意料的错误。还要修改 dp_packet 里的 size:

// 修改 dp_packet 里的 size 属性
struct dp_packet *p = (struct dp_packet *) packet;
dp_packet_set_size(p, dp_packet_size(p) + new_content_length - old_content_length);
  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值