TaterLi's LazyBlog

自言自语,不喜绕路,科学上网,远离天国.

@TaterLi2周前

02/4
14:53
技术控

LwIP 代码分析(发送一个UDP包) – 第四集

其实发送一个包就这么多.

                /* UDP 缓冲区申请 */
                udpbuf = netbuf_new();
                /* 申请内存 */
                netbuf_alloc(udpbuf, strlen((char *)udpdemo_buf));
                /* 把数据复制到payload里去. */
                memcpy(udpbuf->p->payload, (void *)udpdemo_buf, strlen((const char *)udpdemo_buf));
                /* payload 其实也可以直接修改. */
                ((uint32_t *)udpbuf->p->payload)[0] = xTaskGetTickCount();
                /* 这一步把数据发送出去. */
                err = netconn_send(udpconn, udpbuf);
                /* UDP总会发成功的. */
                netbuf_delete(udpbuf);

其中netbuf_new跟进去,发现其实就是一个calloc.

netbuf_alloc就是申请到内存,指针指向申请到的位置.

memcpy是标准函数,不用多说.

netbuf_delete就是删除calloc到的东西.

那么其实就剩下了netconn_send,这个函数看起来应该比较复杂,因为其他地方什么都没干.进去一看,还是apimsg的安全调用.不得不说安全性十足,而我们一开始就知道,直接看fn就行,其他都是安全需要.

1396行的错误判断,我们一开始就见过.就是如果这个conn有问题,就不要往下继续了.

接下来判断PCB是有效的,再跳转到NETCONN_UDP,明显这个函数只适合UDP.LWIP_CHECKSUM_ON_COPY又刚好是打开的,那么就是要校验.而后续一个是udp_send_chksum,一个是udp_sendto_chksum.

如果是本机发,就走到上面得判断,下面的判断就是有目标端口,目标地址什么的,其实最终是调用udp_sendto_chksum的.

进去就是ip_route,因为IP_IS_ANY_TYPE_VAL恒定为0,所以没被编译进去.

实际上,包就是从ip_route这一段,数据就出去了.这就是到达了IP层,记得七层模型里面,TCP还在比较靠上的一层.

可见,ip_route是查找到源网卡,以便后续构建.接着在sendto里面再获取了本地的src_ip(发送者IP).

到了udp_sendto_if_src_chksum,里面很多判断,这个代码尤其长.

/** Same as udp_sendto_if_src(), but with checksum */
err_t
udp_sendto_if_src_chksum(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip,
                     u16_t dst_port, struct netif *netif, u8_t have_chksum,
                     u16_t chksum, const ip_addr_t *src_ip)
{
#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
  struct udp_hdr *udphdr;
  err_t err;
  struct pbuf *q; /* q will be sent down the stack */
  u8_t ip_proto;
  u8_t ttl;

  if ((pcb == NULL) || (dst_ip == NULL) || !IP_ADDR_PCB_VERSION_MATCH(pcb, src_ip) ||
      !IP_ADDR_PCB_VERSION_MATCH(pcb, dst_ip)) {
    return ERR_VAL;
  }

#if LWIP_IPV4 && IP_SOF_BROADCAST
  /* broadcast filter? */
  if (!ip_get_option(pcb, SOF_BROADCAST) &&
#if LWIP_IPV6
      IP_IS_V4(dst_ip) &&
#endif /* LWIP_IPV6 */
      ip_addr_isbroadcast(dst_ip, netif)) {
    LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
      ("udp_sendto_if: SOF_BROADCAST not enabled on pcb %p\n", (void *)pcb));
    return ERR_VAL;
  }
#endif /* LWIP_IPV4 && IP_SOF_BROADCAST */

  /* if the PCB is not yet bound to a port, bind it here */
  if (pcb->local_port == 0) {
    LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_send: not yet bound to a port, binding now\n"));
    err = udp_bind(pcb, &pcb->local_ip, pcb->local_port);
    if (err != ERR_OK) {
      LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: forced port bind failed\n"));
      return err;
    }
  }

  /* not enough space to add an UDP header to first pbuf in given p chain? */
  if (pbuf_header(p, UDP_HLEN)) {
    /* allocate header in a separate new pbuf */
    q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);
    /* new header pbuf could not be allocated? */
    if (q == NULL) {
      LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: could not allocate header\n"));
      return ERR_MEM;
    }
    if (p->tot_len != 0) {
      /* chain header q in front of given pbuf p (only if p contains data) */
      pbuf_chain(q, p);
    }
    /* first pbuf q points to header pbuf */
    LWIP_DEBUGF(UDP_DEBUG,
                ("udp_send: added header pbuf %p before given pbuf %p\n", (void *)q, (void *)p));
  } else {
    /* adding space for header within p succeeded */
    /* first pbuf q equals given pbuf */
    q = p;
    LWIP_DEBUGF(UDP_DEBUG, ("udp_send: added header in given pbuf %p\n", (void *)p));
  }
  LWIP_ASSERT("check that first pbuf can hold struct udp_hdr",
              (q->len >= sizeof(struct udp_hdr)));
  /* q now represents the packet to be sent */
  udphdr = (struct udp_hdr *)q->payload;
  udphdr->src = lwip_htons(pcb->local_port);
  udphdr->dest = lwip_htons(dst_port);
  /* in UDP, 0 checksum means 'no checksum' */
  udphdr->chksum = 0x0000;

  /* Multicast Loop? */
#if (LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS) || (LWIP_IPV6 && LWIP_IPV6_MLD)
  if (((pcb->flags & UDP_FLAGS_MULTICAST_LOOP) != 0) && ip_addr_ismulticast(dst_ip)) {
    q->flags |= PBUF_FLAG_MCASTLOOP;
  }
#endif /* (LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS) || (LWIP_IPV6 && LWIP_IPV6_MLD) */

  LWIP_DEBUGF(UDP_DEBUG, ("udp_send: sending datagram of length %"U16_F"\n", q->tot_len));

#if LWIP_UDPLITE
  /* UDP Lite protocol? */
  if (pcb->flags & UDP_FLAGS_UDPLITE) {
    u16_t chklen, chklen_hdr;
    LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP LITE packet length %"U16_F"\n", q->tot_len));
    /* set UDP message length in UDP header */
    chklen_hdr = chklen = pcb->chksum_len_tx;
    if ((chklen < sizeof(struct udp_hdr)) || (chklen > q->tot_len)) {
      if (chklen != 0) {
        LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP LITE pcb->chksum_len is illegal: %"U16_F"\n", chklen));
      }
      /* For UDP-Lite, checksum length of 0 means checksum
         over the complete packet. (See RFC 3828 chap. 3.1)
         At least the UDP-Lite header must be covered by the
         checksum, therefore, if chksum_len has an illegal
         value, we generate the checksum over the complete
         packet to be safe. */
      chklen_hdr = 0;
      chklen = q->tot_len;
    }
    udphdr->len = lwip_htons(chklen_hdr);
    /* calculate checksum */
#if CHECKSUM_GEN_UDP
    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_UDP) {
#if LWIP_CHECKSUM_ON_COPY
      if (have_chksum) {
        chklen = UDP_HLEN;
      }
#endif /* LWIP_CHECKSUM_ON_COPY */
      udphdr->chksum = ip_chksum_pseudo_partial(q, IP_PROTO_UDPLITE,
        q->tot_len, chklen, src_ip, dst_ip);
#if LWIP_CHECKSUM_ON_COPY
      if (have_chksum) {
        u32_t acc;
        acc = udphdr->chksum + (u16_t)~(chksum);
        udphdr->chksum = FOLD_U32T(acc);
      }
#endif /* LWIP_CHECKSUM_ON_COPY */

      /* chksum zero must become 0xffff, as zero means 'no checksum' */
      if (udphdr->chksum == 0x0000) {
        udphdr->chksum = 0xffff;
      }
    }
#endif /* CHECKSUM_GEN_UDP */

    ip_proto = IP_PROTO_UDPLITE;
  } else
#endif /* LWIP_UDPLITE */
  {      /* UDP */
    LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP packet length %"U16_F"\n", q->tot_len));
    udphdr->len = lwip_htons(q->tot_len);
    /* calculate checksum */
#if CHECKSUM_GEN_UDP
    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_UDP) {
      /* Checksum is mandatory over IPv6. */
      if (IP_IS_V6(dst_ip) || (pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
        u16_t udpchksum;
#if LWIP_CHECKSUM_ON_COPY
        if (have_chksum) {
          u32_t acc;
          udpchksum = ip_chksum_pseudo_partial(q, IP_PROTO_UDP,
            q->tot_len, UDP_HLEN, src_ip, dst_ip);
          acc = udpchksum + (u16_t)~(chksum);
          udpchksum = FOLD_U32T(acc);
        } else
#endif /* LWIP_CHECKSUM_ON_COPY */
        {
          udpchksum = ip_chksum_pseudo(q, IP_PROTO_UDP, q->tot_len,
            src_ip, dst_ip);
        }

        /* chksum zero must become 0xffff, as zero means 'no checksum' */
        if (udpchksum == 0x0000) {
          udpchksum = 0xffff;
        }
        udphdr->chksum = udpchksum;
      }
    }
#endif /* CHECKSUM_GEN_UDP */
    ip_proto = IP_PROTO_UDP;
  }

  /* Determine TTL to use */
#if LWIP_MULTICAST_TX_OPTIONS
  ttl = (ip_addr_ismulticast(dst_ip) ? udp_get_multicast_ttl(pcb) : pcb->ttl);
#else /* LWIP_MULTICAST_TX_OPTIONS */
  ttl = pcb->ttl;
#endif /* LWIP_MULTICAST_TX_OPTIONS */

  LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP checksum 0x%04"X16_F"\n", udphdr->chksum));
  LWIP_DEBUGF(UDP_DEBUG, ("udp_send: ip_output_if (,,,,0x%02"X16_F",)\n", (u16_t)ip_proto));
  /* output to IP */
  NETIF_SET_HWADDRHINT(netif, &(pcb->addr_hint));
  err = ip_output_if_src(q, src_ip, dst_ip, ttl, pcb->tos, ip_proto, netif);
  NETIF_SET_HWADDRHINT(netif, NULL);

  /* @todo: must this be increased even if error occurred? */
  MIB2_STATS_INC(mib2.udpoutdatagrams);

  /* did we chain a separate header pbuf earlier? */
  if (q != p) {
    /* free the header pbuf */
    pbuf_free(q);
    q = NULL;
    /* p is still referenced by the caller, and will live on */
  }

  UDP_STATS_INC(udp.xmit);
  return err;
}

看起来是不是又长又头疼.稍微把一些安全性判断去掉.代码就很短了.

/** Same as udp_sendto_if_src(), but with checksum */
err_t
udp_sendto_if_src_chksum(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip,
                         u16_t dst_port, struct netif *netif, u8_t have_chksum,
                         u16_t chksum, const ip_addr_t *src_ip)
{
    struct udp_hdr *udphdr;
    err_t err;
    struct pbuf *q; /* q will be sent down the stack */
    u8_t ip_proto;
    u8_t ttl;

    /* pbuf_header是给添加头信息的.给p增加UDP_HLEN长的头,成功返回0,非0都是失败. */
    if (pbuf_header(p, UDP_HLEN))
    {
        /* 申请新的pbuf */
        q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);
        /* new header pbuf could not be allocated? */
        if (q == NULL)
        {
            return ERR_MEM;
        }
        if (p->tot_len != 0)
        {
            /* 把新的pbuf放到chain里,就是q的next是p.先发q,然后q是个头,发送完q就发p,数据. */
            pbuf_chain(q, p);
        }
    }
    else
    {
        /* adding space for header within p succeeded */
        /* 因为他是IP头,所以他在pbuf的最开始处. */
        q = p;
    }
    /* q now represents the packet to be sent */
    udphdr = (struct udp_hdr *)q->payload;
    udphdr->src = lwip_htons(pcb->local_port);
    udphdr->dest = lwip_htons(dst_port);
    /* in UDP, 0 checksum means 'no checksum' */
    udphdr->chksum = 0x0000;

    /* Multicast Loop? */
    if (((pcb->flags & UDP_FLAGS_MULTICAST_LOOP) != 0) && ip_addr_ismulticast(dst_ip))
    {
        q->flags |= PBUF_FLAG_MCASTLOOP;
    }

    udphdr->len = lwip_htons(q->tot_len);
    /* calculate checksum */
    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_UDP)
    {
        /* Checksum is mandatory over IPv6. */
        if (IP_IS_V6(dst_ip) || (pcb->flags & UDP_FLAGS_NOCHKSUM) == 0)
        {
            u16_t udpchksum;
            if (have_chksum)
            {
                u32_t acc;
                udpchksum = ip_chksum_pseudo_partial(q, IP_PROTO_UDP,
                                                     q->tot_len, UDP_HLEN, src_ip, dst_ip);
                acc = udpchksum + (u16_t)~(chksum);
                udpchksum = FOLD_U32T(acc);
            }
            else
            {
                udpchksum = ip_chksum_pseudo(q, IP_PROTO_UDP, q->tot_len,
                                             src_ip, dst_ip);
            }

            /* chksum zero must become 0xffff, as zero means 'no checksum' */
            if (udpchksum == 0x0000)
            {
                udpchksum = 0xffff;
            }
            udphdr->chksum = udpchksum;
        }
    }

    ip_proto = IP_PROTO_UDP;

    /* Determine TTL to use */
    ttl = pcb->ttl;

    /* output to IP */
    err = ip_output_if_src(q, src_ip, dst_ip, ttl, pcb->tos, ip_proto, netif);

    /* did we chain a separate header pbuf earlier? */
    if (q != p)
    {
        /* free the header pbuf */
        pbuf_free(q);
        q = NULL;
        /* p is still referenced by the caller, and will live on */
    }

    return err;
}

当我们断点在这个位置的时候,udphdr已经写入(udp头)

查看变量看到.

注意这里是0x781E其实是0x1E78,因为是我们的CPU是LE,但是以太网是BE.正是7800端口,目标端口.

进来后,需要判断have_chksum,这个参数又是超级早传递进来的.当NETBUF_FLAG_CHKSUM是有效时候,这个参数就有效.而默认这个是没的,所以就是全校验(否则,有效就是部分校验).

我们知道有校验就行,不关心怎么校验的.然后继续跑就到ip_output_if_src,这个总是是发包了吧.(确实这样,但是这里还有很多层.).

进入后立马进入ip4_output_if_opt_src函数,又继续用同样方法,添加一个IP头大小的内容.

IP头总算是一帧的最开始,然后到iphdr = (struct ip_hdr *)p->payload就是把iphdr定义为pbuf的开始数据.关于IP头,可以看看定义.(实际上还是不对,还有个叫以太网帧的玩意).

随后960行判断帧有没有大于MTU大小,大于的话还得拆分帧.

记得这个发送其实是到etharp_output,还不是终点,是不是有点恐怖,这深度.不,这还没到,还要封装以太网帧.可见LwIP给我们干了好多啊.

LwIP 代码分析(发送一个UDP包) – 第四集