u8 api开发报类型不匹配错误_深入学习IP数据报发送过程

IP协议的功能

回顾一下前面写的关于IP协议的文章:

IP协议基础扫盲班

IP地址相关知识深入了解~

IP数据报分析

IP数据报结构、IP分片的原理与处理

IP数据报的收发

回顾一下前面的文章所提及的知识点,总结一下IP协议的功能,得到以下结论:

  1. 编址(目标端的IP地址),数据传输的过程当中就必须表明要发送目标端的IP地址

  2. 寻址和路由(根据对方的IP地址,寻找最佳路径传输信息);

  3. 数据报的分片和重组。

  4. 传递服务是不可靠的(IP协议只是尽自己最大努力去传输数据包),它也是无连接的协议

IP数据报发送

IP协议是网络层的主要协议,在上层传输协议(如TCP/UDP)需要发送数据时,会将数据封装起来,然后传递到IP层,IP协议首先会根据上层协议的目标IP地址选择一个合适的网卡进行发送数据( 路由),然后IP协议将再次封装数据形成IP数据报,主要的操作就是填写IP数据报首部对应的各个字段:目标IP地址、源IP地址、协议类型、生存时间等,最后在IP层通过回调函数netif->output(即etharp_output()函数)将IP数据报投递给ARP,再调用网卡底层发送函数进行发送,这样子自上而下的数据就发送出去,IP协议以目标IP地址作为目标主机的身份地址。

/**

* ip_output_if的简化版接口。它找到发送数据包的netif网络接口并调用ip_output_if来完成实际工作。

*

* @param p 要发送的数据包(p->payload(有效负载)指向数据,如果dest == LWIP_IP_HDRINCL,则p已包含IP头和p->有效负载指向该IP头)

* @param src 要发送的源IP地址(如果src == IP4_ADDR_ANY,则用发送的netif绑定的IP地址用作源地址)

* @param dest 目的IP地址

* @param ttl 要在IP标头中设置的TTL值(生存时间)

* @param tos 用于在IP标头中设置的TOS值

* @param proto 将在IP头中设置对应的上层协议

* @return ERR_OK 如果数据包发送正常就返回ok,

* 如果p没有足够的空间用于IP /LINK标头,则为ERR_BUF

* 其他则返回netif->output返回的错误

* @return ERR_RTE如果没有找到路线

* 请参阅ip_output_if()以获取更多返回值

*/

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;

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;

}

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

}

路由过程的实现

路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动,发送端必然需要找到一个网卡将数据报发送出去,而实现这个过程的函数就是 ip4_route_src()

其实lwip对 ip4_route_src()函数进行了重新定义,实际上是调用了 ip4_route()函数。这个函数的原理就是根据指定的IP地址找到合适的网卡 netif,然后返回,前面的文章也提到过,lwip的网卡是通过 netif_list列表管理的,那么找网卡的操作也必然是遍历网卡列表 netif_list,判断网卡是否已经挂载并且IP地址是否有效,如果连网卡都找不到,那就不用发送数据了,返回null。

#define ip4_route_src(src, dest) ip4_route(dest)

/**

*为给定的IP地址查找适当的网络接口。

*它搜索网络接口列表。找到匹配项

*

*@param dest 要查找路由的目标IP地址

*@return 发送到达目的地的网卡 netif

*/

struct netif *

ip4_route(const ip4_addr_t *dest)

{

#if !LWIP_SINGLE_NETIF

struct netif *netif;

LWIP_ASSERT_CORE_LOCKED();

#if LWIP_MULTICAST_TX_OPTIONS

/*默认使用管理选择的接口进行多播*/

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_list */

NETIF_FOREACH(netif) {

/* 如果网卡已经挂载并且IP地址是有效的 */

if (netif_is_up(netif) && netif_is_link_up(netif) && !ip4_addr_isany_val(*netif_ip4_addr(netif))) {

/* 网络掩码匹配? */

if (ip4_addr_netcmp(dest, netif_ip4_addr(netif), netif_ip4_netmask(netif))) {

/* 返回找到的网卡netif */

return netif;

}

/* 网关在非广播接口上匹配?(即在点对点接口中对等) */

if (((netif->flags & NETIF_FLAG_BROADCAST) == 0) && ip4_addr_cmp(dest, netif_ip4_gw(netif))) {

/* 返回找到的网卡netif */

return netif;

}

}

}

#if LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF /**如果打开环回地址的宏定义 */

/* loopif is disabled, looopback traffic is passed through any netif */

if (ip4_addr_isloopback(dest)) {

/*不检查环回流量的链接*/

if (netif_default != NULL && netif_is_up(netif_default)) {

return netif_default;

}

/*默认netif没有启动,只需使用任何netif进行环回流量*/

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)) {

/*找不到匹配的netif,默认的netif不可用。建议使用LWIP_HOOK_IP4_ROUTE()*/

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;

}

ip4_output_if函数

找到网卡之后就调用 ip4_output_if()函数将数据发送出去,这个函数会指定发送数据的网卡,同时会将来自上层协议(tcp、udp)的数据进行封装,组成IP数据报再发送,不过这个函数层层调用,比较麻烦,具体如下:。

/**

* 在网络接口上发送IP数据包。这个函数构造IP数据包首部并计算IP头校验和,

* 如果源IP地址为NULL,在发送的时候就填写发送网卡的IP地址为源IP地址

* 如果目标IP地址是LWIP_IP_HDRINCL,则假定pbuf已经存在包括IP头和有效负载指向它而不是数据。

*

* @param p 要发送的数据包(p->payload(有效负载)指向数据,如果dest == LWIP_IP_HDRINCL,则p已包含IP头和p->有效负载指向该IP头)

* @param src 要发送的源IP地址(如果src == IP4_ADDR_ANY,则用发送的netif绑定的IP地址用作源地址)

* @param dest 目的IP地址

* @param ttl 要在IP标头中设置的TTL值(生存时间)

* @param tos 用于在IP标头中设置的TOS值

* @param proto 将在IP头中设置对应的上层协议

* @param netif 发送此数据包的netif

* @return ERR_OK 如果数据包发送正常就返回ok,

* 如果p没有足够的空间用于IP /LINK标头,则为ERR_BUF

* 其他则返回netif->output返回的错误

*

* @note ip_id:RFC791“某些主机可能只需使用

* 独立于目的地的唯一标识符“

*/

err_t

ip4_output_if(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)

{

#if IP_OPTIONS_SEND

return ip4_output_if_opt(p, src, dest, ttl, tos, proto, netif, NULL, 0);

}

/**

* 与ip_output_if()相同,但可以包含IP选项:

*

* @param ip_options指向IP选项的指针,复制到IP头中

* @param optlen ip_options的长度

*/

err_t

ip4_output_if_opt(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 */

const ip4_addr_t *src_used = src;

if (dest != LWIP_IP_HDRINCL) {

if (ip4_addr_isany(src)) {

src_used = netif_ip4_addr(netif);

}

}

#if IP_OPTIONS_SEND

return ip4_output_if_opt_src(p, src_used, dest, ttl, tos, proto, netif,

ip_options, optlen);

#else /* IP_OPTIONS_SEND */

return ip4_output_if_src(p, src_used, dest, ttl, tos, proto, netif);

#endif /* IP_OPTIONS_SEND */

}

ip4_output_if_opt_src

首先看看这个函数到底做了什么吧:在上层协议递交数据包后,通过层层调用,最终到 ip4_output_if_opt_src()函数中处理,它的处理如下:6ae63f7f1dde22ddc3bb722d00d213ac.png代码的实现如下:注释非常丰富。主要过程就是:

  1. 判断是否填写好IP数据报首部?若目标IP地址为LWIPIPHDRINCL表示已经填写好IP数据报首部,且payload指针也指向了IP数据报首部。

  2. 如果没有填写IP数据报首部,调用 pbuf_add_header()函数调整数据区域指针以指向IP数据报首部。

  3. 填写IP数据报中的生存时间、服务类型、上层协议、目标IP地址、版本号与首部长度、数据报总长度、标志位和分片偏移量、标识、源IP地址等内容,总之就是将IP数据报首部的内容该填的都填上。

  4. 如果目标IP地址是自己的网卡IP地址,调用环回输入函数 netif_loop_output()发送IP数据报给自己,这种处理一般是用于测试代码。

  5. 如果IP数据报太大,数据报总长度大于网卡的MTU,则需要进行分片处理,调用 ip4_frag()函数进行发送。

  6. 直接调用注册的 netif->output接口传递给ARP,实际上就是调用 etharp_output()函数,在这里它会将IP地址解析成对应的 MAC地址,并且调用网卡发送函数进行发送。

/**

* 与ip4_output_if_opt()相同,当源地址是'IP4_ADDR_ANY'时,'src'地址不会被netif地址替换

*/

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();

LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p);

MIB2_STATS_INC(mib2.ipoutrequests);

/* 应该要构建IP首部还是已经包含在pbuf中了?如果是要构建IP数据报首部 */

if (dest != LWIP_IP_HDRINCL) {

u16_t ip_hlen = IP_HLEN;

#if IP_OPTIONS_SEND

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)) {

/* 选项字段太长 */

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;

}

/* 选项字段按照4字节对齐 */

optlen_aligned = (u16_t)((optlen + 3) & ~3);

ip_hlen = (u16_t)(ip_hlen + optlen_aligned);

/* 首先写入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"));

IP_STATS_INC(ip.err);

MIB2_STATS_INC(mib2.ipoutdiscards);

return ERR_BUF;

}

MEMCPY(p->payload, ip_options, optlen);

if (optlen < optlen_aligned) {

/* 剩余字节清零 */

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头 */

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"));

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);

IPH_PROTO_SET(iphdr, proto);

#if CHECKSUM_GEN_IP_INLINE

chk_sum += PP_NTOHS(proto | (ttl << 8));

#endif /* CHECKSUM_GEN_IP_INLINE */

/* 构建目的IP地址,此处的目的IP地址不能为NULL */

ip4_addr_copy(iphdr->dest, *dest);

#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);

IPH_TOS_SET(iphdr, 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;

if (src == NULL) {

ip4_addr_copy(iphdr->src, *IP4_ADDR_ANY4); /** 构建源IP地址 */

} else {

/* 此处的源IP地址不能为NULL */

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;

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) {

iphdr->_chksum = (u16_t)chk_sum; /* network order */

}

#if LWIP_CHECKSUM_CTRL_PER_NETIF

else {

IPH_CHKSUM_SET(iphdr, 0);

}

#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/

#else /* CHECKSUM_GEN_IP_INLINE */

IPH_CHKSUM_SET(iphdr, 0);

#if CHECKSUM_GEN_IP

IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) {

IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, ip_hlen));

}

#endif /* CHECKSUM_GEN_IP */

#endif /* CHECKSUM_GEN_IP_INLINE */

} else {

/* IP头已包含在pbuf中 */

if (p->len < IP_HLEN) {

/** pbuf的长度小于IP数据报首部长度(20字节) */

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; /** 直接从数据区域获取IP数据报首部 */

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_cmp(dest, netif_ip4_addr(netif))

#if !LWIP_HAVE_LOOPIF

|| ip4_addr_isloopback(dest)

#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) {

netif_loop_output(netif, p);

}

#endif /* LWIP_MULTICAST_TX_OPTIONS */

#endif /* ENABLE_LOOPBACK */

#if IP_FRAG

/** 要发送的数据报大于mtu,需要分片,此处的前提是使能了IP_FRAG (IP分片) */

if (netif->mtu && (p->tot_len > netif->mtu)) {

return ip4_frag(p, netif, dest); /** 调用IP数据报分片函数将数据报分片发送出去 */

}

#endif /* IP_FRAG */

LWIP_DEBUGF(IP_DEBUG, ("ip4_output_if: call netif->output()\n"));

return netif->output(netif, p, dest); /** 如果不需要分片就直接通过网卡发送出去,netif->output() */

}

最后提个醒

此外:上层协议是不会直接调用 ip4_output()函数的,lwip是通过宏定义将 ip4_output()函数进行重新定义:

#define ip_output(p, src, dest, ttl, tos, proto) ip4_output(p, src, dest, ttl, tos, proto)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值