【lwip】09-IPv4协议&超全源码实现分析


目录
  • https://www.it610.com/article/1290714377560858624.htm

    检验和计算可能由网络网络驱动,协议驱动,甚至是硬件完成。

    高层校验通常是由协议执行,并将完成后的包转交给硬件。

    比较新的网络硬件可以执行一些高级功能,如IP检验和计算,这被成为checksum offloading。网络驱动不会计算校验和,只是简单将校验和字段留空或填入无效信息,交给硬件计算。

    发送数据时首部校验和计算:二进制反码求和。

    • 把IP数据包的校验和字段置为全0。
    • 将首部中的每 2 个字节当作一个数,依次求和。
    • 把结果取反码。
    • 把得到的结果存入校验和字段中。

    接收数据时,首部校验和验证过程:

    • 首部中的每 2 个字节当作一个数,依次进行求和,包括校验和字段。
    • 检查计算出的校验和的结果是否全为1(反码应为16个0)。
    • 如果等于零,说明被整除,校验和正确。否则,校验和就是错误的,协议栈要抛弃这个数据包。

    为什么计算出的校验和结果全为1?

    因为如果校验依时次求和,不包含校验和字段的话,得出的值就是校验和字段的反码。

    校验和的反码和校验和求和,当然是全1啦。

    9.3.11 二进制反码求和

    IP/ICMP/IGMP/TCP/UDP等协议的校验和算法都是相同的。

    二进制反码求和:(求和再反码,结果一致)

    • 对两个二进制数进行加法运算。
    • 加法规则:0+0=0,0+1=1,1+1=10(进位1加到下一bit)。
    • 若最高两位相加仍然有进位,则在最后的结果中+1即可。
    • 对最终结果取反码。

    相关源码参考LwIP\core\inet_chksum.c中的lwip_standard_chksum()

    这里提取版本3分析:

    • 前期先确保4字节对齐,如果不是4字节对齐,就补到4字节对齐。

    • 后面采用32 bit累加。溢出后,在低位+1。

      • 为什么?:这里读者可能会有个疑问,IP数据包的校验和不是要求16 bit求和的吗?这里为什么能用32 bit求和?
      • 答:起始要求是16 bit,但是实际计算时只要大于16 bit即可,因为到最后,可以把高位折叠加到低位。
      • 例子:按32bit累加,溢出就在低位+1。其实就是两组两个(高、低)16 bit对应累加,低16 bit累加的进位给高16 bit里加回1了。而高16 bit累加的进位在底16 bit里加回1了(手动)。这样,累加到最后剩下32bit。把高16bit和低16bit进行累加,进位再加1即可快速得到16bit的校验和。
    • 数据后部分可能不是8字节对齐,所以剩余的字节也需要16bit校验和处理。

    思路图:

    由于目的是16 bit的校验和。其实可以看成两组2个8bit对应相加,低8bit组进位给高8bit组,高8bit组进位给低8bit组。所以相加值是对应高、低8bit相互独立的。

    而下面函数就是利用这个特性,如果首字节为奇地址,先单独取出来放到t的高地址,因为后续的统计字节顺序是返的。等待全部统计完毕后,再把两个字节顺序调换即可。

    如果是偶地址开始,那符合校验和规则,最后不需要调换字节顺序。

    #if (LWIP_CHKSUM_ALGORITHM == 3) /* Alternative version #3 */
    /**
     * An optimized checksum routine. Basically, it uses loop-unrolling on
     * the checksum loop, treating the head and tail bytes specially, whereas
     * the inner loop acts on 8 bytes at a time.
     *
     * @arg start of buffer to be checksummed. May be an odd byte address.
     * @len number of bytes in the buffer to be checksummed.
     * @return host order (!) lwip checksum (non-inverted Internet sum)
     *
     * by Curt McDowell, Broadcom Corp. December 8th, 2005
     */
    u16_t
    lwip_standard_chksum(const void *dataptr, int len)
    {
      const u8_t *pb = (const u8_t *)dataptr; /* 取数据的地址 */
      const u16_t *ps;
      u16_t t = 0;
      const u32_t *pl;
      u32_t sum = 0, tmp;
    
      int odd = ((mem_ptr_t)pb & 1); /* 判断是否为奇地址 */
    
      if (odd && len > 0) { /* 如果不是2直接对齐 */
        /* 缓存奇地址上的字节,存于 t 的高位。数据地址偏移为偶,2字节对齐。 */
        /* 存到高位是为了和后面字节序保持一致,方便在最后一次性更正。 */
        ((u8_t *)&t)[1] = *pb++;
        len--; /* 字节数-1 */
      }
    
      /* 2字节对齐的数据起始地址 */
      ps = (const u16_t *)(const void *)pb;
    
      if (((mem_ptr_t)ps & 3) && len > 1) {/* 如果不是4字节对齐 */
        /* 把多出来的两字节保存到sum */
        sum += *ps++;
        len -= 2;
      }
    
      /* 4字节对齐的数据起始地址 */
      pl = (const u32_t *)(const void *)ps;
    
      while (len > 7)  {
        tmp = sum + *pl++;          /* ping */
        if (tmp < sum) {
          tmp++;                    /* 溢出,手动+1 */
        }
    
        sum = tmp + *pl++;          /* pong */
        if (sum < tmp) {
          sum++;                    /* 溢出,手动+1 */
        }
    
        len -= 8;
      }
    
      /* 折叠高、低16bit */
      sum = FOLD_U32T(sum);
    
      /* 处理剩余的字节 */
      ps = (const u16_t *)pl;
    
      /* 2字节处理 */
      while (len > 1) {
        sum += *ps++;
        len -= 2;
      }
    
      /* 剩余单字节 */
      if (len > 0) {                /* 补到前面t的低位 */
        ((u8_t *)&t)[0] = *(const u8_t *)ps;
      }
    
      sum += t;                     /* 把t也一起16bit校验和 */
    
      /* 两次折叠高、低16bit */
      sum = FOLD_U32T(sum);
      sum = FOLD_U32T(sum);
    
      if (odd) { /* 因为前面是从第二个字节和第三个字节开始进行统计的,字节序反了,这里在结果更正。 */
        sum = SWAP_BYTES_IN_WORD(sum);
      }
    
      return (u16_t)sum; /* 返回校验和 */
    }
    #endif
    

    9.3.12 源IP字段

    占用32 bit。

    为源主机的IP地址。

    9.3.13 目标IP字段

    占用32 bit。

    为目标主机的IP地址。

    9.3.14 选项字段

    0到40字节。

    对于IP数据报首部来说,其大小必须为4字节的整数倍。

    如果选项字段长度不为4的倍数,则需要用0进行填充。

    在 LwIP 中只识别选项字段,不会处理选项字段的内容。

    该字段在IPv6报文中已经被移除了。

    9.3.15 数据区字段

    IP 数据报的最后的一个字段,装载着当前IP数据报的数据,是上层协议的数据报。

    9.3.16 对应wireshark包分析

    9.4 IP首部数据结构

    注意:网络字节序是大端的。

    ipv4的IP首部数据结构:对应IP首部报文图。

    /* The IPv4 header */
    struct ip_hdr {
      /* version / header length */
      PACK_STRUCT_FLD_8(u8_t _v_hl); /* 版本号字段(4)+首部长度字段(4)单位字 */
      /* type of service */
      PACK_STRUCT_FLD_8(u8_t _tos); /* 服务类型TOS字段(8) */
      /* total length */
      PACK_STRUCT_FIELD(u16_t _len); /* 总长度字段(16)单位字节 */
      /* identification */
      PACK_STRUCT_FIELD(u16_t _id); /* 标识字段字段(16) */
      /* fragment offset field */
      PACK_STRUCT_FIELD(u16_t _offset); /* 标志字段(3)+分片偏移量字段(13)单位两字 */
    #define IP_RF 0x8000U        /* 标志字段第一位:字段保留 */
    #define IP_DF 0x4000U        /* 标志字段第二位:不发片掩码 */
    #define IP_MF 0x2000U        /* 标志字段第三位:更多分片掩码 */
    #define IP_OFFMASK 0x1fffU   /* 分片偏移量字段的掩码 */
      /* time to live */
      PACK_STRUCT_FLD_8(u8_t _ttl); /* TTL字段(8) */
      /* protocol*/
      PACK_STRUCT_FLD_8(u8_t _proto); /* 上层协议类型字段(8) */
      /* checksum */
      PACK_STRUCT_FIELD(u16_t _chksum); /* 首部校验和字段(16) */
      /* source and destination IP addresses */
      PACK_STRUCT_FLD_S(ip4_addr_p_t src); /* 源IP字段(32) */
      PACK_STRUCT_FLD_S(ip4_addr_p_t dest); /* 目的IP字段(32) */
    } PACK_STRUCT_STRUCT;
    

    由于IP首部部分字段的操作涉及到bit,所以lwip也封装出对应的宏操作。

    /* 均为网络字节序 */
    /* Macros to get struct ip_hdr fields: */
    #define IPH_V(hdr)  ((hdr)->_v_hl >> 4) /* 获取版本号 */
    #define IPH_HL(hdr) ((hdr)->_v_hl & 0x0f) /* 获取首部长度字段值 */
    #define IPH_HL_BYTES(hdr) ((u8_t)(IPH_HL(hdr) * 4)) /* 获取首部长度,单位字节 */
    #define IPH_TOS(hdr) ((hdr)->_tos) /* 获取服务类型TOS */
    #define IPH_LEN(hdr) ((hdr)->_len) /* 获取IP数据报总长度 */
    #define IPH_ID(hdr) ((hdr)->_id) /* 获取标识字段 */
    #define IPH_OFFSET(hdr) ((hdr)->_offset) /* 获取标志字段+分片偏移量字段 */
    #define IPH_OFFSET_BYTES(hdr) ((u16_t)((lwip_ntohs(IPH_OFFSET(hdr)) & IP_OFFMASK) * 8U)) /* 获取分片偏移量,单位字节 */
    #define IPH_TTL(hdr) ((hdr)->_ttl) /* 获取TTL */
    #define IPH_PROTO(hdr) ((hdr)->_proto) /* 获取协议类型 */
    #define IPH_CHKSUM(hdr) ((hdr)->_chksum) /* 获取首部校验和字段 */
    
    /* Macros to set struct ip_hdr fields: */
    #define IPH_VHL_SET(hdr, v, hl) (hdr)->_v_hl = (u8_t)((((v) << 4) | (hl))) /* 设置版本号 */
    #define IPH_TOS_SET(hdr, tos) (hdr)->_tos = (tos) /* 设置服务类型TOS */
    #define IPH_LEN_SET(hdr, len) (hdr)->_len = (len) /* 设置总长度字段值 */
    #define IPH_ID_SET(hdr, id) (hdr)->_id = (id) /* 设置标识 */
    #define IPH_OFFSET_SET(hdr, off) (hdr)->_offset = (off) /* 设置标志字段+分片偏移量字段 */
    #define IPH_TTL_SET(hdr, ttl) (hdr)->_ttl = (u8_t)(ttl) /* 设置TTL */
    #define IPH_PROTO_SET(hdr, proto) (hdr)->_proto = (u8_t)(proto) /* 设置协议类型 */
    #define IPH_CHKSUM_SET(hdr, chksum) (hdr)->_chksum = (chksum) /* 设置首部校验和 */
    

    9.5 网卡路由

    9.5.1 路由网卡匹配

    从官方源码看,匹配网卡的流程:

    ip4_route_src()

    • 先函数LWIP_HOOK_IP4_ROUTE_SRC()匹配。
    • 然后到ip4_route()基函数匹配。

    9.5.2 路由网卡匹配基函数

    ip4_route()

    • 多播IP优先匹配多播专用网卡ip4_default_multicast_netif

    • 遍历网卡:

      • 有效网卡先匹配子网;
      • 子网匹配失败就匹配网关,网卡不能有广播能力。
    • 环回IP:如果开启了各个网卡的环回功能,且没有创建环回网卡:

      • 说明:因为创建了环回网卡,在遍历链表时,就会把环回IP 127.x.x.x都会匹配到环回网卡。
      • 对于环回IP,优先匹配默认网卡netif_default
      • 再遍历网卡,第一个协议栈有效的网卡即可。
    • 钩子匹配:(由用户实现)

      • LWIP_HOOK_IP4_ROUTE_SRC(src, dest);
      • LWIP_HOOK_IP4_ROUTE(dest);
    • 以上都没有匹配成功,则使用netif_default,必须条件:

      • 默认网卡netif_default存在;
      • 默认网卡协议栈有效;
      • 默认网卡数据链路有效;
      • 默认网卡IP有效。
      • 匹配的目的IP不能为环回IP(因为如果是环回IP,前面已经匹配过了,除非没有开启该功能)
    /**
     * 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.
     *
     * @param dest the destination IP address for which to find the route
     * @return the netif on which to send to reach dest
     */
    struct netif *
    ip4_route(const ip4_addr_t *dest)
    {
    #if !LWIP_SINGLE_NETIF
      struct netif *netif;
    
      /* 确保在tcpip内核锁内 */
      LWIP_ASSERT_CORE_LOCKED();
    
    #if LWIP_MULTICAST_TX_OPTIONS /* 开启了组播TX功能 */
      /* 如果目的IP是一个组播地址,且协议栈内创建了组播专用网卡,则选用组播专用网卡接口 */
      if (ip4_addr_ismulticast(dest) && ip4_default_multicast_netif) {
        return ip4_default_multicast_netif;
      }
    #endif /* LWIP_MULTICAST_TX_OPTIONS */
    
      /* bug #54569: in case LWIP_SINGLE_NETIF=1 and LWIP_DEBUGF() disabled, the following loop is optimized away */
      LWIP_UNUSED_ARG(dest);
    
      /* 遍历网卡链表 */
      NETIF_FOREACH(netif) {
        /* 网卡协议栈有效,网卡链路层也有效,网卡的IP地址不为全0 */
        if (netif_is_up(netif) && netif_is_link_up(netif) && !ip4_addr_isany_val(*netif_ip4_addr(netif))) {
          /* 匹配传入的目的IP和网卡是否处于同一个子网 */
          if (ip4_addr_net_eq(dest, netif_ip4_addr(netif), netif_ip4_netmask(netif))) {
            /* 匹配成功。返回netif,以转发IP数据包 */
            return netif;
          }
          /* 当前网卡子网匹配不成功 */
          /* 那就匹配下网关,匹配规则:网卡没有广播功能,目的IP和网关IP一致。也算匹配成功,因为上层的目的是到网关。当前网卡能把数据传达到网卡。 */
          if (((netif->flags & NETIF_FLAG_BROADCAST) == 0) && ip4_addr_eq(dest, netif_ip4_gw(netif))) {
            /* 匹配成功。返回netif,以转发IP数据包 */
            return netif;
          }
        }
      }
    
      /* 到这,遍历网卡,匹配失败 */
    
    /* 开启了环回功能,但是没有创建环回网卡。(因为如果创建了环回网卡,在遍历网卡链表时就已经处理过了,这里不需要再处理) */
    #if LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF
      /* 如果目的IP是一个环回IP地址,则优先返回默认网卡,否则返回网卡链表中第一个协议栈使能了的网卡作为当前环回网卡 */
      if (ip4_addr_isloopback(dest)) { /* 目的IP是一个环回IP */
        if ((netif_default != NULL) && netif_is_up(netif_default)) {
          /* 优先考虑默认网卡。如果有默认网卡,且默认网卡协议栈使能了,则以此网卡作为本次环回网卡 */
          return netif_default;
        }
        /* 默认网卡没有匹配成功,则从网卡链表中找一个协议栈已使能的网卡作为本次环回网卡 */
        NETIF_FOREACH(netif) {
          if (netif_is_up(netif)) {
            return netif;
          }
        }
        return NULL; /* 都没找到,那就匹配失败 */
      }
    #endif /* LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF */
    
    #ifdef LWIP_HOOK_IP4_ROUTE_SRC /* 如果开启了匹配网卡的钩子宏函数 */
      netif = LWIP_HOOK_IP4_ROUTE_SRC(NULL, dest); /* 使用宏钩子匹配 */
      if (netif != NULL) {
        return netif;
      }
    #elif defined(LWIP_HOOK_IP4_ROUTE) /* 网卡匹配钩子 */
      netif = LWIP_HOOK_IP4_ROUTE(dest); /* 使用宏钩子匹配 */
      if (netif != NULL) {
        return netif;
      }
    #endif
    #endif /* !LWIP_SINGLE_NETIF */
    
      /* 上述方案都无法匹配到网卡,就检查网卡网卡是否正常,正常则就返回默认网卡 */
    
      if ((netif_default == NULL) || !netif_is_up(netif_default) || !netif_is_link_up(netif_default) ||
          ip4_addr_isany_val(*netif_ip4_addr(netif_default)) || ip4_addr_isloopback(dest)) {
        LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip4_route: No route to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
                    ip4_addr1_16(dest), ip4_addr2_16(dest), ip4_addr3_16(dest), ip4_addr4_16(dest)));
        IP_STATS_INC(ip.rterr);
        MIB2_STATS_INC(mib2.ipoutnoroutes);
        return NULL;
      }
    
      return netif_default; /* 返回默认网络接口 */
    }
    

    9.5.3 路由网卡匹配支持源IP和目的IP网卡匹配的接口

    匹配网卡,一般是按照目的IP来匹配,但是可以通过LWIP_HOOK_IP4_ROUTE_SRC()钩子宏函数来实现源IP地址和目的IP地址匹配。

    ip4_route_src()

    • 如果源IP地址不为空,则会先传入LWIP_HOOK_IP4_ROUTE_SRC()钩子函数来匹配网卡。
    • 钩子函数匹配失败或者源IP地址为空,则由ip4_route()只根据目的IP地址匹配。
    #ifdef LWIP_HOOK_IP4_ROUTE_SRC
    /**
     * Source based IPv4 routing must be fully implemented in
     * LWIP_HOOK_IP4_ROUTE_SRC(). This function only provides the parameters.
     */
    struct netif *
    ip4_route_src(const ip4_addr_t *src, const ip4_addr_t *dest)
    {
      if (src != NULL) {
        /* 当src==NULL时,钩子会从ip4_route(dest)调用 */
        struct netif *netif = LWIP_HOOK_IP4_ROUTE_SRC(src, dest);
        if (netif != NULL) {
          return netif;
        }
      }
      return ip4_route(dest);
    }
    #endif /* LWIP_HOOK_IP4_ROUTE_SRC */
    

    9.5.4 路由网卡匹配的钩子函数

    通过分析前面的基函数和接口函数,可发现其实现是支持宏钩子函数,即是支持用户自己实现网卡匹配的逻辑的。

    有两个宏钩子:

    • LWIP_HOOK_IP4_ROUTE_SRC(src, dest):钩子入口参数有源IP和目的IP。
    • LWIP_HOOK_IP4_ROUTE(dest):钩子入口参数只有目的IP。

    9.5.5 收包网卡匹配

    当IP层收到一个IP报文时,也要收包网卡匹配。

    而且IP包的输入和输出的网卡匹配是不一样的,比如普通的IP单播包,输出时,只需要找到目的IP和网卡处于同一个子网或者是该网卡的网关即可匹配。而输入时,需要明确目的IP就是该网卡IP。

    收包的网卡匹配除了ip4_input_accept()这个主要函数外,还有很多独立的匹配条件,具体看IP层输入章节。

    这里只分析ip4_input_accept()

    • 在调用该API前,应该先配置全局IP数据结构成员值:struct ip_globals ip_data;

    • 需要被匹配的网卡必须在协议栈方向使能了,且IP地址为有效地址。

      • 单播包,目的地址和网卡地址一致,网卡匹配成功。
      • 广播包,IP地址bit全1,必定是广播地址。如果网卡就被广播能力,且IP地址的主机号bit全1,也是子网广播地址。都匹配成功。
      • 环回,没有环回网卡,且目的IP地址为环回IP IPADDR_LOOPBACK。匹配成功。
    /** Return true if the current input packet should be accepted on this netif */
    static int
    ip4_input_accept(struct netif *netif)
    {
      LWIP_DEBUGF(IP_DEBUG, ("ip_input: iphdr->dest 0x%"X32_F" netif->ip_addr 0x%"X32_F" (0x%"X32_F", 0x%"X32_F", 0x%"X32_F")\n",
                             ip4_addr_get_u32(ip4_current_dest_addr()), ip4_addr_get_u32(netif_ip4_addr(netif)),
                             ip4_addr_get_u32(ip4_current_dest_addr()) & ip4_addr_get_u32(netif_ip4_netmask(netif)),
                             ip4_addr_get_u32(netif_ip4_addr(netif)) & ip4_addr_get_u32(netif_ip4_netmask(netif)),
                             ip4_addr_get_u32(ip4_current_dest_addr()) & ~ip4_addr_get_u32(netif_ip4_netmask(netif))));
    
      /* 网卡是否在协议栈中使能且网卡地址有效? */
      if ((netif_is_up(netif)) && (!ip4_addr_isany_val(*netif_ip4_addr(netif)))) {
        /* 是否是单播到这个接口地址? */
        if (ip4_addr_eq(ip4_current_dest_addr(), netif_ip4_addr(netif)) ||
            /* 或者广播这个接口的网络地址? */
            ip4_addr_isbroadcast(ip4_current_dest_addr(), netif)
    #if LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF /* 如果开启环回功能,但是没有创建环回网卡 */
            /* 目的IP是一个环回的IP地址,也就是给当前网卡的,能匹配成功 */
            || (ip4_addr_get_u32(ip4_current_dest_addr()) == PP_HTONL(IPADDR_LOOPBACK))
    #endif /* LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF */
           ) {
          LWIP_DEBUGF(IP_DEBUG, ("ip4_input: packet accepted on interface %c%c\n",
                                 netif->name[0], netif->name[1]));
          /* accept on this netif */
          return 1;
        }
    #if LWIP_AUTOIP
        /* 更改netif地址后,链路本地地址的连接必须保持(RFC3927 ch. 1.9) */
        /* 即是netif网卡地址更新为可路由地址了,原本本地链路地址的连接必须保持,所以数据包能得到当前网卡。匹配成功 */
        if (autoip_accept_packet(netif, ip4_current_dest_addr())) {
          LWIP_DEBUGF(IP_DEBUG, ("ip4_input: LLA packet accepted on interface %c%c\n",
                                 netif->name[0], netif->name[1]));
          /* accept on this netif */
          return 1;
        }
    #endif /* LWIP_AUTOIP */
      }
      return 0;
    }
    

    9.6 IP层数据流图

    9.7 IP层输出

    ipv4。

    当上层需要发送数据时,会先将自己的数据包组装在一个pbuf中。并将payload指针指向对应协议首部。

    然后调用ip_output()发送数据,需要给ip_output()函数提供源IP、目的IP、协议类型、TTL等重要信息让其组IP包。

    该函数直接或者间接调用ip4_route_src()根据目的IP选出一个匹配的网卡作为本次IP数据包传输网卡。

    选出后调用ip4_output_if()进行IP数据包组包,并调用netif->output()发送出去。或者调用netif_loop_output()环回到本网卡。

    9.7.1 发送数据报

    上层调用ip_output()把数据转交给IP层处理,lwip支持ipv4和ipv6,这里默认分析ipv4,因为ipv6也是一大块,后面有时间再完整分析下。

    /**
     * @ingroup ip
     * Output IP packet, netif is selected by source address
     */
    #define ip_output(p, src, dest, ttl, tos, proto) \
            (IP_IS_V6(dest) ? \
            ip6_output(p, ip_2_ip6(src), ip_2_ip6(dest), ttl, tos, proto) : \
            ip4_output(p, ip_2_ip4(src), ip_2_ip4(dest), ttl, tos, proto))
    

    9.7.2 ip层前期处理:ip4_output()

    ipv4发包:

    • 检查pbuf的引用ref是否为1。为1才能说明当前pbuf没有被其它地方引用,因为IP层处理可能会改变这个pbuf的部分指针值,如payload。
    • 调用ip4_route_src()匹配网卡。
    • 调用ip4_output_if()把数据包传入IP层处理。
    /**
     * Simple interface to ip_output_if. It finds the outgoing network
     * interface and calls upon ip_output_if to do the actual work.
     *
     * @param p the packet to send (p->payload points to the data, e.g. next
                protocol header; if dest == LWIP_IP_HDRINCL, p already includes an
                IP header and p->payload points to that IP header)
     * @param src the source IP address to send from (if src == IP4_ADDR_ANY, the
     *         IP  address of the netif used to send is used as source address)
     * @param dest the destination IP address to send the packet to
     * @param ttl the TTL value to be set in the IP header
     * @param tos the TOS value to be set in the IP header
     * @param proto the PROTOCOL to be set in the IP header
     *
     * @return ERR_RTE if no route is found
     *         see ip_output_if() for more return values
     */
    err_t
    ip4_output(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
               u8_t ttl, u8_t tos, u8_t proto)
    {
      struct netif *netif;
      
      /* 下传到IP层的pbuf的ref必须为1,即是没有被其它地方引用,因为pbuf下传到IP层后,pbuf的payload指针会被更改。
        如果这个pbuf被其它地方引用了,可能会导致数据混乱。 */
      LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p);
    
      /* 根据目的IP地址为数据报寻找一个合适的网络接口(匹配网卡) */
      if ((netif = ip4_route_src(src, dest)) == NULL) { /* 没找到,记录信息,返回错误 */
        LWIP_DEBUGF(IP_DEBUG, ("ip4_output: No route to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
                               ip4_addr1_16(dest), ip4_addr2_16(dest), ip4_addr3_16(dest), ip4_addr4_16(dest)));
        IP_STATS_INC(ip.rterr);
        return ERR_RTE;
      }
    
      /* 匹配到网卡,传入IP层处理:组包、发送 */
      return ip4_output_if(p, src, dest, ttl, tos, proto, netif);
    }
    

    9.7.3 发包前的网卡匹配

    IP层收到上层的数据包后,需要匹配到网络接口,才能组IP包发出去。

    这里调用ip4_route_src()进行网卡匹配。具体分析参考前面。

    9.7.4 组建、发送IP包

    注意几个函数的区别:

    • ip4_output_if():这个函数封装了底层IP层组包、发送的实现函数。
    • ip4_output_if_src():这个函数就是IP层组包、发送的实现函数。不支持IP首部的选项字段。
    • ip4_output_if_opt():这也是IP层组包、发送的实现函数,会用选中的网卡IP地址覆盖传入的源IP地址。支持IP首部的选项字段。
    • ip4_output_if_opt_src():这也是IP层组包、发送的实现函数,不会用选中的网卡IP地址覆盖传入的源IP地址。支持IP首部的选项字段。

    相关宏:

    IP_OPTIONS_SEND

    • IP首报文首部选项字段宏开关。
    • 如果开启了该宏,则会调用上述带_opt字样的函数,操作IP首部报文的选项字段。

    LWIP_IP_HDRINCL:缺省为NULL

    • 如果把这个宏当目的IP地址传入IP层组包、发送的相关函数ip4_output_if()或其底层函数时,表示当前这个pbuf已经组好IP首部了。
    • 一般用于TCP重传。

    LWIP_CHECKSUM_CTRL_PER_NETIF

    • 允许每个网卡配置checksum功能。

    相关变量:

    • ip_id:IP首部标识,全局值。

    我们就分析ip4_output_if_opt_src()函数,比较全。

    ip4_output_if_opt_src()

    • 先处理选项字段,在处理IP首部其它字段。
    • struct pbuf *p:传输层协议需要发送的数据包pbuf,payload指针已指向传输层协议首部。
    • const ip4_addr_t *src:源IP地址。
    • const ip4_addr_t *dest:目的IP地址。
    • u8_t ttl:IP首部TTL字段。
    • u8_t tos:IP首部TOS字段。
    • u8_t proto:IP首部上层协议字段。
    • struct netif *netif:发送IP数据报的网卡。
    • void *ip_options:IP首部选项字段值。
    • u16_t optlen:IP首部选项字段的长度。

    概要内容:

    • 通过目的IP判断当前pbuf是否已经组好IP报文首部。如果组好了,就不需要继续重组了。如tcp重传。

    • 如果传入的pbuf报文还没组好IP报文首部,则根据传入的相关数据和IP报文内容进行组包。

    • 组好包后检查目的IP是否是环回IP(如环回IP、当前网卡的IP),如果是就调用netif_loop_output()进行环回处理。

    • 如果不是环回数据包,就需要发到数据链路。

      • IP分片:如果IP报文总长大于网卡MTU,则需要调用ip4_frag()进行IP分片。
      • 如果不需要IP分片,直接调用netif->output()将IP报文发出。
    err_t
    ip4_output_if_opt_src(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
                          u8_t ttl, u8_t tos, u8_t proto, struct netif *netif, void *ip_options,
                          u16_t optlen)
    {
    #endif /* IP_OPTIONS_SEND */
      struct ip_hdr *iphdr;
      ip4_addr_t dest_addr;
    #if CHECKSUM_GEN_IP_INLINE
      u32_t chk_sum = 0;
    #endif /* CHECKSUM_GEN_IP_INLINE */
    
      LWIP_ASSERT_CORE_LOCKED(); /* 确保在tcpip内核锁内 */
      LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p); /* pbuf没有被其它地方引用 */
    
      MIB2_STATS_INC(mib2.ipoutrequests); /* 流量统计 */
    
      /* IP首部是否已经组建好 */
      if (dest != LWIP_IP_HDRINCL) { /* IP首部未组建好 */
        u16_t ip_hlen = IP_HLEN; /* 默认IP首部长度 */
    #if IP_OPTIONS_SEND /* 开启了IP首部选项字段操作 */
        u16_t optlen_aligned = 0;
        if (optlen != 0) {
    #if CHECKSUM_GEN_IP_INLINE
          int i;
    #endif /* CHECKSUM_GEN_IP_INLINE */
          if (optlen > (IP_HLEN_MAX - IP_HLEN)) { /* 判断IP首部字段是否超限 */
            /* optlen 过长,导致IP首部超出限制 */
            LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip4_output_if_opt: optlen too long\n"));
            /* 状态信息统计 */
            IP_STATS_INC(ip.err);
            MIB2_STATS_INC(mib2.ipoutdiscards);
            return ERR_VAL;
          }
          /* 根据协议要求,IP首部选项字段长度要求是4字节的整数倍 */
          optlen_aligned = (u16_t)((optlen + 3) & ~3); /* 4字节往大对齐 */
          ip_hlen = (u16_t)(ip_hlen + optlen_aligned); /* 总长度字段 */
          /* 先处理选项字段 */
          /* pbuf payload偏移到IP首部选项字段位置 */
          if (pbuf_add_header(p, optlen_aligned)) {
            LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip4_output_if_opt: not enough room for IP options in pbuf\n"));
            /* payload偏移失败,统计信息返回错误 */
            IP_STATS_INC(ip.err);
            MIB2_STATS_INC(mib2.ipoutdiscards);
            return ERR_BUF;
          }
          /* 先处理选项字段,拷贝选项字段值到IP首部中 */
          MEMCPY(p->payload, ip_options, optlen);
          if (optlen < optlen_aligned) {
            /* 多余字节补0 */
            memset(((char *)p->payload) + optlen, 0, (size_t)(optlen_aligned - optlen));
          }
    #if CHECKSUM_GEN_IP_INLINE
          /* 先统计这部分的首部校验和 */
          for (i = 0; i < optlen_aligned / 2; i++) {
            chk_sum += ((u16_t *)p->payload)[i];
          }
    #endif /* CHECKSUM_GEN_IP_INLINE */
        }
    #endif /* IP_OPTIONS_SEND */
        /* 常见IP首部处理 */
        /* pbuf payload指针偏移 */
        if (pbuf_add_header(p, IP_HLEN)) {
          LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip4_output: not enough room for IP header in pbuf\n"));
          /* payload偏移失败,统计信息返回错误 */
          IP_STATS_INC(ip.err);
          MIB2_STATS_INC(mib2.ipoutdiscards);
          return ERR_BUF;
        }
    
        iphdr = (struct ip_hdr *)p->payload;
        LWIP_ASSERT("check that first pbuf can hold struct ip_hdr",
                    (p->len >= sizeof(struct ip_hdr)));
    
        IPH_TTL_SET(iphdr, ttl); /* 填写TTL字段 */
        IPH_PROTO_SET(iphdr, proto); /* 填写上层协议字段 */
    #if CHECKSUM_GEN_IP_INLINE
        chk_sum += PP_NTOHS(proto | (ttl << 8)); /* 首部校验和统计 */
    #endif /* CHECKSUM_GEN_IP_INLINE */
    
        ip4_addr_copy(iphdr->dest, *dest); /* 填写目的IP字段 */
    #if CHECKSUM_GEN_IP_INLINE
        /* 首部校验和统计 */
        chk_sum += ip4_addr_get_u32(&iphdr->dest) & 0xFFFF;
        chk_sum += ip4_addr_get_u32(&iphdr->dest) >> 16;
    #endif /* CHECKSUM_GEN_IP_INLINE */
    
        IPH_VHL_SET(iphdr, 4, ip_hlen / 4); /* 填写IP版本号+IP首部长度 */
        IPH_TOS_SET(iphdr, tos); /* 填写TOS服务字段 */
    #if CHECKSUM_GEN_IP_INLINE
        /* 首部校验和统计 */
        chk_sum += PP_NTOHS(tos | (iphdr->_v_hl << 8));
    #endif /* CHECKSUM_GEN_IP_INLINE */
        IPH_LEN_SET(iphdr, lwip_htons(p->tot_len)); /* 填写总长度字段 */
    #if CHECKSUM_GEN_IP_INLINE
        /* 首部校验和统计 */
        chk_sum += iphdr->_len;
    #endif /* CHECKSUM_GEN_IP_INLINE */
        IPH_OFFSET_SET(iphdr, 0); /* 填写标志字段+分片偏移量字段 */
        IPH_ID_SET(iphdr, lwip_htons(ip_id)); /* 填写标识字段 */
    #if CHECKSUM_GEN_IP_INLINE
        /* 首部校验和统计 */
        chk_sum += iphdr->_id;
    #endif /* CHECKSUM_GEN_IP_INLINE */
        ++ip_id; /* ip首部标识,全局值 */
    
        /* 填写源IP地址字段 */
        if (src == NULL) {
          ip4_addr_copy(iphdr->src, *IP4_ADDR_ANY4);
        } else {
          /* src cannot be NULL here */
          ip4_addr_copy(iphdr->src, *src);
        }
    
    #if CHECKSUM_GEN_IP_INLINE
        /* 首部校验和统计 */
        chk_sum += ip4_addr_get_u32(&iphdr->src) & 0xFFFF;
        chk_sum += ip4_addr_get_u32(&iphdr->src) >> 16;
        /* 二次16bit折叠。因为一次可能会溢出。第二次是为了解决溢出。 */
        chk_sum = (chk_sum >> 16) + (chk_sum & 0xFFFF);
        chk_sum = (chk_sum >> 16) + chk_sum;
        chk_sum = ~chk_sum; /* 反码 */
        IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) { /* 当前网卡支持IP首部的checksum功能 */
          iphdr->_chksum = (u16_t)chk_sum; /* 填写首部校验和字段。网络字节序,因为统计的时候就以网络字节序统计,所以这里不必转换 */
        }
    #if LWIP_CHECKSUM_CTRL_PER_NETIF
        else {/* 如果不支持checksum功能 */
          IPH_CHKSUM_SET(iphdr, 0); /* 填写首部校验和字段为0 */
        }
    #endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/
    #else /* CHECKSUM_GEN_IP_INLINE */
        IPH_CHKSUM_SET(iphdr, 0); /* 默认填写首部校验和字段为0 */
    #if CHECKSUM_GEN_IP
        IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) {
          IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, ip_hlen)); /* IP首部校验和字段更新为实际值 */
        }
    #endif /* CHECKSUM_GEN_IP */
    #endif /* CHECKSUM_GEN_IP_INLINE */
      } else { /* IP头已经包含在p。如TCP重传 */
        if (p->len < IP_HLEN) { /* 长度校验 */
          LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip4_output: LWIP_IP_HDRINCL but pbuf is too short\n"));
          IP_STATS_INC(ip.err);
          MIB2_STATS_INC(mib2.ipoutdiscards);
          return ERR_BUF;
        }
        iphdr = (struct ip_hdr *)p->payload;
        ip4_addr_copy(dest_addr, iphdr->dest); /* 获取目的IP地址 */
        dest = &dest_addr;
      }
      /* 状态记录 */
      IP_STATS_INC(ip.xmit);
    
      LWIP_DEBUGF(IP_DEBUG, ("ip4_output_if: %c%c%"U16_F"\n", netif->name[0], netif->name[1], (u16_t)netif->num));
      ip4_debug_print(p);
    
    #if ENABLE_LOOPBACK /* 环回功能 */
      if (ip4_addr_eq(dest, netif_ip4_addr(netif)) /* 目的IP为源网卡IP,则是环回 */
    #if !LWIP_HAVE_LOOPIF
          || ip4_addr_isloopback(dest) /* 目的IP是环回IP字段,也是环回 */
    #endif /* !LWIP_HAVE_LOOPIF */
         ) {
        /* 数据包环回,则不用通过数据链路层,直达对应网卡的环回链表即可 */
        LWIP_DEBUGF(IP_DEBUG, ("netif_loop_output()"));
        return netif_loop_output(netif, p);
      }
    #if LWIP_MULTICAST_TX_OPTIONS
      if ((p->flags & PBUF_FLAG_MCASTLOOP) != 0) { /* 该pbuf是要环回的UDP组播 */
        netif_loop_output(netif, p); /* 环回到本网卡 */
      }
    #endif /* LWIP_MULTICAST_TX_OPTIONS */
    #endif /* ENABLE_LOOPBACK */
    #if IP_FRAG
      /* 如果接口mtu设置为0,不用分片 */
      /* 分片检查 */
      if (netif->mtu && (p->tot_len > netif->mtu)) { /* IP报文超出网卡MTU,则需要分片处理 */
        return ip4_frag(p, netif, dest); /* 需要分片处理 */
      }
    #endif /* IP_FRAG */
    
      /* 不需要分片处理 */
      LWIP_DEBUGF(IP_DEBUG, ("ip4_output_if: call netif->output()\n"));
      return netif->output(netif, p, dest); /* IP层发送数据包。至此,IP报文处理完毕,下一步交给ARP或者直接到数据链路处理。 */
    }
    

    9.7.5 IP数据报分片

    注意:lwip分片偏移不支持IP首部带选项字段的。

    从IP报文首部就可知,有分片概念。

    不是每个底层网卡都能承载每个 IP 数据报长度的报文。如:

    • 以太网帧最大能承载 1500 个字节的数据。
    • 某些广域网链路的帧可承载不超过 576 字节的数据。

    一个链路层帧能承载的最大数据量叫做最大传送单元(Maximum TransmissionUnit,MTU)。

    IP 数据报的分片偏移量是用 8 的整数倍记录的,所以每个数据报中的分片数据大小也必须是 8 的整数倍。

    IP数据报分片主要关注IP首部的标识字段、标志字段和分片偏移量字段。具体往前看。

    参考图:

    相关源码实现在ip4_frag.c

    相关宏:

    • LWIP_NETIF_TX_SINGLE_PBUF:分片是否支持新建一整个pbuf处理。

      • 1:分片时,直接申请各个IP分片包的pbuf即可(含IP首部+数据区)。
      • 0:分片时,申请各个分片的管理区,MEMP_FRAG_PBUF类型。其数据结构为pbuf_custom_ref。该数据结构包含本次IP分片包的原IP报文pbuf地址,释放引用的api,指向分片IP报文数据区的pbuf。然后将这个分片IP首部的pbuf和这个IP报文数据区的pbuf拼接起来即可。组成新的分片IP报文。

    相关数据结构:

    pbuf_custom数据结构:

    /** A custom pbuf that holds a reference to another pbuf, which is freed
     * when this custom pbuf is freed. This is used to create a custom PBUF_REF
     * that points into the original pbuf. */
    struct pbuf_custom_ref {
      /** 'base class' */
      struct pbuf_custom pc; /* 用户的控制区。包含一个pbuf和一个释放该pbuf的api */
      /* 指向被引用的原始pbuf的指针 */
      struct pbuf *original;
    };
    

    pbuf_custom_ref数据结构:

    struct pbuf_custom {
      /* The actual pbuf */
      struct pbuf pbuf;
      /**This function is called when pbuf_free deallocates this pbuf(_custom) */
      pbuf_free_custom_fn custom_free_function;
    };
    

    相关数据结构图:

    • 没开启LWIP_NETIF_TX_SINGLE_PBUF宏的IP分片报文数据结构:

    • 开启LWIP_NETIF_TX_SINGLE_PBUF宏的分片IP报文数据结构(按简单的画):

    ip4_frag()

    • 与分片重组ip4_reass()这个API对应。

    • 需要注意的是:需要检查本次分片处理传入的原IP报文struct pbuf *p是否也是一个分片包。如果是,那么它可能不是顶层原IP报文分片的最后一片,这样的话,在本次分片处理最后一片的分片IP报文首部标志字段的还有更多分片标志位不能置位0。因为在顶层未分片IP报文角度看来,这还不是真正意义上的最后一片。

    • 下面函数分析时,按LWIP_NETIF_TX_SINGLE_PBUF宏分支分析更加助于理解。

    • 处于非LWIP_NETIF_TX_SINGLE_PBUF宏分支:如果读者需要分析,然后看不懂这个分支,可以看下我的笔记:

      • 先申请一个保存分片IP首部的pbuf:rambuf
      • 然后再申请一个pbuf_custom_ref数据结构的伪pbuf:pcr
      • 然后把未分片的IP报文的pbuf对应分片的数据区地址给到pcr->pc->pbuf->payload,共享数据区内存嘛。
      • 然后把分片IP首部的pbuf和分片IP的数据pbuf拼接起来:pbuf_cat(rambuf, pcr->pc->pbuf->payload);,这样就组成了分片的IP报文了。
    /**
     * Fragment an IP datagram if too large for the netif.
     *
     * Chop the datagram in MTU sized chunks and send them in order
     * by pointing PBUF_REFs into p.
     *
     * @param p ip packet to send
     * @param netif the netif on which to send
     * @param dest destination ip address to which to send
     *
     * @return ERR_OK if sent successfully, err_t otherwise
     */
    err_t
    ip4_frag(struct pbuf *p, struct netif *netif, const ip4_addr_t *dest)
    {
      struct pbuf *rambuf; /* 分片的pbuf结构 */
    #if !LWIP_NETIF_TX_SINGLE_PBUF
      /* 用于处理分片IP报文与原IP报文数据区pbuf内存共享使用 */
      struct pbuf *newpbuf;
      u16_t newpbuflen = 0;
      u16_t left_to_copy;
    #endif
      struct ip_hdr *original_iphdr;
      struct ip_hdr *iphdr;
      const u16_t nfb = (u16_t)((netif->mtu - IP_HLEN) / 8); /* 分片中允许的最大数据量 */
      u16_t left, fragsize; /* 待发送数据长度和当前发送的分片的数据长度 */
      u16_t ofo; /* 当前分片偏移量 */
      int last; /* 是否为最后一个分片 */
      u16_t poff = IP_HLEN; /* 发送的数据在原始数据报pbuf中的偏移量 */
      u16_t tmp;
      int mf_set; /* 传入的pbuf是否是分片包,后续是否还有更多分片。 */
    
      original_iphdr = (struct ip_hdr *)p->payload;
      iphdr = original_iphdr;
      if (IPH_HL_BYTES(iphdr) != IP_HLEN) { /* IP首部长度字段 */
        /* ip4_frag() 不支持IP首部带选项字段的IP报文分片 */
        return ERR_VAL;
      }
      LWIP_ERROR("ip4_frag(): pbuf too short", p->len >= IP_HLEN, return ERR_VAL);
    
      /* 保存原始的分片偏移量字段 */
      tmp = lwip_ntohs(IPH_OFFSET(iphdr));
      ofo = tmp & IP_OFFMASK;
      /* 判断pbuf包是否已经被分片,被分片后后续是否还有更多分片。如果有,那么本次分片的最后一个分片不能标志后续没有更多分片。 */
      mf_set = tmp & IP_MF;
       
      /* 获取还剩多少数据还没分片处理 */
      left = (u16_t)(p->tot_len - IP_HLEN);
    
      while (left) { /* 循环分片处理 */
        /* 当前分片需要填充的数据size */
        fragsize = LWIP_MIN(left, (u16_t)(nfb * 8));
    
    #if LWIP_NETIF_TX_SINGLE_PBUF /* 分片支持新建整个pbuf */
        /* 申请新的pbuf内存资源装载当前分片 */
        rambuf = pbuf_alloc(PBUF_IP, fragsize, PBUF_RAM);
        if (rambuf == NULL) {
          goto memerr;
        }
        LWIP_ASSERT("this needs a pbuf in one piece!",
                    (rambuf->len == rambuf->tot_len) && (rambuf->next == NULL));
        /* 拷贝IP报文数据区的数据到分片IP包中 */
        poff += pbuf_copy_partial(p, rambuf->payload, fragsize, poff);
        /* pbuf腾出IP首部空间 */
        if (pbuf_add_header(rambuf, IP_HLEN)) {
          pbuf_free(rambuf);
          goto memerr;
        }
        /* 填充原IP报文首部到当前分片,后面再处理分片的IP首部 */
        SMEMCPY(rambuf->payload, original_iphdr, IP_HLEN);
        iphdr = (struct ip_hdr *)rambuf->payload;
    #else /* LWIP_NETIF_TX_SINGLE_PBUF */ /* 分片支持数据区共享。这个宏分支,感兴趣的同学可以看下 */
        /* 先申请分片IP首部的pbuf */
        rambuf = pbuf_alloc(PBUF_LINK, IP_HLEN, PBUF_RAM);
        if (rambuf == NULL) {
          goto memerr;
        }
        LWIP_ASSERT("this needs a pbuf in one piece!",
                    (rambuf->len >= (IP_HLEN)));
        /* 拷贝原IP报文首部到分片IP报文首部 */
        SMEMCPY(rambuf->payload, original_iphdr, IP_HLEN);
        iphdr = (struct ip_hdr *)rambuf->payload;
    
        /* 本次分片IP报文占用的size */
        left_to_copy = fragsize;
        while (left_to_copy) { /* 分片IP报文组装完毕为止 */
          struct pbuf_custom_ref *pcr; /* 引用原IP报文数据区pbuf的伪pbuf */
          u16_t plen = (u16_t)(p->len - poff); /* 原IP报文当前pbuf节点还剩多少数据没处理 */
          LWIP_ASSERT("p->len >= poff", p->len >= poff);
          newpbuflen = LWIP_MIN(left_to_copy, plen); /* 选出能处理的size */
    
          if (!newpbuflen) { /* 原IP报文当前pbuf所有数据已经分片处理完毕,可以处理下一个pbuf */
            poff = 0;
            p = p->next; /* 跳到下一个分片IP报文处理继续组装当前IP分片 */
            continue;
          }
          pcr = ip_frag_alloc_pbuf_custom_ref(); /* 为伪pbuf pcr申请资源 */
          if (pcr == NULL) {
            pbuf_free(rambuf);
            goto memerr;
          }
          /* 把原IP报文数据区当前分片的数据pbuf引用到pcr->pc->pbuf这个pbuf中 */
          newpbuf = pbuf_alloced_custom(PBUF_RAW, newpbuflen, PBUF_REF, &pcr->pc,
                                        (u8_t *)p->payload + poff, newpbuflen);
          if (newpbuf == NULL) {
            ip_frag_free_pbuf_custom_ref(pcr);
            pbuf_free(rambuf);
            goto memerr;
          }
      
          pbuf_ref(p); /* 当前pbuf引用值+1 */
          pcr->original = p; /* 保存当前pbuf地址到pcr */
          pcr->pc.custom_free_function = ipfrag_free_pbuf_custom; /* 专门的伪pbuf释放API */
    
          /* 拼接pbuf,把本次分片IP首部、数据区的各个pbuf都链在一起 */
          pbuf_cat(rambuf, newpbuf);
          left_to_copy = (u16_t)(left_to_copy - newpbuflen); /* 检查本次分片IP报文是否整理完毕 */
          if (left_to_copy) { /* 还没填充完毕,需要继续填充 */
            poff = 0;
            p = p->next;
          }
        }
        poff = (u16_t)(poff + newpbuflen); /* 分片在原IP报文中的偏移值更新 */
    #endif /* LWIP_NETIF_TX_SINGLE_PBUF */
    
        /* 更正分片IP首部 */
        /* 本次分片是否为最后一次分片 */
        last = (left <= netif->mtu - IP_HLEN);
    
        /* 设置新的偏移量和MF标志 */
        tmp = (IP_OFFMASK & (ofo));
        if (!last || mf_set) { /* 本函数分片处理的最后一片 且 传入的原IP报文不是分片报文 */
          /* 标志位第三位更多分片标记为0,已经为顶层原IP报文的最后一片 */
          tmp = tmp | IP_MF;
        }
        IPH_OFFSET_SET(iphdr, lwip_htons(tmp)); /* 重写分片IP首部的标志位字段+分片偏移量字段 */
        IPH_LEN_SET(iphdr, lwip_htons((u16_t)(fragsize + IP_HLEN))); /* 重写分片IP报文总长度 */
        IPH_CHKSUM_SET(iphdr, 0); /* 设置分片IP报文首部校验字段,默认为0 */
    #if CHECKSUM_GEN_IP
        IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) {
          IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN)); /* 更新分片IP报文首部校验字段 */
        }
    #endif /* CHECKSUM_GEN_IP */
    
        /* 发送分片IP报文 */
        netif->output(netif, rambuf, dest);
        /* 分片状态信息记录 */
        IPFRAG_STATS_INC(ip_frag.xmit);
    
        /* Unfortunately we can't reuse rambuf - the hardware may still be
         * using the buffer. Instead we free it (and the ensuing chain) and
         * recreate it next time round the loop. If we're lucky the hardware
         * will have already sent the packet, the free will really free, and
         * there will be zero memory penalty.
         */
        /* 释放分片空间。因为这rambuf只是分片函数内部处理创建的,所以调用netif->output()发送出去后要在这里释放掉。 */
        pbuf_free(rambuf);
        /* 需要处理的分片待发送数据减少 */
        left = (u16_t)(left - fragsize);
        /* 分片偏移量增加 */
        ofo = (u16_t)(ofo + nfb);
      }
      MIB2_STATS_INC(mib2.ipfragoks);
      return ERR_OK;
    memerr:
      MIB2_STATS_INC(mib2.ipfragfails);
      return ERR_MEM;
    }
    

    9.8 IP层输入

    9.8.1 接收数据报

    翻看前面网络接口层章节应该知道底层接收数据流,在网卡驱动收到数据,调用netif->input()把对应API和pbuf转发到lwip内核线程执行。

    以以太网为例,网卡接收数据线程通过把数据给到netif->input(),该函数内部外包以太网链路层ethernet_input()到lwip内核线程去跑,如果是ARP协议的以太网帧,则传到ARP模块etharp_input()处理。如果是IPv4的以太网帧,则传到ip4_input()处理。

    相关宏:

    LWIP_HOOK_IP4_INPUT(p, inp):IPV4接收数据包钩子函数。

    • 接收数据时检查了IP报文的版本为IPV4,就会把这个IP报文传给这个钩子函数。
    • 如果钩子放回true,则表示由钩子处理,外部丢弃。

    LWIP_IP_ACCEPT_UDP_PORT(dst_port):在netif关闭时接受私人广播通信用。

    ip4_input()

    • IP报文校验。

    • 传入钩子LWIP_HOOK_IP4_INPUT(p, inp)

    • 匹配目的网卡。就是判断当前IP报文是不是给我的。

      • 多播包:

        • 开启了IGMP:当前网卡在当前IP报文目的IP组播内,匹配成功。
        • 没有开启IGMP:当前网卡有效即可匹配成功。
      • 广播包和单播包:都调用ip4_input_accept()API匹配。前面有分析。

        • 先匹配收到该IP报文的网卡;
        • 再遍历网卡链表。注意:如果没有环回功能或者有环回网卡,且IP报文目的IP地址是环回字段的IP地址,不能遍历网卡链表。因为环回,需要用环回接口,在前面匹配网卡就应该配上了,不会跑到这里。
      • 开启了DHCP,且面前没有匹配上网卡:

        • 通过IP报文可以判断当前IP报文是否是UDP协议。
        • 通过UDP协议解析目的端口是否是DHCP客户端端口LWIP_IANA_PORT_DHCP_CLIENT(68)。
        • 是DHCP报文,直接收到当前网卡。不需要做目的IP校验。
    • 没有匹配到网卡,即是IP报文不是给我们的,但是开启了IP_FORWARD转发功能。如果目的IP不是广播地址,可以调用ip4_forward()进行转发。

    • 匹配到网卡,处理IP报文,如果IP报文被分片了,需要调用ip4_reass()重组。

    • 收到完整的IP报文后,根据IP报文的上层协议类型字段,给到对应的协议模块。lwip支持:

      • IP_PROTO_UDP:UDP协议。udp_input(p, inp);
      • IP_PROTO_TCP:TCP协议。tcp_input(p, inp);
      • IP_PROTO_ICMP:ICMP协议。icmp_input(p, inp);
      • IP_PROTO_IGMP:IGMP协议。igmp_input(p, inp, ip4_current_dest_addr());
      • LWIP_RAW:原报文上报。
    /**
     * 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.
     *
     * @param p the received IP packet (p->payload points to IP header)
     * @param inp the netif on which this packet was received
     * @return ERR_OK if the packet was processed (could return ERR_* if it wasn't
     *         processed, but currently always returns ERR_OK)
     */
    err_t
    ip4_input(struct pbuf *p, struct netif *inp)
    {
      const struct ip_hdr *iphdr;
      struct netif *netif;
      u16_t iphdr_hlen;
      u16_t iphdr_len;
    #if IP_ACCEPT_LINK_LAYER_ADDRESSING || LWIP_IGMP
      int check_ip_src = 1;
    #endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING || LWIP_IGMP */
    #if LWIP_RAW
      raw_input_state_t raw_status;
    #endif /* LWIP_RAW */
    
      LWIP_ASSERT_CORE_LOCKED();
    
      IP_STATS_INC(ip.recv);
      MIB2_STATS_INC(mib2.ipinreceives);
    
      /* 识别IP头 */
      iphdr = (struct ip_hdr *)p->payload;
      if (IPH_V(iphdr) != 4) { /* 如果IP版本不是ipv4,丢弃该报文 */
        LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_WARNING, ("IP packet dropped due to bad version number %"U16_F"\n", (u16_t)IPH_V(iphdr)));
        ip4_debug_print(p);
        pbuf_free(p);
        IP_STATS_INC(ip.err);
        IP_STATS_INC(ip.drop);
        MIB2_STATS_INC(mib2.ipinhdrerrors);
        return ERR_OK;
      }
    
    #ifdef LWIP_HOOK_IP4_INPUT
      if (LWIP_HOOK_IP4_INPUT(p, inp)) { /* 传入IP接收数据包钩子函数处理 */
        /* IP报文也就被处理了 */
        return ERR_OK;
      }
    #endif
    
      /* 获取IP首部长度(以字节为单位) */
      iphdr_hlen = IPH_HL_BYTES(iphdr);
      /* 获取IP报文长度(以字节为单位) */
      iphdr_len = lwip_ntohs(IPH_LEN(iphdr));
    
      /* 修剪pbuf。对于小于60字节的数据包尤其需要这样做 */
      if (iphdr_len < p->tot_len) {
        pbuf_realloc(p, iphdr_len);
      }
    
      /* 报头长度超过第一个pbuf长度,或者IP长度超过总pbuf长度? */
      if ((iphdr_hlen > p->len) || (iphdr_len > p->tot_len) || (iphdr_hlen < IP_HLEN)) {
        if (iphdr_hlen < IP_HLEN) {
          LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
                      ("ip4_input: short IP header (%"U16_F" bytes) received, IP packet dropped\n", iphdr_hlen));
        }
        if (iphdr_hlen > p->len) {
          LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
                      ("IP header (len %"U16_F") does not fit in first pbuf (len %"U16_F"), IP packet dropped.\n",
                       iphdr_hlen, p->len));
        }
        if (iphdr_len > p->tot_len) {
          LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
                      ("IP (len %"U16_F") is longer than pbuf (len %"U16_F"), IP packet dropped.\n",
                       iphdr_len, p->tot_len));
        }
        /* 校验错误,丢弃当前pbuf,并记录相关信息 */
        pbuf_free(p);
        IP_STATS_INC(ip.lenerr);
        IP_STATS_INC(ip.drop);
        MIB2_STATS_INC(mib2.ipindiscards);
        return ERR_OK;
      }
    
      /* 首部校验和字段校验 */
    #if CHECKSUM_CHECK_IP
      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;
        }
      }
    #endif
    
      /* 拷贝IP地址到对齐的ip_addr_t。ip_data为全局变量 */
      ip_addr_copy_from_ip4(ip_data.current_iphdr_dest, iphdr->dest);
      ip_addr_copy_from_ip4(ip_data.current_iphdr_src, iphdr->src);
    
      /* 将报文与接口匹配,即这个报文是我们的吗? */
      if (ip4_addr_ismulticast(ip4_current_dest_addr())) { /* 多播包 */
    #if LWIP_IGMP /* IGMP处理 */
        if ((inp->flags & NETIF_FLAG_IGMP) && (igmp_lookfor_group(inp, ip4_current_dest_addr()))) { /* 当前网卡在IP报文目的IP的组播组内 */
          /* IGMP snooping交换机需要允许源地址为0.0.0.0 (RFC 4541) */
          ip4_addr_t allsystems;
          IP4_ADDR(&allsystems, 224, 0, 0, 1);
          if (ip4_addr_eq(ip4_current_dest_addr(), &allsystems) &&
              ip4_addr_isany(ip4_current_src_addr())) {
            check_ip_src = 0; /* 标记后面忽略源IP地址 */
          }
          netif = inp; /* 组播匹配成功 */
        } else {
          netif = NULL; /* 组播匹配失败 */
        }
    #else /* LWIP_IGMP */
        if ((netif_is_up(inp)) && (!ip4_addr_isany_val(*netif_ip4_addr(inp)))) {
          netif = inp; /* 如果不支持IGMP协议功能,但是当前网卡收到这个组播包,就直接匹配当前网卡即可 */
        } else {
          netif = NULL;
        }
    #endif /* LWIP_IGMP */
      } else { /* 单播或广播IP包 */
        /* 优先匹配当前网卡 */
        if (ip4_input_accept(inp)) {
          netif = inp; /* 匹配成功 */
        } else { /* 当前网卡匹配失败,就遍历网卡链表进行匹配 */
          netif = NULL;
    #if !LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF
          /* 如果网卡没有环回功能,或者已经创建了环回网卡。如果当前IP报文的目的IP是环回类型的,则不会跑到这里,而是在对应网卡的环回数据队列中。 */
          if (!ip4_addr_isloopback(ip4_current_dest_addr())) /* IP报文的目的IP不能是环回类型 */
    #endif /* !LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF */
          {
    #if !LWIP_SINGLE_NETIF
            NETIF_FOREACH(netif) { /* 遍历网卡链表 */
              if (netif == inp) { /* 跳过已经尝试匹配的网卡 */
                /* we checked that before already */
                continue;
              }
              if (ip4_input_accept(netif)) { /* 进行匹配 */
                break;
              }
            }
    #endif /* !LWIP_SINGLE_NETIF */
          }
        }
      }
    
    #if IP_ACCEPT_LINK_LAYER_ADDRESSING
      /* 通过DHCP报文时,不考虑目的地址。DHCP流量使用链路层寻址(如以太网MAC),所以我们不能对IP进行过滤。
       * 参考RFC 1542章节3.1.1,参考RFC 2131)。
       * 
       * 如果想在netif关闭时接受私有广播通信,定义LWIP_IP_ACCEPT_UDP_PORT(dst_port),例如:
       * #define LWIP_IP_ACCEPT_UDP_PORT(dst_port) ((dst_port) == PP_NTOHS(12345))
       */
      if (netif == NULL) {
        /* 远端端口是DHCP服务器? */
        if (IPH_PROTO(iphdr) == IP_PROTO_UDP) { /* UDP协议类型 */
          const struct udp_hdr *udphdr = (const struct udp_hdr *)((const u8_t *)iphdr + iphdr_hlen);
          LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE, ("ip4_input: UDP packet to DHCP client port %"U16_F"\n",
                                                  lwip_ntohs(udphdr->dest)));
          if (IP_ACCEPT_LINK_LAYER_ADDRESSED_PORT(udphdr->dest)) { /* 目的端口是DHCP客户端端口。能通过协议栈IP层 */
            LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE, ("ip4_input: DHCP packet accepted.\n"));
            netif = inp; /* 匹配成功 */
            check_ip_src = 0; /* 标记后面忽略源IP地址 */
          }
        }
      }
    #endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING */
    
      /* 广播或多播包的源地址?兼容RFC 1122: 3.2.1.3 */
    #if LWIP_IGMP || IP_ACCEPT_LINK_LAYER_ADDRESSING
      if (check_ip_src /* 忽略源IP地址的跳过:如IGMP和DHCP */
    #if IP_ACCEPT_LINK_LAYER_ADDRESSING
          /* 允许DHCP服务器源地址为0.0.0.0 (RFC 1.1.2.2: 3.2.1.3/a) */
          && !ip4_addr_isany_val(*ip4_current_src_addr())
    #endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING */
         )
    #endif /* LWIP_IGMP || IP_ACCEPT_LINK_LAYER_ADDRESSING */
      {
        if ((ip4_addr_isbroadcast(ip4_current_src_addr(), inp)) || /* 广播包 */
            (ip4_addr_ismulticast(ip4_current_src_addr()))) { /* 或者是多播包 */
          /* IP报文无效 */
          LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("ip4_input: packet source is not valid.\n"));
          /* 释放资源并记录相关信息 */
          pbuf_free(p);
          IP_STATS_INC(ip.drop);
          MIB2_STATS_INC(mib2.ipinaddrerrors);
          MIB2_STATS_INC(mib2.ipindiscards);
          return ERR_OK;
        }
      }
    
      /* 网卡匹配完毕 */
    
      if (netif == NULL) { /* 网卡匹配失败 */
        /* 不给我们包,路由或丢弃 */
        LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE, ("ip4_input: packet not for us.\n"));
    #if IP_FORWARD /* 支持路由转发 */
        if (!ip4_addr_isbroadcast(ip4_current_dest_addr(), inp)) { /* 广播包不能转发 */
          /* 尝试在(其他)接口转发IP报文 */
          ip4_forward(p, (struct ip_hdr *)p->payload, inp);
        } else
    #endif /* IP_FORWARD */
        { /* 广播包不支持转发,丢弃 */
          IP_STATS_INC(ip.drop);
          MIB2_STATS_INC(mib2.ipinaddrerrors);
          MIB2_STATS_INC(mib2.ipindiscards);
        }
        pbuf_free(p);
        return ERR_OK;
      }
    
      /* 网卡匹配成功,数据包是给我们的 */
    
      /* 数据包由多个分片组成? */
      if ((IPH_OFFSET(iphdr) & PP_HTONS(IP_OFFMASK | IP_MF)) != 0) {
    #if IP_REASSEMBLY /* 支持IP分片重组 */
        LWIP_DEBUGF(IP_DEBUG, ("IP packet is a fragment (id=0x%04"X16_F" tot_len=%"U16_F" len=%"U16_F" MF=%"U16_F" offset=%"U16_F"), calling ip4_reass()\n",
                               lwip_ntohs(IPH_ID(iphdr)), p->tot_len, lwip_ntohs(IPH_LEN(iphdr)), (u16_t)!!(IPH_OFFSET(iphdr) & PP_HTONS(IP_MF)), (u16_t)((lwip_ntohs(IPH_OFFSET(iphdr)) & IP_OFFMASK) * 8)));
        /* 重新组装包 */
        p = ip4_reass(p);
        if (p == NULL) { /* IP包还没组装好 */
          return ERR_OK;
        }
        /* IP报文重组好了,更新IP首部指针 */
        iphdr = (const struct ip_hdr *)p->payload;
    #else /* IP_REASSEMBLY == 0, 不支持分片重组 */
        /* 不支持分片重组,遇到分片IP报文,直接丢弃 */
        pbuf_free(p);
        LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("IP packet dropped since it was fragmented (0x%"X16_F") (while IP_REASSEMBLY == 0).\n",
                    lwip_ntohs(IPH_OFFSET(iphdr))));
        IP_STATS_INC(ip.opterr);
        IP_STATS_INC(ip.drop);
        /* unsupported protocol feature */
        MIB2_STATS_INC(mib2.ipinunknownprotos);
        return ERR_OK;
    #endif /* IP_REASSEMBLY */
      }
    
    #if IP_OPTIONS_ALLOWED == 0 /* no support for IP options in the IP header? */
    
    #if LWIP_IGMP
      /* 在IGMP消息中有一个额外的“路由器警报”选项,我们允许但不监督 */
      /* 如果IP报文带选项字段,lwip只支持IGMP */
      if ((iphdr_hlen > IP_HLEN) &&  (IPH_PROTO(iphdr) != IP_PROTO_IGMP)) {
    #else
      if (iphdr_hlen > IP_HLEN) { /* LWIP不支持接收处理带选项字段的IP报文。(IGMP协议除外) */
    #endif /* LWIP_IGMP */
        LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("IP packet dropped since there were IP options (while IP_OPTIONS_ALLOWED == 0).\n"));
        pbuf_free(p);
        IP_STATS_INC(ip.opterr);
        IP_STATS_INC(ip.drop);
        /* unsupported protocol feature */
        MIB2_STATS_INC(mib2.ipinunknownprotos);
        return ERR_OK;
      }
    #endif /* IP_OPTIONS_ALLOWED == 0 */
    
      /* 发送到上层 */
      LWIP_DEBUGF(IP_DEBUG, ("ip4_input: \n"));
      ip4_debug_print(p);
      LWIP_DEBUGF(IP_DEBUG, ("ip4_input: p->len %"U16_F" p->tot_len %"U16_F"\n", p->len, p->tot_len));
    
      /* 更新这个全局IP数据 */
      ip_data.current_netif = netif; /* 上层处理该数据包的网卡 */
      ip_data.current_input_netif = inp; /* 数据链路接收到该数据包的网卡 */
      ip_data.current_ip4_header = iphdr; /* IP报文首部 */
      ip_data.current_ip_header_tot_len = IPH_HL_BYTES(iphdr); /* IP首部长度(单位:字节) */
    
    #if LWIP_RAW /* RAW */
      /* 传到RAW输入 */
      raw_status = raw_input(p, inp);
      if (raw_status != RAW_INPUT_EATEN) /* RAW传入失败,那就传给其它上层协议模块 */
    #endif /* LWIP_RAW */
      {
        pbuf_remove_header(p, iphdr_hlen); /* 移到有效载荷,不需要检查 */
    
        switch (IPH_PROTO(iphdr)) { /* 根据IP报文协议字段传入对应协议模块 */
    #if LWIP_UDP
          case IP_PROTO_UDP: /* UDP协议 */
    #if LWIP_UDPLITE
          case IP_PROTO_UDPLITE: /* UDP-Lite协议 */
    #endif /* LWIP_UDPLITE */
            MIB2_STATS_INC(mib2.ipindelivers);
            udp_input(p, inp); /* 传入UDP协议模块 */
            break;
    #endif /* LWIP_UDP */
    #if LWIP_TCP
          case IP_PROTO_TCP: /* TCP协议 */
            MIB2_STATS_INC(mib2.ipindelivers);
            tcp_input(p, inp); /* 传入TCP协议模块 */
            break;
    #endif /* LWIP_TCP */
    #if LWIP_ICMP
          case IP_PROTO_ICMP: /* ICMP协议 */
            MIB2_STATS_INC(mib2.ipindelivers);
            icmp_input(p, inp); /* 传入ICMP协议模块 */
            break;
    #endif /* LWIP_ICMP */
    #if LWIP_IGMP
          case IP_PROTO_IGMP: /* IGMP协议 */
            igmp_input(p, inp, ip4_current_dest_addr()); /* 传入IGMP协议模块 */
            break;
    #endif /* LWIP_IGMP */
          default: /* 其它 */
    #if LWIP_RAW
            if (raw_status == RAW_INPUT_DELIVERED) {
              MIB2_STATS_INC(mib2.ipindelivers);
            } else
    #endif /* LWIP_RAW */
            {
    #if LWIP_ICMP
              /* 不是广播包,也不是多播包,数据链路层给到我们网卡不支持的协议,发送ICMP目的协议不可达 */
              if (!ip4_addr_isbroadcast(ip4_current_dest_addr(), netif) &&
                  !ip4_addr_ismulticast(ip4_current_dest_addr())) {
                pbuf_header_force(p, (s16_t)iphdr_hlen); /* 移动到ip头,不需要检查 */
                icmp_dest_unreach(p, ICMP_DUR_PROTO); /* 发送ICMP目的协议不可达 */
              }
    #endif /* LWIP_ICMP */
    
              LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("Unsupported transport protocol %"U16_F"\n", (u16_t)IPH_PROTO(iphdr)));
    
              IP_STATS_INC(ip.proterr);
              IP_STATS_INC(ip.drop);
              MIB2_STATS_INC(mib2.ipinunknownprotos);
            }
            pbuf_free(p); /* 丢弃,释放pbuf */
            break;
        }
      }
    
      /* @todo: this is not really necessary... */
      ip_data.current_netif = NULL;
      ip_data.current_input_netif = NULL;
      ip_data.current_ip4_header = NULL;
      ip_data.current_ip_header_tot_len = 0;
      ip4_addr_set_any(ip4_current_src_addr());
      ip4_addr_set_any(ip4_current_dest_addr());
    
      return ERR_OK;
    }
    

    9.8.2 IP数据报转发

    如果数据包给到我们网卡数据链路层了,但是目的IP不是给我们网卡IP层的,那可能是想通过我们网卡转发该包。

    相关宏:

    • IP_FORWARD_ALLOW_TX_ON_RX_NETIF

      • 允许ip_forward()在接收到数据包的netif上发送数据包。这应该只用于无线网络。
      • 为1时,请确保netif驱动程序正确标记传入的链路层广播/组播数据包等使用相应的pbuf标志!

    调用ip4_forward()将数据包转发:

    • 先判断IP包是否能转发。主要调用ip4_canforward()判断。

      • 钩子函数:LWIP_HOOK_IP4_CANFORWARD(src, dest)。现有用户实现钩子裁定能否转发当前IP包。
      • 链路层的广播包。不能转发。
      • 链路层的多播包。不能转发。
      • 目的IP为255.x.x.x的IP包。不能转发。
      • 目的IP为127.x.x.x(环回)的IP包。不能转发。
      • 目的IP为169.254.x.x(本地链路IP)的IP包,不能转发。
    • 匹配新网卡。调用ip4_route_src(src, dest)路由匹配网卡,进行转发该IP包。

    • 检查路由匹配成功的网卡。

      • 一般情况下,是不能转发回原链路的。
      • 如果需要转发回原链路,一般只能用于无线网卡。
    • IP报文TTL字段值减1。如果TTL值为0,则,丢弃该IP包,并通过ICMP icmp_dest_unreach(struct pbuf *p, enum icmp_dur_type t)告知源端路由不可达。(如果当前报文也是ICMP报文,则不用回复ICMP路由不可达)

    • TTL字段值更新后,IP报文的首部校验和字段也要更新。

      • 技巧:由于IP报文首部只是TTL字段减1,所以首部校验和字段只需要加1即可。
    • 使用新路由匹配的网卡把当前IP包转发出去。

      • 需要分片:调用ip4_frag()转发出去。
      • 不需要分片:调用网卡IP接口netif->output()转发出去。

    ip4_canforward()

    /**
     * Determine whether an IP address is in a reserved set of addresses
     * that may not be forwarded, or whether datagrams to that destination
     * may be forwarded.
     * @param p the packet to forward
     * @return 1: can forward 0: discard
     */
    static int
    ip4_canforward(struct pbuf *p)
    {
      u32_t addr = lwip_htonl(ip4_addr_get_u32(ip4_current_dest_addr()));
    
    #ifdef LWIP_HOOK_IP4_CANFORWARD
      /* 先钩子函数裁定 */
      int ret = LWIP_HOOK_IP4_CANFORWARD(p, addr);
      if (ret >= 0) {
        return ret;
      }
    #endif /* LWIP_HOOK_IP4_CANFORWARD */
    
      if (p->flags & PBUF_FLAG_LLBCAST) {
        /* don't route link-layer broadcasts */
        return 0;
      }
      if ((p->flags & PBUF_FLAG_LLMCAST) || IP_MULTICAST(addr)) {
        /* don't route link-layer multicasts (use LWIP_HOOK_IP4_CANFORWARD instead) */
        return 0;
      }
      if (IP_EXPERIMENTAL(addr)) {
        return 0;
      }
      if (IP_CLASSA(addr)) {
        u32_t net = addr & IP_CLASSA_NET;
        if ((net == 0) || (net == ((u32_t)IP_LOOPBACKNET << IP_CLASSA_NSHIFT))) {
          /* don't route loopback packets */
          return 0;
        }
      }
      return 1;
    }
    

    ip4_forward()

    /**
     * Forwards an IP packet. It finds an appropriate route for the
     * packet, decrements the TTL value of the packet, adjusts the
     * checksum and outputs the packet on the appropriate interface.
     *
     * @param p the packet to forward (p->payload points to IP header)
     * @param iphdr the IP header of the input packet
     * @param inp the netif on which this packet was received
     */
    static void
    ip4_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp)
    {
      struct netif *netif;
    
      PERF_START;
      LWIP_UNUSED_ARG(inp);
    
      if (!ip4_canforward(p)) {
        goto return_noroute;
      }
    
      /* RFC3927 2.7:不转发链路本地地址 */
      if (ip4_addr_islinklocal(ip4_current_dest_addr())) {
        LWIP_DEBUGF(IP_DEBUG, ("ip4_forward: not forwarding LLA %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
                               ip4_addr1_16(ip4_current_dest_addr()), ip4_addr2_16(ip4_current_dest_addr()),
                               ip4_addr3_16(ip4_current_dest_addr()), ip4_addr4_16(ip4_current_dest_addr())));
        goto return_noroute;
      }
    
      /* 路由匹配网卡,找到将此IP包转发到的网卡 */
      netif = ip4_route_src(ip4_current_src_addr(), ip4_current_dest_addr());
      if (netif == NULL) {
        LWIP_DEBUGF(IP_DEBUG, ("ip4_forward: no forwarding route for %"U16_F".%"U16_F".%"U16_F".%"U16_F" found\n",
                               ip4_addr1_16(ip4_current_dest_addr()), ip4_addr2_16(ip4_current_dest_addr()),
                               ip4_addr3_16(ip4_current_dest_addr()), ip4_addr4_16(ip4_current_dest_addr())));
        /* @todo: send ICMP_DUR_NET? */
        goto return_noroute;
      }
    #if !IP_FORWARD_ALLOW_TX_ON_RX_NETIF /* 不允许转发到原链路(无线网卡可以) */
      /* 不要将数据包转发到到达的同一个网络接口上 */
      if (netif == inp) {
        LWIP_DEBUGF(IP_DEBUG, ("ip4_forward: not bouncing packets back on incoming interface.\n"));
        goto return_noroute;
      }
    #endif /* IP_FORWARD_ALLOW_TX_ON_RX_NETIF */
    
      /* 已经路由匹配到能转发当前IP包的网卡了 */
    
      /* TTL减1 */
      IPH_TTL_SET(iphdr, IPH_TTL(iphdr) - 1);
    
      if (IPH_TTL(iphdr) == 0) { /* TTL值已经到达0了 */
        MIB2_STATS_INC(mib2.ipinhdrerrors);
    #if LWIP_ICMP
        /* 不要发送ICMP报文来响应ICMP报文 */
        if (IPH_PROTO(iphdr) != IP_PROTO_ICMP) {
          icmp_time_exceeded(p, ICMP_TE_TTL);
        }
    #endif /* LWIP_ICMP */
        return;
      }
    
      /* 更新IP报文首部校验和。技巧:首部校验和加1即可。注意网络字节序。 */
      if (IPH_CHKSUM(iphdr) >= PP_HTONS(0xffffU - 0x100)) {
        IPH_CHKSUM_SET(iphdr, (u16_t)(IPH_CHKSUM(iphdr) + PP_HTONS(0x100) + 1));
      } else {
        IPH_CHKSUM_SET(iphdr, (u16_t)(IPH_CHKSUM(iphdr) + PP_HTONS(0x100)));
      }
    
      /* 当我们有多个netifs,其中至少有一个具有校验和卸载功能时,IP转发需要将各个校验和字段设置为0,以防止HW硬件算法计算无效的校验和 */
      /* https://github.com/lwip-tcpip/lwip/commit/61c67fc2295e522c7a12175581d6928c3951c0bf */
      /* http://savannah.nongnu.org/bugs/?func=detailitem&item_id=56288 */
      /* 这里有点没看明白:如果网卡没有硬件校验和卸载功能,lwip协议栈网卡开启CHECKSUM_GEN_IP宏使用软件校验和功能,那在这里转发IP包时,岂不是把数据包校验和字段都清空了? */
      if (CHECKSUM_GEN_IP || NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_GEN_IP)) { /* 如果使用软件生成了校验和,这里需要清空 */
        IPH_CHKSUM_SET(iphdr, 0);
      }
      switch (IPH_PROTO(iphdr)) { /* 上层协议的校验和也清空 */
    #if LWIP_UDP
    #if LWIP_UDPLITE
      case IP_PROTO_UDPLITE:
    #endif
      case IP_PROTO_UDP:
        if (CHECKSUM_GEN_UDP || NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_GEN_UDP)) {
          ((struct udp_hdr *)((u8_t *)iphdr + IPH_HL_BYTES(iphdr)))->chksum = 0;
        }
        break;
    #endif
    #if LWIP_TCP
      case IP_PROTO_TCP:
        if (CHECKSUM_GEN_TCP || NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_GEN_TCP)) {
          ((struct tcp_hdr *)((u8_t *)iphdr + IPH_HL_BYTES(iphdr)))->chksum = 0;
        }
        break;
    #endif
    #if LWIP_ICMP
      case IP_PROTO_ICMP:
        if (CHECKSUM_GEN_ICMP || NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_GEN_ICMP)) {
          ((struct icmp_hdr *)((u8_t *)iphdr + IPH_HL_BYTES(iphdr)))->chksum = 0;
        }
        break;
    #endif
      default:
        /* there's really nothing to do here other than satisfying 'switch-default' */
        break;
      }
    
      LWIP_DEBUGF(IP_DEBUG, ("ip4_forward: forwarding packet to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
                             ip4_addr1_16(ip4_current_dest_addr()), ip4_addr2_16(ip4_current_dest_addr()),
                             ip4_addr3_16(ip4_current_dest_addr()), ip4_addr4_16(ip4_current_dest_addr())));
    
      IP_STATS_INC(ip.fw);
      MIB2_STATS_INC(mib2.ipforwdatagrams);
      IP_STATS_INC(ip.xmit);
    
      PERF_STOP("ip4_forward");
      /* 分片检查&处理 */
      /* mtu为0,表示该网卡不支持分片 */
      if (netif->mtu && (p->tot_len > netif->mtu)) { /* 需要分片 */
        if ((IPH_OFFSET(iphdr) & PP_NTOHS(IP_DF)) == 0) {
    #if IP_FRAG
          /* 分片发包 */
          ip4_frag(p, netif, ip4_current_dest_addr());
    #else /* IP_FRAG */
          /* @todo: send ICMP Destination Unreachable code 13 "Communication administratively prohibited"? */
    #endif /* IP_FRAG */
        } else {
    #if LWIP_ICMP
          /* 发送ICMP目的不可达代码4: "Fragmentation Needed and DF Set" */
          icmp_dest_unreach(p, ICMP_DUR_FRAG);
    #endif /* LWIP_ICMP */
        }
        return;
      }
      /* 不需要分片就直接发出IP层 */
      netif->output(netif, p, ip4_current_dest_addr());
      return;
    return_noroute:
      MIB2_STATS_INC(mib2.ipoutnoroutes);
    }
    

    9.8.3 IP数据报重组

    如果IP层收到一个IP报文,目的IP是给我们网卡IP层的,且检查当前IP包的首部是是一个分片包,则需要IP报文重组。

    重组IP报文的源码实现比较复杂,所以需要耐心分析。

    重装IP报文比分片IP报文要难,就是因为,重装IP报文的每个IP包到达的时间不是按顺序的,会出现后发的IP分片包比先发的IP分片包早到达(网络路由问题),这需要我们按序重组好。

    注意:lwip当前不支持IP首部带选项字段的IP报文进行分片和重组。

    9.8.3.1 相关数据结构

    代码的数据结构是非常重要的,通过对数据结构的逻辑处理能封装出各种功能的API。

    一个完整IP报文由多个IP分片包组成。

    这个IP报文用struct ip_reassdata数据结构管理;

    而每个IP分片包用struct ip_reass_helper数据结构管理。

    • 为了节省空间,这个数据结构和IP包pbuf的空间共用。把收到的分片pbuf的IP包首部重置为这个数据结构。因为最终的IP报文只需要一个IP首部即可,每个分片的IP首部空间可以利用起来。

    lwip协议栈用全局变量struct ip_reassdata *reassdatagrams;管理整个协议栈的各个在重组中的IP报文,为单向链表。

    重组中的IP报文数据结构struct ip_reassdata

    /** IP reassembly helper struct.
     * This is exported because memp needs to know the size.
     */
    struct ip_reassdata {
      struct ip_reassdata *next; /* 单向链表节点 */
      struct pbuf *p; /* pbuf链表。是当前IP分片包的pbuf,各个分片的链,是由struct ip_reass_helper数据结构管理。 */
      struct ip_hdr iphdr; /* 当前IP报文的IP首部 */
      u16_t datagram_len; /* 已经收到的IP报长度或IP报文总长度。如果收到最后一个分片,则当前值为IP报文总长度。 */
      u8_t flags; /* 是否收到最后一个分片 */
      u8_t timer; /* 设置超时间隔 */
    };
    

    各个IP分片包的数据结构struct ip_reass_helper

    struct ip_reass_helper {
      PACK_STRUCT_FIELD(struct pbuf *next_pbuf); /* 下一个IP分片 */
      PACK_STRUCT_FIELD(u16_t start); /* 分片中数据在IP报文的起始位置 */
      PACK_STRUCT_FIELD(u16_t end); /* 分片中数据在IP报文的结束位置 */
    } PACK_STRUCT_STRUCT;
    

    9.8.3.2 相关宏

    IP_REASS_MAXAGE:默认15。为每个重组IP报文有效期。超时还没重组好就需要删除重组IP报文。

    IP_REASS_MAX_PBUFS:系统重组IP报文所有pbuf节点上限值。

    IP_REASS_FREE_OLDEST:若开启,遇到系统重组IP报文所有pbuf节点达到系统上限值IP_REASS_MAX_PBUFS后,允许释放旧的重组IP报文所有IP分片。

    9.8.3.3 相关函数

    ip4_reass(struct pbuf *p)

    • IPv4分片报文重组函数。
    • 供给lwip内核接收IP包时使用的API。
    • 不支持待选项字段的IP报文进行重组。

    ip_reass_enqueue_new_datagram(struct ip_hdr *fraghdr, int clen):新建一个重组IP报文条目并插入reassdatagrams链表中。

    ip_reass_dequeue_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev):释放一个重组IP报文条目。

    ip_reass_free_complete_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev):释放一个重组IP报文条目及其所有pbuf。

    ip_reass_remove_oldest_datagram(struct ip_hdr *fraghdr, int pbufs_needed):删除老的重组IP报文。

    ip_reass_chain_frag_into_datagram_and_validate(struct ip_reassdata *ipr, struct pbuf *new_p, int is_last):插入IP分片到对应重组IP报文。

    9.8.3.4 ip4_reass()

    源码参考:ip4_frag.c

    IP报文重组调用ip4_reass()先分析该函数的总体框架,后面再分析各个函数的细节:

    • 检查IP分片包。

      • 收到一个IP分片后,检查IP分片的合法性。IP首部长度是否符合要求。目前LWIP不支持带选项的IP包分片和重组。
      • IP首部长度不能大于IP包长度。
      • 确保重组中的IP报文所有pbuf节点不能超过系统上限值IP_REASS_MAX_PBUFS。如果预判超出了,需要调用ip_reass_remove_oldest_datagram()删除最老的重组中的IP报文,直至够空间记录当前IP分片包的pbuf节点数为止。
    • 记录IP分片包到reassdatagrams链表中对应IP报文。

      • 找到当前IP分片的重组IP报文:检索reassdatagrams链表,是否有当前IP分片的IP报文。如果没有,需要调用ip_reass_enqueue_new_datagram()创建一个新的重组IP报文数据结构,并插入到reassdatagrams链表中。如果有,还需要检查当前IP分片是否是IP报文中的第一个IP分片包,如果是,需要把这个IP分片包的IP首部更新到重组IP报文数据结构的IP报文首部字段ip_reassdata->iphdr

        • 匹配重组IP报文条件:源IP、目的IP、IP标识这三个字段一致即可。
      • 预判IP报文是否溢出:如果当前IP分片包是最后一个,可以通过IP分片偏移量加上当前IP分片包的长度可以计算出完整的IP报文长度,如果这个长度溢出IP报文的长度字段,则丢弃,并删除重组中的IP报文。

      • 当前IP分片插入重组IP报文的IP分片链表中。调用ip_reass_chain_frag_into_datagram_and_validate()将其插入。返回值由以下三个:

        • IP_REASS_VALIDATE_TELEGRAM_FINISHED:所有IP分片已经接收完毕,可以完成IP报文重组工作。
        • IP_REASS_VALIDATE_PBUF_QUEUED:当前IP分片正常插入,但是当前IP报文还没有接收完所有IP分片。
        • IP_REASS_VALIDATE_PBUF_DROPPED:当前IP分片插入失败,即是丢弃当前IP分片。
      • 更新reassdatagrams链表中所有pbuf节点的数量值ip_reass_pbufcount(全局)。因为系统设置有上限IP_REASS_MAX_PBUFS,所以要动态记录。

    • 当前IP分片是否为最后一个分片。

      • 如果是最后一个分片,则更新重组IP报文数据结构中的长度值为总长度值ip_reassdata->datagram_len,标记重组IP报文收到最后一个IP分片ip_reassdata->flags |= IP_REASS_FLAG_LASTFRAG
    • IP报文的所有IP分片包是否已经接收完毕。

      • 如果所有IP分片都收到了,则可进行重组。
    • 重组完整IP报文。遍历所有IP分片,并合并。节省空间。

      • 统计IP报文的总长度。首部+数据=IP_HLEN+ip_reassdata->datagram_len
      • 把重组IP报文数据结构中的IP首部字段拷贝到第一个分片的IP首部作为完整IP报文的首部。
      • 遍历重组IP报文中所有IP分片,将其合并,除了首个IP分片的IP首部作为完整IP报文的首部外,其它IP分片的IP首部都移除。
      • 至此,完整的IP报文pbuf重组完成。
    /**
     * Reassembles incoming IP fragments into an IP datagram.
     *
     * @param p points to a pbuf chain of the fragment
     * @return NULL if reassembly is incomplete, ? otherwise
     */
    struct pbuf *
    ip4_reass(struct pbuf *p)
    {
      struct pbuf *r; /* pbuf指针 */
      struct ip_hdr *fraghdr; /* IP首部指针 */
      struct ip_reassdata *ipr; /* 重组IP报文指针 */
      struct ip_reass_helper *iprh; /* IP分片节点管理数据结构指针 */
      u16_t offset, len, clen; /* 分片偏移量,分片长度,分片pbuf节点数 */
      u8_t hlen; /* IP分片首部长度 */
      int valid; /* 插入IP分片后的结果 */
      int is_last; /* 最后一个分片标志 */
    
      IPFRAG_STATS_INC(ip_frag.recv);
      MIB2_STATS_INC(mib2.ipreasmreqds);
    
      fraghdr = (struct ip_hdr *)p->payload; /* 获取IP分片首部 */
    
      if (IPH_HL_BYTES(fraghdr) != IP_HLEN) { /* 不支持带选项字段的IP报文重组 */
        LWIP_DEBUGF(IP_REASS_DEBUG, ("ip4_reass: IP options currently not supported!\n"));
        IPFRAG_STATS_INC(ip_frag.err);
        goto nullreturn;
      }
    
      offset = IPH_OFFSET_BYTES(fraghdr); /* 获取分片偏移量 */
      len = lwip_ntohs(IPH_LEN(fraghdr)); /* 获取分片包长度 */
      hlen = IPH_HL_BYTES(fraghdr); /* 获取分片首部长度 */
      if (hlen > len) { /* 校验 */
        /* invalid datagram */
        goto nullreturn;
      }
      len = (u16_t)(len - hlen); /* 获取IP分片数据字段长度 */
    
      /* 检查是否允许我们排队更多的数据报 */
      clen = pbuf_clen(p); /* 获取当前IP分片包含多少个pbuf节点 */
      /* 预测当前分片插入重组IP报文后,整个系统的重组IP报文的所有pbuf节点是否超过系统上限值IP_REASS_MAX_PBUFS。
         若超过,需要释放老的重组IP报文来腾出空间 */
      if ((ip_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS) { /* 预测的结果超过系统上限值 */
    #if IP_REASS_FREE_OLDEST /* 允许释放老的重组IP报文 */
        if (!ip_reass_remove_oldest_datagram(fraghdr, clen) || /* 释放失败 */
            ((ip_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS)) /* 或者尽可能释放了,但是还是不能满足当前IP分片的插入 */
    #endif /* IP_REASS_FREE_OLDEST */
        { /* 需要丢弃当前IP分片 */
          /* 没有数据报被释放,仍然有太多的pbuf进入队列 */
          LWIP_DEBUGF(IP_REASS_DEBUG, ("ip4_reass: Overflow condition: pbufct=%d, clen=%d, MAX=%d\n",
                                       ip_reass_pbufcount, clen, IP_REASS_MAX_PBUFS));
          IPFRAG_STATS_INC(ip_frag.memerr);
          /* @todo: 发送ICMP时间超过这里? */
          /* 丢弃当前IP分片 */
          goto nullreturn;
        }
      }
    
      /* 在当前数据报队列中查找分片所属的重组IP报文,记住队列中的前一个,以便以后退出队列 */
      for (ipr = reassdatagrams; ipr != NULL; ipr = ipr->next) {
        /* 检查传入的片段是否与当前出现在重组缓冲区中的片段相匹配。如果是这样,我们继续将这个片段复制到缓冲区中 */
        if (IP_ADDRESSES_AND_ID_MATCH(&ipr->iphdr, fraghdr)) { /* 匹配成功。根据源IP、目的IP和IP标识这三个字段进行匹配。 */
          LWIP_DEBUGF(IP_REASS_DEBUG, ("ip4_reass: matching previous fragment ID=%"X16_F"\n",
                                       lwip_ntohs(IPH_ID(fraghdr))));
          IPFRAG_STATS_INC(ip_frag.cachehit);
          break; /* 跳出 */
        }
      }
    
      if (ipr == NULL) { /* 还没有当前IP分片的重组IP报文呢,需要新建 */
        /* 创建新的重组IP报文,并插入到全局链表reassdatagrams */
        ipr = ip_reass_enqueue_new_datagram(fraghdr, clen);
        if (ipr == NULL) { /* 创建或插入失败,丢弃当前IP分片 */
          goto nullreturn;
        }
      } else { /* 已经存在 */
        if (((lwip_ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) == 0) && /* IP报文的第一个IP分片 */
            ((lwip_ntohs(IPH_OFFSET(&ipr->iphdr)) & IP_OFFMASK) != 0)) { /* 还没有记录过第一个分片的IP首部 */
          /* 最先收到的IP分片不一定是IP报文的第一个IP分片。这里需要第一个IP分片的首部是因为对于超过ICMP时间和之后,为了复制所有选项 */
          SMEMCPY(&ipr->iphdr, fraghdr, IP_HLEN); /* IP报文的第一个IP分片的IP首部作为整个IP报文首部的基础 */
        }
      }
    
      /* 此时,我们已经创建了一个新重组IP报文,或者指向一个现有重组IP报文 */
    
      /* 检查整个IP报文的长度字段是否合法 */
      /* 可以通过最后一个IP分片的偏移量和分片数据区长度可以计算出完整的IP报文的数据区总长度 */
      is_last = (IPH_OFFSET(fraghdr) & PP_NTOHS(IP_MF)) == 0;
      if (is_last) {
        u16_t datagram_len = (u16_t)(offset + len);
        if ((datagram_len < offset) || (datagram_len > (0xFFFF - IP_HLEN))) {
          /* u16_t 溢出,无法处理此操作 */
          goto nullreturn_ipr;
        }
      }
      /* 找到插入这个pbuf的正确位置 */
      /* @todo: 如果片段重叠,则修剪pbufs */
      valid = ip_reass_chain_frag_into_datagram_and_validate(ipr, p, is_last);
      if (valid == IP_REASS_VALIDATE_PBUF_DROPPED) { /* 插入失败,丢弃当前IP分片 */
        goto nullreturn_ipr;
      }
      /* 如果我们来到这里,pbuf已经被加入队列 */
    
      /* 更新重组IP报文数据结构相关字段 */
    
      ip_reass_pbufcount = (u16_t)(ip_reass_pbufcount + clen); /* 更新全局当前所有重组IP报文的pbuf节点数,用于判断是否踩上限线 */
      if (is_last) { /* 如果是最后一个分片,则可以知道整个IP报文的长度 */
        u16_t datagram_len = (u16_t)(offset + len); /* 偏移量+当前分片IP包数据区长度就是IP报文数据区总长度 */
        ipr->datagram_len = datagram_len; /* 转为记录IP报文数据区总长度 */
        ipr->flags |= IP_REASS_FLAG_LASTFRAG; /* 标记已经收到最后一个IP分片,且上面长度值改为记录IP报文数据区总长度 */
        LWIP_DEBUGF(IP_REASS_DEBUG,
                    ("ip4_reass: last fragment seen, total len %"S16_F"\n",
                     ipr->datagram_len));
      }
    
      if (valid == IP_REASS_VALIDATE_TELEGRAM_FINISHED) { /* 全部IP分片接收完毕,可以进行重组 */
        struct ip_reassdata *ipr_prev;
        u16_t datagram_len = (u16_t)(ipr->datagram_len + IP_HLEN); /* IP报文总长度 */
    
        r = ((struct ip_reass_helper *)ipr->p->payload)->next_pbuf; /* r指向第二个分片 */
    
        /* 将原始IP头复制回第一个pbuf,作为完整IP报文的IP首部 */
        fraghdr = (struct ip_hdr *)(ipr->p->payload);
        SMEMCPY(fraghdr, &ipr->iphdr, IP_HLEN);
        IPH_LEN_SET(fraghdr, lwip_htons(datagram_len));
        IPH_OFFSET_SET(fraghdr, 0); /* 清空偏移量和标志字段 */
        IPH_CHKSUM_SET(fraghdr, 0); /* 清空首部校验和 */
        /* @todo: do we need to set/calculate the correct checksum? */
    #if CHECKSUM_GEN_IP
        IF__NETIF_CHECKSUM_ENABLED(ip_current_input_netif(), NETIF_CHECKSUM_GEN_IP) {
          IPH_CHKSUM_SET(fraghdr, inet_chksum(fraghdr, IP_HLEN)); /* 重置IP首部校验和 */
        }
    #endif /* CHECKSUM_GEN_IP */
    
        p = ipr->p; /* p指向第一个分片 */
    
        /* 遍历所有分片将其合并到第一个分片的pbuf中 */
        while (r != NULL) {
          iprh = (struct ip_reass_helper *)r->payload; /* 获取IP分片管理区 */
    
          pbuf_remove_header(r, IP_HLEN); /* 隐藏每个后续片段的IP头 */
          pbuf_cat(p, r); /* 合并分片 */
          r = iprh->next_pbuf; /* 指向下一个分片。遍历 */
        }
      
        /* 重组完毕,需要移除重组IP报文相关资源 */
    
        if (ipr == reassdatagrams) { /* reassdatagrams链表是一个没有哨兵的单向非循环链表。如果移除的是首节点,则不需要记录前一个节点 */
          ipr_prev = NULL;
        } else { /* 如果不是首个节点,则需要知道这个节点的前一个节点,才能把当前节点从单向链表中正常删除 */
          for (ipr_prev = reassdatagrams; ipr_prev != NULL; ipr_prev = ipr_prev->next) {
            if (ipr_prev->next == ipr) {
              break; /* 找到了 */
            }
          }
        }
    
        /* 释放重组IP报文的节点 */
        ip_reass_dequeue_datagram(ipr, ipr_prev);
    
        /* 并调整当前排队等待重组的pbuf的数量 */
        clen = pbuf_clen(p);
        LWIP_ASSERT("ip_reass_pbufcount >= clen", ip_reass_pbufcount >= clen);
        ip_reass_pbufcount = (u16_t)(ip_reass_pbufcount - clen);
    
        MIB2_STATS_INC(mib2.ipreasmoks);
    
        /* 返回重组后的IP报文pbuf */
        return p;
      }
      /* 还没收到所有IP分片 */
      LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass_pbufcount: %d out\n", ip_reass_pbufcount));
      return NULL;
    
    nullreturn_ipr: /* 丢弃当前IP分片 */
      LWIP_ASSERT("ipr != NULL", ipr != NULL);
      if (ipr->p == NULL) {
        /* 在创建新的数据报条目后删除pbuf:也删除该条目 */
        LWIP_ASSERT("not firstalthough just enqueued", ipr == reassdatagrams);
        ip_reass_dequeue_datagram(ipr, NULL);
      }
    
    nullreturn:
      LWIP_DEBUGF(IP_REASS_DEBUG, ("ip4_reass: nullreturn\n"));
      IPFRAG_STATS_INC(ip_frag.drop);
      pbuf_free(p); /* 丢弃当前IP分片,释放pbuf资源 */
      return NULL;
    }
    

    9.8.3.5 ip_reass_enqueue_new_datagram()

    ip_reass_enqueue_new_datagram()

    • 申请重组IP报文节点ip_reassdata数据结构的空间。
    • 如果MEMP_REASSDATA内存池空间不足,则可以释放老的重组IP报文节点。
    • 初始化该结构体。
    • 有效期配置为IP_REASS_MAXAGE
    • 插入reassdatagrams链表。
    • 保存IP分片包首部。
    /**
     * Enqueues a new fragment into the fragment queue
     * @param fraghdr points to the new fragments IP hdr
     * @param clen number of pbufs needed to enqueue (used for freeing other datagrams if not enough space)
     * @return A pointer to the queue location into which the fragment was enqueued
     */
    static struct ip_reassdata *
    ip_reass_enqueue_new_datagram(struct ip_hdr *fraghdr, int clen)
    {
      struct ip_reassdata *ipr;
    #if ! IP_REASS_FREE_OLDEST
      LWIP_UNUSED_ARG(clen);
    #endif
    
      /* 申请一个新的reassdata结构体 */
      ipr = (struct ip_reassdata *)memp_malloc(MEMP_REASSDATA);
      if (ipr == NULL) {
    #if IP_REASS_FREE_OLDEST
        /* 空间不足就释放老的重组IP报文条目 */
        if (ip_reass_remove_oldest_datagram(fraghdr, clen) >= clen) {
          ipr = (struct ip_reassdata *)memp_malloc(MEMP_REASSDATA);
        }
        if (ipr == NULL)
    #endif /* IP_REASS_FREE_OLDEST */
        {
          IPFRAG_STATS_INC(ip_frag.memerr);
          LWIP_DEBUGF(IP_REASS_DEBUG, ("Failed to alloc reassdata struct\n"));
          return NULL; /* 申请资源失败 */
        }
      }
      memset(ipr, 0, sizeof(struct ip_reassdata));
      ipr->timer = IP_REASS_MAXAGE;/* 有效期赋值 */
    
      /* 插入单向链表头 */
      ipr->next = reassdatagrams;
      reassdatagrams = ipr;
      /* 复制IP头,以便稍后进行测试和输入 */
      /* @todo: no ip options supported? */
      SMEMCPY(&(ipr->iphdr), fraghdr, IP_HLEN);
      return ipr;
    }
    

    9.8.3.6 ip_reass_dequeue_datagram()

    ip_reass_dequeue_datagram()

    • 释放重组IP报文条目资源。
    • 在过期还没重组好,或者已经重组好,或者需要为新的重组IP报文腾空间,都需要把不需要的重组IP报文删除,并从链表中移除。
    • ipr:需要删除的重组IP报文节点。
    • prev:被删除节点的前一个节点。由调用者提供。“奇怪的设计”
    /**
     * Dequeues a datagram from the datagram queue. Doesn't deallocate the pbufs.
     * @param ipr points to the queue entry to dequeue
     */
    static void
    ip_reass_dequeue_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev)
    {
      if (reassdatagrams == ipr) { /* 如果删除的是链表头,需要更新链表头 */
        /* 更新链表头 */
        reassdatagrams = ipr->next;
      } else {
        /* 它不是第一个,所以它必须有一个有效的'prev' */
        LWIP_ASSERT("sanity check linked list", prev != NULL);
        prev->next = ipr->next; /* 从链表中移除当前重组IP报文条目 */
      }
    
      /* 释放资源 */
      memp_free(MEMP_REASSDATA, ipr);
    }
    

    9.8.3.7 ip_reass_free_complete_datagram()

    ip_reass_free_complete_datagram()

    • 释放重组IP报文节点及其所有pbuf。
    • ICMP:如果收到了第一个IP分片,在重组删除时,需要返回一个ICMP超时。
    • 释放重组IP报文的所有pbuf。
    • 释放重组IP报文。
    /**
     * Free a datagram (struct ip_reassdata) and all its pbufs.
     * Updates the total count of enqueued pbufs (ip_reass_pbufcount),
     * SNMP counters and sends an ICMP time exceeded packet.
     *
     * @param ipr datagram to free
     * @param prev the previous datagram in the linked list
     * @return the number of pbufs freed
     */
    static int
    ip_reass_free_complete_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev)
    {
      u16_t pbufs_freed = 0;
      u16_t clen;
      struct pbuf *p;
      struct ip_reass_helper *iprh;
    
      LWIP_ASSERT("prev != ipr", prev != ipr);
      if (prev != NULL) {
        LWIP_ASSERT("prev->next == ipr", prev->next == ipr);
      }
    
      MIB2_STATS_INC(mib2.ipreasmfails);
    #if LWIP_ICMP
      iprh = (struct ip_reass_helper *)ipr->p->payload;
      if (iprh->start == 0) {
        /* 收到了第一个分片报文,发送ICMP超时 */
        /* 首先,从r->p中取出第一个pbuf. */
        p = ipr->p;
        ipr->p = iprh->next_pbuf;
        /* 然后,将原始头部复制过去 */
        SMEMCPY(p->payload, &ipr->iphdr, IP_HLEN);
        icmp_time_exceeded(p, ICMP_TE_FRAG);
        clen = pbuf_clen(p);
        LWIP_ASSERT("pbufs_freed + clen <= 0xffff", pbufs_freed + clen <= 0xffff);
        pbufs_freed = (u16_t)(pbufs_freed + clen);
        pbuf_free(p); /* 释放pbuf */
      }
    #endif /* LWIP_ICMP */
    
      /* 首先,free接收的所有pbufs。需要分别释放各个pbuf,因为它们还没有被链接 */
      p = ipr->p;
      while (p != NULL) {
        struct pbuf *pcur;
        iprh = (struct ip_reass_helper *)p->payload;
        pcur = p;
        /* 在释放之前获取下一个指针 */
        p = iprh->next_pbuf;
        clen = pbuf_clen(pcur);
        LWIP_ASSERT("pbufs_freed + clen <= 0xffff", pbufs_freed + clen <= 0xffff);
        pbufs_freed = (u16_t)(pbufs_freed + clen);
        pbuf_free(pcur);
      }
      /* 然后,从列表中解耦结构ip_reassdata并释放它 */
      ip_reass_dequeue_datagram(ipr, prev);
      LWIP_ASSERT("ip_reass_pbufcount >= pbufs_freed", ip_reass_pbufcount >= pbufs_freed);
      ip_reass_pbufcount = (u16_t)(ip_reass_pbufcount - pbufs_freed);
    
      return pbufs_freed;
    }
    

    9.8.3.8 ip_reass_remove_oldest_datagram()

    ip_reass_remove_oldest_datagram()

    • 删除老的重组IP报文。
    • IP_REASS_FREE_OLDEST宏控制。
    • fraghdr:当前IP分片。传入该参数是因为防止在遍历链表删除老的IP重组IP报文时,跳过这个重组IP报文,因为我们的目的就是为这个重组IP报文新来的IP分片腾空间。
    • pbufs_needed:需要腾出的pbuf数。
    • 逻辑简单:遍历、找到最老的非当前重组IP报文的重组IP报文节点、删除。一直遍历删除到够空闲pbuf数或者没有其它重组IP报文节点为止。
    #if IP_REASS_FREE_OLDEST
    /**
     * Free the oldest datagram to make room for enqueueing new fragments.
     * The datagram 'fraghdr' belongs to is not freed!
     *
     * @param fraghdr IP header of the current fragment
     * @param pbufs_needed number of pbufs needed to enqueue
     *        (used for freeing other datagrams if not enough space)
     * @return the number of pbufs freed
     */
    static int
    ip_reass_remove_oldest_datagram(struct ip_hdr *fraghdr, int pbufs_needed)
    {
      /* @todo Can't we simply remove the last datagram in the linked list behind reassdatagrams? */
      struct ip_reassdata *r, *oldest, *prev, *oldest_prev;
      int pbufs_freed = 0, pbufs_freed_current;
      int other_datagrams;
    
      /* 释放数据报,直到允许进入'pbufs_needed' pbufs队列,但不要释放'fraghdr'所属的数据报! */
      do { /* 外循环:每次循环最多释放一个重组IP报文,但是不一定释放够pbuf节点数。 */
        oldest = NULL;
        prev = NULL;
        oldest_prev = NULL; /* 被删除的节点的前一个节点。单向链表删除节点的逻辑需要。 */
        other_datagrams = 0;
        r = reassdatagrams;
        while (r != NULL) { /* 内循环:遍历reassdatagrams,找出其它最老的重组IP报文 */
          if (!IP_ADDRESSES_AND_ID_MATCH(&r->iphdr, fraghdr)) { /* 和fraghdr不是同一个数据报,都需要被检索 */
            /* 记录和fraghdr不是同一个数据报个数 */
            other_datagrams++;
            if (oldest == NULL) {
              oldest = r;
              oldest_prev = prev;
            } else if (r->timer <= oldest->timer) {
              /* 比之前最老的还要老 */
              oldest = r;
              oldest_prev = prev;
            }
          }
          if (r->next != NULL) {
            prev = r;
          }
          r = r->next;
        }
        if (oldest != NULL) {
          /* 释放资源 */
          pbufs_freed_current = ip_reass_free_complete_datagram(oldest, oldest_prev);
          pbufs_freed += pbufs_freed_current; /* 记录已经释放了多少个pbuf节点 */
        }
      } while ((pbufs_freed < pbufs_needed) && (other_datagrams > 1)); /* 如果释放的pbuf节点满足要求,或者没有其它pbuf节点可以释放了,就退出循环 */
      return pbufs_freed; /* 返回释放pbuf节点的个数 */
    }
    #endif /* IP_REASS_FREE_OLDEST */
    

    9.8.3.9 ip_reass_chain_frag_into_datagram_and_validate()

    ip_reass_chain_frag_into_datagram_and_validate()

    • 检查和插入一个分片到重组数据报中。

    • ipr:重组IP数据报。

    • new_p:新的分片。

    • is_last:是否是最后一个分片。

    • 返回:

      • IP_REASS_VALIDATE_TELEGRAM_FINISHED:当前重组IP数据报已经收到所有分片。
      • IP_REASS_VALIDATE_PBUF_QUEUED:分片成功插入重组IP数据报中,但是该重组IP数据报还没有接收到所有IP分片。
      • IP_REASS_VALIDATE_PBUF_DROPPED:插入分片失败。
    • 获取分片IP首部信息统计到重组IP数据报管理中。

    • 插入分片到重组IP数据报的分片链表中。

    • 如果最后一个IP分片收到了,就检查下所有分片是否都收到。

    /**
     * Chain a new pbuf into the pbuf list that composes the datagram.  The pbuf list
     * will grow over time as  new pbufs are rx.
     * Also checks that the datagram passes basic continuity checks (if the last
     * fragment was received at least once).
     * @param ipr points to the reassembly state
     * @param new_p points to the pbuf for the current fragment
     * @param is_last is 1 if this pbuf has MF==0 (ipr->flags not updated yet)
     * @return see IP_REASS_VALIDATE_* defines
     */
    static int
    ip_reass_chain_frag_into_datagram_and_validate(struct ip_reassdata *ipr, struct pbuf *new_p, int is_last)
    {
      struct ip_reass_helper *iprh, *iprh_tmp, *iprh_prev = NULL;
      struct pbuf *q;
      u16_t offset, len;
      u8_t hlen;
      struct ip_hdr *fraghdr;
      int valid = 1;
    
      /* 从当前分片中提取IP长度和IP分片偏移量 */
      fraghdr = (struct ip_hdr *)new_p->payload;
      len = lwip_ntohs(IPH_LEN(fraghdr));
      hlen = IPH_HL_BYTES(fraghdr);
      if (hlen > len) {
        /* 无效的数据报 */
        return IP_REASS_VALIDATE_PBUF_DROPPED;
      }
      len = (u16_t)(len - hlen); /* 获取IP分片数据区长度 */
      offset = IPH_OFFSET_BYTES(fraghdr); /* 获取偏移量 */
    
      /* IP分片的首部有用的信息已经统计到重组IP报文节点首部了,
         即是IP分片的首部中的数据可以丢弃,该空间可以利用起来,
         当前用于分片连接节点,指向下一个分片。
         即是把IP分片的首部部分空间重置为struct ip_reass_helper数据结构。 */
      LWIP_ASSERT("sizeof(struct ip_reass_helper) <= IP_HLEN",
                  sizeof(struct ip_reass_helper) <= IP_HLEN);
      iprh = (struct ip_reass_helper *)new_p->payload; /* 提前分片IP首部重置为struct ip_reass_helper */
      iprh->next_pbuf = NULL;
      iprh->start = offset; /* 偏移量起始 */
      iprh->end = (u16_t)(offset + len);  /* 偏移量尾部 */
      if (iprh->end < offset) {
        /* u16_t 溢出,无法处理此操作 */
        return IP_REASS_VALIDATE_PBUF_DROPPED;
      }
    
      /* 插入分片链表。按分片偏移量升序插入 */
      for (q = ipr->p; q != NULL;) { /* 遍历分片链表 */
        iprh_tmp = (struct ip_reass_helper *)q->payload; /* 分片节点 */
        if (iprh->start < iprh_tmp->start) { /* 找到位置 */
          iprh->next_pbuf = q; /* 插入操作(链表) */
          if (iprh_prev != NULL) { /* 插入位置前面也有IP分片 */
            /* 而不是具有最低偏移量的分片 */
    #if IP_REASS_CHECK_OVERLAP
            if ((iprh->start < iprh_prev->end) || (iprh->end > iprh_tmp->start)) {
              /* 插入分片与前一分片或后一分片重叠,丢弃 */
              return IP_REASS_VALIDATE_PBUF_DROPPED;
            }
    #endif /* IP_REASS_CHECK_OVERLAP */
            iprh_prev->next_pbuf = new_p; /* 插入操作(链表) */
            if (iprh_prev->end != iprh->start) {
              /* 前一个分片与当前分片不连续,中间还缺数据,所以可以标记还没有接收到所有IP分片 */
              valid = 0;
            }
          } else { /* 当前分片插入的位置在接收到的所有分片之前 */
    #if IP_REASS_CHECK_OVERLAP
            if (iprh->end > iprh_tmp->start) {
              /* 分片与下一个分片重叠,丢弃 */
              return IP_REASS_VALIDATE_PBUF_DROPPED;
            }
    #endif /* IP_REASS_CHECK_OVERLAP */
            /* 更新链表头 */
            ipr->p = new_p;
          }
          break;
        } else if (iprh->start == iprh_tmp->start) { /* 收到重叠IP分片,丢弃 */
          return IP_REASS_VALIDATE_PBUF_DROPPED;
    #if IP_REASS_CHECK_OVERLAP
        } else if (iprh->start < iprh_tmp->end) { /* 重叠:直接丢弃,不需要保留新数据报 */
          return IP_REASS_VALIDATE_PBUF_DROPPED;
    #endif /* IP_REASS_CHECK_OVERLAP */
        } else { /* 插入分片偏移量比所有分片都要大,即是要插到链表最后 */
          /* 检查到目前为止收到的碎片是否没有洞 */
          if (iprh_prev != NULL) {
            if (iprh_prev->end != iprh_tmp->start) {
              /* 数据不连续,当前分片和前一个分片之间缺少分片,标记下未接收完所有IP分片 */
              valid = 0;
            }
          }
        }
        q = iprh_tmp->next_pbuf; /* 遍历下一个分片 */
        iprh_prev = iprh_tmp; /* 保存前一个分片 */
      }
    
      /* 如果q为空,即是到达链表尾了 */
      if (q == NULL) {
        if (iprh_prev != NULL) { /* 如果 */
          /* 这是(目前),偏移量最大的分片:插入链表尾 */
    #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) { /* 还缺少IP分片 */
            valid = 0; /* 标记还没接收到所有IP分片 */
          }
        } else { /* 当前重组IP报文中还没有IP分片 */
    #if IP_REASS_CHECK_OVERLAP
          LWIP_ASSERT("no previous fragment, this must be the first fragment!",
                      ipr->p == NULL);
    #endif /* IP_REASS_CHECK_OVERLAP */
          /* 这是我们收到的这个IP数据报的第一个分片 */
          ipr->p = new_p;
        }
      }
    
      /* 至此,插入完毕,检查是否已经收到所有IP分片 */
    
      /* 如果我们已经收到了最后一个碎片 */
      if (is_last || ((ipr->flags & IP_REASS_FLAG_LASTFRAG) != 0)) { /* 最后一个分片收到了 */
        if (valid) { /* 前面遍历插入分片前面的所有分片时也没有发现有数据不连续 */
          /* 继续检查后面没有被遍历过的IP分片 */
          if ((ipr->p == NULL) || (((struct ip_reass_helper *)ipr->p->payload)->start != 0)) { /* 还没收到第一个分片 */
            valid = 0; /* 标记还没接收到所有IP分片 */
          } else { /* 首尾IP分片都收到了,需要遍历剩余的 */
            /* 并且检查这个数据报之后是否没有漏洞 */
            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; /* 标记还没有接收到所有IP分片 */
                break; /* 退出遍历 */
              }
              iprh_prev = iprh;
              q = iprh->next_pbuf;
            }
            if (valid) { /* 至此,说明当前IP报文的所有IP分片已到达,打个log庆祝下 */
              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);
            }
          }
        }
        /* 按需返回 */
        return valid ? IP_REASS_VALIDATE_TELEGRAM_FINISHED : IP_REASS_VALIDATE_PBUF_QUEUED;
      }
      /* 至此,IP分片插入正常,但是还没有收到所有IP分片 */
      return IP_REASS_VALIDATE_PBUF_QUEUED;
    }
    

    9.8.3.10 重组IP数据报的超时机制

    每个重组的IP数据报都有生命周期,超时都还没接收完所有IP分片包,则需要放弃等待剩余分片,并释放该重组IP数据报所有资源。

    决定删除重组的IP数据报时,需要返回ICMP超时到网络中告知对端放弃本次IP报文接收。

    节拍由IP_TMR_INTERVAL决定,默认1000,即是1秒跑一次。

    每个重组IP报文最大时间为IP_REASS_MAXAGE,默认15。即是系统收到第一个得到的分片IP开始计时,在15秒内没有接收完所有IP报文,便要放弃本次重组。

    ip_reass_tmr()

    • 概述的目的就是遍历重组IP报文链表,检查每个正在重组的IP报文有效期,过期的删除,未过期的减少有效期。
    /**
     * Reassembly timer base function
     * for both NO_SYS == 0 and 1 (!).
     *
     * Should be called every 1000 msec (defined by IP_TMR_INTERVAL).
     */
    void
    ip_reass_tmr(void)
    {
      struct ip_reassdata *r, *prev = NULL;
    
      r = reassdatagrams;
      while (r != NULL) {
        /* 减量计时器,一旦它达到0,清理不完整的分片 */
        if (r->timer > 0) {
          r->timer--; /* 有效期减少 */
          LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass_tmr: timer dec %"U16_F"\n", (u16_t)r->timer));
          prev = r;
          r = r->next; /* 遍历下一个 */
        } else { /* 当前重组IP条目已经过期了 */
          struct ip_reassdata *tmp; /* 过渡变量 */
          LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass_tmr: timer timed out\n"));
          tmp = r;
          /* 在释放之前获取下一个重组IP报文指针 */
          r = r->next;
          /* 删除过期的重组IP报文:删除重组IP报文节点资源及其所有的IP分片pbuf */
          ip_reass_free_complete_datagram(tmp, prev);
        }
      }
    }