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))
View Code

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!!!

(三)时间同步方案讲解

 

posted @ 2021-10-28 11:54  山上有风景  阅读(525)  评论(0)    收藏  举报