源码解读ODL与OpenFlow交换机建立过程

编者按:OpenDaylight两大技术特色:1.采用了OSGi框架;2.引入了SAL,而今天我们主要介绍服务抽象层(SAL)适配的南向协议之一OF协议模块。

OF协议模块启动与消息处理

OSGi框架实例化controller类,初始化其变量包括事件队列、消息监听及交换机监听器集合,然后创建事件处理线程,在创建I/O处理线程。controllerIOThread监听底层交换机连接请求,建立连接则监听消息,当收到消息后判断消息类型再调用相应方法处理:

while (running) {
   try {
       // wait for an incoming connection
       // check interface state every 5sec
       selector.select(5000);
       Iterator<SelectionKey> selectedKeys = selector
                    .selectedKeys().iterator();
       netInterfaceUp = isNetInterfaceUp(netInterfaceUp);
       while (selectedKeys.hasNext()) {
           SelectionKey skey =selectedKeys.next();
           selectedKeys.remove();
           //selector选择器接收连接请求
           if (skey.isValid() &&skey.isAcceptable()) {
                 ((Controller)listener).handleNewConnection(
                               selector,serverSelectionKey);
           }
       }
   } catch (Exception e) {
         continue;
   }
}

handleNewConnection从事件队列中获取处理事件,如果是新增交换机事件,则换存该交换机并通知监听器交换机信息改变;如果是删除或异常事件,则断开I/O连接;如果是OFMessage消息,则通知SwitchHandler来处理该消息。

I/O处理线程中的消息处理

TCP 连接建立后,交换机和控制器就会互相发送 hello 报文(SwitchHandler处理函数handleMessages处理的第一个消息类型)。Hello 报文是使用 OpenFlow 协议的一个对称的数据包。Hello 报文中唯一的内容 是 OpenFlow 报文头中的“类型值=0”。

for (OFMessage msg : msgs) {
   logger.trace("Message received: {}", msg);
   this.lastMsgReceivedTimeStamp = System.currentTimeMillis();
   OFType type = msg.getType();
   switch (type) {
   case HELLO:
       sendFeaturesRequest();
       break;
   case ECHO_REQUEST:
       OFEchoReply echoReply = (OFEchoReply) factory.getMessage(OFType.ECHO_REPLY);
       byte []payload = ((OFEchoRequest)msg).getPayload();
       if (payload != null && payload.length != 0 ) {
            // the response must have the samepayload as the request
            echoReply.setPayload(payload);
            echoReply.setLength((short)(echoReply.getLength() + payload.length) );
       }
       // respond immediately
       asyncSendNow(echoReply, msg.getXid());
       // send features request if not sent yet
       sendFeaturesRequest();
       break;
   case ECHO_REPLY:
       this.probeSent = false;
       break;
   case FEATURES_REPLY:
       processFeaturesReply((OFFeaturesReply) msg);
       break;
   case GET_CONFIG_REPLY:
       // make sure that the switch can send the whole packet to the
       // controller
       if (((OFGetConfigReply) msg).getMissSendLength() == (short) 0xffff) {
            this.state = SwitchState.OPERATIONAL;
       }
       break;
   case BARRIER_REPLY:
       processBarrierReply((OFBarrierReply) msg);
       break;
   case ERROR:
       processErrorReply((OFError) msg);
       break;
   case PORT_STATUS:
       processPortStatusMsg((OFPortStatus) msg);
       break;
   case STATS_REPLY:
       processStatsReply((OFStatisticsReply) msg);
       break;
   case PACKET_IN:
       break;
   default:
       break;
   } // end of switch
   if (isOperational()) {
       ((Controller) core).takeSwitchEventMsg(thisISwitch, msg);
   }
} // end of for

1.首先读取到Hello消息后,发送请求报文(Feature Request)。这是控制器发向交换机的一条Openflow消息,目的是为了获取交换机性能,功能以及一些系统参数。该报文中OpenFlow数据头“类型值=5”。

2.Echo请求(Echo request)和Echo 响应(Echo reply)属于OpenFlow中的对称型报文,他们通常作为在OpenFlow交换机和OpenFlow控制器之间保持连接的消息(Keep-alive)来使用。通常echo请求使用OpenFlow数头“类型值=2”,echo响应使用OpenFlow数据头“类型值=3”。不同各厂商提供的不同实现中,echo请求和响应报文中携带的信息也会有所不同。如果是Echo request消息,则发送Echo reply响应和Feature Request请求消息;如果是Echo reply消息,则标识交换机正常连接。

3. 控制器和交换机之间的连接经过 TCP 建立、Hello报文、功能请求与响应环节后建立。这些连接的存在是 Packet-In 事件发生的前提。交换机怎样触发 Packet-In 事件。当 OpenFlow 交换机收到数据包后,如果流表中与数据包没有任何匹配条目,这时候 Packet-In 事件就被触发了,交换机会将这个数据包封闭到Openflow 协议报文中发送至控制器。 opendaylight中Packet-In 事件是交给了IMessageListener监听器的实现类来处理的,比如DataPacketMuxDemux类,Packet-In 域提供数据包的信息,这些信息都是在那些特定的 Packet-In 被封装的。得到 Packet-In 信息后,控制器根据需要对原始数据包做出处理。

case SWITCH_MESSAGE:
OFMessage msg = ev.getMsg();
if (msg != null) {
IMessageListener listener =messageListeners
.get(msg.getType());
if (listener != null) {
listener.receive(sw, msg);
}
}
break;

4.控制器要发送数据包至交换机时,就会触发 Packet-Out 事件将数据包发送至交换机。这一事件的触发可以看做是控制器主动通知交换机发送一些数据报文的操作。通常,当控制器想对交换机的某一端口进行操作时,就会使用 Packet-Out 报文。

5. 交换机端口状态发生改变(端口 up/down、增添或移除)或者端口配置标志发生改变时,会触发端口状态(Port Status)消息事件的发生。这一消息由 OpenFlow 交换机触发,端口状态(Port Status)消息由 OpenFlow 交换机发往控制器,用于通告交换机端口状态的改变。

6. 当控制器想要改变交换机的配置时,就会发送一个设置配置信息,这信息是控制器—>交换机信息。设置配置信息(Set Configuration)包括:

· 交换机配置标志

· Miss发送长度,表示重新配置后发送至控制器的流的最大8位字节,默认值128

7.获取配置请求的报文(Get Config)中没有内容(只包含 OpenFlow 常规数据头);OpenFlow 交换机通过“TypeCode = 7”识别这个报文。 交换机发出配置答复消息作为反馈,该消息包含了交换机的所有配置信息,配置答复消息包括:

· 交换机配置标志

· Miss发送长度

· 表示发送至控制器的新的流的最大8位字节,默认值128

8.当控制器需要增添、修改或删除交换机中流表的时候,触发 修改流(Flow-Modification)事件。

9.控制器(管理员)试图修改端口配置标志—“admin down,” “no STP,” “no receive,” “no receive STP,” “no flood,” “no FWD,” “no packet-in” 的时候,会触发修改端口(Port-Modify)事件。

10.当控制器试图从交换机处获得不同类型的统计数据信息时,统计(Stats)请求和响应事件被触发。

11.当控制器试图了解其分配给 OpenFlow 交换机的任务是否完成或将在何时完成的时候,Barrier 请求和响应事件将被触发。Barrier 请求消息用OpenFlow 数据头消息“类型值=19”表示。 收到请求消息的交换机,在完成控制器分配的任务后,会发送响应消息至控制器。障碍响应消息用 OpenFlow 数据头消息“类型值=20”表示,并附有 Barrier 请求消息的交换标识。

12.当控制器试图问询 OpenFlow 交换机端口的队列配置时候,触发队列获取配置(Queue Get Configuration)请求和响应事件。队列请求包括所请求队列信息的端口号。队列配置响应消息包括端口号和该端口的队列配置信息。

13.当控制器发送的数据包不能被读出或支持,或者交换机不能执行的时候,就产生了错误事件。所以任何发送至交换机的控制数据包都可能触发错误事件。

链路发现(LLDP)

OF协议模块还提供链路发现服务,它为拓扑模块提供链路数据支持(topomanager实现IListenTopoUpdates的edgeUpdate,它是有TopologyServiceShim中的线程TopologyNotify调用,此线程是个阻塞线程,notifyEdge方法使得notifyQ的成员的增加会触发此线程,而notifyEdge是由DiscoveryService的updateEdge调用的,依次addEdge,processDiscoveryPacket,receiveDataPacketDataPacketMuxDemux通过receive调用receiveDataPacketreceive是由Controller监听SWITCH_MESSAGE消息时调用的)。

public PacketResultreceiveDataPacket(RawPacket inPkt) {
   if (inPkt == null) {
       logger.debug("Ignoring null packet");
       return PacketResult.IGNORED;
   }
   byte[] data = inPkt.getPacketData();
   if (data.length <= 0) {
       logger.trace("Ignoring zero length packet");
       return PacketResult.IGNORED;
   }
   if (!inPkt.getEncap().equals(LinkEncap.ETHERNET)) {
       logger.trace("Ignoring non ethernet packet");
       return PacketResult.IGNORED;
   }
   NodeConnector nodeConnector = inPkt.getIncomingNodeConnector();
   if (((Short)nodeConnector.getID()).equals(NodeConnector.SPECIALNODECONNECTORID)) {
       logger.trace("Ignoring ethernet packet received on special port:"
                +inPkt.getIncomingNodeConnector().toString());
       return PacketResult.IGNORED;
    }
   if (!connectionOutService.isLocal(nodeConnector.getNode())) {
       logger.debug("Discoery packets will not be processed from {} in anon-master controller", nodeConnector.toString());
       return PacketResult.IGNORED;
   }
   Ethernet ethPkt = new Ethernet();
   try {
       ethPkt.deserialize(data, 0, data.length * NetUtils.NumBitsInAByte);
   } catch (Exception e) {
       logger.warn("Failed to decode LLDP packet from {}: {}",inPkt.getIncomingNodeConnector(), e);
       return PacketResult.IGNORED;
   }
   if (ethPkt.getPayload() instanceof LLDP) {
       NodeConnector dst = inPkt.getIncomingNodeConnector();
       if (isEnabled(dst)) {
            if (!processDiscoveryPacket(dst,ethPkt)) {
                // Snoop the discovery pkt ifnot generated from us
                snoopDiscoveryPacket(dst,ethPkt);
            }
            return PacketResult.CONSUME;
       }
   }
   return PacketResult.IGNORED;
}

控制器在执行链路发现过程时,会首先通过一个packet-out消息向所有与之连接的交换机发送LLDP数据包,该消息命令交换机将LLDP数据包发送给所有端口,一旦交换机接收到packet-out消息,他就会把LLDP数据包通过其所有的端口发送给与之连接的设备,如果其邻居交换机是一台OpenFlow交换机,那么该交换机将自行相应的流表查找操作。因为交换机中并没有专门的流表项用于处理LLDP消息,所有它将通过一个packet-in消息将数据包发送给控制器。而控制器在收到packet-in消息后,会对数据包进行分析并在其保存的链路发现表中创建2台交换机之间的链接记录。网络中其他交换机也都采用相同的方式向控制器发送packet-in消息,因此控制器就能够创建完整的网络拓扑视图,基于这样的视图,控制器可以根据业务应用的流量需求,为每台交换机推送下发不同的流表项。链路发现可分为两个部分:

1.LLDP数据分组监听解析,通过LLDP分组中LTV信息获得节点链路状态,保存到本地同时通知拓扑模块变化及时更新。

2.自动链路探测部分,通过控制器的交换机连接信息获取探测节点,发送LLDP探测分组,再由监听部分获取探测分组更新链路。

原文发布于微信公众号 - SDNLAB(SDNLAB)

原文发表时间:2015-06-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏算法修养

Nginx 日志 worker_connections are not enough while connecting to upstream

记一次,排查错误所遇到的问题,和学习到的内容。 上周五,刚上线的项目出现了503 ,查看日志发现如下内容: System.Exception: Request ...

547100
来自专栏Golang语言社区

TCP、UDP、IP 协议分析

互连网早期的时候,主机间的互连使用的是NCP协议。这种协议本身有很多缺陷,如:不能互连不同的主机,不能互连不同的操作系统,没有纠错功能。为了改善这种缺点,大牛弄...

46630
来自专栏开发与安全

linux网络编程之TCP/IP基础(三):IP数据报格式和IP地址路由

一、IP数据报格式 IP数据报格式如下: ? 注:需要注意的是网络数据包以大端字节序传输,当然头部也得是大端字节序,也就是说: The most signifi...

30270
来自专栏猿人谷

总结---6

      1.OSI参考模型有多少层?分别是哪几层?(不建议死记硬背,可以看看我在系列文章第一篇里的描述,效果比较好,不会因为紧张而答不出来)        ...

21160
来自专栏landv

烽火2640路由器命令行手册-04-网络协议配置命令

配置静态ARP映射,静态ARP映射会永久保留在ARP缓存中。如果要删除配置的静态ARP映射的话,使用 no arp 命令。

13820
来自专栏Android中高级开发

Android开发之漫漫长途 XIX——HTTP

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索...

12920
来自专栏QQ会员技术团队的专栏

告知你不为人知的 UDP:连接性和负载均衡

说起网络 socket,大家自然会想到 TCP ,用的最多也是 TCP,UDP 在大家的印象中是作为 TCP 的补充而存在,是无连接、不可靠、无序、无流量控制的...

7.1K110
来自专栏小二的折腾日记

一文总结计算机网络

它只有四层,相当于五层协议中数据链路层和物理层合并为网络接口层。 现在的 TCP/IP 体系结构不严格遵循 OSI 分层概念,应用层可能会直接使用 IP 层或者...

10820
来自专栏木木玲

Java程序员必须掌握的网站知识 —— TCP

31820
来自专栏安富莱嵌入式技术分享

【RL-TCPnet网络教程】第12章 TCP传输控制协议基础知识

本章节为大家讲解TCP(Transmission Control Protocol,传输控制协议),通过本章节的学习,需要大家对TCP有个基本的认识,方便后面章...

13430

扫码关注云+社区

领取腾讯云代金券