#set page(header: [ #set par(spacing: 6pt) #align(center)[#text(size: 11pt)[《网络工程》实验报告]] #v(-0.3em) #line(length: 100%, stroke: (thickness: 1pt)) ],) #show heading: it => box(width: 100%)[ #v(0.50em) #set text(font: hei) #it.body ] #outline(title: "目录",depth: 3, indent: 1em) #pagebreak() #outline( title: [图目录], target: figure.where(kind: image), ) #show heading: it => box(width: 100%)[ #v(0.50em) #set text(font: hei) #counter(heading).display() // #h(0.5em) #it.body ] #set enum(indent: 0.5em,body-indent: 0.5em,) #pagebreak() = 实验概述 == 实验内容 #para[ 本次实验中有三个小实验: + 基于Openbox-S4测试SDN交换功能 + 基于SDN 交换机源码处理openflow协议消息 + 实验三 使用 REST API 接口查询交换机的相关功能数据 本实验报告重点分析第二个小实验的实现细节。 ] == 实验要求 #para[ 1. 熟悉Openbox-S4的基本功能 2. 熟悉OpenFlow协议的基本消息格式 3. 熟悉OpenFlow协议的基本消息处理流程 4. 熟悉REST API接口的基本使用 ] // Display inline code in a small box // that retains the correct baseline. #show raw.where(block: false): it => box( text(font: ("Consolas","FangSong_GB2312"), it), fill: luma(240), inset: (x: 3pt, y: 0pt), outset: (y: 3pt), radius: 2pt, ) // Display block code in a larger block // with more padding. #show raw.where(block: true): it => block( text(font: ("Consolas","FangSong_GB2312"), it), fill: luma(240), inset: 10pt, radius: 4pt, width: 100%, ) = 编写SDN交换机源码处理OpenFlow协议消息 #para[ 本次实验在`main_user_openflow.c`文件中实现了OpenFlow协议处理逻辑。代码通过多个函数处理不同类型的OpenFlow消息,包括Hello消息、Features Request消息、Flow Stats Request消息等。每个函数都根据消息类型生成相应的回复消息,并通过`send_openflow_message`函数发送给控制器。以下将逐一分析每个函数的实现细节。 ] == 本次实验所编写的协议 #para[ 本次实验实现了OpenFlow协议中的大部分消息处理功能。实验代码涵盖了从交换机特性查询到流表统计、端口统计等多种消息类型的处理。以下是实验编写协议的总结。 ] === 实现的协议 #para[ - OFPT_FEATURES_REQUEST:用于获取交换机支持的流表数量、缓冲区大小等特性信息。对应的处理函数为`handle_opfmsg_features_request`。 - OFPT_GET_CONFIG_REQUEST:用于查询交换机的配置信息,如Miss Send Length等。对应的处理函数为`handle_ofpmsg_get_config_request`。 - OFPT_MULTIPART_REQUEST:用于处理多种统计信息请求,包括交换机描述信息、流表信息、端口统计信息等。对应的处理函数包括: - `handle_ofpmsg_desc` - `handle_ofpmsg_flow_stats` - `handle_ofpmsg_aggregate` - `handle_ofpmsg_table` - `handle_ofpmsg_port_stats` - `handle_ofpmsg_group_features` - `handle_ofpmsg_port_desc` - OFPT_PACKET_OUT:用于处理控制器发送的数据包,并根据动作指示将数据包从指定端口发送出去。对应的处理函数为`handle_ofpmsg_packet_out`。 - OFPT_ROLE_REQUEST:用于配置交换机的角色(如主控制器、从控制器等)。对应的处理函数为`handle__opfmsg_role_request`。 ] === 组内分工 #para[ 本次实验由组内成员共同完成,每个成员负责不同的消息处理函数。以下是每个成员负责的函数列表。 #align(center)[#table( columns: (55pt, auto), rows: 15pt, inset: 3pt, align: horizon+center, table.header( [分工], [函数] ), table.cell(rowspan: 7)[王李烜], "handle_opfmsg_features_request", "handle_ofpmsg_desc", "handle_ofpmsg_flow_stats", "handle_ofpmsg_aggregate", "handle_ofpmsg_table", "handle_ofpmsg_port_desc", "handle_opfmsg_role_request", table.cell(rowspan: 2)[廖中煜], "handle_ofpmsg_get_config_request", "handle_ofpmsg_packet_out", table.cell(rowspan: 2)[王誉潞], "handle_ofpmsg_port_stats", "handle_ofpmsg_group_features", )] ] == 相关数据结构 #para[ 在`main_user_openflow.c`中,定义了一些关键的数据结构和全局变量,用于处理OpenFlow协议的消息。主要的数据结构包括`ofp_header`、`ofp_switch_features`、`ofp_flow_stats`和`ofp_port_stats`等。这些数据结构用于处理OpenFlow协议中的不同消息类型,如交换机特性请求、流表统计请求、端口统计请求等。 ] #para[ `ofp_header`结构体用于表示OpenFlow消息头,包含协议版本、消息类型、消息长度和事务ID等字段。`ofp_switch_features`结构体用于表示交换机的特性信息,如数据路径ID、缓冲区数量、流表数量等。`ofp_flow_stats`结构体用于表示流表统计信息,如流表项长度、优先级、数据包计数等。`ofp_port_stats`结构体用于表示端口统计信息,如端口号、接收数据包数、发送数据包数等。 ] == 消息头构建 #para[ 在OpenFlow协议中,每个消息都有一个消息头,用于标识消息的版本、类型、长度和事务ID。代码中实现了`build_opfmsg_header`函数,用于构建OpenFlow消息头。该函数接收消息长度、消息类型和事务ID作为参数,并填充消息头的各个字段。通过调用该函数,可以确保消息能够被正确解析和处理。 ] == 消息处理函数 #para[ 代码中实现了多个OpenFlow消息处理函数,每个函数对应一种OpenFlow消息类型。以下是每个函数的详细分析。 ] === 处理Hello消息 #para[ `handle_opfmsg_hello`函数用于处理控制器发送的Hello消息。Hello消息是OpenFlow协议中的基础消息,用于交换控制器和交换机之间的协议版本信息。函数首先检查消息头中的`version`字段,判断协议版本是否为1.3。如果版本匹配,则打印接收到的Hello消息;否则,生成一个错误消息并发送给控制器。函数返回`HANDLE`表示消息已处理。 ```c static enum ofperr handle_opfmsg_hello(struct ofp_buffer *ofpbuf) { if (ofpbuf->header.version == 0x04) { printf("RECV HELLO!\n\n\n"); } else { struct ofp_buffer *ofpbuf_reply = (struct ofp_buffer *)build_reply_ofpbuf(OFPT_ERROR, ofpbuf->header.xid, sizeof(struct ofp_header)); send_openflow_message(ofpbuf_reply, sizeof(struct ofp_header)); } return HANDLE; } ``` ] === 处理Features Request消息 #para[ `handle_opfmsg_features_request`函数用于处理控制器发送的Features Request消息。该消息用于查询交换机的特性信息,如支持的流表数量、缓冲区大小等。函数生成一个Features Reply消息,并填充交换机的特性信息,如数据路径ID、缓冲区数量、流表数量等。通过`send_openflow_message`函数将回复消息发送给控制器,并返回`HANDLE`表示消息已处理。 ```c static enum ofperr handle_opfmsg_features_request(struct ofp_buffer *ofpbuf) { int feature_reply_len = sizeof(struct ofp_switch_features) + sizeof(struct ofp_header); struct ofp_buffer *ofpbuf_reply = (struct ofp_buffer *)build_opfmsg_reply_ofpbuf(OFPT_FEATURES_REPLY, ofpbuf->header.xid, feature_reply_len); struct ofp_switch_features *feature_reply_msg = (struct ofp_switch_features *)ofpbuf_reply->data; feature_reply_msg->datapath_id = 0x0100000000000000; feature_reply_msg->n_buffers = htonl(46); feature_reply_msg->n_tables = 3; feature_reply_msg->capabilities = 0x7; send_openflow_message(ofpbuf_reply, feature_reply_len); return HANDLE; } ``` ] === 处理Get Config Request消息 #para[ `handle_ofpmsg_get_config_request`函数用于处理控制器发送的Get Config Request消息。该消息用于查询交换机的配置信息,如Miss Send Length等。函数生成一个Get Config Reply消息,并填充交换机的配置信息。通过`send_openflow_message`函数将回复消息发送给控制器,并返回`HANDLE`表示消息已处理。 ```c static enum ofperr handle_ofpmsg_get_config_request(struct ofp_buffer *ofpbuf) { int reply_len = sizeof(struct ofp_switch_config) + sizeof(struct ofp_header); struct ofp_buffer *ofpbuf_reply = (struct ofp_buffer *)build_reply_ofpbuf(OFPT_GET_CONFIG_REPLY, ofpbuf->header.xid, reply_len); struct ofp_switch_config *switch_config_reply = (struct ofp_switch_config *)ofpbuf_reply->data; switch_config_reply->flags = htons(0x0000); switch_config_reply->miss_send_len = htons(32); send_openflow_message(ofpbuf_reply, reply_len); return HANDLE; } ``` ] === 处理Description Request消息 #para[ `handle_ofpmsg_desc`函数用于处理控制器发送的Description Request消息。该消息用于查询交换机的描述信息,如制造商、硬件版本等。函数生成一个Multipart Reply消息,并填充交换机的描述信息。通过`send_openflow_message`函数将回复消息发送给控制器,并返回`HANDLE`表示消息已处理。 ```c static enum ofperr handle_ofpmsg_desc(struct ofp_buffer *ofpbuf) { int reply_len = sizeof(struct ofp_header) + sizeof(struct ofp_multipart) + sizeof(struct ofp_desc_stats); struct ofp_buffer *ofpbuf_reply = (struct ofp_buffer *)build_reply_ofpbuf(OFPT_MULTIPART_REPLY, ofpbuf->header.xid, reply_len); struct ofp_multipart *ofpmp_reply = (struct ofp_multipart *)ofpbuf_reply->data; static const char *default_mfr_desc = "Wanglixuan"; static const char *default_hw_desc = "Lixuan_OpenBox"; static const char *default_sw_desc = "Lixuan_Driver"; static const char *default_serial_desc = "Lixuan OpenBox Series"; static const char *default_dp_desc = "None"; ofpmp_reply->type = htons(OFPMP_DESC); ofpmp_reply->flags = htonl(OFPMP_REPLY_MORE_NO); snprintf(ofpmp_reply->ofpmp_desc[0].mfr_desc, sizeof ofpmp_reply->ofpmp_desc[0].mfr_desc, "%s", default_mfr_desc); snprintf(ofpmp_reply->ofpmp_desc[0].hw_desc, sizeof ofpmp_reply->ofpmp_desc[0].hw_desc, "%s", default_hw_desc); snprintf(ofpmp_reply->ofpmp_desc[0].sw_desc, sizeof ofpmp_reply->ofpmp_desc[0].sw_desc, "%s", default_sw_desc); snprintf(ofpmp_reply->ofpmp_desc[0].serial_num, sizeof ofpmp_reply->ofpmp_desc[0].serial_num, "%s", default_serial_desc); snprintf(ofpmp_reply->ofpmp_desc[0].dp_desc, sizeof ofpmp_reply->ofpmp_desc[0].dp_desc, "%s", default_dp_desc); send_openflow_message(ofpbuf_reply, reply_len); return HANDLE; } ``` ] === 处理Flow Stats Request消息 #para[ `handle_ofpmsg_flow_stats`函数用于处理控制器发送的Flow Stats Request消息。该消息用于查询交换机的流表统计信息。函数生成一个Multipart Reply消息,并设置消息类型为Flow Stats。通过`send_openflow_message`函数将回复消息发送给控制器,并返回`HANDLE`表示消息已处理。 ```c static enum ofperr handle_ofpmsg_flow_stats(struct ofp_buffer *ofpbuf) { int reply_len = sizeof(struct ofp_header) + sizeof(struct ofp_multipart); struct ofp_buffer *reply_buffer = (struct ofp_buffer *)build_reply_ofpbuf(OFPT_MULTIPART_REPLY, ofpbuf->header.xid, reply_len); struct ofp_multipart *multipart_reply = (struct ofp_multipart *)reply_buffer->data; multipart_reply->type = htons(OFPMP_FLOW); multipart_reply->flags = htonl(OFPMP_REPLY_MORE_NO); send_openflow_message(reply_buffer, reply_len); return HANDLE; } ``` ] === 处理Aggregate Stats Request消息 #para[ `handle_ofpmsg_aggregate`函数用于处理控制器发送的Aggregate Stats Request消息。该消息用于查询交换机的聚合统计信息。函数生成一个Multipart Reply消息,并填充聚合统计信息,如数据包计数、字节计数等。通过`send_openflow_message`函数将回复消息发送给控制器,并返回`HANDLE`表示消息已处理。 ```c static enum ofperr handle_ofpmsg_aggregate(struct ofp_buffer *ofpbuf) { int reply_len = sizeof(struct ofp_header) + sizeof(struct ofp_multipart) + sizeof(struct ofp_aggregate_stats_reply); struct ofp_buffer *ofpbuf_reply = (struct ofp_buffer *)build_reply_ofpbuf(OFPT_MULTIPART_REPLY, ofpbuf->header.xid, reply_len); struct ofp_multipart *ofpmp_reply = (struct ofp_multipart *)ofpbuf_reply->data; ofpmp_reply->type = htons(OFPMP_AGGREGATE); ofpmp_reply->flags = htonl(OFPMP_REPLY_MORE_NO); ofpmp_reply->ofpmp_aggregate_reply[0].packet_count = htonll(46); ofpmp_reply->ofpmp_aggregate_reply[0].byte_count = htonll(2025); ofpmp_reply->ofpmp_aggregate_reply[0].flow_count = htonll(200); send_openflow_message(ofpbuf_reply, reply_len); return HANDLE; } ``` ] === 处理Table Stats Request消息 #para[ `handle_ofpmsg_table`函数用于处理控制器发送的Table Stats Request消息。该消息用于查询交换机的流表统计信息。函数生成一个Multipart Reply消息,并填充流表统计信息,如匹配计数、查找计数等。通过`send_openflow_message`函数将回复消息发送给控制器,并返回`HANDLE`表示消息已处理。 ```c static enum ofperr handle_ofpmsg_table(struct ofp_buffer *ofpbuf) { int reply_len = sizeof(struct ofp_header) + sizeof(struct ofp_multipart) + sizeof(struct ofp_table_stats) * 1; struct ofp_buffer *ofpbuf_reply = (struct ofp_buffer *)build_reply_ofpbuf(OFPT_MULTIPART_REPLY, ofpbuf->header.xid, reply_len); struct ofp_multipart *ofpmp_reply = (struct ofp_multipart *)ofpbuf_reply->data; ofpmp_reply->type = htons(OFPMP_TABLE); ofpmp_reply->flags = htonl(OFPMP_REPLY_MORE_NO); ofpmp_reply->table_stats[0].matched_count = htonll(2025); ofpmp_reply->table_stats[0].table_id = 0; ofpmp_reply->table_stats[0].lookup_count = htonll(46); ofpmp_reply->table_stats[0].active_count = htonl(1); send_openflow_message(ofpbuf_reply, reply_len); return HANDLE; } ``` ] === 处理Port Stats Request消息 #para[ `handle_ofpmsg_port_stats`函数用于处理控制器发送的Port Stats Request消息。该消息用于查询交换机的端口统计信息。函数生成一个Multipart Reply消息,并填充端口统计信息,如持续时间、接收数据包数等。通过`send_openflow_message`函数将回复消息发送给控制器,并返回`HANDLE`表示消息已处理。 ```c static enum ofperr handle_ofpmsg_port_stats(struct ofp_buffer *ofpbuf) { int reply_len = sizeof(struct ofp_header) + sizeof(struct ofp_multipart) + sizeof(struct ofp_port_stats) * nmps.cnt; struct ofp_buffer *ofpbuf_reply = (struct ofp_buffer *)build_reply_ofpbuf(OFPT_MULTIPART_REPLY, ofpbuf->header.xid, reply_len); struct ofp_multipart *ofpmp_reply = (struct ofp_multipart *)ofpbuf_reply->data; ofpmp_reply->type = htons(OFPMP_PORT_STATS); ofpmp_reply->flags = htonl(OFPMP_REPLY_MORE_NO); for (int i = 0; i < nmps.cnt; i++) { ofpmp_reply->ofpmp_port_stats[i] = nmps.ports[i].stats; ofpmp_reply->ofpmp_port_stats[i].duration_sec = htonl(2025); ofpmp_reply->ofpmp_port_stats[i].duration_nsec = htonl(51); } send_openflow_message(ofpbuf_reply, reply_len); return HANDLE; } ``` ] === 处理Group Features Request消息 #para[ `handle_ofpmsg_group_features`函数用于处理控制器发送的Group Features Request消息。该消息用于查询交换机的组表特性信息。函数生成一个Multipart Reply消息,并填充组表特性信息,如最大组数等。通过`send_openflow_message`函数将回复消息发送给控制器,并返回`HANDLE`表示消息已处理。 ```c static enum ofperr handle_ofpmsg_group_features(struct ofp_buffer *ofpbuf) { int reply_len = sizeof(struct ofp_header) + sizeof(struct ofp_group_features) + 8; struct ofp_buffer *ofpbuf_reply = (struct ofp_buffer *)build_reply_ofpbuf(OFPT_MULTIPART_REPLY, ofpbuf->header.xid, reply_len); struct ofp_group_features *group = (struct ofp_group_features *)ofpbuf_reply->data; group->types = htons(OFPMP_GROUP_FEATURES); group->max_groups[0] = htonl(0xdeadbeef); send_openflow_message(ofpbuf_reply, reply_len); return HANDLE; } ``` ] === 处理Port Description Request消息 #para[ `handle_ofpmsg_port_desc`函数用于处理控制器发送的Port Description Request消息。该消息用于查询交换机的端口描述信息。函数生成一个Multipart Reply消息,并填充端口描述信息,如端口状态等。通过`send_openflow_message`函数将回复消息发送给控制器,并返回`HANDLE`表示消息已处理。 ```c static enum ofperr handle_ofpmsg_port_desc(struct ofp_buffer *ofpbuf) { int reply_len = sizeof(struct ofp_header) + sizeof(struct ofp_multipart) + sizeof(struct ofp_port) * nmps.cnt; struct ofp_buffer *ofpbuf_reply = (struct ofp_buffer *)build_reply_ofpbuf(OFPT_MULTIPART_REPLY, ofpbuf->header.xid, reply_len); struct ofp_multipart *ofpmp_reply = (struct ofp_multipart *)ofpbuf_reply->data; ofpmp_reply->type = htons(OFPMP_PORT_DESC); ofpmp_reply->flags = htonl(OFPMP_REPLY_MORE_NO); for (int i = 0; i < nmps.cnt; i++) { ofpmp_reply->ofpmp_port_desc[i] = nmps.ports[i].state; } send_openflow_message(ofpbuf_reply, reply_len); return HANDLE; } ``` ] === 处理Packet Out消息 #para[ `handle_ofpmsg_packet_out`函数用于处理控制器发送的Packet Out消息。该消息用于指示交换机将数据包从指定端口发送出去。函数解析消息中的动作列表,判断动作类型是否为`OFPAT_OUTPUT`,并根据动作指示的端口号调用`nms_exec_action`函数将数据包发送出去。函数返回`HANDLE`表示消息已处理。 ```c static enum ofperr handle_ofpmsg_packet_out(struct ofp_buffer *ofpbuf) { struct ofp_packet_out *out = (struct ofp_packet_out *)ofpbuf; struct ofp_action_output *action = (struct ofp_action_output *)&out->actions[0]; int action_len = ntohs(out->actions_len); struct eth_header *eth = (struct eth_header *)&ofpbuf->data[sizeof(struct ofp_packet_out) - sizeof(struct ofp_header) + action_len]; int send_len = ntohs(ofpbuf->header.length) - sizeof(struct ofp_packet_out) - action_len; if (action_len == 0) { nms_exec_action(ntohl(out->in_port), OFPP_FLOOD, eth, send_len, -1); } else { while (action_len > 0) { if (action->type == OFPAT_OUTPUT) { nms_exec_action(ntohl(out->in_port), ntohl(action->port), eth, send_len, -1); } action_len -= sizeof(struct ofp_action_output); action++; } } return HANDLE; } ``` ] === 处理Role Request消息 #para[ `handle_opfmsg_role_request`函数用于处理控制器发送的Role Request消息。该消息用于配置交换机的角色(如主控制器、从控制器等)。函数生成一个Role Reply消息,并填充角色配置信息。通过`send_openflow_message`函数将回复消息发送给控制器,并返回`HANDLE`表示消息已处理。 ```c static enum ofperr handle_opfmsg_role_request(struct ofp_buffer *ofpbuf) { int reply_len = sizeof(struct ofp_header) + sizeof(struct ofp_role); struct ofp_buffer *ofpbuf_reply = (struct ofp_buffer *)build_reply_ofpbuf(OFPT_ROLE_REPLY, ofpbuf->header.xid, reply_len); memcpy(ofpbuf_reply->data, ofpbuf->data, sizeof(struct ofp_role)); ofpbuf_reply->header.type = OFPT_ROLE_REPLY; send_openflow_message(ofpbuf_reply, reply_len); return HANDLE; } ``` ] == 实验结果 #para[ 在控制器与交换机之间进行抓包,分析报文的交互过程。控制器发送不同类型的OpenFlow消息给交换机,交换机接收并处理消息,并返回相应的回复消息。通过抓包分析,可以看到消息的交互过程,包括消息头、消息类型、消息长度等字段的解析和处理。用这种方式来验证协议编写的正确性。 实现的子协议种类较多,这里只展示部分报文内容。 ] === OFPT_ROLE_REPLY #para[ + OFPT_ROLE_REPLY类型消息。此消息用于配置交换机的角色(如主控制器、从控制器等)。下面的@slave_request 是控制器发送的Role Request消息。 #figure(image("slave.png",fit:"stretch",format:"png"),caption:"OFPT_ROLE_REQUEST消息",) 如图@slave_reply 所示,交换机接收并处理后返回的Role Reply消息。 #figure(image("slave_reply.png",fit:"stretch",format:"png"),caption:"OFPT_ROLE_REPLY消息",) 这说明交换机已经接收到了控制器发送的Role Request消息,并返回了Role Reply消息。 ] === OFPT_MULTIPART_REPLY #para[ + OFPMP_DESC类型消息。此消息用于查询交换机的描述信息,包括制造商、硬件版本、软件版本、序列号和数据路径描述等。 #figure(image("lixuanwang.png",fit:"stretch",format:"png"),caption:"OFPMP_DESC消息",) 可以看到图中的消息包含了制造商、硬件版本、软件版本、序列号和数据路径描述等信息(此处使用了自己的信息进行标记与区分)。 + OFPMP_TABLE类型消息。此消息用于查询交换机的流表统计信息,包括匹配计数、查找计数等。 #figure(image("table.png",fit:"stretch",format:"png"),caption:"OFPMP_TABLE消息",) 可以看到图中的消息包含了匹配计数、查找计数等信息(此处修改为了自己设置的值)。 ] = 实验总结 #para[ ] #show heading: it => box(width: 100%)[ #v(0.50em) #set text(font: hei) // #counter(heading).display() // #h(0.5em) #it.body ] #pagebreak() #bibliography("ref.yml",full: true,title: "参考文献",style:"gb-7714-2015-numeric") /* 根据这个网站的格式示范https://github.com/typst/hayagriva/blob/main/docs/file-format.md 为这些网页生成.yml文件 https://opennetworking.org/wp-content/uploads/2014/10/openflow-spec-v1.3.0.pdf https://www.cnblogs.com/goldsunshine/p/7262484.html https://www.jianshu.com/p/acfeae1771b3 https://www.jianshu.com/p/82e238eb8d14 */