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