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> 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
  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请求然后等待回应。


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()方法从套接字读取数据。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容