编程语言
首页 > 编程语言> > 源码解读ODL的MAC地址学习(二)

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

作者:互联网

1 简介

上一篇文章(源码解读ODL的MAC地址学习(一))已经分析了MAC地址学习中的ARP请求的部分源码,下面将接着上一篇文章,介绍一下ARP响应和生成流表的源码。设想的场景不变,如下图所示:

图片

2 ARP响应源码分析

2.1 ARP响应(Packet-In)

PC B接收到OpenFlow交换机转发的ARP请求后,向PC A发送ARP响应。OpenFlow交换机接收到端口2发出的ARP响应后,依据流表项(参看源码解读ODL的MAC地址学习(一) 2.2部分),将Packet-In消息发往OpenFlow控制器。OpenFlow控制器接收到Packet-In消息后,会对ARP响应进行分析,学习MAC地址。


首先对Packet-In数据包的接收和解码我们在源码解读ODL的MAC地址学习(一)的2.2部分已经分析过了,这里就不多做赘述,直接来分析MAC地址的学习。这个部分的代码主要存在在l2switch项目中的addresstracker目录下,首先分析YANG文件。

address-tracker.yang:

module address-tracker {

  ...... 

  grouping address-node-connector {

    list addresses {

      key id;

      leaf id {

        description "A 64-bit key for this observation. This is opaque and should not be interpreted.";

        type uint64;

      }

      leaf mac {

        type yang:mac-address;

        description "MAC address";

      }

      leaf ip {

        type inet:ip-address;

        description "IPv4 or IPv6 address";

      }

      leaf vlan {

        type ethernet:vlan-id;

        description "VLAN id";

      }

      leaf first-seen {

        type int64;

        description "Timestamp (number of ms since January 1, 1970, 00:00:00 GMT) of observing this address for the first time";

      }

      leaf last-seen {

        type int64;

        description "The most recent timestamp (tnumber of ms since January 1, 1970, 00:00:00 GMT) of observing this address";

      }

    }

  }


  augment "/inv:nodes/inv:node/inv:node-connector" {

    ext:augment-identifier "address-capable-node-connector";

    uses address-node-connector;

  }

}

这个YANG文件中比较重要的就是对node-connector的扩展,扩展成address-capable-node-connector,用于在datastore中存储学习到的address信息。下面分析具体的代码,这里面一共有四个JAVA类,其中AddressObserveUsingArp.java、AddressObserveUsingIpv4.java和AddressObserveUsingIpv6.java这三个类差不多,只是监听的数据包不同,所以我们只分析其中一个,这里我们选择分析AddressObserveUsingArp.java://监听ARP数据包

public class AddressObserverUsingArp implements ArpPacketListener {


  public AddressObserverUsingArp(org.opendaylight.l2switch.addresstracker.addressobserver.AddressObservationWriter addressObservationWriter) {

    this.addressObservationWriter = addressObservationWriter;

  }


  @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;

    }

    //调用addressObservationWriter类的addAddress()方法

    addressObservationWriter.addAddress(ethernetPacket.getSourceMac(),

        new IpAddress(arpPacket.getSourceProtocolAddress().toCharArray()),

        rawPacket.getIngress());

  }

}

这个类监听了ARP数据包,得到相应的原始数据包、以太网报文和ARP报文,最后调用addressObservationWriter类的addAddress()方法,将MAC地址、IP地址和数据包的入端口作为参数传到这个函数中。


下面我们分析AddressObservationWriter.java:

public class AddressObservationWriter {

  ......

  public void setTimestampUpdateInterval(long timestampUpdateInterval) {

    this.timestampUpdateInterval = timestampUpdateInterval;  //定义时间戳

  }


  //向datastore中添加Address

  public void addAddress(MacAddress macAddress, IpAddress ipAddress, NodeConnectorRef nodeConnectorRef) {

    if(macAddress == null || ipAddress == null || nodeConnectorRef == null) {

      return;

    }


    // 锁住线程,当前写入线程未结束时不能进行下一个写入线程

    NodeConnectorLock nodeConnectorLock;

    synchronized(this) {

      nodeConnectorLock = lockMap.get(nodeConnectorRef);

      if(nodeConnectorLock == null) {

        nodeConnectorLock = new NodeConnectorLock();

        lockMap.put(nodeConnectorRef, nodeConnectorLock);

      }


    }

    synchronized(nodeConnectorLock) {

      // Ensure previous transaction finished writing to the db

      CheckedFuture<Void, TransactionCommitFailedException> future = futureMap.get(nodeConnectorLock);

      if (future != null) {

        try {

          future.get();

        }

        catch (InterruptedException|ExecutionException e) {

          _logger.error("Exception while waiting for previous transaction to finish", e);

        }

      }


      //初始化addressBuilder

      long now = new Date().getTime();

      final AddressCapableNodeConnectorBuilder acncBuilder = new AddressCapableNodeConnectorBuilder();

      final AddressesBuilder addressBuilder = new AddressesBuilder()

          .setIp(ipAddress)

          .setMac(macAddress)

          .setFirstSeen(now)

          .setLastSeen(now);

      List<Addresses> addresses = null;


      // 从datastore中读取现有的Address

      ReadOnlyTransaction readTransaction = dataService.newReadOnlyTransaction();

      NodeConnector nc = null;

      try {

        Optional<NodeConnector> dataObjectOptional = readTransaction.read(LogicalDatastoreType.OPERATIONAL, (InstanceIdentifier<NodeConnector>) nodeConnectorRef.getValue()).get();

        if(dataObjectOptional.isPresent())

          nc = (NodeConnector) dataObjectOptional.get();

      } catch(Exception e) {

        _logger.error("Error reading node connector {}", nodeConnectorRef.getValue());

        readTransaction.close();

        throw new RuntimeException("Error reading from operational store, node connector : " + nodeConnectorRef, e);

      }

      readTransaction.close();

      if(nc == null) {

        return;

      }

      AddressCapableNodeConnector acnc = (AddressCapableNodeConnector) nc.getAugmentation(AddressCapableNodeConnector.class);


      // Address存在的情况

      if(acnc != null && acnc.getAddresses() != null) {

        //从现有的Address中查找相应的MAC-IP对,并且更新last-seen时间戳

        addresses = acnc.getAddresses();

        for(int i = 0; i < addresses.size(); i++) {

          if(addresses.get(i).getIp().equals(ipAddress) && addresses.get(i).getMac().equals(macAddress)) {

            if((now - addresses.get(i).getLastSeen()) > timestampUpdateInterval) {

              addressBuilder.setFirstSeen(addresses.get(i).getFirstSeen())

                  .setKey(addresses.get(i).getKey());

              addresses.remove(i);

              break;

            } else {

              return;

            }

          }

        }

      }

      //Address不存在的情况,需要写入新的Address

      else {

        addresses = new ArrayList<>();

      }


      if(addressBuilder.getKey() == null) {

        addressBuilder.setKey(new AddressesKey(BigInteger.valueOf(addressKey.getAndIncrement())));

      }

      addresses.add(addressBuilder.build());

      acncBuilder.setAddresses(addresses);


      //定义写入datastore的IID是AddressCapableNodeConnector

      InstanceIdentifier<AddressCapableNodeConnector> addressCapableNcInstanceId =

          ((InstanceIdentifier<NodeConnector>) nodeConnectorRef.getValue())

              .augmentation(AddressCapableNodeConnector.class);

      final WriteTransaction writeTransaction = dataService.newWriteOnlyTransaction();

      // 向datastore中写入Address,其中datastore的类型是operational,IID是AddressCapableNodeConnector

      writeTransaction.merge(LogicalDatastoreType.OPERATIONAL, addressCapableNcInstanceId, acncBuilder.build());

      final CheckedFuture writeTxResultFuture = writeTransaction.submit();

      Futures.addCallback(writeTxResultFuture, new FutureCallback() {

        @Override

        public void onSuccess(Object o) {

          _logger.debug("AddressObservationWriter write successful for tx :{}", writeTransaction.getIdentifier());

        }


        @Override

        public void onFailure(Throwable throwable) {

          _logger.error("AddressObservationWriter write transaction {} failed", writeTransaction.getIdentifier(), throwable.getCause());

        }

      });

      futureMap.put(nodeConnectorLock, writeTxResultFuture);

    }

  }

}

这个类实现的功能就是根据传进来的MAC和IP地址在datastore中查找现有的Address,如果现有的Address中已经存在这个MAC-IP对的话,那么就会更新Address中的last-seen(最新一次的观察时间),如果不存在,那么就向datastore中的AddressCapableNodeConnector IID存入新的Address,这样就学到了MAC地址。此时OpenFlow交换机就学习到了PC B的MAC地址。


2.2 ARP响应(Packet-Out)

学习到了MAC地址之后,ODL控制器就会发送Packet-Out消息,具体的Packet-Out消息发送的源码在源码解读ODL的MAC地址学习(一)的2.3部分,因为已经学习到了目的MAC地址,所以直接将Packet-Out消息行动设置为向目的端口输出,对于设想的场景,就会向端口2输出,这样就实现了PC A向IPv4地址为10.0.0.2的目标地址发送数据包。

3 添加流表项

数据包发送成功之后,ODL控制器还会产生两个流表项下发给OpenFlow交换机:一是匹配此数据包的目的MAC地址,Output是出端口;二是匹配此数据包的源MAC,Output是入端口。这部分的代码主要在l2switch项目中的l2switch-main目录下,这里面主要分析FlowWriterServiceImpl.java

public class FlowWriterServiceImpl implements FlowWriterService {

  ......

  //添加MAC-MAC流表

  @Override

  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.");

    // 如果两个MAC地址相同则不添加流表

    if(sourceMac != null && destMac.equals(sourceMac)) {

      _logger.info("In addMacToMacFlow: No flows added. Source and Destination mac are same.");

      return;

    }

    // 生成table ID

    TableKey flowTableKey = new TableKey((short) flowTableId);


    //build a flow path based on node connector to program flow

    InstanceIdentifier<Flow> flowPath = buildFlowPath(destNodeConnectorRef, flowTableKey);


    //生成流表项的主体

    Flow flowBody = createMacToMacFlow(flowTableKey.getId(), flowPriority, sourceMac, destMac, destNodeConnectorRef);


    //向配置数据中提交流表项

    writeFlowToConfigData(flowPath, flowBody);

  }


  //生成对称的两条流表项

  public void addBidirectionalMacToMacFlows(MacAddress sourceMac,

                                            NodeConnectorRef sourceNodeConnectorRef,

                                            MacAddress destMac,

                                            NodeConnectorRef destNodeConnectorRef) {

    Preconditions.checkNotNull(sourceMac, "Source mac address should not be null.");

    Preconditions.checkNotNull(sourceNodeConnectorRef, "Source port should not be null.");

    Preconditions.checkNotNull(destMac, "Destination mac address should not be null.");

    Preconditions.checkNotNull(destNodeConnectorRef, "Destination port should not be null.");


    if(sourceNodeConnectorRef.equals(destNodeConnectorRef)) {

      _logger.info("In addMacToMacFlowsUsingShortestPath: No flows added. Source and Destination ports are same.");

      return;


    }


    // 在源端口添加目的MAC-源MAC的流表项

    addMacToMacFlow(destMac, sourceMac, sourceNodeConnectorRef);


    // 在目的端口添加目的MAC-源MAC的流表项

    addMacToMacFlow(sourceMac, destMac, destNodeConnectorRef);

  }


  private InstanceIdentifier<Flow> buildFlowPath(NodeConnectorRef nodeConnectorRef, TableKey flowTableKey) {


    // 生成流表项的ID

    FlowId flowId = new FlowId(String.valueOf(flowIdInc.getAndIncrement()));

    FlowKey flowKey = new FlowKey(flowId);

   //生成流表的IID

    return InstanceIdentifierUtils.generateFlowInstanceIdentifier(nodeConnectorRef, flowTableKey, flowKey);

  }


  //具体生成一条mac-mac流表项

  private Flow createMacToMacFlow(Short tableId, int priority,

                                  MacAddress sourceMac, MacAddress destMac, NodeConnectorRef destPort) {


    // 定义流表项实例

    FlowBuilder macToMacFlow = new FlowBuilder() //

        .setTableId(tableId) //

        .setFlowName("mac2mac");


    // 用自己的哈希码作为流表项的id

    macToMacFlow.setId(new FlowId(Long.toString(macToMacFlow.hashCode())));


    // 生成匹配字段的实例

//匹配字段为目的mac

    EthernetMatchBuilder ethernetMatchBuilder = new EthernetMatchBuilder() //

        .setEthernetDestination(new EthernetDestinationBuilder() //

            .setAddress(destMac) //

            .build());

    //如果源mac存在的话添加源mac匹配

    if(sourceMac != null) {

      ethernetMatchBuilder.setEthernetSource(new EthernetSourceBuilder()

          .setAddress(sourceMac)

          .build());

    }

    EthernetMatch ethernetMatch = ethernetMatchBuilder.build();

    Match match = new MatchBuilder()

        .setEthernetMatch(ethernetMatch)

        .build();


    //得到目的port的ID

    Uri destPortUri = destPort.getValue().firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId();

//定义output action

    Action outputToControllerAction = new ActionBuilder() //

        .setOrder(0)

        .setAction(new OutputActionCaseBuilder() //

            .setOutputAction(new OutputActionBuilder() //

                .setMaxLength(0xffff) //

                .setOutputNodeConnector(destPortUri) //定义目的端口为output

                .build()) //

            .build()) //

        .build();


    // 生成action的应用

    ApplyActions applyActions = new ApplyActionsBuilder().setAction(ImmutableList.of(outputToControllerAction))

        .build();


    //将这个action的应用添加到指令中

    Instruction applyActionsInstruction = new InstructionBuilder() //

        .setOrder(0)

        .setInstruction(new ApplyActionsCaseBuilder()//

            .setApplyActions(applyActions) //

            .build()) //

        .build();


    // 生成流表实例

    macToMacFlow

        .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 macToMacFlow.build();

  }


  //向配置数据中提交流表项

  private Future<RpcResult<AddFlowOutput>> writeFlowToConfigData(InstanceIdentifier<Flow> flowPath,

                                                                 Flow flow) {

    final InstanceIdentifier<Table> tableInstanceId = flowPath.<Table>firstIdentifierOf(Table.class);

    final InstanceIdentifier<Node> nodeInstanceId = flowPath.<Node>firstIdentifierOf(Node.class);

    final AddFlowInputBuilder builder = new AddFlowInputBuilder(flow);

    builder.setNode(new NodeRef(nodeInstanceId));

    builder.setFlowRef(new FlowRef(flowPath));

    builder.setFlowTable(new FlowTableRef(tableInstanceId));

    builder.setTransactionUri(new Uri(flow.getId().getValue()));

    return salFlowService.addFlow(builder.build());

  }

}

以上的代码实现了匹配此数据包的目的MAC地址,Output是出端口和匹配此数据包的源MAC的两条流表项

4 总结

经过两篇文章的分析,通过监控ARP请求和ARP响应,即可实现MAC地址学习,这是一种自学习桥接器,可高效的使用于网络中。


标签:表项,MAC,源码,ODL,build,address,new,null
来源: https://blog.51cto.com/u_15127681/2823218