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

  • 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_tip4_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_INLINEu32_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_INLINEint 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_INLINEchk_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 =https://www.huyubaike.com/biancheng/= 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_NETIFelse {/* 如果不支持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_IPIF__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

    经验总结扩展阅读