DN功能实现(七)---修改OVS源码实现ACK机制(控制器-交换机用户态-交换机内核态通信)

本文主要是介绍DN功能实现(七)---修改OVS源码实现ACK机制(控制器-交换机用户态-交换机内核态通信),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

(一)系统部分架构说明

如图所示,其中s1\s4是边缘交换机,s1直接与发送方相连,作为近接受方交换机存在,s4直接与接受方相连,作为近接受方交换机存在.

为了使目的终端获取得到唯一的\可靠\实时传输的UDP数据包,源终端主机发送的数据经由近接收方交换机s1,若流表中没有匹配项,则发送Pakcet-in消息给控制器,控制器获取rudp数据后,通过拓扑发现和时延探测模块获取网络全局拓扑结构,预选取两条节点不相关的主\备份路径{s1-s2-s3-s4,s1-s5-s6-s4}和关键节点{s1,s2,s4,s6},下发对应流表项.数据包在时延最优路径{s1-s2-s3-s4}中进行传输,经过关键节点{s1,s2,s4}时,需要触发ACK报文处理机制,各个关键节点通过控制器进行ACK报文的回送和确认,以保障数据传输的可靠性,减少由于路径故障导致的丢包问题.

其中若是关键节点之间的链路故障\拥塞导致数据包无法实时到达下一个关键节点,则通过内部定时机制或下一个关键节点回送的缺失类型ACK决定对丢失数据包的重传,重传包括原关键节点同时向主路径端口和备份路径端口进行转发,以提高数据传输的实时性和可靠性.如图所示,对于关键节点s2转发数据包{packet1,packet2,packet3}在链路{s2-s3-s4},其中packet2由于链路{s2-s3}故障导致丢失,因此在下一个关键节点s4中无法接收到packet2,通过发送ack报文,请求前一个关键节点进行数据的重传,在关键节点s2接受到重传ack后,从缓存队列中获取对应的数据包,通过端口分别向主路径进行重传和备份路径进行转发,同样关键节点s2内部的定时机制也可以触发重传机制.

(二)ACK机制

补充:RUDP数据报文字段如下图,通过对FrontDPID和PacketNumber字段数据的获取,用于支持ACK机制的实现.

其中各个字段属性如下:

在系统实现中,关键节点交换机(如S4)使用了数据缓存队列缓存所有需要可靠传输服务的RUDP数据包,同时通过开启内核线程实现定时检测队列,如果有新的数据达到,则对最新的数据的包序号进行ACK转发,从内核态通过Generic Netlink机制上传至用户态,用户态通过openflow协议传输至控制器,控制器通过获取的包序号和前一个关键节点交换机(S2)信息,将ACK报文通过OpenFlow协议转发至S2交换机用户空间,S2同样通过Generic Netlink转发至内核态,通过对ACK数据解析,S2获取得到S4中包接收的顺序,因此可以释放数据队列中已经确认的ACK序号前面的所有数据空间,对于丢失的数据,同样通过ACK可以实现重传和转发.

本文主要介绍ACK报文在两个交换机的内核态-用户态-控制器-用户态-内核态之间的转发过程.

其中Generic Netlink学习,可以看前面转载的几篇博客!!!

二:扩展OpenFlow协议,实现控制器和交换机用户态通信

(一)Ryu源码修改

1.修改ofproto/ofproto_v1_3.py,声明新的OpenFlow消息类型

OFPT_GET_ASYNC_REQUEST = 26 # Controller/switch message
OFPT_GET_ASYNC_REPLY = 27 # Controller/switch message
OFPT_SET_ASYNC = 28 # Controller/switch messageOFPT_METER_MOD = 29 # Controller/switch message#与前面的消息类型编码不一致(唯一即可)
OFPT_ACK_REQUEST = 32               # Symmetric message
OFPT_ACK_REPLY = 33                 # Symmetric message

2.修改ofproto/ofproto_v1_3_parser.py,实现对新的ACK消息支持

@_register_parser
@_set_msg_type(ofproto.OFPT_ACK_REQUEST)
class OFPACKRequest(MsgBase):def __init__(self, datapath, data=None):super(OFPACKRequest, self).__init__(datapath)self.data = data@classmethoddef parser(cls, datapath, version, msg_type, msg_len, xid, buf):msg = super(OFPACKRequest, cls).parser(datapath, version, msg_type,msg_len, xid, buf)msg.data = msg.buf[ofproto.OFP_HEADER_SIZE:]return msgdef _serialize_body(self):if self.data is not None:self.buf += self.data@_register_parser
@_set_msg_type(ofproto.OFPT_ACK_REPLY)
class OFPACKReply(MsgBase):def __init__(self, datapath, data=None):super(OFPACKReply, self).__init__(datapath)self.data = data@classmethoddef parser(cls, datapath, version, msg_type, msg_len, xid, buf):msg = super(OFPACKReply, cls).parser(datapath, version, msg_type,msg_len, xid, buf)msg.data = msg.buf[ofproto.OFP_HEADER_SIZE:]return msgdef _serialize_body(self):assert self.data is not Noneself.buf += self.data

3.完成1,2步骤后记得进行Ryu源码的重新编译!步骤在前面的SDN实现中

4.实现Ryu App应用对新的ACK消息进行转发

from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import MAIN_DISPATCHER,CONFIG_DISPATCHER,DEAD_DISPATCHER,HANDSHAKE_DISPATCHER #只是表示datapath数据路径的状态
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.lib.packet import ether_types
from ryu.lib.packet import udp
from ryu.lib.packet import ipv4
from struct import packRUDP_MAGIC = 38217class RudpTrans(app_manager.RyuApp):OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]def __init__(self, *args, **kwargs):super(RudpTrans, self).__init__(*args, **kwargs)self.mac_to_port = {}self.datapaths = {}self.initFlag = False@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)def switch_features_handler(self, ev):datapath = ev.msg.datapathself.datapaths[datapath.id] = datapathofproto = datapath.ofprotoparser = datapath.ofproto_parser# install table-miss flow entry## We specify NO BUFFER to max_len of the output action due to# OVS bug. At this moment, if we specify a lesser number, e.g.,# 128, OVS will send Packet-In with invalid buffer_id and# truncated packet data. In that case, we cannot output packets# correctly.  The bug has been fixed in OVS v2.1.0.match = parser.OFPMatch()actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,ofproto.OFPCML_NO_BUFFER)]self.add_flow(datapath, 0, match, actions)def add_flow(self, datapath, priority, match, actions, buffer_id=None):ofproto = datapath.ofprotoparser = datapath.ofproto_parserinst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]if buffer_id:mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,priority=priority, match=match,instructions=inst)else:mod = parser.OFPFlowMod(datapath=datapath, priority=priority,match=match, instructions=inst)datapath.send_msg(mod)def _send_ack_request(self,dp_id,hash_id,packet_number,type_id):"""发送echo_time报文到datapath"""    print("===============_send_ack_request========start============")print("front dpid / hash id / packet number / type id : ",dp_id, hash_id, packet_number, type_id)datapath = self.datapaths.get(dp_id,None)if datapath == None:returnparser = datapath.ofproto_parserprint("========%d"%packet_number);ack_req = parser.OFPACKRequest(datapath,data=pack("BBBI",dp_id,hash_id,type_id,packet_number)) datapath.send_msg(ack_req)print("==========_send_ack_request=========end=====")@set_ev_cls(ofp_event.EventOFPACKReply,[MAIN_DISPATCHER,CONFIG_DISPATCHER,HANDSHAKE_DISPATCHER])def ack_reply_handler(self,ev):"""处理ACK响应报文,获取控制器到交换机的链路往返时延"""print("==================OFPT_ACK_REPLY============start===========")print(ev.msg.data)info = ev.msg.datafront_dpid = int.from_bytes(info[0:1],'little')hash_id = int.from_bytes(info[1:2],'little')packet_id = int.from_bytes(info[2:6],'little')type_id = int.from_bytes(info[6:7],'little')self._send_ack_request(front_dpid,hash_id,packet_id,type_id)  print("==================OFPT_ACK_REPLY===========end============") 

(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂

更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! ! 

(二)OVS源码修改,实现对新的OpenFlow消息支持

1.修改include/openvswitch/ofp-msgs.h下的enum ofptype和enum ofpraw枚举类型

enum ofptype {OFPTYPE_HELLO, /* OFPRAW_OFPT_HELLO. */OFPTYPE_ERROR, /* OFPRAW_OFPT_ERROR. ......OFPTYPE_ACK_REQUEST, /* OFPRAW_OFPT_ACK_REQUEST */OFPTYPE_ACK_REPLY, /* OFPRAW_OFPT_ACK_REPLY */......
}

注意:注释对应了下面ofpraw枚举的名称

enum ofpraw {....../* OFPT 1.1-1.3 (32): uint8_t[]. */OFPRAW_OFPT_ACK_REQUEST,/* OFPT 1.1-1.3 (33): uint8_t[]. */OFPRAW_OFPT_ACK_REPLY,......
}

注意:序号需要保持与Ryu中的消息序号一致!!!

2.修改在ofproto/ofproto.c文件中handle_flow函数下的handle_single_part_openflow方法,处理自定义的类型

static enum ofperr
handle_single_part_openflow(struct ofconn *ofconn, const struct ofp_header *oh,enum ofptype type)OVS_EXCLUDED(ofproto_mutex)
{switch (type) {/* OpenFlow requests. */......case OFPTYPE_ACK_REQUEST:// VLOG_INFO("--------------------OFPTYPE_ACK_REQUEST--------------------");return handle_ack_request(ofconn,oh);......case OFPTYPE_ACK_REPLY:return 0;......  
}

同样在该文件中实现handle_ack_request方法:

static enum ofperr
handle_ack_request(struct ofconn *ofconn, const struct ofp_header *oh)
{//不同的是,获取数据,发送消息给内核// VLOG_INFO("----handle_ack_request-------start----");if(!ack_init) return OFPERR_OFPAFC_INIT;if(!ack_oh || ack_oh != oh) ack_oh = oh;struct ofpbuf rq_buf = ofpbuf_const_initializer(oh, ntohs(oh->length));//rq_buf.data是我们的数据,但是前面8个字节是结构体ofp_header,所以我们获取真正的数据需要跳过这8个字节数据char *data = rq_buf.data;// uint32_t packetid = ack_parse_packet_number(data+8,rq_buf.size-8);uint8_t front_dpid = *(uint8_t *)(data + 8);uint8_t hash_id = *(uint8_t *)(data + 9);uint8_t typeid = *(uint8_t *)(data + 10);uint32_t packetid = *(uint32_t *)(data + 12);   //存在对齐问题,所以间隔2//通过以上操作,可以获取并解析出控制器转发的ACK消息数据// VLOG_INFO("--handle_ack_request---front dpid:%u--hash id:%u--packet id:%u- type:%u-------",front_dpid,hash_id,packetid,typeid);dpif_netlink_ack_reply(false,front_dpid,hash_id,packetid,typeid);return 0;
}

其中dpif_netlink_ack_reply方案用于将解析的数据通过Generic Netlink转发至内核态,将在下一章中进行详细介绍!

三:实现交换机内核态与用户态通信

(一)用户态主动与内核态通信

1.在内核态进行新的Generic Netlink套接字注册

(1)在datapath/linux/compat/include/linux/openvswitch.h中进行类型声明

/*     ACK.    */#define OVS_ACK_FAMILY     "ovs_ack"
#define OVS_ACK_VERSION 0x1enum ovs_ack_cmd {OVS_ACK_CMD_UNSPEC,OVS_ACK_CMD_ECHO,                          //用户发给内核,该命令将由内核进行处理OVS_ACK_CMD_SET,                           //用戶发送给内核,进行设置OVS_ACK_CMD_REPLY,                         //由内核发送响应给用户态,该命令将由用户态进行处理__OVS_ACK_CMD_MAX,
};
#define OVS_ACK_CMD_MAX (__OVS_ACK_CMD_MAX - 1)enum ovs_ack_attr {OVS_ACK_ATTR_UNSPEC = 0,OVS_ACK_ATTR_INIT,                        //flag标识初始化OVS_ACK_ATTR_VID,                        //8bits无符号整型OVS_ACK_ATTR_OUTPUT,                    //16bits无符号整型OVS_ACK_ATTR_TURNOUT,                    //16bits无符号整型OVS_ACK_ATTR_QUEUE_SIZE,                //16bits无符号整型OVS_ACK_ATTR_TIME_INTERVAL,                //16bits无符合整型OVS_ACK_ATTR_DEVSTATUS,                    //8bits无符号整型OVS_ACK_ATTR_HASH_ID,                    //8bits无符号整型OVS_ACK_ATTR_TYPE,                        //8bits无符号整型OVS_ACK_ATTR_NUMBER,                    //32bits无符号整型__OVS_ACK_ATTR_MAX,
};
#define OVS_ACK_ATTR_MAX (__OVS_ACK_ATTR_MAX - 1)

其中红色的字段与本次的ACK机制有关,其他的是关于其他功能的,这里不需要关心.

另外需要补充,datapath/linux/compat/include/linux/openvswitch.h在编译过程中会自动生成一个include/odp-netlink.h文件,我们在用户空间中使用这个头文件即可.

(2)在datapath/datapath.c中进行实现

Ⅰ.为模块添加Generic Netlink family信息

module_init(dp_init);
module_exit(dp_cleanup);MODULE_DESCRIPTION("Open vSwitch switching datapath");
MODULE_LICENSE("GPL");
MODULE_VERSION(VERSION);
MODULE_ALIAS_GENL_FAMILY(OVS_DATAPATH_FAMILY);
MODULE_ALIAS_GENL_FAMILY(OVS_VPORT_FAMILY);
MODULE_ALIAS_GENL_FAMILY(OVS_FLOW_FAMILY);
MODULE_ALIAS_GENL_FAMILY(OVS_PACKET_FAMILY);
MODULE_ALIAS_GENL_FAMILY(OVS_ACK_FAMILY);
MODULE_ALIAS_GENL_FAMILY(OVS_METER_FAMILY);
MODULE_ALIAS_GENL_FAMILY(OVS_CT_LIMIT_FAMILY);

Ⅱ.定义策略、操作集和family

static const struct nla_policy ack_policy[OVS_ACK_ATTR_MAX + 1] = {    [OVS_ACK_ATTR_INIT]            =    {.type = NLA_FLAG},[OVS_ACK_ATTR_VID]            =    {.type = NLA_U8},[OVS_ACK_ATTR_HASH_ID]        =    {.type = NLA_U8},[OVS_ACK_ATTR_NUMBER]        =    {.type = NLA_U32},[OVS_ACK_ATTR_TYPE]            =    {.type = NLA_U8},[OVS_ACK_ATTR_OUTPUT]        =    {.type = NLA_U16},[OVS_ACK_ATTR_TURNOUT]        =    {.type = NLA_U16},[OVS_ACK_ATTR_QUEUE_SIZE]        =    {.type = NLA_U16},[OVS_ACK_ATTR_TIME_INTERVAL]        =    {.type = NLA_U16},[OVS_ACK_ATTR_DEVSTATUS]    =    {.type = NLA_U8},
};                                                                        //其他的为默认,NLA_UNSPEC表示类型和长度未知static const struct genl_ops dp_ack_genl_ops[] = {{.cmd         =    OVS_ACK_CMD_ECHO,.doit         =     ovs_ack_cmd_execute,.policy     =     ack_policy,.flags         =    0,},{.cmd         =    OVS_ACK_CMD_SET,.doit         =     ovs_ack_cmd_set,.policy     =     ack_policy,.flags         =    0,},
};static struct genl_family dp_ack_genl_family __ro_after_init = {.id      =    0,                   

这篇关于DN功能实现(七)---修改OVS源码实现ACK机制(控制器-交换机用户态-交换机内核态通信)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/696710

相关文章

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

MySQL分表自动化创建的实现方案

《MySQL分表自动化创建的实现方案》在数据库应用场景中,随着数据量的不断增长,单表存储数据可能会面临性能瓶颈,例如查询、插入、更新等操作的效率会逐渐降低,分表是一种有效的优化策略,它将数据分散存储在... 目录一、项目目的二、实现过程(一)mysql 事件调度器结合存储过程方式1. 开启事件调度器2. 创

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端