SDN功能实现(六)---修改OVS与Ryu源码实现自定义echo_time报文(类似echo报文)
功能介绍:实现一个类似ECHO报文处理机制
(一)ECHO报文回顾

注意:一般data数据是控制器填充的时间戳信息,当data再次回到控制器时,控制器获取新的时间戳信息,新的时间戳-data中旧的时间戳 = 控制器到交换机×2的时延 。以此获取了控制器到交换机的时延信息!!!
(二)改进的ECHO TIME报文功能---可以用于获取各个交换机在同一时刻下的本地时间戳信息

一:回顾OVS源码与控制器的交互流程
(一)connmr_run函数处理与控制器的周期性交互
void connmgr_run(struct connmgr *mgr, void (*handle_openflow)(struct ofconn *, const struct ovs_list *msgs)) OVS_EXCLUDED(ofproto_mutex) { if (mgr->in_band) { //首先检查是否存在in_band的控制器 if (!in_band_run(mgr->in_band)) { in_band_destroy(mgr->in_band); mgr->in_band = NULL; } } struct ofconn *ofconn, *next_ofconn; LIST_FOR_EACH_SAFE (ofconn, next_ofconn, node, &mgr->all_conns) { ofconn_run(ofconn, handle_openflow); //调用ofconn_run()处理对ofproto的协议解析和行动,注意传参为函数指针,指向handle_openflow方法!!! } ofmonitor_run(mgr); /* Fail-open maintenance. Do this after processing the ofconns since * fail-open checks the status of the controller rconn. */ if (mgr->fail_open) { fail_open_run(mgr->fail_open); } struct ofservice *ofservice; HMAP_FOR_EACH (ofservice, node, &mgr->services) { struct vconn *vconn; int retval = pvconn_accept(ofservice->pvconn, &vconn); if (!retval) { /* Passing default value for creation of the rconn */ struct rconn *rconn = rconn_create( ofservice->probe_interval, 0, ofservice->dscp, vconn_get_allowed_versions(vconn)); char *name = ofconn_make_name(mgr, vconn_get_name(vconn)); rconn_connect_unreliably(rconn, vconn, name); free(name); ovs_mutex_lock(&ofproto_mutex); ofconn = ofconn_create(mgr, rconn, OFCONN_SERVICE, ofservice->enable_async_msgs); ovs_mutex_unlock(&ofproto_mutex); ofconn_set_rate_limit(ofconn, ofservice->rate_limit, ofservice->burst_limit); } else if (retval != EAGAIN) { VLOG_WARN_RL(&rl, "accept failed (%s)", ovs_strerror(retval)); } } for (size_t i = 0; i < mgr->n_snoops; i++) { struct vconn *vconn; int retval = pvconn_accept(mgr->snoops[i], &vconn); if (!retval) { add_snooper(mgr, vconn); } else if (retval != EAGAIN) { VLOG_WARN_RL(&rl, "accept failed (%s)", ovs_strerror(retval)); } } }
(二)调用ofconn_run()处理对ofproto的协议解析和行动
static void ofconn_run(struct ofconn *ofconn, void (*handle_openflow)(struct ofconn *, const struct ovs_list *msgs)) { struct connmgr *mgr = ofconn->connmgr; for (size_t i = 0; i < N_SCHEDULERS; i++) { struct ovs_list txq; pinsched_run(ofconn->schedulers[i], &txq); do_send_packet_ins(ofconn, &txq); } rconn_run(ofconn->rconn); //负责连接到controller!!! /* Limit the number of iterations to avoid starving other tasks. */ for (int i = 0; i < 50 && ofconn_may_recv(ofconn); i++) { struct ofpbuf *of_msg = rconn_recv(ofconn->rconn); //负责从controller收取消息!!! if (!of_msg) { break; } if (mgr->fail_open) { fail_open_maybe_recover(mgr->fail_open); } struct ovs_list msgs; enum ofperr error = ofpmp_assembler_execute(&ofconn->assembler, of_msg, &msgs, time_msec()); if (error) { ofconn_send_error(ofconn, of_msg->data, error); ofpbuf_delete(of_msg); } else if (!ovs_list_is_empty(&msgs)) { handle_openflow(ofconn, &msgs); //最终调用handle_openflow()(ofproto/ofproto.c)来完成对各个Of消息的处理!!!!! ofpbuf_list_delete(&msgs); } } long long int now = time_msec(); if (now >= ofconn->next_bundle_expiry_check) { ofconn->next_bundle_expiry_check = now + BUNDLE_EXPIRY_INTERVAL; bundle_remove_expired(ofconn, now); } if (now >= ofconn->next_op_report) { ofconn_log_flow_mods(ofconn); } struct ofpbuf *error = ofpmp_assembler_run(&ofconn->assembler, time_msec()); if (error) { ofconn_send(ofconn, error, NULL); } ovs_mutex_lock(&ofproto_mutex); if (!rconn_is_alive(ofconn->rconn)) { ofconn_destroy(ofconn); } else if (!rconn_is_connected(ofconn->rconn)) { ofconn_flush(ofconn); } ovs_mutex_unlock(&ofproto_mutex); }
(三)最终调用handle_openflow()(ofproto/ofproto.c)来完成对各个Of消息的处理!!!!!
static void handle_openflow(struct ofconn *ofconn, const struct ovs_list *msgs) OVS_EXCLUDED(ofproto_mutex) { COVERAGE_INC(ofproto_recv_openflow); struct ofpbuf *msg = ofpbuf_from_list(ovs_list_front(msgs)); enum ofptype type; enum ofperr error = ofptype_decode(&type, msg->data); if (!error) { if (type == OFPTYPE_TABLE_FEATURES_STATS_REQUEST) { handle_table_features_request(ofconn, msgs); } else if (type == OFPTYPE_FLOW_MONITOR_STATS_REQUEST) { handle_flow_monitor_request(ofconn, msgs); } else if (!ovs_list_is_short(msgs)) { error = OFPERR_OFPBRC_BAD_STAT; } else { error = handle_single_part_openflow(ofconn, msg->data, type); } } if (error) { ofconn_send_error(ofconn, msg->data, error); } }
最终调用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_ECHO_REQUEST: return handle_echo_request(ofconn, oh); case OFPTYPE_FEATURES_REQUEST: return handle_features_request(ofconn, oh); case OFPTYPE_GET_CONFIG_REQUEST: return handle_get_config_request(ofconn, oh);
......
二:修改OVS源码,自定义openflow消息类型
回顾:注意前面提及的handle_flow代码:对openflow类型的转换
static void handle_openflow(struct ofconn *ofconn, const struct ovs_list *msgs) OVS_EXCLUDED(ofproto_mutex) { COVERAGE_INC(ofproto_recv_openflow); struct ofpbuf *msg = ofpbuf_from_list(ovs_list_front(msgs)); enum ofptype type; enum ofperr error = ofptype_decode(&type, msg->data); if (!error) { if (type == OFPTYPE_TABLE_FEATURES_STATS_REQUEST) { handle_table_features_request(ofconn, msgs); } else if (type == OFPTYPE_FLOW_MONITOR_STATS_REQUEST) { handle_flow_monitor_request(ofconn, msgs); } else if (!ovs_list_is_short(msgs)) { error = OFPERR_OFPBRC_BAD_STAT; } else { error = handle_single_part_openflow(ofconn, msg->data, type); } } if (error) { ofconn_send_error(ofconn, msg->data, error); } }
查看ofptype_decode方法:发现ofpraw与ofptype是可以相互转换的!!交换机通过从openflow消息获取ofpraw类型,之后通过方法ofptype_from_ofpraw去获取对应的ofptype类型
enum ofperr ofptype_decode(enum ofptype *typep, const struct ofp_header *oh) { enum ofperr error; enum ofpraw raw; error = ofpraw_decode(&raw, oh); *typep = error ? 0 : ofptype_from_ofpraw(raw); return error; }
(一)开始定义openflow消息类型,注意:注释必须加上,必须遵循样式化的形式,会根据这个形式生成相应的数据
1.修改include/openvswitch/ofp-msg.h文件中ofptype枚举类型
/* OpenFlow消息的语义标识符。 每个 OFPTYPE_* 枚举常量表示一个或多个OpenFlow消息的具体格式。 当消息的两个变体基本上具有相同的含义时,它们被分配一个 OFPTYPE_* 值。 这里的注释必须遵循样式化的形式,因为“extract of p msgs”程序在构建时解析它们以生成数据表。 格式只是列出给定类型的每个原始枚举常量,每个常量后跟一个句点。 */ enum ofptype { /* Immutable messages. */ OFPTYPE_HELLO, /* OFPRAW_OFPT_HELLO. */ OFPTYPE_ERROR, /* OFPRAW_OFPT_ERROR. */ OFPTYPE_ECHO_REQUEST, /* OFPRAW_OFPT_ECHO_REQUEST. */ OFPTYPE_ECHO_REPLY, /* OFPRAW_OFPT_ECHO_REPLY. */ OFPTYPE_ECHO_TIME_REQUEST, /* OFPRAW_OFPT_ECHO_TIME_REQUEST. */ OFPTYPE_ECHO_TIME_REPLY, /* OFPRAW_OFPT_ECHO_TIME_REPLY. */ /* Switch configuration messages. */ OFPTYPE_FEATURES_REQUEST, /* OFPRAW_OFPT_FEATURES_REQUEST. */ OFPTYPE_FEATURES_REPLY, /* OFPRAW_OFPT10_FEATURES_REPLY. * OFPRAW_OFPT11_FEATURES_REPLY. * OFPRAW_OFPT13_FEATURES_REPLY. */ OFPTYPE_GET_CONFIG_REQUEST, /* OFPRAW_OFPT_GET_CONFIG_REQUEST. */
2.修改include/openvswitch/ofp-msg.h文件中ofpraw枚举类型,注意:注释包含了(1)适用的openflow版本(2)在ofptype中的枚举id<尤其重要>(3)openflow消息传递的参数
enum ofpraw { /* Immutable standard messages. * * The OpenFlow standard promises to preserve these messages and their numbers * in future versions, so we mark them as <all>, which covers every OpenFlow * version numbered 0x01...0xff, rather than as OF1.0+, which covers only * OpenFlow versions that we otherwise implement. * * Without <all> here, then we would fail to decode "hello" messages that * announce a version higher than we understand, even though there still could * be a version in common with the peer that we do understand. The <all> * keyword is less useful for the other messages, because our OpenFlow channels * accept only OpenFlow messages with a previously negotiated version. */ /* OFPT <all> (0): uint8_t[]. */ OFPRAW_OFPT_HELLO, /* OFPT <all> (1): struct ofp_error_msg, uint8_t[]. */ OFPRAW_OFPT_ERROR, /* OFPT <all> (2): uint8_t[]. */ OFPRAW_OFPT_ECHO_REQUEST, /* OFPT <all> (3): uint8_t[]. */ OFPRAW_OFPT_ECHO_REPLY, /* OFPT 1.1-1.3 (30): uint8_t[]. */ OFPRAW_OFPT_ECHO_TIME_REQUEST, /* OFPT 1.1-1.3 (31): uint8_t[]. */ OFPRAW_OFPT_ECHO_TIME_REPLY, /* Other standard messages. * * The meanings of these messages can (and often do) change from one version * of OpenFlow to another. */ /* OFPT 1.0+ (5): void. */
3.注意:是注释将两个枚举类型关联起来的,所以一定要注意注释的写法。例如:控制器传递的openflow消息类型是ofpraw类型,ofptype_decode匹配之后通过对应的ofptype类型去进行handle_openflow处理!!!
(二)修改handle_flow中的handle_single_part_openflow方法,处理自定义的类型!!!
在ofproto/ofproto.c文件中:
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_ECHO_REQUEST: return handle_echo_request(ofconn, oh); case OFPTYPE_ECHO_TIME_REQUEST: return handle_echo_time_request(ofconn,oh); /* OpenFlow replies. */ case OFPTYPE_ECHO_REPLY: return 0; case OFPTYPE_ECHO_TIME_REPLY: return 0;
模仿ECHO报文的处理!!!!
(三)实现自定义方法
static enum ofperr handle_echo_request(struct ofconn *ofconn, const struct ofp_header *oh) { ofconn_send_reply(ofconn, ofputil_encode_echo_reply(oh)); return 0; } static enum ofperr handle_echo_time_request(struct ofconn *ofconn, const struct ofp_header *oh) { ofconn_send_reply(ofconn, ofputil_encode_echo_time_reply(oh)); return 0; }
在include/openvswitch/ofp-util.h声明ofputil_encode_echo_time_reply方法:
struct ofpbuf *ofputil_encode_echo_request(enum ofp_version); struct ofpbuf *ofputil_encode_echo_reply(const struct ofp_header *); struct ofpbuf *ofputil_encode_echo_time_reply(const struct ofp_header *);
在lib/ofp-util.c中,实现ofputil_encode_echo_time_reply方法,为控制器传来的消息进行回复,添加交换机的本地时间戳!!!
struct ofpbuf * ofputil_encode_echo_reply(const struct ofp_header *rq) { struct ofpbuf rq_buf = ofpbuf_const_initializer(rq, ntohs(rq->length)); ofpraw_pull_assert(&rq_buf); struct ofpbuf *reply = ofpraw_alloc_reply(OFPRAW_OFPT_ECHO_REPLY, rq, rq_buf.size); ofpbuf_put(reply, rq_buf.data, rq_buf.size); return reply; } struct ofpbuf * ofputil_encode_echo_time_reply(const struct ofp_header *rq) { struct timeval tv; struct ofpbuf rq_buf = ofpbuf_const_initializer(rq, ntohs(rq->length)); uint64_t temp = 0; ofpraw_pull_assert(&rq_buf); gettimeofday(&tv, NULL); struct ofpbuf *reply = ofpraw_alloc_reply(OFPRAW_OFPT_ECHO_TIME_REPLY, rq, rq_buf.size+sizeof(tv)); void *dst = ofpbuf_put_uninit(reply, rq_buf.size+sizeof(tv)); memcpy(dst, rq_buf.data, rq_buf.size); temp = htonl(tv.tv_sec); memcpy(dst+rq_buf.size, &temp, sizeof(uint64_t)); temp = 0; temp = htonl(tv.tv_usec); memcpy(dst+rq_buf.size+sizeof(tv.tv_sec), &temp, sizeof(uint64_t)); return reply; }
补充:struct timeval内部的seconds和useconds字段都是无符号整型,占32位,所以我上面的代码其实应该使用uint32_t更加合适.当使用uint64_t会导致赋值前面变为0,比如10-->010.所以导致我在控制器解析时需要跳过多余的字节进行解码(因为内部为0,不需要进行处理)

(三)对openvswitch进行重新编译
1.编译脚本:recompile_1.sh
ovs-dpctl del-dp ovs-system rmmod openvswitch lsmod|grep openvswitch ./boot.sh make clean ./configure --with-linux=/lib/modules/`uname -r`/build make
2.安装脚本:recompile_2.sh
make install
modinfo openvswitch
3.内核插入脚本:recompile_3.sh
make modules_install modprobe openvswitch modprobe -D openvswitch mkdir -p /usr/local/etc/openvswitch ovsdb-tool create /usr/local/etc/openvswitch/conf.db vswitchd/vswitch.ovsschema ls /usr/local/etc/openvswitch
4.ovs启动脚本:start.sh
ovsdb-server --remote=punix:/usr/local/var/run/openvswitch/db.sock --remote=db:Open_vSwitch,Open_vSwitch,manager_options --pidfile --detach ovs-vsctl --no-wait init ovs-vswitchd --pidfile --detach --log-file
注意:如果ovs原本启动,则需要先结束进程!!!
三:修改Ryu源码,自定义openflow消息类型
(一)定义消息类型ryu/ofproto/ofproto_v1_3.py
# enum ofp_type OFPT_HELLO = 0 # Symmetric message OFPT_ERROR = 1 # Symmetric message OFPT_ECHO_REQUEST = 2 # Symmetric message OFPT_ECHO_REPLY = 3 # Symmetric message OFPT_EXPERIMENTER = 4 # Symmetric message OFPT_FEATURES_REQUEST = 5 # Controller/switch message OFPT_FEATURES_REPLY = 6 # Controller/switch message OFPT_GET_CONFIG_REQUEST = 7 # Controller/switch message OFPT_GET_CONFIG_REPLY = 8 # Controller/switch message OFPT_SET_CONFIG = 9 # Controller/switch message OFPT_PACKET_IN = 10 # Async message OFPT_FLOW_REMOVED = 11 # Async message OFPT_PORT_STATUS = 12 # Async message OFPT_PACKET_OUT = 13 # Controller/switch message OFPT_FLOW_MOD = 14 # Controller/switch message OFPT_GROUP_MOD = 15 # Controller/switch message OFPT_PORT_MOD = 16 # Controller/switch message OFPT_TABLE_MOD = 17 # Controller/switch message OFPT_MULTIPART_REQUEST = 18 # Controller/switch message OFPT_MULTIPART_REPLY = 19 # Controller/switch message OFPT_BARRIER_REQUEST = 20 # Controller/switch message OFPT_BARRIER_REPLY = 21 # Controller/switch message OFPT_QUEUE_GET_CONFIG_REQUEST = 22 # Controller/switch message OFPT_QUEUE_GET_CONFIG_REPLY = 23 # Controller/switch message OFPT_ROLE_REQUEST = 24 # Controller/switch message OFPT_ROLE_REPLY = 25 # Controller/switch message OFPT_GET_ASYNC_REQUEST = 26 # Controller/switch message OFPT_GET_ASYNC_REPLY = 27 # Controller/switch message OFPT_SET_ASYNC = 28 # Controller/switch message OFPT_METER_MOD = 29 # Controller/switch message OFPT_ECHO_TIME_REQUEST = 30 # Symmetric message OFPT_ECHO_TIME_REPLY = 31 # Symmetric message
因为默认交换机与控制器通过openflow1.3通信,所以我们修改这一个文件!!!注意:定义的id需要同ovs修改的源码一致!!!
(二)定义消息实现类ryu/ofproto/ofproto_v1_3_parser.py
@_register_parser @_set_msg_type(ofproto.OFPT_ECHO_REQUEST) class OFPEchoRequest(MsgBase): def __init__(self, datapath, data=None): super(OFPEchoRequest, self).__init__(datapath) self.data = data @classmethod def parser(cls, datapath, version, msg_type, msg_len, xid, buf): msg = super(OFPEchoRequest, cls).parser(datapath, version, msg_type, msg_len, xid, buf) msg.data = msg.buf[ofproto.OFP_HEADER_SIZE:] return msg def _serialize_body(self): if self.data is not None: self.buf += self.data @_register_parser @_set_msg_type(ofproto.OFPT_ECHO_REPLY) class OFPEchoReply(MsgBase): def __init__(self, datapath, data=None): super(OFPEchoReply, self).__init__(datapath) self.data = data @classmethod def parser(cls, datapath, version, msg_type, msg_len, xid, buf): msg = super(OFPEchoReply, cls).parser(datapath, version, msg_type, msg_len, xid, buf) msg.data = msg.buf[ofproto.OFP_HEADER_SIZE:] return msg def _serialize_body(self): assert self.data is not None self.buf += self.data @_register_parser @_set_msg_type(ofproto.OFPT_ECHO_TIME_REQUEST) class OFPEchoTimeRequest(MsgBase): def __init__(self, datapath, data=None): super(OFPEchoTimeRequest, self).__init__(datapath) self.data = data @classmethod def parser(cls, datapath, version, msg_type, msg_len, xid, buf): msg = super(OFPEchoTimeRequest, cls).parser(datapath, version, msg_type, msg_len, xid, buf) msg.data = msg.buf[ofproto.OFP_HEADER_SIZE:] return msg def _serialize_body(self): if self.data is not None: self.buf += self.data @_register_parser @_set_msg_type(ofproto.OFPT_ECHO_TIME_REPLY) class OFPEchoTimeReply(MsgBase): def __init__(self, datapath, data=None): super(OFPEchoTimeReply, self).__init__(datapath) self.data = data @classmethod def parser(cls, datapath, version, msg_type, msg_len, xid, buf): msg = super(OFPEchoTimeReply, cls).parser(datapath, version, msg_type, msg_len, xid, buf) msg.data = msg.buf[ofproto.OFP_HEADER_SIZE:] return msg def _serialize_body(self): assert self.data is not None self.buf += self.data
(三)进行重新编译
python3 setup.py install
注意:是否加sudo,取决于你按照Ryu的位置,以及使用的用户
四:测试echo time报文的使用
(一)控制器应用实现:基于时延探测实现:https://www.cnblogs.com/ssyfj/p/14191693.html
TopoDetect.py
from ryu.base import app_manager from ryu.ofproto import ofproto_v1_3 from ryu.controller import ofp_event from ryu.controller.handler import MAIN_DISPATCHER,CONFIG_DISPATCHER,DEAD_DISPATCHER #只是表示datapath数据路径的状态 from ryu.controller.handler import set_ev_cls from ryu.lib import hub from ryu.lib.packet import packet,ethernet from ryu.topology import event,switches from ryu.topology.api import get_switch,get_link,get_host import threading,time,random DELAY_MONITOR_PERIOD = 5 class TopoDetect(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] def __init__(self,*args,**kwargs): super(TopoDetect,self).__init__(*args,**kwargs) self.topology_api_app = self self.name = "topology" self.link_list = None self.switch_list = None self.host_list = None self.dpid2id = {} self.id2dpid = {} self.dpid2switch = {} self.ip2host = {} self.ip2switch = {} self.net_size = 0 self.net_topo = [] self.net_flag = False self.net_arrived = 0 self.monitor_thread = hub.spawn(self._monitor) def _monitor(self): """ 协程实现伪并发,探测拓扑状态 """ while True: #print("------------------_monitor") self._host_add_handler(None) #主机单独提取处理 self.get_topology(None) hub.sleep(DELAY_MONITOR_PERIOD) #5秒一次 @set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER) def switch_feature_handle(self,ev): """ datapath中有配置消息到达 """ #print("------XXXXXXXXXXX------%d------XXXXXXXXXXX------------switch_feature_handle"%self.net_arrived) #print("----%s----------",ev.msg) msg = ev.msg datapath = msg.datapath ofproto = datapath.ofproto ofp_parser = datapath.ofproto_parser match = ofp_parser.OFPMatch() actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,ofproto.OFPCML_NO_BUFFER)] self.add_flow(datapath=datapath,priority=0,match=match,actions=actions,extra_info="config infomation arrived!!") def add_flow(self,datapath,priority,match,actions,idle_timeout=0,hard_timeout=0,extra_info=None): #print("------------------add_flow:") if extra_info != None: print(extra_info) ofproto = datapath.ofproto ofp_parser = datapath.ofproto_parser inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)] mod = ofp_parser.OFPFlowMod(datapath=datapath,priority=priority, idle_timeout=idle_timeout, hard_timeout=hard_timeout, match=match,instructions=inst) datapath.send_msg(mod); @set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER) def packet_in_handler(self,ev): #print("------------------packet_in_handler") msg = ev.msg datapath = msg.datapath ofproto = datapath.ofproto ofp_parser = datapath.ofproto_parser dpid = datapath.id in_port = msg.match['in_port'] pkt = packet.Packet(msg.data) eth_pkt = pkt.get_protocol(ethernet.ethernet) dst = eth_pkt.dst src = eth_pkt.src #self.logger.info("------------------Controller %s get packet, Mac address from: %s send to: %s , send from datapath: %s,in port is: %s" # ,dpid,src,dst,dpid,in_port) self.get_topology(None) @set_ev_cls([event.EventHostAdd]) def _host_add_handler(self,ev): #主机信息单独处理,不属于网络拓扑 self.host_list = get_host(self.topology_api_app) #3.需要使用pingall,主机通过与边缘交换机连接,才能告诉控制器 #获取主机信息字典ip2host{ipv4:host object} ip2switch{ipv4:dpid} for i,host in enumerate(self.host_list): self.ip2switch["%s"%host.ipv4] = host.port.dpid self.ip2host["%s"%host.ipv4] = host events = [event.EventSwitchEnter, event.EventSwitchLeave, event.EventSwitchReconnected, event.EventPortAdd, event.EventPortDelete, event.EventPortModify, event.EventLinkAdd, event.EventLinkDelete] @set_ev_cls(events) def get_topology(self,ev): #print("------+++++++++++------%d------+++++++++++------------get_topology"%self.net_arrived) self.net_flag = False self.net_topo = [] #print("-----------------get_topology") #获取所有的交换机、链路 self.switch_list = get_switch(self.topology_api_app) #1.只要交换机与控制器联通,就可以获取 self.link_list = get_link(self.topology_api_app) #2.在ryu启动时,加上--observe-links即可用于拓扑发现 #获取交换机字典id2dpid{id:dpid} dpid2switch{dpid:switch object} for i,switch in enumerate(self.switch_list): self.id2dpid[i] = switch.dp.id self.dpid2id[switch.dp.id] = i self.dpid2switch[switch.dp.id] = switch #根据链路信息,开始获取拓扑信息 self.net_size = len(self.id2dpid) #表示网络中交换机个数 for i in range(self.net_size): self.net_topo.append([0]*self.net_size) for link in self.link_list: src_dpid = link.src.dpid src_port = link.src.port_no dst_dpid = link.dst.dpid dst_port = link.dst.port_no try: sid = self.dpid2id[src_dpid] did = self.dpid2id[dst_dpid] except KeyError as e: #print("--------------Error:get KeyError with link infomation(%s)"%e) return self.net_topo[sid][did] = [src_port,0] #注意:这里0表示存在链路,后面可以修改为时延 self.net_topo[did][sid] = [dst_port,0] #注意:修改为列表,不要用元组,元组无法修改,我们后面要修改时延 self.net_flag = True #表示网络拓扑创建成功 def show_topology(self): print("-----------------show_topology") print("----------switch network----------") line_info = " " for i in range(self.net_size): line_info+=" s%-5d "%self.id2dpid[i] print(line_info) for i in range(self.net_size): line_info = "s%d "%self.id2dpid[i] for j in range(self.net_size): if self.net_topo[i][j] == 0: line_info+="%-22d"%0 else: line_info+="(%d,%.12f) "%tuple(self.net_topo[i][j]) print(line_info) print("----------host 2 switch----------") for key,val in self.ip2switch.items(): print("%s---s%d"%(key,val))
DelayDetect.py
from ryu.base import app_manager from ryu.base.app_manager import lookup_service_brick from ryu.ofproto import ofproto_v1_3 from ryu.controller import ofp_event from ryu.controller.handler import MAIN_DISPATCHER,CONFIG_DISPATCHER,DEAD_DISPATCHER,HANDSHAKE_DISPATCHER #只是表示datapath数据路径的状态 from ryu.controller.handler import set_ev_cls from ryu.lib import hub from ryu.lib.packet import packet,ethernet from ryu.topology.switches import Switches from ryu.topology.switches import LLDPPacket import time ECHO_REQUEST_INTERVAL = 0.05 DELAY_DETECTING_PERIOD = 5 class DelayDetect(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] def __init__(self,*args,**kwargs): super(DelayDetect,self).__init__(*args,**kwargs) self.name = "delay" self.topology = lookup_service_brick("topology") #注意:我们使用lookup_service_brick加载模块实例时,对于我们自己定义的app,我们需要在类中定义self.name。 self.switches = lookup_service_brick("switches") #此外,最重要的是:我们启动本模块DelayDetect时,必须同时启动自定义的模块!!! 比如:ryu-manager ./TopoDetect.py ./DelayDetect.py --verbose --observe-links self.dpid2switch = {} #或者直接为{},也可以。下面_state_change_handler也会添加进去 self.dpid2echoDelay = {} self.contrlSendTime = None #用来记录最早echo_time发送时间,辅助矫正交换机时间 self.dpid2localSysTime = {} #用来记录所有交换机的统一时间,用于同步 self.src_sport_dst2Delay = {} #记录LLDP报文测量的时延。实际上可以直接更新,这里单独记录,为了单独展示 {”src_dpid-srt_port-dst_dpid“:delay} self.detector_thread = hub.spawn(self._detector) def _detector(self): """ 协程实现伪并发,探测链路时延 """ while True: if self.topology == None: self.topology = lookup_service_brick("topology") if self.topology.net_flag: self._send_echo_time_request() self.get_link_delay() if self.topology.net_flag: try: self.show_delay() self.topology.show_topology() except Exception as err: print("------------------Detect delay failure!!!------------------") hub.sleep(DELAY_DETECTING_PERIOD) #5秒一次 def get_link_delay(self): """ 更新图中的权值信息 """ #print("--------------get_link_delay-----------") for src_sport_dst in self.src_sport_dst2Delay.keys(): src,sport,dst = tuple(map(eval,src_sport_dst.split("-"))) if src in self.dpid2echoDelay.keys() and dst in self.dpid2echoDelay.keys(): sid,did = self.topology.dpid2id[src],self.topology.dpid2id[dst] if self.topology.net_topo[sid][did] != 0: if self.topology.net_topo[sid][did][0] == sport: s_d_delay = self.src_sport_dst2Delay[src_sport_dst]-(self.dpid2echoDelay[src]+self.dpid2echoDelay[dst])/2; if s_d_delay < 0: #注意:可能出现单向计算时延导致最后小于0,这是不允许的。则不进行更新,使用上一次原始值 continue self.topology.net_topo[sid][did][1] = self.src_sport_dst2Delay[src_sport_dst]-(self.dpid2echoDelay[src]+self.dpid2echoDelay[dst])/2 def _send_echo_time_request(self): """ 发送echo_time报文到datapath """ print("===============_send_echo_time_request====================") for datapath in self.dpid2switch.values(): parser = datapath.ofproto_parser localT = time.time() echo_req = parser.OFPEchoTimeRequest(datapath,data=bytes("%.12f"%localT,encoding="utf8")) #获取当前时间 #print("==========_send_echo_request=========2=====") datapath.send_msg(echo_req) if self.contrlSendTime == None: self.contrlSendTime = localT #重要!不要同时发送echo请求,因为它几乎同时会生成大量echo回复。 #在echo_reply_处理程序中处理echo reply时,会产生大量队列等待延迟。 hub.sleep(ECHO_REQUEST_INTERVAL) self.contrlSendTime = None @set_ev_cls(ofp_event.EventOFPEchoTimeReply,[MAIN_DISPATCHER,CONFIG_DISPATCHER,HANDSHAKE_DISPATCHER]) def echo_time_reply_handler(self,ev): """ 处理echo响应报文,获取控制器到交换机的链路往返时延 Controller | echo latency | `|‘ Switch """ print("==================OFPEchoTimeReply============start===========") now_timestamp = time.time() old_timestamp = eval(ev.msg.data[:-16]) switch_time = int.from_bytes(ev.msg.data[-16:-12],'big') + 1.0*int.from_bytes(ev.msg.data[-8:-4],'big')/1000000 print(now_timestamp,old_timestamp,switch_time); try: echo_delay = now_timestamp - old_timestamp self.dpid2echoDelay[ev.msg.datapath.id] = echo_delay self.dpid2localSysTime[ev.msg.datapath.id] = switch_time - echo_delay/2 self.dpid2localSysTime[ev.msg.datapath.id] -= self.dpid2localSysTime[ev.msg.datapath.id] - self.contrlSendTime #最终同一时刻下,各个交换机的本地时间!!! print("-----------------------------",self.dpid2localSysTime[ev.msg.datapath.id]) except: return print("==================OFPEchoTimeReply===========end============") @set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER) def packet_in_handler(self,ev): #处理到达的LLDP报文,从而获得LLDP时延 """ Controller | /|\ \|/ | Switch----->Switch """ msg = ev.msg try: src_dpid,src_outport = LLDPPacket.lldp_parse(msg.data) #获取两个相邻交换机的源交换机dpid和port_no(与目的交换机相连的端口) dst_dpid = msg.datapath.id #获取目的交换机(第二个),因为来到控制器的消息是由第二个(目的)交换机上传过来的 dst_inport = msg.match['in_port'] if self.switches is None: self.switches = lookup_service_brick("switches") #获取交换机模块实例 #获得key(Port类实例)和data(PortData类实例) for port in self.switches.ports.keys(): #开始获取对应交换机端口的发送时间戳 if src_dpid == port.dpid and src_outport == port.port_no: #匹配key port_data = self.switches.ports[port] #获取满足key条件的values值PortData实例,内部保存了发送LLDP报文时的timestamp信息 timestamp = port_data.timestamp if timestamp: delay = time.time() - timestamp self._save_delay_data(src=src_dpid,dst=dst_dpid,src_port=src_outport,lldpdealy=delay) except: return def _save_delay_data(self,src,dst,src_port,lldpdealy): key = "%s-%s-%s"%(src,src_port,dst) self.src_sport_dst2Delay[key] = lldpdealy @set_ev_cls(ofp_event.EventOFPStateChange,[MAIN_DISPATCHER, DEAD_DISPATCHER]) def _state_change_handler(self, ev): datapath = ev.datapath if ev.state == MAIN_DISPATCHER: if not datapath.id in self.dpid2switch: self.logger.debug('Register datapath: %016x', datapath.id) self.dpid2switch[datapath.id] = datapath elif ev.state == DEAD_DISPATCHER: if datapath.id in self.dpid2switch: self.logger.debug('Unregister datapath: %016x', datapath.id) del self.dpid2switch[datapath.id] if self.topology == None: self.topology = lookup_service_brick("topology") #print("-----------------------_state_change_handler-----------------------") #print(self.topology.show_topology()) #print(self.switches) def show_delay(self): #print("-----------------------show echo delay-----------------------") for key,val in self.dpid2echoDelay.items(): print("s%d----%.12f"%(key,val)) #print("-----------------------show LLDP delay-----------------------") for key,val in self.src_sport_dst2Delay.items(): print("%s----%.12f"%(key,val))
(二)启动控制器、启动拓扑、查看结果
ryu-manager ./TopoDetect.py ./DelayDetect.py --verbose --observe-links
sudo mn --topo=linear,4 --controller=remote
1.查看链路时延信息

2.查看各个交换机之间的同步信息(一共4个、这里截图3个)
可以看出几个交换机在该时刻的时间戳相同:1625310749.8460317!!!
(三)时间同步方案讲解


浙公网安备 33010602011771号