Internet Stack

Internet stack aggregation

ns-3源代码目录src / internet提供TCP / IPv4和IPv6相关组件的实现。 这些包括IPv4,ARP,UDP,TCP,IPv6,Neighbor Discovery和其他相关协议。

Internet Nodes不是类Node的子类; 有一些和IP相关的对象会整合到节点上。 它们可以通过手工放在一起,或者通过一个辅助函数InternetStackHelper :: Install()来完成,它将以下参数传递给作为参数的所有节点:

void
InternetStackHelper::Install (Ptr node) const
{
  if (m_ipv4Enabled)
    {
      /* IPv4 stack */
      if (node->GetObject () != 0)
        {
          NS_FATAL_ERROR ("InternetStackHelper::Install (): Aggregating "
                          "an InternetStack to a node with an existing Ipv4 object");
          return;
        }

      CreateAndAggregateObjectFromTypeId (node, "ns3::ArpL3Protocol");
      CreateAndAggregateObjectFromTypeId (node, "ns3::Ipv4L3Protocol");
      CreateAndAggregateObjectFromTypeId (node, "ns3::Icmpv4L4Protocol");
      // Set routing
      Ptr ipv4 = node->GetObject ();
      Ptr ipv4Routing = m_routing->Create (node);
      ipv4->SetRoutingProtocol (ipv4Routing);
    }

  if (m_ipv6Enabled)
    {
      /* IPv6 stack */
      if (node->GetObject () != 0)
        {
          NS_FATAL_ERROR ("InternetStackHelper::Install (): Aggregating "
                          "an InternetStack to a node with an existing Ipv6 object");
          return;
        }

      CreateAndAggregateObjectFromTypeId (node, "ns3::Ipv6L3Protocol");
      CreateAndAggregateObjectFromTypeId (node, "ns3::Icmpv6L4Protocol");
      // Set routing
      Ptr ipv6 = node->GetObject ();
      Ptr ipv6Routing = m_routingv6->Create (node);
      ipv6->SetRoutingProtocol (ipv6Routing);

      /* register IPv6 extensions and options */
      ipv6->RegisterExtensions ();
      ipv6->RegisterOptions ();
    }

  if (m_ipv4Enabled || m_ipv6Enabled)
    {
      /* UDP and TCP stacks */
      CreateAndAggregateObjectFromTypeId (node, "ns3::UdpL4Protocol");
      node->AggregateObject (m_tcpFactory.Create ());
      Ptr factory = CreateObject ();
      node->AggregateObject (factory);
    }
}
 
 

在ns-3(TCP,IP路由)中存在多个实现的情况下,这些对象由factory object(TCP)或routing helper (m_routing)添加。

请注意,路由协议在此功能之外进行配置和设置。 默认情况下,添加以下协议:

void InternetStackHelper::Initialize ()
{
  SetTcp ("ns3::TcpL4Protocol");
  Ipv4StaticRoutingHelper staticRouting;
  Ipv4GlobalRoutingHelper globalRouting;
  Ipv4ListRoutingHelper listRouting;
  Ipv6ListRoutingHelper listRoutingv6;
  Ipv6StaticRoutingHelper staticRoutingv6;
  listRouting.Add (staticRouting, 0);
  listRouting.Add (globalRouting, -10);
  listRoutingv6.Add (staticRoutingv6, 0);
  SetRoutingHelper (listRouting);
  SetRoutingHelper (listRoutingv6);
}

By default, IPv4 and IPv6 are enabled.

Internet Node structure

一个Internet Stack Node包含如下几个组件:

Layer-3 protocols

在TCP/IP网络体系中,在网络接口之上的为网络层IP,包括IPv4,IPv6,ARP等。 Ipv4L3Protocol类是网络层IP实现类,其公共接口通常是Ipv4类,但Ipv4L3Protocol公共API现在也在内部使用。

在Ipv4L3Protocol类中,有一个方法是Receive()。网络层IP从下层获取分组,然后分析出其源地址IP和目的地址IP:

/**
  * Lower layer calls this method after calling L3Demux::Lookup
  * The ARP subclass needs to know from which NetDevice this
  * packet is coming to:
  *    - implement a per-NetDevice ARP cache
  *    - send back arp replies on the right device
  */
void Receive( Ptr device, Ptr p, uint16_t protocol,
const Address &from, const Address &to, NetDevice::PacketType packetType);

首先请注意,Receive()函数对类Node中的ReceiveCallback具有匹配的签名。Receive()函数第一个参数指向Device节点,这个Device是在配置节点前预先安装在Node节点中的协议,而这个函数能够被下层自动调用的前提是通过如下代码来实现的:

RegisterProtocolHandler ( MakeCallback (&Ipv4Protocol::Receive, ipv4),Ipv4L3Protocol::PROT_NUMBER, 0);

Ipv4L3Protocol对象被聚合到Node中; 每个节点只有一个这样的Ipv4L3Protocol对象。高层的协议(如TCP)要发送一个TCP数组分组给Ipv4L3Protocol对象是通过调用函数GetObject ()来获取该节点的底层协议的,如下所示:

Ptr ipv4 = m_node->GetObject ();
if (ipv4 != 0)
  {
    ipv4->Send (packet, saddr, daddr, PROT_NUMBER);
  }

这个类很好地演示了我们在ns-3中利用的两种技术将对象绑定在一起:回调和对象聚合。

一旦IPv4路由确定一个数据包是发送到本地节点的,IPv4对象就会把分组发送给上层协议。 这是通过以下功能完成的:

void
Ipv4L3Protocol::LocalDeliver (Ptr packet, Ipv4Header const&ip, uint32_t iif)

第一步是根据IP协议号找到正确的Ipv4L4Protocol对象。例如,TCP以协议号6注册在demux中。最后,调用Ipv4L4Protocol上的Receive()函数(如TcpL4Protocol :: Receive)。

我们还没有引入Ipv4Interface类。基本上,每个NetDevice都会有一个IP地址与其对应。在Linux中,这个类的Ipv4Interface大致对应于struct in_device;主要目的是提供一个关于接口的详细信息。

所有的类都有适当的Traces,以跟踪发送,接收和丢失的数据包。鼓励用户使用它们,以便找出是否(以及在哪里)丢弃数据包。一个常见的错误是在发送数据包(例如ARP队列)时忘记本地队列的影响。使用UDP发送巨大数据包时就会给用户带来问题。 由于ARP缓存挂起队列是有限的(3个数据报),并且IP分组可能被分段,在发送数据时就有可能造成数据的溢出。在这种情况下,将ARP缓存暂挂大小增加到适当的值是很有用的,例如:

Config::SetDefault ("ns3::ArpCache::PendingQueueSize", UintegerValue (MAX_BURST_SIZE/L2MTU*3));

IPv6实施遵循类似的体系结构。 双堆栈节点(支持IPv4和IPv6)将允许IPv6套接字接收IPv4连接,如同标准的双堆栈系统一样。 绑定并监听IPv6端点的套接字可以接收IPv4连接,并将远程地址作为IPv4映射地址返回。 目前不支持IPV6_V6ONLY套接字选项。

Layer-4 protocols and sockets

在TCP/IP中,网络层的上层为传输层,下面讨论如何把传输层协议和套接字以及应用绑定在一起,每一个传输层协议的实现都是一个套接字工厂,每一个应用程序都需要一个套接字。

例如,要创建一个UDP套接字,应用程序将使用如下的代码片段:

Ptr udpSocketFactory = GetNode ()->GetObject ();
Ptr m_socket = socketFactory->CreateSocket ();
m_socket->Bind (m_local_address);
...

首先。第一行代码是从Node节点中获取一个UDP套接字工厂指针来创建一个套接字(第二行),然后通过第三行代码把套接字绑定到地址上,如果作为参数的地址已经绑定了其他套接字,则会出现错误而不是覆盖。Bind(void)和Bind6(void)函数分别绑定到“0.0.0.0”和“::”。

通过BindToNetDevice(Ptr netdevice)函数也可以将套接字绑定到特定的NetDevice。 BindToNetDevice(Ptr netdevice)会将套接字绑定到“0.0.0.0”和“::”(相当于调用Bind()和Bind6(),除非套接字已经绑定到特定的地址)。 正确的顺序是:

 Ptr udpSocketFactory = GetNode ()->GetObject ();
 Ptr m_socket = socketFactory->CreateSocket ();
 m_socket->BindToNetDevice (n_netDevice);
...

or

Ptr udpSocketFactory = GetNode ()->GetObject ();
Ptr m_socket = socketFactory->CreateSocket ();
m_socket->Bind (m_local_address);
m_socket->BindToNetDevice (n_netDevice);
...

下面的会产生错误:

Ptr udpSocketFactory = GetNode ()->GetObject ();
Ptr m_socket = socketFactory->CreateSocket ();
m_socket->BindToNetDevice (n_netDevice);
m_socket->Bind (m_local_address);
...

到目前为止,我们已经描述了一个套接字工厂(例如class Udp)和一个可以专用的套接字(例如,class UdpSocket)。还有几个关键的对象用来完成分解数据包成为一个或多个sockets。此任务中的关键对象是class Ipv4EndPointDemux。此分解器存储类Ipv4EndPoint的对象。该类包含与套接字关联的寻址/端口元组(本地端口,本地地址,目标端口,目标地址)和接收回调。这个接收回调具有由套接字注册的接收功能。 Ipv4EndPointDemux的Lookup()函数返回一个Ipv4EndPoint对象的列表(可能有一个列表,因为多个套接字可能与数据包匹配)。第4层协议将数据包复制到每个Ipv4EndPoint,并调用其ForwardUp()方法,然后调用由套接字注册的Receive()函数。
在真实系统上使用套接字API时出现的问题是需要使用某种类型的I / O(例如,阻塞,非阻塞,异步...)来管理从套接字读取。 ns-3为套接字I / O实现一个异步模型;应用程序设置一个回调,通知接收到的数据准备被读取,当数据可用时,回调由传输协议调用。这个回调被指定如下:

void Socket::SetRecvCallback (Callback,Ptr,const Address&> receivedData);

正在接收的数据在分组数据缓冲区中传送。 一个示例用法是在PacketSink类中:

m_socket->SetRecvCallback (MakeCallback(&PacketSink::HandleRead, this));

总结一下,UDP内部实现是这样的:

  • UdpImpl类完成UDP socket工厂的功能
  • UdpL4Protocol类实现了独立于套接字的协议逻辑
  • UdpSocketImpl类实现UDP的套接字特定方面
  • Ipv4EndPoint的类,用于存储与套接字关联的寻址元组(本地端口,本地地址,目标端口,目标地址)以及套接字的接收回调。

Example path of a packet

Step in packet sending process
  1. Application在之前会创建一个socket(here,UDPSocket)它会调用Socket::Send()。真实数据或伪数据会通过API传递。
  2. Socket::Send转发给UdpSocketImpl::DoSend(),随后转发给UdpSocketImpl::DoSendTo().这些函数设置正确的源地址和目的地址,处理socket调用,例如bind()和connect()。然后UdpL4Protocol::Send()方法会被调用。在真实实现中,socket必须查询Ipv4路由系统找到正确的源地址去匹配目的地址。
  3. UdpL4Protocol是UDP实现的与套接字无关的协议逻辑。 Send()方法加入UDP头,初始化校验和,并发送数据包给Ipv4层。数据包并不是直接发送给Ipv4层的,而是通过叫做m_downTarget的callback。在这例子中,downTarget是Ipv4L3Protocol,但是也可以是一些其他层。
  4. Ipv4L3Protocol加入IP头并加数据包传递给正确的Ipv4Interface实例,根据UDP层传递下来的路由。在这个例子中,是一个支持Arp的设备。
  5. Ipv4Interface会查找MAC地址如果此NetDevice技术支持Arp。如果有缓存,则发送包给NetDevice,否则它会首先开始Arp请求然后等待回应。


    Internet Stack_第1张图片
Step in packet receiving process
  1. NetDevice调用注册在Node::m_receiveCallback中的方法
  2. 这是一个典型的Node::ReceiveFromDevice()功能
  3. Node::ReceiveFromDevice存储了一系列回调(协议头),可以通过协议号和设备查找。在这个例子中,Ipv4L3Protocol::Receive()会被找到且调用
  4. Ipv4L3Protocol已出IP头,检查校验和,并将数据包传递由Ipv4L3Protocol注册的Ipv4RoutingProtocol。在这个例子中的路由协议会决定这个数据包是给本地的,所以它调用Ipv4L3Protocol::LocalDeliver().这个方法查找协议(在这里是UDP)并且调用Receive()方法。
  5. UdpL4Protocol在这里UDP实现的独立于套接字的协议逻辑。Receive()方法会移除UDP头并且查找每个流的语义状态,也就是在Ipv4EndPointDemux中存储的一个或多个IpV4EndPoint对象(src addr,src port,dest addr,dest port)。在结束时,调用Ipv4EndPoint::ForwardUp()方法。
  6. Ipv4EndPoint有一个回调,在这个回调中,Socket对象可以注册receive方法。在这里,这个回调调用UdpSocketImpl::ForwardUp()方法
    7.当数据准备被读取时,UdpSocketImpl调用Applicaiton设置的Recv() 回调。应用程序会调用套接字Recv()或者RecvFrom()方法从套接字读取数据。


    Internet Stack_第2张图片

你可能感兴趣的:(Internet Stack)