欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

LWIP学习笔记(2)---IP协议实现细节

程序员文章站 2022-07-01 15:31:18
...

IP头

收到的数据首先保存在pbuf结构中,

/* The IPv4 header */
struct ip_hdr {
  /* version / header length */
  PACK_STRUCT_FLD_8(u8_t _v_hl);
  /* type of service */
  PACK_STRUCT_FLD_8(u8_t _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 0x8000U        /* reserved fragment flag */
#define IP_DF 0x4000U        /* don't fragment flag */
#define IP_MF 0x2000U        /* more fragments flag */
#define IP_OFFMASK 0x1fffU   /* mask for fragmenting bits */
  /* time to live */
  PACK_STRUCT_FLD_8(u8_t _ttl);
  /* protocol*/
  PACK_STRUCT_FLD_8(u8_t _proto);
  /* checksum */
  PACK_STRUCT_FIELD(u16_t _chksum);
  /* source and destination IP addresses */
  PACK_STRUCT_FLD_S(ip4_addr_p_t src);
  PACK_STRUCT_FLD_S(ip4_addr_p_t dest);
} PACK_STRUCT_STRUCT;

接收数据流程 ip_input

ip_input()调用 ip4_input() 或者 ip6_input().

ip4_input
ip4_input(struct pbuf *p, struct netif *inp)

在ip4_input()中,首先拿到缓冲区的实际数据:
iphdr = (struct ip_hdr *)p->payload;
接下来解析数据:

iphdr_hlen = IPH_HL(iphdr); //获取32位字数的IP头长度
iphdr_hlen *= 4;  //计算ip头长度
iphdr_len = lwip_ntohs(IPH_LEN(iphdr));  //以字节为单位获取ip长度

如果ip头长度超过第一个pbuf长度或者ip长度超过总pbuf长度,释放pbuf,返回.

接下来检查校验和:

IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_IP) {
      if (inet_chksum(iphdr, iphdr_hlen) != 0) {

    LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
      ("Checksum (0x%"X16_F") failed, IP packet dropped.\n", inet_chksum(iphdr, iphdr_hlen)));
    ip4_debug_print(p);
    pbuf_free(p);
    IP_STATS_INC(ip.chkerr);
    IP_STATS_INC(ip.drop);
    MIB2_STATS_INC(mib2.ipinhdrerrors);
    return ERR_OK;
  }
}

然后复制ip地址到ip_addr_t

ip_addr_copy_from_ip4(ip_data.current_iphdr_dest, iphdr->dest);
ip_addr_copy_from_ip4(ip_data.current_iphdr_src, iphdr->src);

检验数据包是否匹配,即这个包是不是给我的,
是我的包, 置 netif = inp; 给netif结构体赋值,netif结构用来描述一个硬件网络接口.
不是我的包, 置 netif = NULL;

如果netif = NULL; 在判断是否需要转发该数据包:

if (!ip4_addr_isbroadcast(ip4_current_dest_addr(), inp)) {
	ip4_forward(p, iphdr, inp);
} else{
        IP_STATS_INC(ip.drop);
        MIB2_STATS_INC(mib2.ipinaddrerrors);
        MIB2_STATS_INC(mib2.ipindiscards);
      }
      pbuf_free(p);
      return ERR_OK;  
  }

接下来判断包是否由多个分片组成.
是的话,组装数据包 p = ip4_reass§;

接下来判断端口号,根据端口号把数据交付给上层.

#define IP_PROTO_ICMP    1
  #define IP_PROTO_IGMP    2
  #define IP_PROTO_UDP     17
  #define IP_PROTO_UDPLITE 136
  #define IP_PROTO_TCP     6
   // 分别对应:
    icmp_input(p, inp);
    igmp_input(p, inp);
    udp_input(p, inp);
    tcp_input(p, inp);
ip6_input

ip6_input(struct pbuf *p, struct netif *inp)

首先拿缓冲区数据

ip6hdr = (struct ip6_hdr *)p->payload;

然后判断协议版本

if (IP6H_V(ip6hdr) != 6) {
      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("IPv6 packet dropped due to bad version number %"U32_F"\n",
          IP6H_V(ip6hdr)));
      pbuf_free(p);
      IP6_STATS_INC(ip6.err);
      IP6_STATS_INC(ip6.drop);
      return ERR_OK;
    }

接下来判断长度:
如果ip头长度超过第一个pbuf长度或者ip长度超过总pbuf长度,释放pbuf,返回.

然后复制 ipv6地址到ip6_addr_t

ip_addr_copy_from_ip6(ip_data.current_iphdr_dest, ip6hdr->dest);
 ip_addr_copy_from_ip6(ip_data.current_iphdr_src, ip6hdr->src);

接下来判断ipv6地址:
不接受虚拟ipv4地址,不接受组播源地址

接下来检验数据包是否匹配,即这个包是不是给我的.
然后设置 netif = inp;

接下来处理扩展包头

最后根据端口号交付数据.

发送收据 流程 ip_route 和 ip_output_if
ip_route
 #define ip_route(src, dest) \
         (IP_IS_V6(dest) ? \
        ip6_route(ip_2_ip6(src), ip_2_ip6(dest)) : \
        ip4_route_src(ip_2_ip4(dest), ip_2_ip4(src)))

ip4_route_src () 检测源地址src是否为NULL,如果为NULL ,调用 ip4_route()
获取 src 并返回 netif. 如果不为NULL,直接返回netif.

ip_output_if
 #define ip_output_if(p, src, dest, ttl, tos, proto, netif) \
         (IP_IS_V6(dest) ? \
         ip6_output_if(p, ip_2_ip6(src), ip_2_ip6(dest), ttl, tos, proto, netif) : \
         ip4_output_if(p, ip_2_ip4(src), ip_2_ip4(dest), ttl, tos, proto, netif))

ip4_output_if() 调用 ip4_output_if_opt(p, src, dest, ttl, tos, proto, netif, NULL, 0); 在调用 ip4_output_if_src(p, src_used, dest, ttl, tos, proto, netif);
在调用 ip4_output_if_opt_src(p, src, dest, ttl, tos, proto, netif, NULL, 0)
生成ip头.

获取首部长度

u16_t ip_hlen = IP_HLEN;

设置选项 options,同时开始计算校验和 chk_sum

MEMCPY(p->payload, ip_options, optlen);
 if (optlen < optlen_aligned) {
          /* zero the remaining bytes */
         memset(((char*)p->payload) + optlen, 0, optlen_aligned - optlen);
       }

生成ip头

iphdr = (struct ip_hdr *)p->payload;

设置生存时间和协议类型

IPH_TTL_SET(iphdr, ttl);
IPH_PROTO_SET(iphdr, proto);

设置首部长度和服务类型tos

IPH_VHL_SET(iphdr, 4, ip_hlen / 4);
IPH_TOS_SET(iphdr, tos);

设置总长度

IPH_LEN_SET(iphdr, lwip_htons(p->tot_len));

设置偏移量和标识

IPH_OFFSET_SET(iphdr, 0);
IPH_ID_SET(iphdr, lwip_htons(ip_id));

设置源地址和目的地址

ip4_addr_copy(iphdr->dest, *dest);
ip4_addr_copy(iphdr->src, *src);

设置校验和

PH_CHKSUM_SET(iphdr, inet_chksum(iphdr, ip_hlen));

最后调用 netif->output(netif, p, dest); 把数据包交付给链路层