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> node) const
{
if (m_ipv4Enabled)
{
/* IPv4 stack */
if (node->GetObject<Ipv4> () != 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> ipv4 = node->GetObject<Ipv4> ();
Ptr<Ipv4RoutingProtocol> ipv4Routing = m_routing->Create (node);
ipv4->SetRoutingProtocol (ipv4Routing);
}
if (m_ipv6Enabled)
{
/* IPv6 stack */
if (node->GetObject<Ipv6> () != 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> ipv6 = node->GetObject<Ipv6> ();
Ptr<Ipv6RoutingProtocol> 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<Object> ());
Ptr<PacketSocketFactory> factory = CreateObject<PacketSocketFactory> ();
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<NetDevice> device, Ptr<const Packet> 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<Ipv4L3Protocol> ()来获取该节点的底层协议的,如下所示:
Ptr<Ipv4L3Protocol> ipv4 = m_node->GetObject<Ipv4L3Protocol> ();
if (ipv4 != 0)
{
ipv4->Send (packet, saddr, daddr, PROT_NUMBER);
}
这个类很好地演示了我们在ns-3中利用的两种技术将对象绑定在一起:回调和对象聚合。
一旦IPv4路由确定一个数据包是发送到本地节点的,IPv4对象就会把分组发送给上层协议。 这是通过以下功能完成的:
void
Ipv4L3Protocol::LocalDeliver (Ptr<const Packet> 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<Udp> udpSocketFactory = GetNode ()->GetObject<Udp> ();
Ptr<Socket> m_socket = socketFactory->CreateSocket ();
m_socket->Bind (m_local_address);
...
首先。第一行代码是从Node节点中获取一个UDP套接字工厂指针来创建一个套接字(第二行),然后通过第三行代码把套接字绑定到地址上,如果作为参数的地址已经绑定了其他套接字,则会出现错误而不是覆盖。Bind(void)和Bind6(void)函数分别绑定到“0.0.0.0”和“::”。
通过BindToNetDevice(Ptr <NetDevice> netdevice)函数也可以将套接字绑定到特定的NetDevice。 BindToNetDevice(Ptr <NetDevice> netdevice)会将套接字绑定到“0.0.0.0”和“::”(相当于调用Bind()和Bind6(),除非套接字已经绑定到特定的地址)。 正确的顺序是:
Ptr<Udp> udpSocketFactory = GetNode ()->GetObject<Udp> ();
Ptr<Socket> m_socket = socketFactory->CreateSocket ();
m_socket->BindToNetDevice (n_netDevice);
...
or
Ptr<Udp> udpSocketFactory = GetNode ()->GetObject<Udp> ();
Ptr<Socket> m_socket = socketFactory->CreateSocket ();
m_socket->Bind (m_local_address);
m_socket->BindToNetDevice (n_netDevice);
...
下面的会产生错误:
Ptr<Udp> udpSocketFactory = GetNode ()->GetObject<Udp> ();
Ptr<Socket> 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<void, Ptr<Socket>,Ptr<Packet>,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
- Application在之前会创建一个socket(here,UDPSocket)它会调用Socket::Send()。真实数据或伪数据会通过API传递。
- Socket::Send转发给UdpSocketImpl::DoSend(),随后转发给UdpSocketImpl::DoSendTo().这些函数设置正确的源地址和目的地址,处理socket调用,例如bind()和connect()。然后UdpL4Protocol::Send()方法会被调用。在真实实现中,socket必须查询Ipv4路由系统找到正确的源地址去匹配目的地址。
- UdpL4Protocol是UDP实现的与套接字无关的协议逻辑。 Send()方法加入UDP头,初始化校验和,并发送数据包给Ipv4层。数据包并不是直接发送给Ipv4层的,而是通过叫做m_downTarget的callback。在这例子中,downTarget是Ipv4L3Protocol,但是也可以是一些其他层。
- Ipv4L3Protocol加入IP头并加数据包传递给正确的Ipv4Interface实例,根据UDP层传递下来的路由。在这个例子中,是一个支持Arp的设备。
-
Ipv4Interface会查找MAC地址如果此NetDevice技术支持Arp。如果有缓存,则发送包给NetDevice,否则它会首先开始Arp请求然后等待回应。
Step in packet receiving process
- NetDevice调用注册在Node::m_receiveCallback中的方法
- 这是一个典型的Node::ReceiveFromDevice()功能
- Node::ReceiveFromDevice存储了一系列回调(协议头),可以通过协议号和设备查找。在这个例子中,Ipv4L3Protocol::Receive()会被找到且调用
- Ipv4L3Protocol已出IP头,检查校验和,并将数据包传递由Ipv4L3Protocol注册的Ipv4RoutingProtocol。在这个例子中的路由协议会决定这个数据包是给本地的,所以它调用Ipv4L3Protocol::LocalDeliver().这个方法查找协议(在这里是UDP)并且调用Receive()方法。
- UdpL4Protocol在这里UDP实现的独立于套接字的协议逻辑。Receive()方法会移除UDP头并且查找每个流的语义状态,也就是在Ipv4EndPointDemux中存储的一个或多个IpV4EndPoint对象(src addr,src port,dest addr,dest port)。在结束时,调用Ipv4EndPoint::ForwardUp()方法。
-
Ipv4EndPoint有一个回调,在这个回调中,Socket对象可以注册receive方法。在这里,这个回调调用UdpSocketImpl::ForwardUp()方法
7.当数据准备被读取时,UdpSocketImpl调用Applicaiton设置的Recv() 回调。应用程序会调用套接字Recv()或者RecvFrom()方法从套接字读取数据。