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