在SDN软件交换机ofsoftswitch13中实现自定义experimenter消息

当前Openflow协议中的消息基本可以满足我们对SDN交换机的管理需要,但是在很多情况下,我们更希望能够与openflow交换机交互自定义的管理消息,这就需要用到openflow的实验消息(experimenter messages)。本文以SDN软件交换机ofsoftswitch13为例,来说明如何实现通过自定义的experimenter消息来与交换机通信。

实际上,ofsoftswitch13中已经有几个实验消息实现的范例,因此我侧重于分析ofsoftswitch13工程中的实现过程。当我们将experimenter消息的处理过程分析清楚后,添加自定义的实验消息就会变得非常简单。总体来说,交换机对openflow消息的处理可以划分为三个过程:

  • 一是对收到的openflow消息的解析过程。这个过程实现的功能是将数据包从网络字节序的线格式(wire format)解析为主机字节序的openflow消息格式。
  • 二是对消息处理过程。比如对交换机进行配置等。这个过程比较直接,下文就不展开分析了。
  • 三是交换机向控制器回复openflow消息的过程。这与第一个过程的步骤几乎是相反的,主要实现的功能是将待发送消息的内容打包为网络字节序的线格式(wire format),然后发送给控制器的过程。

如果弄清楚了上述三个过程的实现,那么实现自定义的experimenter消息的步骤就会变得非常清晰。下面首先说明ofsoftswitch13对自定义的experiment消息的解析流程,以及交换机向控制器回复openflow消息的流程。最后说明自定义experimenter消息的工程实现操作步骤。

自定义exp消息的处理流程

交换机收到openflow消息之后,首先对消息内容进行解析。然后根据解析得到的数据包类型调用相应的处理函数。

exp消息处理函数的调用过程

前文SDN软件交换机中数据包的处理流程对openflow交换机的数据包处理过程进行了分析,文中提到了位于文件oflib/ofl-messages-unpack.c中的openflow消息的解析函数ofl_msg_unpack(),交换机对自定义experimenter消息的解析也正是从该函数开始的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ofl_err
ofl_msg_unpack(uint8_t *buf, size_t buf_len, struct ofl_msg_header **msg, uint32_t *xid, struct ofl_exp *exp) {
struct ofp_header *oh;
oh = (struct ofp_header *)buf;
switch (oh->type) {
...
case OFPT_EXPERIMENTER:
if (exp == NULL || exp->msg == NULL || exp->msg->unpack == NULL) {
OFL_LOG_WARN(LOG_MODULE, "Received EXPERIMENTER message, but no callback was given.");
error = ofl_error(OFPET_BAD_REQUEST, OFPBRC_BAD_EXPERIMENTER);
} else {
error = exp->msg->unpack(oh, &len, (struct ofl_msg_experimenter **)msg);
}
break;
...
)
}

ofl_msg_unpack()对openflow子类型消息解析函数的调用中,我们可以发现experimenter消息的调用与其他openflow消息的调用有所不同,使用的调用方式是这样的:

1
exp->msg->unpack(oh, &len, (struct ofl_msg_experimenter **)msg)

其中exp是通过函数参数传入的。形参为struct ofl_exp *exp。传入的值是dp->exp,见文件udatapath/datapath.c

1
ofl_msg_unpack(buffer->data, buffer->size, &msg, &(sender.xid), dp->exp);

因此,在函数ofl_msg_unpack()(位于文件oflib/ofl-messages-unpack.c)中使用exp->msg->unpack(...)这种调用方式会等价为ofl_exp_msg_unpack(...)(位于文件udatapath/datapath.c)。而函数ofl_exp_msg_unpack(...)的实现位于文件oflib-exp/ofl-exp.c中。

exp消息处理的函数指针

在这里补充一下exp消息处理函数指针的初始化位置。exp消息解析函数指针在datapath初始化的时候传给了datapath,参见文件udatapath/datapath.c中的datapath初始化函数:

1
2
3
4
5
6
7
8
struct datapath *
dp_new(void) {
struct datapath *dp;
dp = xmalloc(sizeof(struct datapath));
...
dp->exp = &dp_exp;
...
}

其中,dp_exp是在该文件中定义的函数指针结构体,指向exp消息的处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* Callbacks for processing experimenter messages in OFLib. */
static struct ofl_exp_msg dp_exp_msg =
{.pack = ofl_exp_msg_pack,
.unpack = ofl_exp_msg_unpack,
.free = ofl_exp_msg_free,
.to_string = ofl_exp_msg_to_string};

static struct ofl_exp dp_exp =
{.act = NULL,
.inst = NULL,
.match = NULL,
.stats = NULL,
.msg = &dp_exp_msg};

exp消息解析实现

自定义的exp消息实现于文件oflib-exp/ofl-exp.c中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ofl_err
ofl_exp_msg_unpack(struct ofp_header *oh, size_t *len, struct ofl_msg_experimenter **msg) {
struct ofp_experimenter_header *exp;
...
exp = (struct ofp_experimenter_header *)oh;

switch (htonl(exp->experimenter)) {
case (OPENFLOW_VENDOR_ID): {
return ofl_exp_openflow_msg_unpack(oh, len, msg);
}
case (NX_VENDOR_ID): {
return ofl_exp_nicira_msg_unpack(oh, len, msg);
}
case (MUTIQUEUE_VENDOR_ID): {
return ofl_exp_mutiqueue_msg_unpack(oh, len, msg);
}
default: {
OFL_LOG_WARN(LOG_MODULE, "Trying to unpack unknown EXPERIMENTER message (%u).", htonl(exp->experimenter));
return ofl_error(OFPET_BAD_REQUEST, OFPBRC_BAD_EXPERIMENTER);
}
}
}

下面以openflow extention queue msg为例讲解exp消息解析的实现。

openflow extention queue msg结构体定义

queue_msg的结构定义如下,位于oflib-exp/ofl-exp-openflow.h中:

1
2
3
4
5
6
7
8
9
10
11
12
struct ofl_exp_openflow_msg_header {
struct ofl_msg_experimenter header; /* OPENFLOW_VENDOR_ID */

uint32_t type;
};

struct ofl_exp_openflow_msg_queue {
struct ofl_exp_openflow_msg_header header; /* OFP_EXT_QUEUE_MODIFY|DELETE */

uint32_t port_id;
struct ofl_packet_queue *queue;
};

头文件中扩展消息定义如下,如include/openflow/openflow-ext.h`文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum ofp_extension_commands { /* Queue configuration commands */
/* Queue Commands */
OFP_EXT_QUEUE_MODIFY, /* Add and/or modify */
OFP_EXT_QUEUE_DELETE, /* Remove a queue */
OFP_EXT_SET_DESC, /* Set ofp_desc_stat->dp_desc */

OFP_EXT_COUNT
};

struct ofp_extension_header {
struct ofp_header header;
uint32_t vendor; /* OPENFLOW_VENDOR_ID. */
uint32_t subtype; /* One of ofp_extension_commands */
};
OFP_ASSERT(sizeof(struct ofp_extension_header) == 16);


struct openflow_queue_command_header {
struct ofp_extension_header header;
uint32_t port; /* Port for operations */
uint8_t pad[4]; /* Align to 64-bits */
uint8_t body[0]; /* Body of ofp_queue objects for op. */
};
OFP_ASSERT(sizeof(struct openflow_queue_command_header) == 24);

openflow extention queue unpack

案例中的experimenter消息unpack的最终实现位于oflib-exp/ofl-exp-openflow.c文件中。该函数实现的功能是将交换机收到的原始数据包(以网络字节存储于内存),解析为用户定义的实验消息格式(以主机字节序存储于内存)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
ofl_err
ofl_exp_openflow_msg_unpack(struct ofp_header *oh, size_t *len, struct ofl_msg_experimenter **msg) {
struct ofp_extension_header *exp;
...
exp = (struct ofp_extension_header *)oh;

if (ntohl(exp->vendor) == OPENFLOW_VENDOR_ID) {

switch (ntohl(exp->subtype)) {
case (OFP_EXT_QUEUE_MODIFY):
case (OFP_EXT_QUEUE_DELETE): {
struct openflow_queue_command_header *src;
struct ofl_exp_openflow_msg_queue *dst;
ofl_err error;

if (*len < sizeof(struct openflow_queue_command_header)) {
OFL_LOG_WARN(LOG_MODULE, "Received EXT_QUEUE_MODIFY message has invalid length (%zu).", *len);
return ofl_error(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
}
*len -= sizeof(struct openflow_queue_command_header);

src = (struct openflow_queue_command_header *)exp;

dst = (struct ofl_exp_openflow_msg_queue *)malloc(sizeof(struct ofl_exp_openflow_msg_queue));
dst->header.header.experimenter_id = ntohl(exp->vendor);
dst->header.type = ntohl(exp->subtype);
dst->port_id = ntohl(src->port);

error = ofl_structs_packet_queue_unpack((struct ofp_packet_queue *)(src->body), len, &(dst->queue));
if (error) {
free(dst);
return error;
}

(*msg) = (struct ofl_msg_experimenter *)dst;
return 0;
}
...
default: {
OFL_LOG_WARN(LOG_MODULE, "Trying to unpack unknown Openflow Experimenter message.");
return ofl_error(OFPET_BAD_REQUEST, OFPBRC_BAD_EXPERIMENTER);
}
}
} else {
OFL_LOG_WARN(LOG_MODULE, "Trying to unpack non-Openflow Experimenter message.");
return ofl_error(OFPET_BAD_REQUEST, OFPBRC_BAD_EXPERIMENTER);
}
free(msg);
return 0;
}

exp消息的处理

消息解析完成之后,exp消息数据包的形态就由opfbuf结构解析为了对应的exp结构体。前文SDN软件交换机中数据包的处理流程openflow消息的处理这一小节中提到,函数handle_control_msg()会根据消息的不同类型分配给不同的处理函数,实验消息也是其中的一种,由dp_exp_message()函数进行处理。

从exp处理函数的上层调用接口来看,remote_rconn_run调用消息解析函数之后,如果解析无误,则继续调用handle_control_msg()函数对消息内容进行处理。与ofl_msg_unpack()类似,函数handle_control_msg()定义于文件udtapath/dp_control.c中,会根据消息的不同类型分配给不同的处理函数,主要代码入下。

1
2
3
4
5
6
7
8
9
ofl_err handle_control_msg(struct datapath *dp, struct ofl_msg_header *msg, const struct sender *sender) {
switch (msg->type) {
...
case OFPT_EXPERIMENTER: {
return dp_exp_message(dp, (struct ofl_msg_experimenter *)msg, sender);
}
...
}
}

其中需要注意的问题是代码内存块的释放:如果处理exp消息的调用函数发现错误,则需要返回错误代码,并且保持传入的exp消息未有任何改动,由handle_control_msg()的主调函数(remote_rconn_run())放内存;如果exp消息的调用函数处理正常,则需要被调函数(dp_exp_message())释放数据包内存。

函数dp_exp_message()定义于文件udatapath/dp_exp.c中,该函数根据experimenter消息的ID调用相应的处理函数。如果你自定义了一种新的实验消息,就需要在这里增加对实验消息的处理方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ofl_err
dp_exp_message(struct datapath *dp,
struct ofl_msg_experimenter *msg,
const struct sender *sender) {

switch (msg->experimenter_id) {
case (OPENFLOW_VENDOR_ID): {
struct ofl_exp_openflow_msg_header *exp = (struct ofl_exp_openflow_msg_header *)msg;

switch(exp->type) {
case (OFP_EXT_QUEUE_MODIFY): {
return dp_ports_handle_queue_modify(dp, (struct ofl_exp_openflow_msg_queue *)msg, sender);
}
case (OFP_EXT_QUEUE_DELETE): {
return dp_ports_handle_queue_delete(dp, (struct ofl_exp_openflow_msg_queue *)msg, sender);
}
case (OFP_EXT_SET_DESC): {
return dp_handle_set_desc(dp, (struct ofl_exp_openflow_msg_set_dp_desc *)msg, sender);
}
default: {
VLOG_WARN_RL(LOG_MODULE, &rl, "Trying to handle unknown experimenter type (%u).", exp->type);
return ofl_error(OFPET_BAD_REQUEST, OFPBRC_BAD_EXPERIMENTER);
}
}
}
default: {
return ofl_error(OFPET_BAD_REQUEST, OFPBRC_BAD_EXPERIMENTER);
}
}
}

交换机向控制器回复openflow消息流程

交换机在处理控制器下发的实验消息时,经常需要对消息做出回应。比如控制器查询交换机中某个队列的状态时,交换机需要回复队列状态信息。

下面举个例子来说明ofsoftswitch13中交换机的回复流程。

准备消息内容

交换机对控制器的查询消息解析完成后,会将消息转交给对应的处理函数。比如udatapath/dp_control.c文件中的函数handle_control_echo_request()会对echo进行回复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Handles echo request messages. */
static ofl_err
handle_control_echo_request(struct datapath *dp,
struct ofl_msg_echo *msg,
const struct sender *sender) {
struct ofl_msg_echo reply =
{{.type = OFPT_ECHO_REPLY},
.data_length = msg->data_length,
.data = msg->data};
dp_send_message(dp, (struct ofl_msg_header *)&reply, sender);

ofl_msg_free((struct ofl_msg_header *)msg, dp->exp);
return 0;
}

调用发送函数

处理函数准备好返回给交换机的信息后,调用dp_send_message()函数发送数据包。函数dp_send_message()位于文件udatapath/datapath.c文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int
dp_send_message(struct datapath *dp, struct ofl_msg_header *msg,
const struct sender *sender) {
struct ofpbuf *ofpbuf;
uint8_t *buf;
size_t buf_size;
int error;
...
error = ofl_msg_pack(msg, sender == NULL ? 0 : sender->xid, &buf, &buf_size, dp->exp);
if (error) {
VLOG_WARN_RL(LOG_MODULE, &rl, "There was an error packing the message!");
return error;
}
ofpbuf = ofpbuf_new(0);
ofpbuf_use(ofpbuf, buf, buf_size);
ofpbuf_put_uninit(ofpbuf, buf_size);

/* Choose the connection to send the packet to.
1) By default, we send it to the main connection
2) If there's an associated sender, send the response to the same
connection the request came from
3) If it's a packet in, use the auxiliary connection
*/
ofpbuf->conn_id = MAIN_CONNECTION;
if (sender != NULL)
ofpbuf->conn_id = sender->conn_id;
if (msg->type == OFPT_PACKET_IN)
ofpbuf->conn_id = PTIN_CONNECTION;

error = send_openflow_buffer(dp, ofpbuf, sender);
if (error) {
VLOG_WARN_RL(LOG_MODULE, &rl, "There was an error sending the message!");
/* TODO Zoltan: is delete needed? */
ofpbuf_delete(ofpbuf);
return error;
}
return 0;
}

转换传输格式(pack)

dp_senf_message()首先需要对传入的消息内容打包为可以直接发送openflow格式数据包,这个步骤通过调用打包函数ofl_msg_pack()来实现,该函数位于文件oflib/ofl-messages-pack.c中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int
ofl_msg_pack(struct ofl_msg_header *msg, uint32_t xid, uint8_t **buf, size_t *buf_len, struct ofl_exp *exp) {
struct ofp_header *oh;
int error = 0;
switch (msg->type) {
...
case OFPT_ECHO_REQUEST:
case OFPT_ECHO_REPLY: {
error = ofl_msg_pack_echo((struct ofl_msg_echo *)msg, buf, buf_len);
break;
}
case OFPT_EXPERIMENTER: {
if (exp == NULL || exp->msg == NULL || exp->msg->pack == NULL) {
OFL_LOG_WARN(LOG_MODULE, "Trying to pack experimenter msg, but no callback was given.");
error = -1;
} else {
error = exp->msg->pack((struct ofl_msg_experimenter *)msg, buf, buf_len);
}
break;
}
...
}
}

除实验消息外,openflow协议消息的pack函数大多位于文件oflib/ofl-messages-pack.c中。实验消息的处理函数统一位于oflib-exp目录。pack函数实现的功能都是将软件交换机中解析用到的格式转化为可以直接进行网络传输的报文格式。仍然以echo消息的pack为例,其实现过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
static int
ofl_msg_pack_echo(struct ofl_msg_echo *msg, uint8_t **buf, size_t *buf_len) {
uint8_t *data;

*buf_len = sizeof(struct ofp_header) + msg->data_length;
*buf = (uint8_t *)malloc(*buf_len);

if (msg->data_length > 0) {
data = (*buf) + sizeof(struct ofp_header);
memcpy(data, msg->data, msg->data_length);
}
return 0;
}

最后在dp_send_messages()函数中调用send_openflow_buffer(dp, ofpbuf, sender);即可将消息发送给控制器了。

自定义exp消息的工程实现

工程实现部分主要说明在原有的工程结构下,我们需要修改或增加的文件位置。

增加exp头文件

  • include/openflow/
    在该目录下增加自定义消息的定义,实验消息的ID等。

增加exp消息解析函数

  • oflib-exp/
    在该目录下增加自定义的实验消息的处理函数,主要包括如下四个函数:

    1
    2
    3
    4
    5
    {       .pack      = ofl_exp_msg_pack,
    .unpack = ofl_exp_msg_unpack,
    .free = ofl_exp_msg_free,
    .to_string = ofl_exp_msg_to_string
    };
  • oflib-exp/ofl-exp.c
    在实验消息入口处注册自定义exp消息解析的入口

增加exp消息处理函数

  • udatapath/dp_exp.c
    增加对自定义exp消息的处理函数,并且在dp_exp_message()函数中注册自定义exp消息处理的入口

修改makefile文件

  • include/openflow/automake.mk
    在makefile对应位置包含增加的自定义exp头文件

  • oflib-exp/automake.mk
    在makefile对应位置包含增加的自定义exp头文件

  • lib/automake.mk
    增加对应的oflib-exp中的编译文件

添加日志模块

  • oflib-exp/vlog-module.def
    增加自定义exp模块名
0%