前言
相关文章如下:
1、sdn开发环境的搭建(win7环境)
2、sdn控制器的使用(ubuntu环境搭建、controller使用、mininet的使用)
3、odl源码编译生成发行版控制器
4、md-sal应用程序开发指南
5、应用程序集成到odl控制器
6、yang模型详解
7、md-sal的l2switch源码分析
L2switch架构概述
L2switch作为odl的网络基础模块,依赖于openflowplugin模块的消息,同时也调用它的流表下发功能,整个L2switch模块的架构可以粗略的理解为以下架构图。
其中packethandler、hosttracker、addresstracker模块已在上一篇文章《https://www.sdnlab.com/18285.html》中详细介绍过,此文主要介绍L2switch组件中剩余的其他三个模块: loopremover、l2switch-main、arphandler模块。
ArpHandler模块
Arphandler模块主要是处理openflow交换机以packetin方式上送的arp报文,在54-arphandler.xml配置文件当中有一个设置开关,可以设置arp报文的两种处理方式,一种是is-proactive-flood-mode(主动flood模式),如果设置为true,则new一个ProactiveFloodFlowWriter,而该类实现了OpendaylightInventoryListener以及DataChangeListener接口,针对每一个up的交换机端口安装Flood 流表,如果一个数据包没有匹配其他任意的流表那么这个包就会被广播出去。
如果is-proactive-flood-mode设置为false,则new一个InitialFlowWriter,该类实现了OpendaylightInventoryListener,当发生onNodeUpdated事件之后,每台交换机上安装一个把所有Arp包送到控制器的流(addInitialFlows),同时new一个ArpPacketHandler,用于处理控制器发送来的ARP包,而ArpPacketHandler采用PacketDispatcher发送arp回网络(包括单播sendPacketOut与泛洪floodPacket)。而采用InventoryReader来决定给哪个连接的节点发送数据包。
先分析第一种情况,当is-proactive-flood-mode设置成true时,此时采用的是主动flood模式,在交换机上每个端口都下发一条flood到其他非互联端口的流表,这样arp报文上到交换机时,没有匹配流表,就走flood流表,交换机直接将该报文flood到其他端口。代码入口为ArpHandlerModule .java的createInstance函数。
进入ProactiveFlood模式,new出ProactiveFloodFlowWriter对象,并且注册登记OpendaylightInventoryListener接收器,以及注册监听/Nodes/Node/NodeConnector/ StpStatusAwareNodeConnector数据树的变化。
当交换机连上odl控制器,openflowplugin会发出onNodeUpdated的通知事件,此时进入到flood流表下发流程。
当数据树/Nodes/Node/NodeConnector/StpStatusAwareNodeConnector发生变化,也会进入flood流表下发流程,这里是使用StpStatusDataChangeEventProcessor开一个线程来做。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
private class StpStatusDataChangeEventProcessor implements Runnable { AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> instanceIdentifierDataObjectAsyncDataChangeEvent; @Override public void run() { _logger.debug("In flow refresh thread."); if (threadReschedule) { _logger.debug("Rescheduling thread"); stpStatusDataChangeEventProcessor.schedule(this, flowInstallationDelay, TimeUnit.MILLISECONDS); threadReschedule = false; return; } flowRefreshScheduled = false; installFloodFlows(); } |
从代码来开,某一时刻只能有一个线程来做installFloodFlows流程,在installFloodFlows当中,先在datastore当中找出已经发现的端口(这些端口信息是由openflowplugin写入的),过滤掉互连端口和local端口,最终调用salFlowService接口下发flood流表,此时下发的flood流表并未保存在数据库当中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
private Future<RpcResult<AddFlowOutput>> writeFlowToSwitch(NodeId nodeId, Flow flow) { InstanceIdentifier<Node> nodeInstanceId = InstanceIdentifier.<Nodes>builder(Nodes.class) .<Node, NodeKey>child(Node.class, new NodeKey(nodeId)).build(); InstanceIdentifier<Table> tableInstanceId = nodeInstanceId.<FlowCapableNode>augmentation(FlowCapableNode.class) .<Table, TableKey>child(Table.class, new TableKey(flowTableId)); InstanceIdentifier<Flow> flowPath = tableInstanceId .<Flow, FlowKey>child(Flow.class, new FlowKey(new FlowId(String.valueOf(flowIdInc.getAndIncrement())))); final AddFlowInputBuilder builder = new AddFlowInputBuilder(flow) .setNode(new NodeRef(nodeInstanceId)) .setFlowTable(new FlowTableRef(tableInstanceId)) .setFlowRef(new FlowRef(flowPath)) .setTransactionUri(new Uri(flow.getId().getValue())); return salFlowService.addFlow(builder.build()); } |
再看Reactive模式,在ArpHandlerModule.java的createInstance函数new出InitialFlowWriter对象,该对象注册了OpendaylightInventoryListener的监听器,等待onNodeUpdated事件,在InitialFlowWriter当中只是下发流表将arp报文全部上送控制器,让控制器来统一处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
private Flow createArpToControllerFlow(Short tableId, int priority) { // start building flow FlowBuilder arpFlow = new FlowBuilder() // .setTableId(tableId) // .setFlowName("arptocntrl"); // use its own hash code for id. arpFlow.setId(new FlowId(Long.toString(arpFlow.hashCode()))); EthernetMatchBuilder ethernetMatchBuilder = new EthernetMatchBuilder() .setEthernetType(new EthernetTypeBuilder() .setType(new EtherType(Long.valueOf(KnownEtherType.Arp.getIntValue()))).build()); Match match = new MatchBuilder() .setEthernetMatch(ethernetMatchBuilder.build()) .build(); List<Action> actions = new ArrayList<Action>(); actions.add(getSendToControllerAction()); if(isHybridMode) { actions.add(getNormalAction()); } // Create an Apply Action ApplyActions applyActions = new ApplyActionsBuilder().setAction(actions) .build(); // Wrap our Apply Action in an Instruction Instruction applyActionsInstruction = new InstructionBuilder() // .setOrder(0) .setInstruction(new ApplyActionsCaseBuilder()// .setApplyActions(applyActions) // .build()) // .build(); // Put our Instruction in a list of Instructions arpFlow .setMatch(match) // .setInstructions(new InstructionsBuilder() // .setInstruction(ImmutableList.of(applyActionsInstruction)) // .build()) // .setPriority(priority) // .setBufferId(OFConstants.OFP_NO_BUFFER) // .setHardTimeout(flowHardTimeout) // .setIdleTimeout(flowIdleTimeout) // .setCookie(new FlowCookie(BigInteger.valueOf(flowCookieInc.getAndIncrement()))) .setFlags(new FlowModFlags(false, false, false, false, false)); return arpFlow.build(); } private Action getSendToControllerAction() { Action sendToController = new ActionBuilder() .setOrder(0) .setKey(new ActionKey(0)) .setAction(new OutputActionCaseBuilder() .setOutputAction(new OutputActionBuilder() .setMaxLength(0xffff) .setOutputNodeConnector(new Uri(OutputPortValues.CONTROLLER.toString())) .build()) .build()) .build(); return sendToController; } |
有了流表之后,控制器会收到arp类型的packetin报文,所以还需要针对这些报文做arp响应,相当于由odl控制器来代理arp响应。
在ArpHandlerModule.java的createInstance函数当中,还new出InventoryReader和ArpPacketHandler,而在ArpPacketHandler当中是有监听ArpPacketListener消息通过的,该消息是由packethandler发出的,一旦有arp报文packetin进来,就会触发onArpPacketReceived函数的调用,进而进入dispatchPacket处理流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public void dispatchPacket(byte[] payload, NodeConnectorRef ingress, MacAddress srcMac, MacAddress destMac) { inventoryReader.readInventory(); String nodeId = ingress.getValue().firstIdentifierOf(Node.class).firstKeyOf(Node.class, NodeKey.class).getId().getValue(); NodeConnectorRef srcConnectorRef = inventoryReader.getControllerSwitchConnectors().get(nodeId); if(srcConnectorRef == null) { refreshInventoryReader(); srcConnectorRef = inventoryReader.getControllerSwitchConnectors().get(nodeId); } NodeConnectorRef destNodeConnector = inventoryReader.getNodeConnector(ingress.getValue().firstIdentifierOf(Node.class), destMac); if(srcConnectorRef != null) { if(destNodeConnector != null) { sendPacketOut(payload, srcConnectorRef, destNodeConnector); } else { floodPacket(nodeId, payload, ingress, srcConnectorRef); } } else { _logger.info("Cannot send packet out or flood as controller node connector is not available for node {}.", nodeId); } } |
在dispatchPacket当中,会根据inventoryReader当中保存的nodeId---List键值对查找(inventoryReader.readInventory();中做了保存上述键值对),当查到目标mac曾经在nodeconnector 上出现的destNodeConnector,则采用单播方式将该arp往该端口发送packetout报文 ,sendPacketOut(payload, srcConnectorRef, destNodeConnector);函数就是做这个的。当未找到目标mac的destNodeConnector,也就是说,该mac以前没有在哪一个nodeconnector上出现过,则采用广播方式进行packetout,对应于floodPacket(nodeId, payload, ingress, srcConnectorRef);函数。
这就是arp带答的所有流程,其中需要注意的是arp带答只有在配置文件当中设置is-proactive-flood-mode为false,当然这种方式的优点是减少openflow交换机的流量(因为有广播),缺点就是增加了控制器的处理报文压力。
Loopremover模块
Loopremover模块主要用于环路的消除,比如当两台交换机当中有两条或者两条以上的链路时,为了防止环路的出现,需要借助STP生成树来破环,在配置文件当中52-loopremover.xml定义了是否需要安装LLDP流表,默认是需要安装LLDP流表,因此new 一个InitialFlowWriter对象,而该对象实现了OpendaylightInventoryListener,也就是会监听OpendaylightInventoryListener的notification,在onNodeUpdated事件当中提交一个InitialFlowWriterProcessor到线程池,此时就会执行addInitialFlows,此函数通过调用openflowplugin实现添加lldp流表。
于此同时,还会new出TopologyLinkDataChangeHandler对象,一直监听OPERATIONAL数据库上的/NetworkTopology/Topology/Link数据节点,当Link发生变化时(比方说新建了一条link),就会开一个线程TopologyDataChangeEventProcessor来做stp生成树计算,在这其中先是将link加入networkGraph(networkGraphService.addLinks(links);函数就是做这个),然后调用updateNodeConnectorStatus(readWriteTransaction);函数,在该函数当中更新nodeconnector生成树状态Forwarding还是Discarding。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private void updateNodeConnectorStatus(ReadWriteTransaction readWriteTransaction) { List<Link> allLinks = networkGraphService.getAllLinks(); if(allLinks == null || allLinks.isEmpty()) { return; } List<Link> mstLinks = networkGraphService.getLinksInMst(); for(Link link : allLinks) { if(mstLinks != null && !mstLinks.isEmpty() && mstLinks.contains(link)) { updateNodeConnector(readWriteTransaction, getSourceNodeConnectorRef(link), StpStatus.Forwarding); updateNodeConnector(readWriteTransaction, getDestNodeConnectorRef(link), StpStatus.Forwarding); } else { updateNodeConnector(readWriteTransaction, getSourceNodeConnectorRef(link), StpStatus.Discarding); updateNodeConnector(readWriteTransaction, getDestNodeConnectorRef(link), StpStatus.Discarding); } } } |
opologyLinkDataChangeHandler.java – 监听拓扑上的数据改变事件,当发生改变事件时,等待graph-refresh-delay秒后,通告NetworkGraphImpl更新。同时把在数据拓扑里的每条链路的STP的状态置成“转发”或者“丢弃”。
转发Forwarding -- 转发状态的链路可以有flood
丢弃 Discarding-- 丢弃状态的链路不可以有flood
NetworkGraphImpl.java --创建一个无环路的网络图。
l2switch-main模块
l2switch-main主要负责安装流表,在58-l2switchmain.xml中配置is-install-dropall-flow是否安装dropall流表,默认为真,则new 一个InitialFlowWriter,此InitialFlowWriter实现了OpendaylightInventoryListener来监听onNodeUpdated事件,一旦有此事件则安装dropall 流表(addInitialFlows),并且58-l2switchmain.xml配置了is-learning-only-mode为false(意味着二层交换机会响应网络流量并且安装优化网络流量的流,比如MAC-to-MAC流),因此会创建ReactiveFlowWriter,此ReactiveFlowWriter会安装基于源MAC和目的MAC的规则匹配的流表,一旦收到arp报文,就会安装mac-mac流表,这样便于mac学习。
在L2SwitchMainModule.java当中的createInstance函数new出ReactiveFlowWriter实现监听ArpPacketListener,一旦有arp上送控制器,则触发onArpPacketReceived函数,然后进入writeFlows函数,最后调用的是addBidirectionalMacToMacFlows函数,该函数就是将destMac-----sourceMac的相互通信流表下发到交换机。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public void addMacToMacFlow(MacAddress sourceMac, MacAddress destMac, NodeConnectorRef destNodeConnectorRef) { Preconditions.checkNotNull(destMac, "Destination mac address should not be null."); Preconditions.checkNotNull(destNodeConnectorRef, "Destination port should not be null."); // do not add flow if both macs are same. if(sourceMac != null && destMac.equals(sourceMac)) { _logger.info("In addMacToMacFlow: No flows added. Source and Destination mac are same."); return; } // get flow table key TableKey flowTableKey = new TableKey((short) flowTableId); //build a flow path based on node connector to program flow InstanceIdentifier<Flow> flowPath = buildFlowPath(destNodeConnectorRef, flowTableKey); // build a flow that target given mac id Flow flowBody = createMacToMacFlow(flowTableKey.getId(), flowPriority, sourceMac, destMac, destNodeConnectorRef); // commit the flow in config data writeFlowToConfigData(flowPath, flowBody); } |
作者简介:鸿哥,硕士研究生,国内某通信设备公司软件研发工程师,主要从事云计算、SDN技术开发