LWIP (chapter 4)IP协议和源码

IP协议 Internet Protocol(网际互连协议)

IP协议报

在这里插入图片描述
上图是IP 协议报文的首部head
在这里插入图片描述
也就是上图所示的head。IP数据报分为Head+data,为以太网帧的数据段。下图所示的红色箭头。
IP数据报所有内容都存在以太网帧的数据段,以太网传输中不会对该数据段解析。
在这里插入图片描述
回到第一张图,最左边为bit 0,最右边为bit 32,数据发送的时候从bit 0 开始,然后发送第二行的32bit,也是从左边开始发送
1.IP version :IP协议的版本号 V4 值为 4。相同版本的协议才能互相解析。
2.Hdr len: head 的长度,单位是32bit也就是4字节。4bit 2^4=16 数字为0~15。15*32bit = 60字节 为head最大长度
3.Type of service: 路由器使用的字段。这里部关注
4.total len: 总长度理论值16bit 0~65535,head + data的长度 。长度大于底层最大可传输长度是会分包。
5.fragment ID:表示IP包的ID,每发送一个IP包+1。如果IP包被拆分为数个子包(分片),那么分片都是属于这个IP包的,所以ID为同一个。
6.R:保留 DF:是否可以分片 MF:1时代表为分片 0时代表分片最后一个或者是不分片的唯一包
7:time to live:IP数据包能够被转发的次数,防止IP报在网络中无休止的转发
8.protocol: 哪种协议发的IP 包,些就是IP包要提交给谁。
9.校验
10.source IP:源IP地址
11.destination IP:目标IP地址
12.可选head 子段
13.IP data

IP 地址

最初设计互联网络时,为了便于寻址以及层次化构造网络,每个IP地址包括两个标识码(ID),即网络ID和主机ID。同一个物理网络上的所有主机都使用同一个网络ID,网络上的一个主机(包括网络上工作站,服务器等)有一个主机ID与其对应。Internet委员会定义了5种IP地址类型以适合不同容量的网络,即A类~E类。
在这里插入图片描述
net id 网络号,host id 主机号

对于ABC类地址
net id 网络号 可以理解为小区。例如 北京路130号,130号一个小区里面有200户。这200户对应的是host id,每户一个.
小区对应网络号,住户对应每台网络设备。
小区是唯一的,但是住户号都差不多一样,例如1栋1单元101 每个小区都可能有这样一个编号的住户。
怎么看住户是在一个小区,显然是看他们的小区号,也就是net id
IP地址是 48 位的数字,我们怎么知道哪些是 net id,哪些是host id,通过子网掩码
ip 地址和子网掩码求与得出的就是网络号。

DE类地址
用作特殊用途,不进行网络号和主机号的区分。
D类多播通信
E类保留

特殊IP地址
1)127.x.x.x 回环地址 ,这个地址的数据包不会发送到网络上。
2)网络地址,无主机号
3)直接广播地址 主机号全为1
4)直接受限广播地址255.255.255.255
5)网络号全为0,表示本地主机,可以使用这样的地址通信。
6)本网络本机 0.0.0.0

网络地址转换NAT

在这里插入图片描述
一般局域网都是用192.168.x.x IP来标识主机。如果主机A需要访问因特网,它是否需要 一个公网IP呢,这样每台连上因特网的设备都要一个公网IP,那么IP地址很可能不够用。
路由器带NAT功能
路由器(NAT)会给每个连接外网的设备分配一个NAT端口号,A访问外网 用的 路由器的IP+设备端口号
在这里插入图片描述
例如A通过80端口把数据发往212.138.3.12:80端口
结果NAT后实际发送的是路由器IP:NAT 端口号
数据返回的时候回NAT把端口转回A请求的端口号。
在这里插入图片描述

数据流向

在这里插入图片描述
在这里插入图片描述


代码解析
1.判断ip 地址是否为广播地址

/**
 * Determine if an address is a broadcast address on a network interface 
 * 
 * @param addr address to be checked
 * @param netif the network interface against which the address is checked
 * @return returns non-zero if the address is a broadcast address
 */
u8_t ip_addr_isbroadcast(struct ip_addr *addr, struct netif *netif)
{
  u32_t addr2test;

  addr2test = addr->addr;
  /* all ones (broadcast) or all zeroes (old skool broadcast) */
  if ((~addr2test == IP_ADDR_ANY_VALUE) ||  /*FF:FF:FF:FF 为255:255:255:255 当前子网的广播地址*/
      (addr2test == IP_ADDR_ANY_VALUE))     /*主机启动是还没有IP发出的广播*/
    return 1;
  /* no broadcast support on this network interface? */
  else if ((netif->flags & NETIF_FLAG_BROADCAST) == 0)
    /* the given address cannot be a broadcast address
     * nor can we check against any broadcast addresses */
    return 0;
  /* address matches network interface address exactly? => no broadcast */
  else if (addr2test == netif->ip_addr.addr)
    return 0;
  /*  on the same (sub) network... */
  else if (ip_addr_netcmp(addr, &(netif->ip_addr), &(netif->netmask))
         /* ...and host identifier bits are all ones? =>... */
          && ((addr2test & ~netif->netmask.addr) ==
           (IP_ADDR_BROADCAST_VALUE & ~netif->netmask.addr)))/*同网段内的有限广播*/
    /* => network broadcast address */
    return 1;
  else
    return 0;
}

IP 数据报
防止编译器字节对齐对网络包数据结构的破坏

struct ip_hdr {
  /* version / header length / type of service */
  PACK_STRUCT_FIELD(u16_t _v_hl_tos);
  /* total length */
  PACK_STRUCT_FIELD(u16_t _len);
  /* identification */
  PACK_STRUCT_FIELD(u16_t _id);
  /* fragment offset field */
  PACK_STRUCT_FIELD(u16_t _offset);
#define IP_RF 0x8000        /* reserved fragment flag */
#define IP_DF 0x4000        /* dont fragment flag */
#define IP_MF 0x2000        /* more fragments flag */
#define IP_OFFMASK 0x1fff   /* mask for fragmenting bits */
  /* time to live / protocol*/
  PACK_STRUCT_FIELD(u16_t _ttl_proto);
  /* checksum */
  PACK_STRUCT_FIELD(u16_t _chksum);
  /* source and destination IP addresses */
  PACK_STRUCT_FIELD(struct ip_addr src);
  PACK_STRUCT_FIELD(struct ip_addr dest); 
} PACK_STRUCT_STRUCT;


IP层输出
在这里插入图片描述
ip_route寻找一个最优netif
遍历netif链表,查找和ip数据包相同ip子网的netif定义为最优网络发送设备,否则使用默认netif

/* ip_route:
 *
 * Finds the appropriate network interface for a given IP address. It searches the
 * list of network interfaces linearly. A match is found if the masked IP address of
 * the network interface equals the masked IP address given to the function.
 */

struct netif *
ip_route(struct ip_addr *dest)
{
  struct netif *netif;

  for(netif = netif_list; netif != NULL; netif = netif->next) {
    if (ip_addr_netcmp(dest, &(netif->ip_addr), &(netif->netmask))) {
      return netif;
    }
  }

  return netif_default;
}

ip_output_if
发送一个IP数据报,需要安装IP首部的规则打包。

原地址IP,目的地址IP,ttl,tos,上层协议(例如TCP)

err_t
ip_output(struct pbuf *p, struct ip_addr *src, struct ip_addr *dest,
          u8_t ttl, u8_t tos, u8_t proto)
{
  struct netif *netif;

  if ((netif = ip_route(dest)) == NULL) {
    LWIP_DEBUGF(IP_DEBUG, ("ip_output: No route to 0x%"X32_F"\n", dest->addr));
    IP_STATS_INC(ip.rterr);
    return ERR_RTE;
  }

  return ip_output_if(p, src, dest, ttl, tos, proto, netif);
}

首先理清楚
IP层是由IP层的上层调用,所以IP数据报包含 IP HEAD

在这里插入图片描述

如果Playload 大于 下层传输的MTU就需要分片发送

以太网层发送的最大MTU为1500
有一个静态数组
static u8_t buf[LWIP_MEM_ALIGN_SIZE(IP_FRAG_MAX_MTU + MEM_ALIGNMENT - 1)];

在这里插入图片描述
分包的数据每次往里面装填

在这里插入图片描述
分片的问题主要在于IP 分片HEAD 部分的组合,在源Playload 中截取 适当长度的数据装入 单次发送的buf中。

写入的offset值需要是8的倍数 nfb = (mtu - IP_HLEN) / 8;

通过这个函数进行数据的装填。
poff += pbuf_copy_partial(p, (u8_t*)iphdr + IP_HLEN, cop, poff);
装填完成后对新的IP HEAD 部分进行编写

/* Correct header */
    IPH_OFFSET_SET(iphdr, htons(tmp));
    IPH_LEN_SET(iphdr, htons(cop + IP_HLEN));
    IPH_CHKSUM_SET(iphdr, 0);
    IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN));

到目前为止都是使用IP数据报,下层的以太网帧的头部并没有出现
所以需要一个以太网数据报

 header = pbuf_alloc(PBUF_LINK, 0, PBUF_RAM);
    if (header != NULL) {
      pbuf_chain(header, rambuf);
      netif->output(netif, header, dest);
      IPFRAG_STATS_INC(ip_frag.xmit);
      snmp_inc_ipfragcreates();
      pbuf_free(header);

在这里插入图片描述

IP层数据输入
在这里插入图片描述
ip 数据的输入,由netif->input数据递交到ethernet_input
在递交pbuf给IP 层之前会移动playload的位置。
源MAC,目的MAC +类型 6+6+2 = 14字节,所以以太网帧的playload移动量为14
playload的位置移动后提交给IP层的pbuf 为IP数据报,已经移除了以太网帧的首部。

/* ip_input:
 *
 * This function is called by the network interface device driver when an IP packet is
 * received. The function does the basic checks of the IP header such as packet size
 * being at least larger than the header size etc. If the packet was not destined for
 * us, the packet is forwarded (using ip_forward). The IP checksum is always checked.
 *
 * Finally, the packet is sent to the upper layer protocol input function.
 */

void
ip_input(struct pbuf *p, struct netif *inp)

1.ip_input 收到数据报后,首先判断数据报的版本和当前是否一致
2.当前数据报是否是发给自己的,遍历所有netif

当发现IP 首部中数据报是分片的那么就会产生数据的组合

IP 数据包的分片组合

这里是整个IP协议最为复杂的地方
复议一下最底层的数据结构pbuf
在这里插入图片描述
pbuf的装载是在最底层的Low_level_input
中断接收函数 ethernetif_input call Low_level_input
在这里插入图片描述
最底层以太网卡接收数据的类型。
frame = ETH_Get_Received_Frame();
len = frame.length;
buffer = (u8 *)frame.buffer;
到这里来自网络设备的数据和长度值已经获取。
创建一个pbuf p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
这里使用的长度是网卡接收的数据长度。
如果len>可分配内存的块,就会分配出多个pbuf ,通过pbuf->next连接。
pbuf chain 表示的是一个 数据报,简单说就是网卡收到的一帧数据,硬被拆开了几个。这里是一个数据包用了几个内存块存放

IP 数据报的内容过长的时候 以太网帧或者网络阶段上的设备一帧能传输的最大数据小于IP数据报的时候,就需要把IP报分成多个frame发送。Frame 是网络设备层面的帧。

帧>=pbuf,也就是说一个pbuf或者多个pbuf组成一个帧。

IP分片分的是帧。组合也组合的是帧。至于帧有多少个pbuf是另外的事情。一般来说都分了片就不要再分pbuf了,不然台复杂了。

在这里插入图片描述
Fragment ID表示IP数据包 ID。DF:是否可以分片。 MF:1时代表为分片 0时代表分片最后一个或者是不分片的唯一包
Fragment offset 表示的是分片(当前帧)在IP数据报中的位移量(8字节为单位)

那么判断分片是不是一个IP数据报通过ID判断。组合的位置通过Offset判断。
也就是说,需要一个结构表示IP数据报,然后它由很多分片构成,这些片包含位置信息
在一段时间内可能有多个IP数据报在组合,所以需要多个这样的结构。
ip_reassdata

struct ip_reassdata {
  struct ip_reassdata *next;  待组合的IP数据报链表chain
  struct pbuf *p;             分片的pbuf
  struct ip_hdr iphdr;        IP数据报的首部
  u16_t datagram_len;         已收到数据的长度
  u8_t flags;
  u8_t timer;
};

在这里插入图片描述

这是用来表示待组合的IP数据报。
接下来需要用来描述分片和分片的位置信息,分片的位置信息在分片的首部有记录。
LWIP把分片的Pbuf playload 首部前8个字节用来存储 分片信息。
在这里插入图片描述

代码部分
ip_reass(struct pbuf *p)

  当前IP分片包的首部 
  fraghdr = (struct ip_hdr*)p->payload;
  
  获取当前分片的offset,从首部中获取信息
  offset = (ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) * 8;
  
  HL 是首部长度 _LEN是分片z总长度,,等式得到的是数据长度
  len = ntohs(IPH_LEN(fraghdr)) - IPH_HL(fraghdr) * 4;

  /* Check if we are allowed to enqueue more datagrams. */
  pbuf本身可能是chain结构,这个由底层接收的时候决定
  clen = pbuf_clen(p);

后面接着一小段应该没有太大问题

上图所示reassdatagrams 表示待组合IP数据报的链表头,遍历所有IP数据报链表遭到当前分片是属于哪个IP数据报
 for (ipr = reassdatagrams; ipr != NULL; ipr = ipr->next) {
    /* Check if the incoming fragment matches the one currently present
       in the reassembly buffer. If so, we proceed with copying the
       fragment into the buffer. */
       IP地址和IP符合条件那么找到目标待组合IP数据报
    if (IP_ADDRESSES_AND_ID_MATCH(&ipr->iphdr, fraghdr)) {
      LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass: matching previous fragment ID=%"X16_F"\n",
        ntohs(IPH_ID(fraghdr))));
      IPFRAG_STATS_INC(ip_frag.cachehit);
      break;
    }
    ipr_prev = ipr;
  }
   如果没有找到目标IP数据报,那么当前的分片是一个新的待组合IP数据报,开辟一个新的ipr
    if (ipr == NULL) {
  /* Enqueue a new datagram into the datagram queue */
    ipr = ip_reass_enqueue_new_datagram(fraghdr, clen);
    /* Bail if unable to enqueue */
    if(ipr == NULL) {
      goto nullreturn;
    }
  } else {
    找到了如果分片首部中offset为 0表示为第一个分片并且ipr是没有被记录过的
    if (((ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) == 0) && 
      ((ntohs(IPH_OFFSET(&ipr->iphdr)) & IP_OFFMASK) != 0)) {
      /* ipr->iphdr is not the header from the first fragment, but fraghdr is
       * -> copy fraghdr into ipr->iphdr since we want to have the header
       * of the first fragment (for ICMP time exceeded and later, for copying
       * all options, if supported)*/
      SMEMCPY(&ipr->iphdr, fraghdr, IP_HLEN);
    }
  }
if (ip_reass_chain_frag_into_datagram_and_validate(ipr, p)) 

把分片包插入到对应的位置,也就是链表的对应位置。如果完成那么
代表IP数据报已经完整可以向上提交,提交的是pbuf (pbuf chain)这个时候是依靠*next,是不会真正的pbuf chain
所以第一个pbuf必须要具备IP数据报的首部。所以需要覆盖会来。

 /* copy the original ip header back to the first pbuf */
    fraghdr = (struct ip_hdr*)(ipr->p->payload);
    SMEMCPY(fraghdr, &ipr->iphdr, IP_HLEN);

把所有的pbuf 形成 pbuf chain

  while(r != NULL) {
      iprh = (struct ip_reass_helper*)r->payload;

      /* hide the ip header for every succeding fragment */
      pbuf_header(r, -IP_HLEN);
      pbuf_cat(p, r);
      r = iprh->next_pbuf;
    }

插入函数

ip_reass_chain_frag_into_datagram_and_validate
计算长度和offset
  /* Extract length and fragment offset from current fragment */
  fraghdr = (struct ip_hdr*)new_p->payload; 
  len = ntohs(IPH_LEN(fraghdr)) - IPH_HL(fraghdr) * 4;
  offset = (ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) * 8;
  计算 start 和end 位置
  iprh = (struct ip_reass_helper*)new_p->payload;
  iprh->next_pbuf = NULL;
  iprh->start = offset;
  iprh->end = offset + len;

在这里插入图片描述

接着 for (q = ipr->p; q != NULL;)
q是ipr
iprh_tmp = (struct ip_reass_helper*)q->payload;
所以iprh_tmp 是当前IP数据报母链第一个分片

for (q = ipr->p; q != NULL;) {
    iprh_tmp = (struct ip_reass_helper*)q->payload;
    如果ipr的第一个分片开始位置大于新分片起始位置,说明新分片需要插入到第一个位置
    if (iprh->start < iprh_tmp->start) {
      /* the new pbuf should be inserted before this */
     所以新分片的下一个是旧分片1
      iprh->next_pbuf = q;
      if (iprh_prev != NULL) {
        /* not the fragment with the lowest offset */
#if IP_REASS_CHECK_OVERLAP
        if ((iprh->start < iprh_prev->end) || (iprh->end > iprh_tmp->start)) {
          /* fragment overlaps with previous or following, throw away */
          goto freepbuf;
        }
#endif /* IP_REASS_CHECK_OVERLAP */
        iprh_prev->next_pbuf = new_p;
      } else {
        /* fragment with the lowest offset */
        母链指向新分片,完成插入
        ipr->p = new_p;
      }
      break;
      如果offset start位置相等说明是同一个分片
    } else if(iprh->start == iprh_tmp->start) {
      /* received the same datagram twice: no need to keep the datagram */
      goto freepbuf;
#if IP_REASS_CHECK_OVERLAP
    重叠是有问题的,不做处理
    } else if(iprh->start < iprh_tmp->end) {
      /* overlap: no need to keep the new datagram */
      goto freepbuf;
#endif /* IP_REASS_CHECK_OVERLAP */
    } else {
      /* Check if the fragments received so far have no wholes. */
      if (iprh_prev != NULL) {
        if (iprh_prev->end != iprh_tmp->start) {
          /* There is a fragment missing between the current
           * and the previous fragment */
          valid = 0;
        }
      }
    }
    指向源母联下一个分片
    q = iprh_tmp->next_pbuf;
    iprh_prev = iprh_tmp;
  }
链表前部的指定
if (q == NULL) {
    if (iprh_prev != NULL) {
      /* this is (for now), the fragment with the highest offset:
       * chain it to the last fragment */
#if IP_REASS_CHECK_OVERLAP
      LWIP_ASSERT("check fragments don't overlap", iprh_prev->end <= iprh->start);
#endif /* IP_REASS_CHECK_OVERLAP */
      iprh_prev->next_pbuf = new_p;
      if (iprh_prev->end != iprh->start) {
        valid = 0;
      }
    } else {
#if IP_REASS_CHECK_OVERLAP
      LWIP_ASSERT("no previous fragment, this must be the first fragment!",
        ipr->p == NULL);
#endif /* IP_REASS_CHECK_OVERLAP */
      /* this is the first fragment we ever received for this ip datagram */
      ipr->p = new_p;
    }
  }

最后一部分对 分片组合完成的处理

 /* At this point, the validation part begins: */
  /* If we already received the last fragment */
  if ((ipr->flags & IP_REASS_FLAG_LASTFRAG) != 0) {
    /* and had no wholes so far */
    if (valid) {
      /* then check if the rest of the fragments is here */
      /* Check if the queue starts with the first datagram */
      if (((struct ip_reass_helper*)ipr->p->payload)->start != 0) {
        valid = 0;
      } else {
        /* and check that there are no wholes after this datagram */
        iprh_prev = iprh;
        q = iprh->next_pbuf;
        while (q != NULL) {
          iprh = (struct ip_reass_helper*)q->payload;
          if (iprh_prev->end != iprh->start) {
            valid = 0;
            break;
          }
          iprh_prev = iprh;
          q = iprh->next_pbuf;
        }
        /* if still valid, all fragments are received
         * (because to the MF==0 already arrived */
        if (valid) {
          LWIP_ASSERT("sanity check", ipr->p != NULL);
          LWIP_ASSERT("sanity check",
            ((struct ip_reass_helper*)ipr->p->payload) != iprh);
          LWIP_ASSERT("validate_datagram:next_pbuf!=NULL",
            iprh->next_pbuf == NULL);
          LWIP_ASSERT("validate_datagram:datagram end!=datagram len",
            iprh->end == ipr->datagram_len);
        }
      }
    }

flow 总结
1.ETH FRAME INPUT
在这里插入图片描述
input 向上递交数据
在这里插入图片描述
IP层数据处理,与递交
在这里插入图片描述
2.IP OUTPUT
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值