源码解读ODL的MAC地址学习(一)

2018-03-30 18:15:19 浏览数 (1)

1 简介

我们知道同一子网中主机之间互相传送信息需要用到MAC地址,而我们第一次发送信息的时候只有IP地址而没有MAC地址,所以我们就要进行MAC地址自学习。

交换机中的MAC地址自学习是指在交换机中有一个MAC地址与交换机每个接口的对应表,每当有数据包经过交换机转发的时候,如果它的表中没有这个MAC地址的对应关系就会往所有端口转发数据包,当目标机从某个端口返回信息的时候它就知道了这个MAC地址对应的哪个端口,于是会把这个对应关系加入表中,这个过程就是交换机的MAC地址自学习。

2 ODL中MAC地址原理

前文已经介绍了MAC地址自学的一般过程,在ODL中,我们监控ARP数据包来学习MAC地址,将ARP数据包通过Packet-In消息发给OpenFlow控制器,那么就可以在不过度加重OpenFlow控制器负载的情况下,实现MAC地址学习。

ODL MAC地址学习的源码主要集中在l2switch和OpenFlowPlugin模块:

红框标注的内容就是l2switch项目中和MAC地址学习有关的,未标注的部分是链路和节点发现;OpenFlow项目中涉及到MAC地址学习的不多,只有一个定义Packet-In和Packet-Out的YANG文件:packet-processing.yang。下面我们会从ARP处理流程出发,具体分析源码,由于篇幅有限,源码中不是很重要的部分没有贴出来,要看完整的源码请下面网址下载: https://github.com/opendaylight/l2switch/tree/stable/beryllium https://github.com/opendaylight/openflowplugin/tree/stable/beryllium 同时重要代码的具体分析直接在注释里面。

2.1 ARP处理流程

我们设想一种场景,如图所示:

PC A准备向IPv4地址为10.0.0.2的目标地址发送数据包。此时,假设PC A的ARP表中不存在10.0.0.2这一表项,那么PC A不知道10.0.0.2这一IPv4地址对应的以太网地址,所以准备使用ARP解决该问题。

PC A通过L2广播发送ARP请求,PC B接收该请求后发送ARP响应。PC A接收到ARP响应后将PC B的以太网地址存储到ARP表中。这样,PC A就可以向10.0.0.2这一IPv4地址发送数据包。

以上就是ARP的处理流程,下面我们对流程中的细节进行相应的阐述。

2.2 ARP请求(Packet-In)

首先,PC A发送ARP请求,由OpenFlow交换机接收到该请求,OpenFlow交换机接收到PC A发来的ARP请求之后,检索流表,此时的流表是通过Proactive模式加载的流表,其中的流表项的匹配字段是入端口,行动是上报控制器,这个流表项的具体实现代码存在于l2switch项目中arphandler目录下的ProactiveFloodFlowWriter.java:

代码语言:javascript复制
Match match = new MatchBuilder()    //匹配端口
          .setInPort(nc.getId())
          .build();
设置匹配字段为入端口。
if(outerSaNodeConnector == null) {
  outputActions.add(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());
}

设置OutPut的action为controller,即上报给控制器。

流表项匹配成功之后,OpenFlow交换机执行通过Packet-In消息向OpenFlow控制器转发ARP请求帧的行动。此时ODL控制器就要做两个处理,一是接受Packet-In消息,而是对Packet-In消息进行解码。

首先看一下Packet-In消息的接收。Packet-In消息的定义YANG文件存在于OpenFlowPlugin项目中的packet-processing.yang:

代码语言:javascript复制
notification packet-received {
        description "Delivery of incoming packet wrapped in openflow structure.";
    	leaf connection-cookie {
            type connection-cookie;
        }
        
        leaf flow-cookie {
            type flow-type:flow-cookie;
        }
        
        leaf table-id {
        	type table-type:table-id;
        }
        
        leaf packet-in-reason {
	    	type identityref {
	    		base packet-in-reason;
	    	}
	   	}
        
        container match {
        		uses match-type:match;
        }
        
        uses raw-packet;
}

将收到的Packet-In的数据包定义成notification,那么编译之后就会出现PacketProcessingListener.java接口类,为了订阅这个消息来对收到的数据包进行处理,在代码中就要实现这个接口类(implements PacketProcessingListener)。

下面我们来分析对Packet-In数据包的解码,代码的实现逻辑是:先将packet-received数据包解码成ethernet-packet数据包,再将ethernet-packet数据包解码成相应的arp-packet、ipv4-packet和ipv6-packet数据包。具体的实现代码存在于l2switch项目中的packethandler这个目录下。首先我们分析YANG文件:

首先是packet.yang:

代码语言:javascript复制
grouping packet-chain-grp {
    list packet-chain {
      choice packet {
         case raw-packet {
           uses raw-packet-fields;
         }
      }
    }
  }

这个YANG文件定义了基本数据包,便于具体数据包的YANG文件扩展,该YANG文件中比较重要的就是这个grouping,其他YANG文件扩展的就是packet-chain。同时这个YANG文件还有一点需要注意:这个YANG文件的文件名是packet.yang,但是它的module名是base-packet,所以import的时候用的是base-packet,而不是packet。

下面我们分析ethernet-packet.yang,至于arp-packet.yang,ipv4-packet.yang和ipv6-packet.yang文件和这个文件差不多,就不多做分析了。

代码语言:javascript复制
ethernet-packet.yang:
import base-packet {
  prefix bpacket;
  revision-date 2014-05-28;
}
……
notification ethernet-packet-received {
  uses bpacket:packet-chain-grp {
    augment "packet-chain/packet" {
      case ethernet-packet {
        uses ethernet-packet-fields;
      }
    }
  }
  uses bpacket:packet-payload;
}

该YANG文件中最重要的就是这个notification,从上述代码中可以发现它扩展了packet.yang中packet-chain中的packet,添加了一个情况:ethernet-packet。

分析完了YANG文件,下面我们来看下具体的解码java代码,前面提到过,解码分为两个步骤,先将packet-received数据包解码成ethernet-packet数据包,再将ethernet-packet数据包解码成相应的arp-packet、ipv4-packet和ipv6-packet数据包。下面我们分析三个java文件: AbstarctPacketDecoder.java:

代码语言:javascript复制
public abstract class AbstractPacketDecoder<ConsumedPacketNotification, ProducedPacketNotification extends Notification>
    implements  NotificationProviderService.NotificationInterestListener , AutoCloseable {  //NotificationInterestListener是一个监听者可能感兴趣的notification
 //ConsumedPacketNotification作为解码的notification
 //ProducedPacketNotification作为解码后的notification被发出去
 
 /**
   * Keeps track of listeners registered for the notification that a decoder produces.
   * @param aClass
   */ 
  @Override
  public synchronized void onNotificationSubscribtion(Class<? extends Notification> aClass) {
    if (aClass !=null && aClass.equals(producedPacketNotificationType)) {
      if(listenerRegistration == null) {
        NotificationListener notificationListener = getConsumedNotificationListener();
        listenerRegistration = notificationProviderService.registerNotificationListener(notificationListener);//注册监听器
      }
    }
  }
 
 /**
   * Every extended decoder should call this method on a receipt of a input packet notification.
   * This method would make sure it decodes only when necessary and publishes corresponding event
   * on successful decoding.
   */
  public void decodeAndPublish(final ConsumedPacketNotification consumedPacketNotification) {
    decodeAndPublishExecutor.submit(new Runnable() {
        @Override
        public void run() {
            ProducedPacketNotification packetNotification=null;
            if(consumedPacketNotification!= null && canDecode(consumedPacketNotification)) {
              packetNotification = decode(consumedPacketNotification);  //将ConsumedPacketNotification解码,解码后的类型是ProducedPacketNotification
            }
            if(packetNotification != null) {
              notificationProviderService.publish(packetNotification); ////将ConsumedPacketNotification解码后的再发出去 
            }
        }
    });
  }
public abstract ProducedPacketNotification decode(ConsumedPacketNotification consumedPacketNotification);  //定义一个抽象的解码函数,便于其他java类覆写
}

这个文件定义了一个抽象java类,用于实现注册监听器、将ConsumedPacketNotification消息解码成ProducedPacketNotification类型的消息,并把这个消息发出去。这个java类中并没有实现具体的解码过程,只定义了一个抽象的decode()方法,便于其他的java类覆写。 EthernetDecoder.java:

代码语言:javascript复制
//实现PacketProcessingListener接口,为了监听Packet-ProcessingYANG中的PacketReceived这个notification
public class EthernetDecoder extends AbstractPacketDecoder<PacketReceived, EthernetPacketReceived> implements PacketProcessingListener {
  ......
 
  public EthernetDecoder(NotificationProviderService notificationProviderService) {
    super(EthernetPacketReceived.class, notificationProviderService);
  }
  //重写AbstarctPacketDecoder类中的decodeAndPublish()函数
  //即将RawPacket解码成EthernetPacket类型的消息并发出去
  @Override
  public void onPacketReceived(PacketReceived packetReceived) {
    decodeAndPublish(packetReceived);  //重写AbstarctPacketDecoder类中的decodeAndPublish()函数,即将RawPacket解码成EthernetPacket类型的消息
  }
 
  //将PacketReceived到的RawPacket解码成EthernetPacket
  @Override
  public EthernetPacketReceived decode(PacketReceived packetReceived) {
    byte[] data = packetReceived.getPayload();
    EthernetPacketReceivedBuilder builder = new EthernetPacketReceivedBuilder();
 
    //创建 RawPacket实例
    RawPacketBuilder rpb = new RawPacketBuilder()
        .setIngress(packetReceived.getIngress())
        .setConnectionCookie(packetReceived.getConnectionCookie())
        .setFlowCookie(packetReceived.getFlowCookie())
        .setTableId(packetReceived.getTableId())
        .setPacketInReason(packetReceived.getPacketInReason())
        .setPayloadOffset(0)
        .setPayloadLength(data.length);
    if(packetReceived.getMatch() != null ){
        rpb.setMatch(new MatchBuilder(packetReceived.getMatch()).build());
    }
    RawPacket rp = rpb.build();
    ArrayList<PacketChain> packetChain = new ArrayList<PacketChain>();
    packetChain.add(new PacketChainBuilder()
      .setPacket(rp)
      .build());
    //将RawPacket解码成EthernetPacket
    try {
      EthernetPacketBuilder epBuilder = new EthernetPacketBuilder();//定义一个EthernetPacket实例
 
      epBuilder.setDestinationMac(new MacAddress(HexEncode.bytesToHexStringFormat(BitBufferHelper.getBits(data, 0, 48))));//得到目的MAC地址
      epBuilder.setSourceMac(new MacAddress(HexEncode.bytesToHexStringFormat(BitBufferHelper.getBits(data, 48, 48))));//得到源MAC地址
 
      // 将可选字段802.1Q反序列化
      Integer nextField = BitBufferHelper.getInt(BitBufferHelper.getBits(data, 96, 16));
      int extraHeaderBits = 0;
      ArrayList<Header8021q> headerList = new ArrayList<Header8021q>();
      while(nextField.equals(ETHERTYPE_8021Q) || nextField.equals(ETHERTYPE_QINQ)) {
        Header8021qBuilder hBuilder = new Header8021qBuilder();
        hBuilder.setTPID(Header8021qType.forValue(nextField));
 
        // Read 2 more bytes for priority (3bits), drop eligible (1bit), vlan-id (12bits)
        byte[] vlanBytes = BitBufferHelper.getBits(data, 112   extraHeaderBits, 16);
 
        // Remove the sign & right-shift to get the priority code
        hBuilder.setPriorityCode((short) ((vlanBytes[0] & 0xff) >> 5));
 
        // Remove the sign & remove priority code bits & right-shift to get drop-eligible bit
        hBuilder.setDropEligible(1 == (((vlanBytes[0] & 0xff) & 0x10) >> 4));
 
        // Remove priority code & drop-eligible bits, to get the VLAN-id
        vlanBytes[0] = (byte) (vlanBytes[0] & 0x0F);
        hBuilder.setVlan(new VlanId(BitBufferHelper.getInt(vlanBytes)));
 
        // Add 802.1Q header to the growing collection
        headerList.add(hBuilder.build());
 
        // Reset value of "nextField" to correspond to following 2 bytes for next 802.1Q header or EtherType/Length
        nextField = BitBufferHelper.getInt(BitBufferHelper.getBits(data, 128   extraHeaderBits, 16));
 
        // 802.1Q header means payload starts at a later position
        extraHeaderBits  = 32;
      }
      // Set 802.1Q headers
      if(!headerList.isEmpty()) {
        epBuilder.setHeader8021q(headerList);
      }
 
      // Deserialize the EtherType or Length field
      if(nextField >= ETHERTYPE_MIN) {
        epBuilder.setEthertype(KnownEtherType.forValue(nextField));
      } else if(nextField <= LENGTH_MAX) {
        epBuilder.setEthernetLength(nextField);
      } else {
        _logger.debug("Undefined header, value is not valid EtherType or length.  Value is "   nextField);
      }
 
      // Determine start & end of payload
      int payloadStart = ( 112   extraHeaderBits) / NetUtils.NumBitsInAByte;
      int payloadEnd = data.length - 4;
      epBuilder.setPayloadOffset(payloadStart);
      epBuilder.setPayloadLength(payloadEnd-payloadStart);
 
      // Deserialize the CRC
      epBuilder.setCrc(BitBufferHelper.getLong(BitBufferHelper.getBits(data, (data.length - 4) * NetUtils.NumBitsInAByte, 32)));
 
      // Set EthernetPacket field
      packetChain.add(new PacketChainBuilder()
        .setPacket(epBuilder.build())
        .build());
 
      // Set Payload field
      builder.setPayload(data);
    } catch(BufferException be) {
      _logger.info("Exception during decoding raw packet to ethernet.");
    }
    builder.setPacketChain(packetChain);
    return builder.build();
  }
  .......
}

这个类监听PacketReceived消息,当收到数据包时,将此时的数据包作为RawPacket并解码成EthernetPacket数据包,最后将EthernetPacketReceived消息发出去,这里面的PacketReceived数据包就是Packet-In数据包,这个的定义YANG存在与OpenFlowPlugin项目中,我们在前面已经分析过了,这里就不多做赘述。

ArpDecoder.java

代码语言:javascript复制
//实现EthernetPacketListener接口,为了监听ethernet-packet YANG中的ethernet-packet-received这个notification
public class ArpDecoder extends AbstractPacketDecoder<EthernetPacketReceived, ArpPacketReceived>
    implements EthernetPacketListener {    
   ......
  ////重写AbstarctPacketDecoder类中的decodeAndPublish()函数
  //即将EthernetPacket解码成ArpPacket类型的消息并发出去
  @Override
  public ArpPacketReceived decode(EthernetPacketReceived ethernetPacketReceived) {
    ArpPacketReceivedBuilder arpReceivedBuilder = new ArpPacketReceivedBuilder();
 
    // 得到packet-chain中的最后一个数据包,这就是以太网数据包
    List<PacketChain> packetChainList = ethernetPacketReceived.getPacketChain();
    EthernetPacket ethernetPacket = (EthernetPacket)packetChainList.get(packetChainList.size()-1).getPacket();
    int bitOffset = ethernetPacket.getPayloadOffset() * NetUtils.NumBitsInAByte;
    byte[] data = ethernetPacketReceived.getPayload();  //有效载荷
 
    ArpPacketBuilder builder = new ArpPacketBuilder();  //创建ArpPacket实例
    try {
      // Decode the hardware-type (HTYPE) and protocol-type (PTYPE) fields
      builder.setHardwareType(KnownHardwareType.forValue(BitBufferHelper.getInt(BitBufferHelper.getBits(data, bitOffset   0, 16))));  //硬件类型
      builder.setProtocolType(KnownEtherType.forValue(BitBufferHelper.getInt(BitBufferHelper.getBits(data, bitOffset 16, 16))));   //协议类型
 
      // Decode the hardware-length and protocol-length fields
      builder.setHardwareLength(BitBufferHelper.getShort(BitBufferHelper.getBits(data, bitOffset 32, 8)));  //硬件地址长度
      builder.setProtocolLength(BitBufferHelper.getShort(BitBufferHelper.getBits(data, bitOffset 40, 8)));   //协议地址长度
 
      // Decode the operation field
      builder.setOperation(KnownOperation.forValue(BitBufferHelper.getInt(BitBufferHelper.getBits(data, bitOffset 48, 16))));  //ar-op
 
      // Decode the address fields
      //硬件地址
      int indexSrcProtAdd = 64   8 * builder.getHardwareLength();
      int indexDstHardAdd = indexSrcProtAdd   8 * builder.getProtocolLength();
      int indexDstProtAdd = indexDstHardAdd   8 * builder.getHardwareLength();
      if(builder.getHardwareType().equals(KnownHardwareType.Ethernet)) {
        builder.setSourceHardwareAddress(HexEncode.bytesToHexStringFormat(BitBufferHelper.getBits(data, bitOffset   64, 8 * builder.getHardwareLength())));
        builder.setDestinationHardwareAddress(HexEncode.bytesToHexStringFormat(BitBufferHelper.getBits(data, bitOffset   indexDstHardAdd, 8 * builder.getHardwareLength())));
      } else {
        _logger.debug("Unknown HardwareType -- sourceHardwareAddress and destinationHardwareAddress are not decoded");
      }
      //IP地址
      if(builder.getProtocolType().equals(KnownEtherType.Ipv4) || builder.getProtocolType().equals(KnownEtherType.Ipv6)) {
        builder.setSourceProtocolAddress(InetAddress.getByAddress(BitBufferHelper.getBits(data, bitOffset   indexSrcProtAdd, 8 * builder.getProtocolLength())).getHostAddress());
        builder.setDestinationProtocolAddress(InetAddress.getByAddress(BitBufferHelper.getBits(data, bitOffset   indexDstProtAdd, 8 * builder.getProtocolLength())).getHostAddress());
      } else {
        _logger.debug("Unknown ProtocolType -- sourceProtocolAddress and destinationProtocolAddress are not decoded");
      }
    } catch(BufferException | UnknownHostException e) {
      _logger.debug("Exception while decoding APR packet", e.getMessage());
    }
 
    //build arp
    packetChainList.add(new PacketChainBuilder()
      .setPacket(builder.build())
      .build());
    arpReceivedBuilder.setPacketChain(packetChainList);
 
    // carry forward the original payload.
    arpReceivedBuilder.setPayload(ethernetPacketReceived.getPayload());
 
    return arpReceivedBuilder.build();  
  }
  ......
}

由于ARP报文存在与以太网的报文的帧头部,所以为了得到ARP报文,就要订阅EthernetPacketReceived消息,所以ArpDecoder这个类实现了EthernetPacketListener接口。ARP报文的格式如下图所示:

具体的解码过程就是根据的ARP报文格式,具体的代码解释在注释中,这里就不多做赘述了。

EthernetPacket数据包也可以解码成Ipv4Packet和Ipv6Pcket数据包,相应的实现代码在Ipv4Decoder.java和Ipv6Decoder.java中,代码逻辑类似,这里因为篇幅限制就不多做解释,感兴趣的可以私信我。

以上就是收到ARP请求,并将相应的数据包解码成ArpPacket、Ipv4Packet和Ipv6Pcket数据包。

2.3 ARP请求(Packet-Out)

为了将来自PC A的ARP请求广播至OpenFlow网络内,ODL控制器将Packet-Out消息发送至OpenFlow交换机,接受到Packet-Out消息的OpenFlow交换机,将ARP请求广播至端口2和端口3。具体的实现代码在l2switch项目中的arphandler目录下。

首先我们来分析ArpPacketHandler.java,这个java类是这个部分的核心。

代码语言:javascript复制
//为了处理ARP Packet则订阅ArpPacketReceived消息,即实现ArpPacketListener接口
public class ArpPacketHandler implements ArpPacketListener {
   
  //接收到报文
  @Override
  public void onArpPacketReceived(ArpPacketReceived packetReceived) {
    if(packetReceived == null || packetReceived.getPacketChain() == null) {
      return;
    }
 
    RawPacket rawPacket = null;       //原始数据包
    EthernetPacket ethernetPacket = null;   //以太网数据包
    ArpPacket arpPacket = null;      //Arp数据包
	//得到相应的原始数据包、以太网数据包和Arp数据包
    for(PacketChain packetChain : packetReceived.getPacketChain()) {
      if(packetChain.getPacket() instanceof RawPacket) {
        rawPacket = (RawPacket) packetChain.getPacket();
      } else if(packetChain.getPacket() instanceof EthernetPacket) {
        ethernetPacket = (EthernetPacket) packetChain.getPacket();
      } else if(packetChain.getPacket() instanceof ArpPacket) {
        arpPacket = (ArpPacket) packetChain.getPacket();
      }
    }
    if(rawPacket == null || ethernetPacket == null || arpPacket == null) {
      return;
    }
    //调用packetDispatcher类中的dispatchPacket()函数
    packetDispatcher.dispatchPacket(packetReceived.getPayload(),    //有效载荷
        rawPacket.getIngress(),   //数据包入端口
        ethernetPacket.getSourceMac(),  //源MAC地址
        ethernetPacket.getDestinationMac());   //目的MAC地址
  }
 
}

ArpPacketHandler.java实现了对ARP数据包的监听,同时对数据包进行相应的处理,得到相应的原始数据包、以太网数据包和ARP数据包,调用packetDispatcher类中的dispatchPacket()函数,传入相应的参数,这个函数主要的作用是将数据包Packet-Out出去。下面我们就来分析下packetDispatcher.java。

代码语言:javascript复制
public class PacketDispatcher {
   ......
  public void dispatchPacket(byte[] payload, NodeConnectorRef ingress, MacAddress srcMac, MacAddress destMac) {
    inventoryReader.readInventory();  //调用inventoryReader类中的readInventory()函数,得到switchNodeConnectors和controllerSwitchConnectors
    //这里的nodeId是入端口所在的node的id,这里面的node指的是交换机
    String nodeId = ingress.getValue().firstIdentifierOf(Node.class).firstKeyOf(Node.class, NodeKey.class).getId().getValue();
    NodeConnectorRef srcConnectorRef = inventoryReader.getControllerSwitchConnectors().get(nodeId);//定义入端口的reference
 
    if(srcConnectorRef == null) {
      refreshInventoryReader();
      srcConnectorRef = inventoryReader.getControllerSwitchConnectors().get(nodeId);  //得到入端口的reference
    }
    NodeConnectorRef destNodeConnector = inventoryReader.getNodeConnector(ingress.getValue().firstIdentifierOf(Node.class), destMac);  //得到目的MAC地址对应的交换机上的端口
    if(srcConnectorRef != null) {
      if(destNodeConnector != null) {  //目的端口不为空,即已经学到mac地址
        sendPacketOut(payload, srcConnectorRef, destNodeConnector);  //将PacketOut数据包单播出去
      } else {
        floodPacket(nodeId, payload, ingress, srcConnectorRef);  //将PacketOut数据包广播出去
      }
    } else {
      _logger.info("Cannot send packet out or flood as controller node connector is not available for node {}.", nodeId);
    }
  }
   //将PacketOut数据包广播出去,广播的端口是所有SwitchNodeConnectors
  public void floodPacket(String nodeId, byte[] payload, NodeConnectorRef origIngress, NodeConnectorRef controllerNodeConnector) {
 
    List<NodeConnectorRef> nodeConnectors = inventoryReader.getSwitchNodeConnectors().get(nodeId);
 
    if(nodeConnectors == null) {
      refreshInventoryReader();  //刷新一次
      nodeConnectors = inventoryReader.getSwitchNodeConnectors().get(nodeId);
      if(nodeConnectors == null) {
        _logger.info("Cannot flood packets, as inventory doesn't have any node connectors for node {}", nodeId);
        return;
      }
    }
	//循环,对于每一个SwitchNodeConnectors,调用sendPacketOut()函数,
    for(NodeConnectorRef ncRef : nodeConnectors) { 
      String ncId = ncRef.getValue().firstIdentifierOf(NodeConnector.class).firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId().getValue();
      // Don't flood on discarding node connectors & origIngress
      if(!ncId.equals(origIngress.getValue().firstIdentifierOf(NodeConnector.class).firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId().getValue())) {
        sendPacketOut(payload, origIngress, ncRef);
      }
    }
  }
  //将PacketOut数据包单播出去,入端口是ingress,出端口是egress
  public void sendPacketOut(byte[] payload, NodeConnectorRef ingress, NodeConnectorRef egress) {   //payload, srcConnectorRef, destNodeConnector
    if(ingress == null || egress == null) return;
    InstanceIdentifier<Node> egressNodePath = getNodePath(egress.getValue());
    TransmitPacketInput input = new TransmitPacketInputBuilder() //
        .setPayload(payload) //
        .setNode(new NodeRef(egressNodePath)) //
        .setEgress(egress) //
        .setIngress(ingress) //
        .build();
    packetProcessingService.transmitPacket(input);  //生成PcketOut数据包
  }
  ......
}

这个类主要的实现功能是将数据包广播或者单播出去,分析代码可以发现,对于已知目的端口数据包直接从目的端口(destNodeConnector)Packet-Out出去,不知道目的端口的就向所有的SwitchNodeConnectors端口广播出去,那么我们现在就需要得到SwitchNodeConnectors,这部分的代码在InventoryReader中。

代码语言:javascript复制
public class InventoryReader {
    .......
  private boolean refreshData = false;  //true的时候才能读取
 
  public InventoryReader(DataBroker dataService) {
    this.dataService = dataService;
    controllerSwitchConnectors = new HashMap<String, NodeConnectorRef>();  //定义controllerSwitchConnectors
    switchNodeConnectors = new HashMap<String, List<NodeConnectorRef>>(); //定义switchNodeConnectors 
  }
  
  //返回controllerSwitchConnectors
  public HashMap<String, NodeConnectorRef> getControllerSwitchConnectors() {
    return controllerSwitchConnectors;
  }
  
   //返回switchNodeConnectors
  public HashMap<String, List<NodeConnectorRef>> getSwitchNodeConnectors() {
    return switchNodeConnectors;
  }
  
  //从dataStore中读取出controllerSwitchConnectors和switchNodeConnectors
  public void readInventory() {
    // Only run once for now
    if(!refreshData) {
      return;
    }
	//锁住这个线程,当一次读取未完成的时候不能进行下次读取
    synchronized(this) {
      if(!refreshData)
        return;
      // 读Inventory
      InstanceIdentifier.InstanceIdentifierBuilder<Nodes> nodesInsIdBuilder = InstanceIdentifier.<Nodes>builder(Nodes.class);
      Nodes nodes = null;
      ReadOnlyTransaction readOnlyTransaction = dataService.newReadOnlyTransaction();
 
      //读nodes,node指的是交换机
      try {
        Optional<Nodes> dataObjectOptional = null;
        dataObjectOptional = readOnlyTransaction.read(LogicalDatastoreType.OPERATIONAL, nodesInsIdBuilder.build()).get();
        if(dataObjectOptional.isPresent())
          nodes = (Nodes) dataObjectOptional.get();
      } catch(InterruptedException e) {
        _logger.error("Failed to read nodes from Operation data store.");
        readOnlyTransaction.close();
        throw new RuntimeException("Failed to read nodes from Operation data store.", e);
      } catch(ExecutionException e) {
        _logger.error("Failed to read nodes from Operation data store.");
        readOnlyTransaction.close();
        throw new RuntimeException("Failed to read nodes from Operation data store.", e);
      }
 
      if(nodes != null) {
        // 得到每一个node的端口(nodeConnector)
        for(Node node : nodes.getNode()) {
          ArrayList<NodeConnectorRef> nodeConnectorRefs = new ArrayList<NodeConnectorRef>();
          List<NodeConnector> nodeConnectors = node.getNodeConnector();
          if(nodeConnectors != null) {
            for(NodeConnector nodeConnector : nodeConnectors) {
              // 读取端口的STP状态
              StpStatusAwareNodeConnector saNodeConnector = nodeConnector.getAugmentation(StpStatusAwareNodeConnector.class);
			  //排除STP状态为丢弃(discarding)的端口
              if(saNodeConnector != null && StpStatus.Discarding.equals(saNodeConnector.getStatus())) {
                continue;
              }
              if(nodeConnector.getKey().toString().contains("LOCAL")) {   //排除端口名中有local的
                continue;
              }
              NodeConnectorRef ncRef = new NodeConnectorRef(
                  InstanceIdentifier.<Nodes>builder(Nodes.class).<Node, NodeKey>child(Node.class, node.getKey())
                      .<NodeConnector, NodeConnectorKey>child(NodeConnector.class, nodeConnector.getKey()).build());
              nodeConnectorRefs.add(ncRef);  //没有local
            }
          }
 
          switchNodeConnectors.put(node.getId().getValue(), nodeConnectorRefs);   //没有local,用于广播
          NodeConnectorRef ncRef = new NodeConnectorRef(
              InstanceIdentifier.<Nodes>builder(Nodes.class).<Node, NodeKey>child(Node.class, node.getKey())
                  .<NodeConnector, NodeConnectorKey>child(NodeConnector.class, new NodeConnectorKey(new NodeConnectorId(node.getId().getValue()   ":LOCAL"))).build());
          _logger.debug("Local port for node {} is {}", node.getKey(), ncRef);
          controllerSwitchConnectors.put(node.getId().getValue(), ncRef);  //有local   用于单播
        }
      }
      readOnlyTransaction.close();
      refreshData = false;
    }
  }
 
   //得到相应mac地址对应的端口
  public NodeConnectorRef getNodeConnector(InstanceIdentifier<Node> nodeInsId, MacAddress macAddress) {
    if(nodeInsId == null || macAddress == null) {
      return null;
    }
 
    NodeConnectorRef destNodeConnector = null;
    long latest = -1;
    ReadOnlyTransaction readOnlyTransaction = dataService.newReadOnlyTransaction();
    try {
	   //读node
      Optional<Node> dataObjectOptional = null;
      dataObjectOptional = readOnlyTransaction.read(LogicalDatastoreType.OPERATIONAL, nodeInsId).get();
      if(dataObjectOptional.isPresent()) {
        Node node = (Node) dataObjectOptional.get();
        _logger.debug("Looking address{} in node : {}", macAddress, nodeInsId);
		//查找每一个node上端口对应的host的MAC地址是否与传进来的macAddress相匹配
        if(node.getNodeConnector() != null) {
          for(NodeConnector nc : node.getNodeConnector()) {
            //不找STP状态为丢弃的端口对应的host的mac地址
            StpStatusAwareNodeConnector saNodeConnector = nc.getAugmentation(StpStatusAwareNodeConnector.class);
            if(saNodeConnector != null && StpStatus.Discarding.equals(saNodeConnector.getStatus())) {
              continue;
            }
            _logger.debug("Looking address{} in nodeconnector : {}", macAddress, nc.getKey());
            AddressCapableNodeConnector acnc = nc.getAugmentation(AddressCapableNodeConnector.class);
            if(acnc != null) {
              List<Addresses> addressesList = acnc.getAddresses();  //得到mac地址
              for(Addresses add : addressesList) {
			     //对于比配上的mac地址,得到端口并修改lastseen
                if(macAddress.equals(add.getMac())) {
                  if(add.getLastSeen() > latest) {
                    destNodeConnector = new NodeConnectorRef(nodeInsId.child(NodeConnector.class, nc.getKey()));
                    latest = add.getLastSeen();
                    _logger.debug("Found address{} in nodeconnector : {}", macAddress, nc.getKey());
                    break;
                  }
                }
              }
            }
          }
        } else {
          _logger.debug("Node connectors data is not present for node {}", node.getId());
        }
      }
    } catch(InterruptedException e) {
      _logger.error("Failed to read nodes from Operation data store.");
      readOnlyTransaction.close();
      throw new RuntimeException("Failed to read nodes from Operation data store.", e);
    } catch(ExecutionException e) {
      _logger.error("Failed to read nodes from Operation data store.");
      readOnlyTransaction.close();
      throw new RuntimeException("Failed to read nodes from Operation data store.", e);
    }
    readOnlyTransaction.close();
    return destNodeConnector;
  }
 
}

这个类主要实现了两个功能,一是得到相应的controllerSwitchConnectors和switchNodeConnectors用于Packet-Out数据包的单播和广播,controllerSwitchConnectors是端口名有local的端口,实际中,switch-to-switch ports和switch-to-controller ports是含有local的,而connected-to-host ports是不含local的;二是根据host的mac地址得到连接的端口。

根据以上的分析,由于PC A不知道IPv4地址为10.0.0.2的MAC地址,所以要将ARP请求广播。

3 总结

以上就是ARP请求的具体分析,ARP请求完了之后会有ARP响应,ARP相应结束ODL控制器也会下发相应的流表,由于篇幅限制,这部分的源码分析在我的下一篇文章中(源码解读ODL MAC学习(二))。

0 人点赞