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

时间:2022-05-07
本文章向大家介绍源码解读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探测分组,再由监听部分获取探测分组更新链路。