当前位置: 首页 > article >正文

lwip的UDP实现

lwip实现

IP结构体

lwip中定义结构体如下:

struct ip_hdr {
  /* version / header length */
  PACK_STRUCT_FLD_8(u8_t _v_hl);
  /* type of service */
  PACK_STRUCT_FLD_8(u8_t _tos);
  /* total length */
  PACK_STRUCT_FIELD(u16_t _len);
  /* identification */
  PACK_STRUCT_FIELD(u16_t _id);
  /* fragment offset field */
  PACK_STRUCT_FIELD(u16_t _offset);
#define IP_RF 0x8000U        /* reserved fragment flag */
#define IP_DF 0x4000U        /* don't fragment flag */
#define IP_MF 0x2000U        /* more fragments flag */
#define IP_OFFMASK 0x1fffU   /* mask for fragmenting bits */
  /* time to live */
  PACK_STRUCT_FLD_8(u8_t _ttl);
  /* protocol*/
  PACK_STRUCT_FLD_8(u8_t _proto);
  /* checksum */
  PACK_STRUCT_FIELD(u16_t _chksum);
  /* source and destination IP addresses */
  PACK_STRUCT_FLD_S(ip4_addr_p_t src);
  PACK_STRUCT_FLD_S(ip4_addr_p_t dest);
} PACK_STRUCT_STRUCT;

UDP结构体

在lwip中定义的UDP报文首部数据结构如下:

struct udp_hdr{
	uint16_t src;
	uint16_t dest;
	uint16_t len;
	uint16_t chksum;
}

lwip中udp控制块定义如下:

struct udp_pcb {
/** Common members of all PCB types */
  IP_PCB;

/* Protocol specific PCB members */

  struct udp_pcb *next;

  u8_t flags;
  /** ports are in host byte order */
  u16_t local_port, remote_port;

#if LWIP_MULTICAST_TX_OPTIONS
#if LWIP_IPV4
 //指定多播数据包的传出网络接口的IPv4地址
  ip4_addr_t mcast_ip4;
#endif /* LWIP_IPV4 */
  //指定物理接口
  u8_t mcast_ifindex;
  //多播数据包的生存时间,即被丢弃前可经过的最大路由跳数
  u8_t mcast_ttl;
#endif 

#if LWIP_UDPLITE
  //仅用于UDP_LITE的校验
  u16_t chksum_len_rx, chksum_len_tx;
#endif /* LWIP_UDPLITE */

  /** receive callback function */
  udp_recv_fn recv;
  /** user-supplied argument for the recv callback */
  void *recv_arg;
};

IP_PCB控制块结构体定义,准确来说,它是所有控制块的父类:

/** This is the common part of all PCB types. It needs to be at the
   beginning of a PCB type definition. It is located here so that
   changes to this common part are made in one location instead of
   having to change all PCB structs. */
#define IP_PCB                             \
  /* ip addresses in network byte order */ \
  ip_addr_t local_ip;                      \
  ip_addr_t remote_ip;                     \
  /* Bound netif index */                  \
  u8_t netif_idx;                          \
  /* Socket options */                     \
  u8_t so_options;                         \
  /* Type Of Service */                    \
  u8_t tos;                                \
  /* Time To Live */                       \
  u8_t ttl                                 \
  /* link layer address resolution hint */ \
  IP_PCB_NETIFHINT

在RTOS中,线程取代了进程,应用线程持有特定的端口号,当UDP收到一个报文时,会对链表上的PCB进行遍历并匹配本地持有特定端口号的UDP控制块。

UDP控制块中的callbak函数会在lwip接收数据时被调用,它与recv字段有关。

UDP发送

从线程发送的数据会被udp_sendto_if_src进行处理,最后转交到IP层(如果不出意外的话)。

发送函数如下:

其实就是先检查几遍,再把UDP首部添加到pbuf中,然后填写UDP各个字段,最后输出到IP层。

1.判断套接字是否绑定本地端口,如果没有则进行绑定

2.判断数据包是否太大,导致无法添加首部,如果pbuf中没有足够的空间,那么就再申请一个pbuf

3.获取pbuf地址,填写udp_hdr的四个成员

4.如果设置了多播选项,多播循环被启用且目标地址是多播地址时,设置标志位

5.设置生存时间(其实这里省略了非常多的宏定义及代码),并输出数据报到IP层

6.如果额外申请了pbuf存放首部,在输出到IP层后可以回收该首部。

/** @ingroup udp_raw
 * Same as @ref udp_sendto_if, but with source address */
err_t
udp_sendto_if_src(struct udp_pcb *pcb, struct pbuf *p,
                  const ip_addr_t *dst_ip, u16_t dst_port, struct netif *netif, 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;

  /* if the PCB is not yet bound to a port, bind it here */
  if (pcb->local_port == 0) {
    err = udp_bind(pcb, &pcb->local_ip, pcb->local_port);
    if (err != ERR_OK) {
      return err;
    }
  }

  /* packet too large to add a UDP header without causing an overflow? */
  if ((u16_t)(p->tot_len + UDP_HLEN) < p->tot_len) {
    return ERR_MEM;
  }

  /* not enough space to add an UDP header to first pbuf in given p chain? */
  if (pbuf_add_header(p, UDP_HLEN)) {
    /* allocate header in a separate new pbuf */
    q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);
    if (q == NULL) {
      return ERR_MEM;
    }
    if (p->tot_len != 0) {
      /* chain header q in front of given pbuf p */
      pbuf_chain(q, p);
    }
  } else {
    q = p;
  }
  udphdr = (struct udp_hdr *)q->payload;
  udphdr->src = lwip_htons(pcb->local_port);
  udphdr->dest = lwip_htons(dst_port);
  udphdr->chksum = 0x0000;

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

  /* 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_UDP, netif);

  if (q != p) {
    pbuf_free(q);
  }

  return err;
}

UDP接收

从IP层接收的数据会被UDP_input进行处理,最后转交到线程(如果不出意外的话)。

接收函数:

1.检查长度,如果PCB中记录UDP头部长度小于8字节,那么程序报错

2.迭代PCB,找到符合的端口号,如果没有,就使用第一个端口。

具体实现逻辑:判断uncon_pcb为NULL时,使用指针uncon_pcb保存的pcb。因为uncon_pcb被初始化为NULL,程序只有第一次会保存,那么uncon_pcb一定是第一个迭代到的pcb。如果到链表尾部都没找到,循环判断pcb为NULL并终止,那么就赋值pcb为uncon_pcb。

3.找到对应的控制块后,使用callback函数递交给具体线程应用。

4.如果找不到对应控制块,这种情况说明链表上没有pcb,如果启用了ICMP选项,那么返回端口不可达ICMP报文。

接收函数有点长,所以笔者删除了一些条件编译和不重要的注释。
/**
 * If no pcb is found or the datagram is incorrect, the
 * pbuf is freed.
 * @param p pbuf to be demultiplexed to a UDP PCB (p->payload pointing to the UDP header)
 * @param inp network interface on which the datagram was received.
 */
void
udp_input(struct pbuf *p, struct netif *inp)
{
  struct udp_hdr *udphdr;
  struct udp_pcb *pcb, *prev;
  struct udp_pcb *uncon_pcb;
  u16_t src, dest;
  u8_t broadcast;
  u8_t for_us = 0;

  /* Check minimum length (UDP header) */
  if (p->len < UDP_HLEN) {
    pbuf_free(p);
    goto end;
  }

  udphdr = (struct udp_hdr *)p->payload;

  /* is broadcast packet ? */
  broadcast = ip_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif());

  /* convert src and dest ports to host byte order */
  src = lwip_ntohs(udphdr->src);
  dest = lwip_ntohs(udphdr->dest);

  pcb = NULL;
  prev = NULL;
  uncon_pcb = NULL;

  for (pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) {
    /* compare PCB local addr+port to UDP destination addr+port */
    if ((pcb->local_port == dest) &&
        (udp_input_local_match(pcb, inp, broadcast) != 0)) {
      if ((pcb->flags & UDP_FLAGS_CONNECTED) == 0) {
        if (uncon_pcb == NULL) {
          /* the first unconnected matching PCB */
          uncon_pcb = pcb;
#if LWIP_IPV4
        } else if (broadcast && ip4_current_dest_addr()->addr == IPADDR_BROADCAST) {
          /* global broadcast address (only valid for IPv4; match was checked before) */
          if (!IP_IS_V4_VAL(uncon_pcb->local_ip) || !ip4_addr_eq(ip_2_ip4(&uncon_pcb->local_ip), netif_ip4_addr(inp))) {
            /* uncon_pcb does not match the input netif, check this pcb */
            if (IP_IS_V4_VAL(pcb->local_ip) && ip4_addr_eq(ip_2_ip4(&pcb->local_ip), netif_ip4_addr(inp))) {
              /* better match */
              uncon_pcb = pcb;
            }
          }
#endif /* LWIP_IPV4 */
        }
      }

      /* compare PCB remote addr+port to UDP source addr+port */
      if ((pcb->remote_port == src) &&
          (ip_addr_isany_val(pcb->remote_ip) ||
           ip_addr_eq(&pcb->remote_ip, ip_current_src_addr()))) {
        /* the first fully matching PCB */
        if (prev != NULL) {
          /* move the pcb to the front of udp_pcbs so that is
             found faster next time */
          prev->next = pcb->next;
          pcb->next = udp_pcbs;
          udp_pcbs = pcb;
        } else {
          UDP_STATS_INC(udp.cachehit);
        }
        break;
      }
    }

    prev = pcb;
  }
  /* no fully matching pcb found? then look for an unconnected pcb */
  if (pcb == NULL) {
    pcb = uncon_pcb;
  }

  /* Check checksum if this is a match or if it was directed at us. */
  if (pcb != NULL) {
    for_us = 1;
  } else {
#if LWIP_IPV4
    if (!ip_current_is_v6()) {
      for_us = ip4_addr_eq(netif_ip4_addr(inp), ip4_current_dest_addr());
    }
#endif /* LWIP_IPV4 */
  }

  if (for_us) {
#if LWIP_UDPLITE
      if (ip_current_header_proto() == IP_PROTO_UDPLITE) {
        /* Do the UDP Lite checksum */
        u16_t chklen = lwip_ntohs(udphdr->len);
        if (chklen < sizeof(struct udp_hdr)) {
          if (chklen == 0) {
            /* For UDP-Lite, checksum length of 0 means checksum
               over the complete packet (See RFC 3828 chap. 3.1) */
            chklen = p->tot_len;
          } else {
            /* At least the UDP-Lite header must be covered by the
               checksum! (Again, see RFC 3828 chap. 3.1) */
            goto chkerr;
          }
        }
        if (ip_chksum_pseudo_partial(p, IP_PROTO_UDPLITE,
                                     p->tot_len, chklen,
                                     ip_current_src_addr(), 		 
                                     ip_current_dest_addr()) != 0) {
          goto chkerr;
        }
      } else
#endif /* LWIP_UDPLITE */
    if (pbuf_remove_header(p, UDP_HLEN)) {
      pbuf_free(p);
      goto end;
    }

    if (pcb != NULL) {
      /* callback */
      if (pcb->recv != NULL) {
        /* now the recv function is responsible for freeing p */
        pcb->recv(pcb->recv_arg, pcb, p, ip_current_src_addr(), src);
      } else {
        /* no recv function registered? then we have to free the pbuf! */
        pbuf_free(p);
        goto end;
      }
    } else {
#if LWIP_ICMP || LWIP_ICMP6
      /* No match was found, send ICMP destination port unreachable unless
         destination address was broadcast/multicast. */
      if (!broadcast && !ip_addr_ismulticast(ip_current_dest_addr())) {
        /* move payload pointer back to ip header */
        pbuf_header_force(p, (s16_t)(ip_current_header_tot_len() + UDP_HLEN));
        icmp_port_unreach(ip_current_is_v6(), p);
      }
#endif /* LWIP_ICMP || LWIP_ICMP6 */
      pbuf_free(p);
    }
  } else {
    pbuf_free(p);
  }
end:
  return;
}


http://www.kler.cn/a/551036.html

相关文章:

  • 【2024】Wavelet Mixture of Experts for Time Series Forecasting
  • 函数的返回值的使用
  • C# 运算符
  • Fink与Hadoop的简介以及联系
  • WhatRuns指纹识别下载安装使用教程,图文教程(超详细)
  • 【全栈】SprintBoot+vue3迷你商城-细节解析(1):Token、Jwt令牌、Redis、ThreadLocal变量
  • 安全问答—安全的基本架构
  • NLP基础-人工评估(Human Evaluation)
  • 如何选择合适的超参数来训练Bert和TextCNN模型?
  • PyCharm 中的 %reset -f 功能:一键重置控制台变量
  • 【MySQL】我在广州学Mysql 系列——Mysql 日志管理详解
  • Ubuntu虚拟机NDK编译ffmpeg
  • 富唯智能可重构柔性装配产线:以智能协同赋能制造业升级
  • 洗衣洗鞋上门预约融合小程序新模式
  • 50页PDF|数字化转型成熟度模型与评估(附下载)
  • Word中样式的管理
  • 百度千帆平台对接DeepSeek官方文档
  • Linux驱动学习(一)--符号表
  • [LeetCode力扣hot100]-二叉树相关手撕题
  • Ubuntu 下 systemd 介绍