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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_