ndnSIM learning -- the whole process from consumer sending interest package to producer returning data package

Keywords: C++ Programming network

preface

In the last article ndnSIM learning (8) -- ndn-simple.cpp of examples analyzes each function line by line In, we analyze the underlying principle of the whole ndn-simple.cpp. As the core, it is naturally the working process of the whole network: how do consumers send interest packets? How do producers return packets based on interest packets? Because this part is relatively independent and lengthy from the previous article, I put forward this part separately as an article.

Figure 1: understand the whole contracting and receiving process

  • The yellow color can be understood as a preprocessing process. The originator executes the "Yellow + blue" process
  • The middle node executes the "green + blue" process
  • The last node (producer or consumer) executes the "green + Blue + purple" process

be careful! The connecting part between the two colors may have more or less processes, and the actual description shall prevail. Although the text description is not comprehensive, it is recommended to take this article as a reference, and the actual code shall prevail

Producer contract

In the last article, we learned that the simulation process of the whole ns3 software is: add events - > the emulator executes all events.

Then, how to add the event of Consumer sending interest package? The answer is: we set the interest frequency of consumers to 10, so we send 10 packets per second. Specifically, the type of Consumer is ConsumerCbr, and the contract issuing is controlled by the function ScheduleNextPacket. The core code is as follows——

void
ConsumerCbr::ScheduleNextPacket()
{
  if (m_firstTime) {
    m_sendEvent = Simulator::Schedule(Seconds(0.0), &Consumer::SendPacket, this);
    m_firstTime = false;
  }
  else if (!m_sendEvent.IsRunning())
    m_sendEvent = Simulator::Schedule((m_random == 0) ? Seconds(1.0 / m_frequency)
                                                      : Seconds(m_random->GetValue()),
                                      &Consumer::SendPacket, this);
}

The event request of this function calls the Consumer::SendPacket function of the base class, which creates an interest packet, and then uses M_ Applink - > onreceiveinterest

void
Consumer::SendPacket()
{
  // ... omit the previous seq processing, and the following is the core logic

  // shared_ptr<Interest> interest = make_shared<Interest> ();
  shared_ptr<Interest> interest = make_shared<Interest>();
  interest->setNonce(m_rand->GetValue(0, std::numeric_limits<uint32_t>::max()));
  interest->setName(*nameWithSequence);
  interest->setCanBePrefix(false);
  time::milliseconds interestLifeTime(m_interestLifeTime.GetMilliSeconds());
  interest->setInterestLifetime(interestLifeTime);

  WillSendOutInterest(seq);

  m_transmittedInterests(interest, this, m_face);
  m_appLink->onReceiveInterest(*interest);  // Core logic

  ScheduleNextPacket();
}

AppLinkService::onReceiveInterest calls the LinkService::receiveInterest function, which triggers the afterReceiveInterest signal of LinkService.

Wait, do you feel familiar with this signal? Go back to the Forwarder constructor. The faceTable in it connects the afterReceiveInterest signal of each face to the Forwarder::startProcessInterest.

  m_faceTable.afterAdd.connect([this] (const Face& face) {
    face.afterReceiveInterest.connect(
      [this, &face] (const Interest& interest, const EndpointId& endpointId) {
        this->startProcessInterest(FaceEndpoint(face, endpointId), interest);
      });
    face.afterReceiveData.connect(
      [this, &face] (const Data& data, const EndpointId& endpointId) {
        this->startProcessData(FaceEndpoint(face, endpointId), data);
      });
    face.afterReceiveNack.connect(
      [this, &face] (const lp::Nack& nack, const EndpointId& endpointId) {
        this->startProcessNack(FaceEndpoint(face, endpointId), nack);
      });
    face.onDroppedInterest.connect(
      [this, &face] (const Interest& interest) {
        this->onDroppedInterest(FaceEndpoint(face, 0), interest);
      });
  });

However, it seems almost interesting, because the afterReceiveInterest signal of LinkService is triggered earlier, and here is the afterReceiveInterest signal of face. Is there any connection between them? Let's look at the header file definition of LinkService, combined with the constructor of face, and find that there is a mystery in it.

// Link service class
class LinkService : protected virtual LinkServiceCounters, noncopyable
{
// The previous function is omitted, and the key is the member function inside
private:
  Face* m_face;  
  Transport* m_transport;
};

// When installing all, ndnStack calls this function to generate link and physical transmission assistants for the node and bind them to the face
shared_ptr<Face>
StackHelper::PointToPointNetDeviceCallback(Ptr<Node> node, Ptr<L3Protocol> ndn,
                                           Ptr<NetDevice> device) const
{
  // Omitted earlier, the link assistant and physical transport layer assistant created here are managed by this interface
  auto linkService = make_unique<::nfd::face::GenericLinkService>(opts);

  auto transport = make_unique<NetDeviceTransport>(node, netDevice,
                                                   constructFaceUri(netDevice),
                                                   constructFaceUri(remoteNetDevice));

  auto face = std::make_shared<Face>(std::move(linkService), std::move(transport));
  face->setMetric(1);

  ndn->addFace(face);
  NS_LOG_LOGIC("Node " << node->GetId() << ": added Face as face #"
                       << face->getLocalUri());

  return face;
}

// The constructor of Face. When constructing Face, it will be bundled with link service and physical transport layer service
Face::Face(unique_ptr<LinkService> service, unique_ptr<Transport> transport)
  : afterReceiveInterest(service->afterReceiveInterest)
  , afterReceiveData(service->afterReceiveData)
  , afterReceiveNack(service->afterReceiveNack)
  , onDroppedInterest(service->onDroppedInterest)
  , afterStateChange(transport->afterStateChange)
  , m_id(INVALID_FACEID)
  , m_service(std::move(service))
  , m_transport(std::move(transport))
  , m_counters(m_service->getCounters(), m_transport->getCounters())
  , m_metric(0)
{
  m_service->setFaceAndTransport(*this, *m_transport);
  m_transport->setFaceAndLinkService(*this, *m_service);
}

Note that Signal inherits noncopyable, and the afterreceivedata (Service - > afterreceivedata) in the constructor of Face binds its own Signal with the Signal of link service, so the afterReceiveInterest of LinkService is connected to the Forwarder::startProcessInterest through Face.

In other words, the afterReceiveInterest signal of LinkService is equivalent to the Forwarder::startProcessInterest function!

The Forwarder::startProcessInterest is handed over to the Forwarder::onIncomingInterest function. See the article for details ndnSIM learning (VII) -- forwarding processing forwarder.cpp, forwarder.hpp , the function flow chart is as follows


In short, the Forwarder processes interest packets. If found, Strategy::sendData will be executed. Otherwise, it's onOutgoingInterest. Jump down and keep looking back. Here, let's assume we don't find it and continue to jump down.

Here, execute the face.sendInterest function and LinkService::sendInterest, then genericlinkservice:: dosendenterest to sendNetPacket for Frag fragmentation to the link layer. After fragmentation, execute sendLpPacket, and then send it to the physical transport layer through sendPacket for Transport::send, and then to NetDeviceTransport::doSend, Handed over to PointToPointNetDevice::Send, where TransmitStart is executed, and the ball is kicked to PointToPointChannel::TransmitStart. Finally, after countless kicks, we finally saw this sentence——

  Simulator::ScheduleWithContext (m_link[wire].m_dst->GetNode ()->GetId (),
                                  txTime + m_delay, &PointToPointNetDevice::Receive,
                                  m_link[wire].m_dst, p->Copy ());

Great, I thank your family! That is to say, if you continue to look for the next hop, you will eventually add a PointToPointNetDevice::Receive event to the target node dst. Personally, I'm curious: after each package is sent, the simulation system will be delayed for a period of time txTime. How is this realized. It's actually implemented here——

bool
PointToPointNetDevice::TransmitStart (Ptr<Packet> p)
{
  // The previous settings are omitted

  Time txTime = m_bps.CalculateBytesTxTime (p->GetSize ());  // Here it is, in m_bps is calculated in combination with P - > getsize()
  Time txCompleteTime = txTime + m_tInterframeGap;

  NS_LOG_LOGIC ("Schedule TransmitCompleteEvent in " << txCompleteTime.GetSeconds () << "sec");
  Simulator::Schedule (txCompleteTime, &PointToPointNetDevice::TransmitComplete, this);

  bool result = m_channel->TransmitStart (p, this, txTime);
  if (result == false)
    {
      m_phyTxDropTrace (p);
    }
  return result;
}

After the PointToPointNetDevice::Receive event is triggered, m_promiscCallback callback function. God, it's a headache to see the callback function. After one hour's investigation, I finally found that the callback function is bound to the Node::PromiscReceiveFromDevice function. This function kicks the ball to the ReceiveFromDevice, which executes the m of the node_ Handlers, and this protocol handler... I vomited. Why is it a callback function again! Or callback function family.

Fortunately, it's easy to find. The callback function is executed by Node::RegisterProtocolHandler m_handlers.push_back . I was lucky this time. I had sharp eyes and saw such code in the constructor of NetDeviceTransport at a glance

  m_node->RegisterProtocolHandler(MakeCallback(&NetDeviceTransport::receiveFromNetDevice, this),
                                  L3Protocol::ETHERNET_FRAME_TYPE, m_netDevice,
                                  true /*promiscuous mode*/);

That is, Node::ReceiveFromDevice kicks the ball to NetDeviceTransport::receiveFromNetDevice. This function removes the header and throws it to Transport::receive. This function finally transfers the data to the link layer function LinkService::receivePacket, which kicks GenericLinkService::doReceivePacket. Wait, here's Tingting first. Let's take a look at the code

void
GenericLinkService::doReceivePacket(const Block& packet, const EndpointId& endpoint)
{
  try {
    // Omit the previous code

    bool isReassembled = false;
    Block netPkt;
    lp::Packet firstPkt;
    std::tie(isReassembled, netPkt, firstPkt) = m_reassembler.receiveFragment(endpoint, pkt);
    if (isReassembled) {
      this->decodeNetPacket(netPkt, firstPkt, endpoint);
    }
  }
  catch (const tlv::Error& e) {
    // Omit this part of the code
  }
}

That is, this function is responsible for receiving each Fragment, merging them, and checking their integrity. If it is complete, the decodeNetPacket decoding function can be executed. Just paste the code for the specific content, which will not be discussed in detail here.

void
GenericLinkService::decodeNetPacket(const Block& netPkt, const lp::Packet& firstPkt,
                                    const EndpointId& endpointId)
{
  try {
    switch (netPkt.type()) {
      case tlv::Interest:
        if (firstPkt.has<lp::NackField>()) {
          this->decodeNack(netPkt, firstPkt, endpointId);
        }
        else {
          this->decodeInterest(netPkt, firstPkt, endpointId);
        }
        break;
      case tlv::Data:
        this->decodeData(netPkt, firstPkt, endpointId);
        break;
      default:
        ++this->nInNetInvalid;
        NFD_LOG_FACE_WARN("unrecognized network-layer packet TLV-TYPE " << netPkt.type() << ": DROP");
        return;
    }
  }
  catch (const tlv::Error& e) {
    ++this->nInNetInvalid;
    NFD_LOG_FACE_WARN("packet parse error (" << e.what() << "): DROP");
  }
}

Because we receive Interest packages here, we execute GenericLinkService::decodeInterest. In this function, the netPkt input of Block type is forcibly converted to the Interest type, then the Interest package is labeled according to LP:: packet & firstpkt, and finally LinkService::receiveInterest is executed. Rocket launch, Wuhu!

LinkService::receiveInterest is transferred to the Forwarder::startProcessInterest of the Forwarder layer through the afterReceiveInterest signal, and then to the Forwarder::onIncomingInterest. what? You don't understand here? I've analyzed it before. There's no need to talk about it. If you can't understand it, go back and see it.

Here, the face.sendInterest function and LinkService::sendInterest are executed. Since the producer has arrived and the producer is managed by AppLinkService, it is not genericlinkservice:: dosendenterest, but AppLinkService:: dosendenterest. This function adds the event App::OnInterest, but actually executes Producer::OnInterest according to its virtual characteristics.

Consumer feedback

After receiving the interest package with Producer::OnInterest, the consumer returns a data package, calls AppLinkService::onReceiveData, and then calls LinkService::receiveData to trigger afterReceiveData. As previously analyzed, the signal will be connected to the Forwarder::startProcessData of the Forwarder layer and then to the Forwarder::onIncomingData. See the article for details ndnSIM learning (VII) -- forwarding processing forwarder.cpp, forwarder.hpp , the function flow chart is as follows

Finally, execute onOutgoingData, skip to Face::sendData, then skip to LinkService::sendData, continue to skip to GenericLinkService::doSendData, skip to GenericLinkService::sendNetPacket, skip to GenericLinkService::sendLpPacket, skip to LinkService::sendPacket, and finally it's time to go to the physical transport layer.

Give it to the physical transport layer to execute Transport::send, NetDeviceTransport::doSend, PointToPointNetDevice::Send, where TransmitStart is executed and PointToPointChannel::TransmitStart is kicked. Add a PointToPointNetDevice::Receive event to the target node dst, and execute m_promiscCallback callback function (this callback function is bound to Node::PromiscReceiveFromDevice function).

This function kicks the ball to ReceiveFromDevice, which executes the m of the node_ Handlers, execute NetDeviceTransport::receiveFromNetDevice through the callback function. This function removes the header and throws it to Transport::receive. This function finally transfers the data to the link layer function LinkService::receivePacket. This function kicks GenericLinkService::doReceivePacket and executes the decodeNetPacket decoding function.

okay! Finally to a different place from the front! Because this is a packet, execute decodeData and go to LinkService::receiveData to get the afterReceiveData signal. The signal will be connected to the Forwarder::startProcessData of the Forwarder layer and then to the Forwarder::onIncomingData.

Well, after one lap, I won't take the second lap. Suppose I have reached the place of consumers now. Execute onOutgoingData, skip to Face::sendData, and then skip to LinkService::sendData. Because the consumer has arrived, and the consumer is managed by AppLinkService, it is managed by AppLinkService::doSendData. This function adds the event App::Data, but actually executes Consumer::Data according to its virtual characteristics.

Ness! It's all done!

summary

Although the process is really dizzy, the logic of the whole code is still very clear. The code is divided into network layer, link layer and physical transport layer. Each layer does its own thing. Combined, it is the whole process from consumer sending interest packets to producer returning data packets.

Posted by burn1337 on Tue, 26 Oct 2021 03:07:56 -0700