前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >源码解读ODL的MAC地址学习(一)

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

作者头像
SDNLAB
发布2018-03-30 18:15:19
2.2K0
发布2018-03-30 18:15:19
举报
文章被收录于专栏:SDNLAB

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学习(二))。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2016-08-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 SDNLAB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 简介
  • 2 ODL中MAC地址原理
    • 2.1 ARP处理流程
      • 2.2 ARP请求(Packet-In)
        • 2.3 ARP请求(Packet-Out)
        • 3 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档