目录
主要分析源码实现。
源码部分,本章节也只分析协议实现部分和最原始的南北接口。
北向协议栈接口和套接字接口的封装后面有独立章节分析。
即是UDP RAW接口。
友链:
IP协议只能完成数据报在互联网主机之间的传递,交付。
而传输层主要负责向两个主机中进程之间的通信提供服务。
传输层的几个重要的任务:
UDP 是 User Datagram Protocol 的简称,中文名是用户数据报协议。
UDP特点:
UDP 报文协议根据对应的端口号传递到目标主机的应用线程的。
传输层到应用层的唯一标识是通过端口号决定的,两个线程之间进行通信必须用端口号进行识别。
使用“IP 地址 + 端口号”来区分主机不同的线程。
范围:[0,65535],因为只有2字节:
常见的UDP协议端口号:
端
口
号
协
议
说明
0
保留
7
echo
报文回送服务器端口
53
DNS
域名服务器端口
69
TFT
中小型文件传输协议端口
123
NTP
网络时问协议端口
是用来同步网络中各个计算机时问的协议
161
SNMP
简单网络管理协议端口
UDP 报文也被称为用户数据报。
UDP报文封装:
UDP的数据区就是用户程序的数据了。
UDP报文格式:
源端口号:用户发送数据的进程锁绑定的本地端口号。
目的端口号:远端主机用户进程接收数据绑定的端口号。
总长度:是UDP数据报的总长度:UDP首部+UDP数据区。
RFC 3828 chap. 3.1
。RFC 3828 chap. 3.1
。检验和:UDP协议为UDP伪首部+UDP首部+UDP数据区所有数据都加入校验和。UDP LITE协议为“总长度”指定的长度加入校验和,从UDP伪首部算起,再加上伪首部校验和。
填入0时,表示不进行校验和。而在实际计算校验和得到的结果刚好为0时,则向校验和字段填入0FFFF。
UDP LITE:UDP协议的校验和是UDP首部和UDP数据区,如果数据区很多数据,一个校验失败就丢弃了,代价有点大,所以衍生出UDP LITE。只校验UDP报文前面指定数据长度的数据。一般用于实时适配、实时通话等这些要求通信速度快,可靠性要求不高的业务中。
UDP校验和的计算包括了三部分:UDP伪首部+UDP首部+UDP数据区。
UDP伪首部包含IP首部一些字段。其目的是让UDP验证数据是否已经正确到达目的地。
UDP伪首部只参与校验,不参与实际发送。
伪首部中UDP总长度和UDP首部的总长度字段一致。
参考udp.c
、udp.h
文件
UDP首部长度:
#define UDP_HLEN 8
UDP首部数据结构:
struct udp_hdr {
PACK_STRUCT_FIELD(u16_t src); /* 源端口号 */
PACK_STRUCT_FIELD(u16_t dest); /* 目的端口号 */
PACK_STRUCT_FIELD(u16_t len); /* 总长度 */
PACK_STRUCT_FIELD(u16_t chksum); /* 校验和 */
} PACK_STRUCT_STRUCT;
UDP控制块是整个UDP协议实现的核心部分。
LWIP使用UDP控制块来描述一个UDP连接的所有相关信息,包括源端口号、目的端口号、源IP、目的IP等等。
LWIP为每个UDP连接都分配一个UDP控制块,并用链表udp_pcbs
链起来。
但是LWIP也给UDP控制块数量设限制,MEMP_NUM_UDP_PCB
为UDP控制块的内存池数量。该宏缺省为8。
UDP控制块数据结构:
#if LWIP_NETIF_USE_HINTS
#define IP_PCB_NETIFHINT ;struct netif_hint netif_hints
#else /* LWIP_NETIF_USE_HINTS */
#define IP_PCB_NETIFHINT
#endif /* LWIP_NETIF_USE_HINTS */
/* 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地址 */ \
ip_addr_t local_ip; \
ip_addr_t remote_ip; \
/* 绑定的netif的索引 */ \
u8_t netif_idx; \
/* 套接口选项 */ \
u8_t so_options; \
/* 服务类型 */ \
u8_t tos; \
/* TTL */ \
u8_t ttl \
/* 链路层地址解析提示 */ \
IP_PCB_NETIFHINT
/** the UDP protocol control block */
struct udp_pcb {
IP_PCB; /* UDP控制块和IP协议相关的字段 */
struct udp_pcb *next; /* UDP控制块链表节点 */
u8_t flags; /* 控制块状态 */
u16_t local_port, remote_port; /* 本地端口号和远端端口号 */
#if LWIP_MULTICAST_TX_OPTIONS /* 支持组播相关 */
#if LWIP_IPV4
/* 组播数据包的出网络接口,通过IPv4地址(如果没有'any') */
ip4_addr_t mcast_ip4;
#endif /* LWIP_IPV4 */
/* 组播数据包的出网络接口,根据接口索引(如果非零) */
u8_t mcast_ifindex;
/* 发送数据时,组播报文的TTL值 */
u8_t mcast_ttl;
#endif /* LWIP_MULTICAST_TX_OPTIONS */
#if LWIP_UDPLITE /* 支持UDP LITE */
u16_t chksum_len_rx, chksum_len_tx; /* 接收、发送数据时需要进行校验的数据长度 */
#endif /* LWIP_UDPLITE */
/* 接收回调函数 */
udp_recv_fn recv;
/* 接收回调函数参数 */
void *recv_arg;
};
#define UDP_LOCAL_PORT_RANGE_START 0xc000
#define UDP_LOCAL_PORT_RANGE_END 0xffff
#define UDP_ENSURE_LOCAL_PORT_RANGE(port) ((u16_t)(((port) & (u16_t)~UDP_LOCAL_PORT_RANGE_START) + UDP_LOCAL_PORT_RANGE_START))
UDP的端口号由全局值udp_port
累加管理。
其初始值有两次初始:第一次是变量赋值,第二次是调用udp_init()
进行随机初始。
变量初始值:
/* last local UDP port */
static u16_t udp_port = UDP_LOCAL_PORT_RANGE_START;
随机初始化:
需要开启LWIP随机宏LWIP_RAND
。
/**
端口号申请是有udp_port
进行累加,溢出就复位到UDP_LOCAL_PORT_RANGE_START
。
/**
* Allocate a new local UDP port.
*
* @return a new (free) local UDP port number
*/
static u16_t
udp_new_port(void)
{
u16_t n = 0;
struct udp_pcb *pcb;
again:
if (udp_port++ == UDP_LOCAL_PORT_RANGE_END) { /* 累加获取 */
udp_port = UDP_LOCAL_PORT_RANGE_START; /* 溢出复位 */
}
/* Check all PCBs. */
for (pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) { /* 检查是否有重复 */
if (pcb->local_port == udp_port) { /* 重复 */
if (++n > (UDP_LOCAL_PORT_RANGE_END - UDP_LOCAL_PORT_RANGE_START)) {
return 0; /* 如果所有端口号都重复了,返回申请失败 */
}
goto again; /* 重新申请 */
}
}
return udp_port; /* 申请成功 */
}
UDP控制块的操作函数相对简单,因为没有流量控制、没有确认机制等等。
udp_new()
:
从MEMP_UDP_PCB
内存池中获取UDP控制块资源。
初始化部分字段。
/**
@ingroup udp_raw
Creates a new UDP pcb which can be used for UDP communication. The
pcb is not active until it has either been bound to a local address
or connected to a remote address.
@see MEMP_NUM_UDP_PCB
*
@return The UDP PCB which was created. NULL if the PCB data structure
could not be allocated.
*
@see udp_remove()
*/
struct udp_pcb *
udp_new(void)
{
struct udp_pcb *pcb;
LWIP_ASSERT_CORE_LOCKED(); /* 内核锁确认 */
pcb = (struct udp_pcb )memp_malloc(MEMP_UDP_PCB); / 申请UDP控制块资源 /
if (pcb != NULL) {
memset(pcb, 0, sizeof(struct udp_pcb));
pcb->ttl = UDP_TTL; / UDP数据出口默认的TTL值 /
#if LWIP_MULTICAST_TX_OPTIONS / 多播TX相关 /
udp_set_multicast_ttl(pcb, UDP_TTL);
#endif / LWIP_MULTICAST_TX_OPTIONS */
}
return pcb;
}
udp_remove()
:
struct udp_pcb *pcb
:需要删除的UDP控制块。
/**
@ingroup udp_raw
Removes and deallocates the pcb.
*
@param pcb UDP PCB to be removed. The PCB is removed from the list of
UDP PCB's and the data structure is freed from memory.
*
@see udp_new()
*/
void
udp_remove(struct udp_pcb *pcb)
{
struct udp_pcb *pcb2;
LWIP_ASSERT_CORE_LOCKED(); /* 内核所内 */
LWIP_ERROR("udp_remove: invalid pcb", pcb != NULL, return);
mib2_udp_unbind(pcb);
/* 先从udp_pcbs链表中移除 /
if (udp_pcbs == pcb) {
/ 如果当前UDP控制块是udp_pcbs的链表头,则直接更新链表头即可移除 /
udp_pcbs = udp_pcbs->next;
} else { / 需要遍历udp_pcbs,把当前UDP控制块移除 /
for (pcb2 = udp_pcbs; pcb2 != NULL; pcb2 = pcb2->next) {
if (pcb2->next != NULL && pcb2->next == pcb) {
pcb2->next = pcb->next;
break;
}
}
}
/ 释放内存资源 */
memp_free(MEMP_UDP_PCB, pcb);
}
当UDP服务于应用程序时,数据流需要底层和应用层进行对接,就需要把UDP控制块绑定到本地IP和端口号。
绑定控制块时需要注意的是:
udp_pcbs
链表。小笔记:在没有设置SOF_REUSEADDR
选项功能时,需要确保一个UDP报文最多只能到达一个应用程序。即是一个网络接口中的一个端口号。需要注意的是任意IP。
udp_bind()
:
struct udp_pcb *pcb
:需要绑定本地IP和端口号的UDP控制块。
ip_addr_t *ipaddr
:UDP控制块需要绑定的本地IP地址。
u16_t port
:UDP控制块需要绑定的本地端口号。
udp_new_port()
随机生成端口号。先检查下当前UDP控制块有没有插入了udp_pcbs
链表,因为绑定成功后,需要插入该链表。已经插入了,就不需要重复操作。
检查绑定的IP地址。传入为空,则赋值为全0的IP地址。
检查绑定的端口号。
如果为0,则调用udp_new_port()
生成一个并绑定。
如果不为0,则遍历udp_pcbs
链表,判断是否有其它UDP控制块重复使用这个端口号。确保一个UDP报文最多只有一个应用程序去向。相同条件:端口号相同且IP报文能到达这个服务。IP报文能否到达这个服务,可以通过以下判断(其一即符合要求):重复端口号的UDP控制块绑定的IP对比当前UDP控制块需要绑定的IP。
这里需要注意是否开启SO_REUSEADDR
选项,即是立即启用端口号的功能。由宏SO_REUSE
决定有没有这个功能,用户在代码中设置SO_REUSEADDR
是否开启该功能。
把需要绑定的IP和端口号填入UDP控制块。绑定成功。
确保当前UDP控制块插入了udp_pcbs
链表。
/**
@ingroup udp_raw
Bind an UDP PCB.
*
@param pcb UDP PCB to be bound with a local address ipaddr and port.
@param ipaddr local IP address to bind with. Use IP_ANY_TYPE to
bind to all local interfaces.
@param port local UDP port to bind with. Use 0 to automatically bind
to a random port between UDP_LOCAL_PORT_RANGE_START and
UDP_LOCAL_PORT_RANGE_END.
*
ipaddr & port are expected to be in the same byte order as in the pcb.
*
@return lwIP error code.
- ERR_OK. Successful. No error occurred.
- ERR_USE. The specified ipaddr and port are already bound to by
another UDP PCB.
*
@see udp_disconnect()
*/
err_t
udp_bind(struct udp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port)
{
struct udp_pcb *ipcb;
u8_t rebind;
#if LWIP_IPV6 && LWIP_IPV6_SCOPES
ip_addr_t zoned_ipaddr;
#endif /* LWIP_IPV6 && LWIP_IPV6_SCOPES */
LWIP_ASSERT_CORE_LOCKED();
#if LWIP_IPV4
/* Don't propagate NULL pointer (IPv4 ANY) to subsequent functions /
if (ipaddr == NULL) {
ipaddr = IP4_ADDR_ANY; / 如果传入绑定本地IP为NULL,则绑定为全0,表示任意本地IP /
}
#else / LWIP_IPV4 /
LWIP_ERROR("udp_bind: invalid ipaddr", ipaddr != NULL, return ERR_ARG);
#endif / LWIP_IPV4 */
LWIP_ERROR("udp_bind: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_bind(ipaddr = "));
ip_addr_debug_print(UDP_DEBUG | LWIP_DBG_TRACE, ipaddr);
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, (", port = %"U16_F")\n", port));
rebind = 0;
/* 检查下当前UDP控制块是否已经插入到udp_pcbs链表中,如果插入了,后面绑定成功后就不需要重新插入了 */
for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
if (pcb == ipcb) {
rebind = 1;
break;
}
}
#if LWIP_IPV6 && LWIP_IPV6_SCOPES /* IPV6暂时跳过 / / If the given IP address should have a zone but doesn't, assign one now.
This is legacy support: scope-aware callers should always provide properly
zoned source addresses. Do the zone selection before the address-in-use
check below; as such we have to make a temporary copy of the address. */
if (IP_IS_V6(ipaddr) && ip6_addr_lacks_zone(ip_2_ip6(ipaddr), IP6_UNKNOWN)) {
ip_addr_copy(zoned_ipaddr, *ipaddr);
ip6_addr_select_zone(ip_2_ip6(&zoned_ipaddr), ip_2_ip6(&zoned_ipaddr));
ipaddr = &zoned_ipaddr;
}
#endif /* LWIP_IPV6 && LWIP_IPV6_SCOPES */
/* 确定下本地端口号 /
if (port == 0) {
port = udp_new_port(); / 如果没有指定端口号,则由内部生成一个临时端口号 /
if (port == 0) { / 端口号资源不足,申请失败 /
LWIP_DEBUGF(UDP_DEBUG, ("udp_bind: out of free UDP ports\n"));
return ERR_USE;
}
} else { / 端口号申请成功 /
/ 检查下有没有其它UDP控制块绑定了相同端口号且IP报文能到达这个服务,这样的话可能会导致一个UDP包有多个应用程序去向。 /
for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
if (pcb != ipcb) { / 需要跳过当前UDP控制块 /
#if SO_REUSE / 支持SO_REUSEADDR
选项:立即启用端口号 /
if (!ip_get_option(pcb, SOF_REUSEADDR) ||
!ip_get_option(ipcb, SOF_REUSEADDR)) / 两个其中一个没有设置SO_REUSEADDR选项功能,则不能重复使用能到达该IP下的相同端口号 /
#endif / SO_REUSE /
{
if ((ipcb->local_port == port) && / 端口号相同 /
(((IP_GET_TYPE(&ipcb->local_ip) == IP_GET_TYPE(ipaddr)) &&
(ip_addr_eq(&ipcb->local_ip, ipaddr) ||
ip_addr_isany(ipaddr) ||
ip_addr_isany(&ipcb->local_ip))) ||
(IP_GET_TYPE(&ipcb->local_ip) == IPADDR_TYPE_ANY) ||
(IP_GET_TYPE(ipaddr) == IPADDR_TYPE_ANY))) {
/ 端口号相同且(IP一致或有任意IP),则这个UDP报文到达应用程序就没有唯一性。错误 */
LWIP_DEBUGF(UDP_DEBUG,
("udp_bind: local port %"U16_F" already bound by another pcb\n", port));
return ERR_USE;
}
}
}
}
}
/* 到此,相关数据检查完毕,符合要求 */
ip_addr_set_ipaddr(&pcb->local_ip, ipaddr); /* 绑定本地IP */
pcb->local_port = port; /* 绑定本地端口号 /
mib2_udp_bind(pcb);
/ UDP控制块还没有激活就需要激活:插入udp_pcbs链表 /
if (rebind == 0) {
pcb->next = udp_pcbs;
udp_pcbs = pcb;
}
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("udp_bind: bound to "));
ip_addr_debug_print_val(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, pcb->local_ip);
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, (", port %"U16_F")\n", pcb->local_port));
return ERR_OK; / 绑定成功 */
}
udp控制块也可以绑定指定网卡。
udp_bind_netif()
:
udp_pcb *pcb
:需要绑定网卡的UDP控制块。
struct netif *netif
:UDP控制块需要绑定的网卡。
获取网卡索引,绑定到UDP控制块。
/**
@ingroup udp_raw
Bind an UDP PCB to a specific netif.
After calling this function, all packets received via this PCB
are guaranteed to have come in via the specified netif, and all
outgoing packets will go out via the specified netif.
*
@param pcb UDP PCB to be bound.
@param netif netif to bind udp pcb to. Can be NULL.
*
@see udp_disconnect()
*/
void
udp_bind_netif(struct udp_pcb *pcb, const struct netif *netif)
{
LWIP_ASSERT_CORE_LOCKED();
if (netif != NULL) {
pcb->netif_idx = netif_get_index(netif); /*获取网卡索引绑定到UDP控制块 */
} else {
pcb->netif_idx = NETIF_NO_INDEX; /* 取消绑定 */
}
}
(本地行为)
UDP协议是没有连接状态的,但是为什么UDP可以调用udp_connect()
这个函数?有什么用?
先了解下UDP sendto() 函数传输数据过程 :
如果需要频繁发送,那第一阶段和第三阶段是重复多余的,所以可以使用 已连接(connect)UDP 控制块。
所以udp_connect()
这个函数的目的是把UDP控制块注册长期目标IP和端口号,这样中途调用发送函数时,不需要重新注册和注销。
可以使用udp_disconnect()
进行注销。
udp_connect()
:
struct udp_pcb *pcb
:需要连接的UDP控制块。
ip_addr_t *ipaddr
:远端IP地址。
u16_t port
:远端端口号。
先检查有没有绑定了本地应用程序:即是UDP控制块有没有绑定了本地IP(包括任意IP)和本地端口号。还没有绑定,则调用udp_bind()
进行绑定。
注册远端IP和远端端口号。
标记当前UDP控制块状态为已连接状态。
确保当前UDP控制块已激活:即是是否插入了udp_pcbs
链表。还没插入就需要插入处理。
/**
@ingroup udp_raw
Sets the remote end of the pcb. This function does not generate any
network traffic, but only sets the remote address of the pcb.
*
@param pcb UDP PCB to be connected with remote address ipaddr and port.
@param ipaddr remote IP address to connect with.
@param port remote UDP port to connect with.
*
@return lwIP error code
*
ipaddr & port are expected to be in the same byte order as in the pcb.
*
The udp pcb is bound to a random local port if not already bound.
*
@see udp_disconnect()
*/
err_t
udp_connect(struct udp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port)
{
struct udp_pcb *ipcb;
LWIP_ASSERT_CORE_LOCKED(); /* 内核所内 */
LWIP_ERROR("udp_connect: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_ERROR("udp_connect: invalid ipaddr", ipaddr != NULL, return ERR_ARG);
/* 确保已经绑定了本地端口号 /
if (pcb->local_port == 0) { / 本地端口号还没绑定,则需要先绑定 */
err_t err = udp_bind(pcb, &pcb->local_ip, pcb->local_port);
if (err != ERR_OK) {
return err;
}
}
/* 注册远端IP / ip_addr_set_ipaddr(&pcb->remote_ip, ipaddr); #if LWIP_IPV6 && LWIP_IPV6_SCOPES / [lzm][test][可暂时跳过] / / If the given IP address should have a zone but doesn't, assign one now,
/* 注册远端端口号 /
pcb->remote_port = port;
/ UDP控制块状态标记上已连接(本地已连接) */
pcb->flags |= UDP_FLAGS_CONNECTED;
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("udp_connect: connected to "));
ip_addr_debug_print_val(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
pcb->remote_ip);
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, (", port %"U16_F")\n", pcb->remote_port));
/* 检查下是否插入了udp_pcbs链表,如果没有插入,则需要插入处理 */
for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
if (pcb == ipcb) {
return ERR_OK;
}
}
pcb->next = udp_pcbs;
udp_pcbs = pcb;
return ERR_OK;
}
(本地行为)
就是本地注销远端IP和远端端口号的绑定。
udp_disconnect()
:
重置UDP控制块远端IP和远端端口号字段。
解绑本地网卡。
标记UDP控制块为未连接。
/**
@ingroup udp_raw
Remove the remote end of the pcb. This function does not generate
any network traffic, but only removes the remote address of the pcb.
*
@param pcb the udp pcb to disconnect.
*/
void
udp_disconnect(struct udp_pcb *pcb)
{
LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("udp_disconnect: invalid pcb", pcb != NULL, return);
/* 重置远端IP */
#if LWIP_IPV4 && LWIP_IPV6
if (IP_IS_ANY_TYPE_VAL(pcb->local_ip)) {
ip_addr_copy(pcb->remote_ip, *IP_ANY_TYPE);
} else {
#endif
ip_addr_set_any(IP_IS_V6_VAL(pcb->remote_ip), &pcb->remote_ip);
#if LWIP_IPV4 && LWIP_IPV6
}
#endif
/* 重置远端端口号 /
pcb->remote_port = 0;
/ 解绑本地网卡 /
pcb->netif_idx = NETIF_NO_INDEX;
/ 标记PCB为未连接 */
udp_clear_flags(pcb, UDP_FLAGS_CONNECTED);
}
udp_recv()
只是用于UDP控制块注册接收函数。
/**
* @ingroup udp_raw
* Set a receive callback for a UDP PCB.
* This callback will be called when receiving a datagram for the pcb.
*
* @param pcb the pcb for which to set the recv callback
* @param recv function pointer of the callback function
* @param recv_arg additional argument to pass to the callback function
*/
void
udp_recv(struct udp_pcb *pcb, udp_recv_fn recv, void *recv_arg)
{
LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("udp_recv: invalid pcb", pcb != NULL, return);
/* remember recv() callback and user data */
pcb->recv = recv;
pcb->recv_arg = recv_arg;
}
当底层网卡在IP层的IP有所更新时,需要把UDP控制块中本地IP绑定就的IP也更新。
即是当IP地址改变时,将从netif.c调用此函数检查并更新。
udp_netif_ip_addr_changed()
:
ip_addr_t *old_addr
:旧IP。
ip_addr_t *new_addr
:新IP。
检索LWIP中UDP控制块链表udp_pcbs
,把绑定就的IP更新到新的IP去。
/** This function is called from netif.c when address is changed
*
@param old_addr IP address of the netif before change
@param new_addr IP address of the netif after change
*/
void udp_netif_ip_addr_changed(const ip_addr_t *old_addr, const ip_addr_t *new_addr)
{
struct udp_pcb *upcb;
if (!ip_addr_isany(old_addr) && !ip_addr_isany(new_addr)) { /* 新旧IP不一致才有意义 /
for (upcb = udp_pcbs; upcb != NULL; upcb = upcb->next) { / 检索所有已激活的UDP控制块 /
if (ip_addr_eq(&upcb->local_ip, old_addr)) { / 找到绑定需要更新IP的UDP控制块 */
ip_addr_copy(upcb->local_ip, *new_addr); /* 更新 */
}
}
}
}
注意校验和相关宏:
LWIP_CHECKSUM_ON_COPY
:在支持使用数据区已经计算好的UDP数据区校验和。CHECKSUM_GEN_UDP
:在软件中生成出UDP数据包的校验和。在分析前先说明需要分析的几个函数的关系:
udp_send()
:UDP RAW的接口,需要的参数只需要UDP和用户数据即可。
udp_sendto()
:UDP RAW的接口,对比上面函数,可以指定远端IP和与远端端口号。
udp_sendto_if()
:UDP RAW的接口,对比上面udp_sendto()
函数,该函数还能指定网卡。
udp_sendto_if_src()
:UDP RAW的接口,也是UDP发送数据的基函数,是实现组装UDP包,和转交到IP层的接口函数。上面的函数都是必须经过该函数实现的。
先分析UDP发送数据基函数,即是组装UDP报文的函数。
然后再分析其它封装这个基函数的相关API。
udp_sendto_if_src()
:
struct udp_pcb *pcb
:负责本次数据交互的UDP控制块。
struct pbuf *p
:需要发送的数据的pbuf。
ip_addr_t *dst_ip
:远端IP。
u16_t dst_port
:远端端口号。
struct netif *netif
:本地网卡,即是发送本次UDP报文的网络接口。
ip_addr_t *src_ip
:源IP地址。
检查传入的参数是否异常。
检查当前UDP控制块有没有绑定了本地IP(包括任意IP)和本地端口号。还没绑定就需要调用udp_bind()
进行绑定。
预测检查UDP报文长度:UDP数据区追加UDP首部后是否溢出,溢出丢弃。
检查pbuf长度能否扩充到链路层报文:不能就申请多一个pbuf q,包含么UDP首部+IP首部+链路层首部,然后拼接到当前pbuf,让其拥有足够空间。
填充UDP报文首部的几个字段。
其中,UDP首部的长度字段和校验和需要按协议类型和相关宏处理。
最后把UDP报文转交给IP层:调用ip_output_if_src()
转发出去。
/** @ingroup udp_raw
/** 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; /* UDP首部 */
err_t err;
struct pbuf *q; /* 组装好的UDP报文,用于推到发送缓冲区 /
u8_t ip_proto; / IP协议 /
u8_t ttl; / TTL */
LWIP_ASSERT_CORE_LOCKED(); /* 内核锁内 */
/* 校验必要参数 */
LWIP_ERROR("udp_sendto_if_src: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if_src: invalid pbuf", p != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if_src: invalid dst_ip", dst_ip != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if_src: invalid src_ip", src_ip != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if_src: invalid netif", netif != NULL, return ERR_ARG);
/* UDP控制块绑定的本地IP类型和需要组装的IP报文中的源IP地址和目的IP地址类型一致 */
if (!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)) {
/ 如果本次UDP报文的目的IP是广播地址,但是用户又没有设置SOF_BROADCAST选项,则不能广播 */
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 */
/* 如果这个UDP控制块还没有绑定到本地端口,则将其绑定 /
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); / 绑定本地IP(包括任意IP)和本地端口号 /
if (err != ERR_OK) { / 绑定失败就不能发送 */
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: forced port bind failed\n"));
return err;
}
}
if ((u16_t)(p->tot_len + UDP_HLEN) < p->tot_len) {
return ERR_MEM; /* UDP报文溢出 /
}
/ pbuf扩展UDP首部 /
if (pbuf_add_header(p, UDP_HLEN)) {
/ 原pbuf长度不足,需要申请相关首部空间:UDP首部+IP报文 /
q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);
if (q == NULL) {
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: could not allocate header\n"));
return ERR_MEM; / 申请UDP报文空间失败 /
}
if (p->tot_len != 0) {
/ 把p拼接到q去 */
pbuf_chain(q, p);
}
LWIP_DEBUGF(UDP_DEBUG,
("udp_send: added header pbuf %p before given pbuf %p\n", (void *)q, (void )p));
} else { / p这个pbuf可以扩展UDP首部空间 */
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就是需要发送的UDP报文pbuf */
/* 组装UDP报文 */
udphdr = (struct udp_hdr *)q->payload;
udphdr->src = lwip_htons(pcb->local_port); /* UDP报文源端口 /
udphdr->dest = lwip_htons(dst_port); / UDP报文目的端口 /
/ UDP报文校验和字段,先初始化为0,表示不计算校验和 */
udphdr->chksum = 0x0000;
#if LWIP_MULTICAST_TX_OPTIONS /* 组播TX / if (((pcb->flags & UDP_FLAGS_MULTICAST_LOOP) != 0) && ip_addr_ismulticast(dst_ip)) { / 如果当前这路UDP支持组播环回,且目的IP是个组播IP,则标记当前pbuf为要环回的UDP组播 / q->flags |= PBUF_FLAG_MCASTLOOP; } #endif / LWIP_MULTICAST_TX_OPTIONS */
LWIP_DEBUGF(UDP_DEBUG, ("udp_send: sending datagram of length %"U16_F"\n", q->tot_len));
#if LWIP_UDPLITE /* 支持UDP LITE协议 /
if (pcb->flags & UDP_FLAGS_UDPLITE) { / 当前这路UDP为UDP LITE协议 /
u16_t chklen, chklen_hdr;
LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP LITE packet length %"U16_F"\n", q->tot_len));
/ 设置UDP首部总长度字段和伪首部UDP总长度字段为需要进行校验和的长度 /
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));
}
/ 对于UDP LITE协议,校验和长度字段为0时,表示对整个UDP报文进行校验和计算(校验和字段除外)。
(See RFC 3828 chap. 3.1) /
chklen_hdr = 0; / UDP LITE校验和长度字段 /
chklen = q->tot_len; / UDP LITE需要进行校验和的UDP报文数据长度 /
}
udphdr->len = lwip_htons(chklen_hdr); / UDP报文校验和长度字段值 */
#if CHECKSUM_GEN_UDP /* UDP支持校验和 / IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_UDP) { / 网卡需要检查UDP校验和 / #if LWIP_CHECKSUM_ON_COPY / 拷贝时需要计算校验和 / if (have_chksum) { chklen = UDP_HLEN; / UDP数据已经有校验和,则还需要再计算UDP首部的校验和 / } #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) { / 如果是已经有校验和,则把UDP首部(函伪首部)的校验和和已有的UDP数据校验和进行校验和即可 / u32_t acc; acc = udphdr->chksum + (u16_t)~(chksum); udphdr->chksum = FOLD_U32T(acc); } #endif / LWIP_CHECKSUM_ON_COPY */
/* 校验和刚好为0时必须变成0xffff,因为在UDP协议中,校验和字段为0表示“没有校验和” */
if (udphdr->chksum == 0x0000) {
udphdr->chksum = 0xffff;
}
}
#endif /* CHECKSUM_GEN_UDP */
/* IP协议字段标记为UDPLITE协议 */
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);
#if CHECKSUM_GEN_UDP /* UDP支持校验和计算 /
IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_UDP) { / 网卡需要检查UDP校验和 /
/ 校验和在IPv6中是必须的 /
if (IP_IS_V6(dst_ip) || (pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
u16_t udpchksum;
#if LWIP_CHECKSUM_ON_COPY
if (have_chksum) {
/ UDP数据已生成校验和,则只需要继续计算UDP首部的校验和即可 /
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 /
{ / 不使用UDP数据区计算好的校验和,咱们UDP协议自己独立生成 /
udpchksum = ip_chksum_pseudo(q, IP_PROTO_UDP, q->tot_len,
src_ip, dst_ip); / 计算UDP报文校验和 */
}
/* 校验和刚好为0时必须变成0xffff,因为在UDP协议中,校验和字段为0表示“没有校验和” */
if (udpchksum == 0x0000) {
udpchksum = 0xffff;
}
/* 设置UDP报文校验和 */
udphdr->chksum = udpchksum;
}
}
#endif /* CHECKSUM_GEN_UDP /
/ IP协议字段标记为UDP协议 */
ip_proto = IP_PROTO_UDP;
}
/* TTL相关 / #if LWIP_MULTICAST_TX_OPTIONS / 多播TX的TTL / ttl = (ip_addr_ismulticast(dst_ip) ? udp_get_multicast_ttl(pcb) : pcb->ttl); #else / LWIP_MULTICAST_TX_OPTIONS / ttl = pcb->ttl; / UDP控制块中的默认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));
/* 把UDP报文涉及网卡用户数据配置到这个网卡中 /
NETIF_SET_HINTS(netif, &(pcb->netif_hints));
/ UDP报文转交给IP层 */
err = ip_output_if_src(q, src_ip, dst_ip, ttl, pcb->tos, ip_proto, netif);
NETIF_RESET_HINTS(netif);
/* @todo: must this be increased even if error occurred? */
MIB2_STATS_INC(mib2.udpoutdatagrams);
if (q != p) {
/* 释放在这里申请的pbuf /
pbuf_free(q);
q = NULL;
/ p is still referenced by the caller, and will live on */
}
UDP_STATS_INC(udp.xmit);
return err;
}
从用户的角度看,用户前期配置好UDP控制块后,后面发送数据只需要提供两个参数:UDP控制块和需要发送的数据即可。
所以就有了udp_send()
函数,该函数的实现是层层封装UDP发送数据的基函数udp_sendto_if_src()
实现的:udp_send()
--> udp_sendto()
--> udp_sendto_if()
--> udp_sendto_if_src()
。
udp_send()
:没有指定远端IP和端口号则使用这个UDP PCB中的。
udp_pcb *pcb
:负责本次发送的UDP控制块。
struct pbuf *p
:需要发送的UDP数据。
/**
@ingroup udp_raw
Sends the pbuf p using UDP. The pbuf is not deallocated.
*
@param pcb UDP PCB used to send the data.
@param p chain of pbuf's to be sent.
*
The datagram will be sent to the current remote_ip & remote_port
stored in pcb. If the pcb is not bound to a port, it will
automatically be bound to a random port.
*
@return lwIP error code.
- ERR_OK. Successful. No error occurred.
- ERR_MEM. Out of memory.
- ERR_RTE. Could not find route to destination address.
- ERR_VAL. No PCB or PCB is dual-stack
- More errors could be returned by lower protocol layers.
*
@see udp_disconnect() udp_sendto()
*/
err_t
udp_send(struct udp_pcb *pcb, struct pbuf *p)
{
LWIP_ERROR("udp_send: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_ERROR("udp_send: invalid pbuf", p != NULL, return ERR_ARG);
if (IP_IS_ANY_TYPE_VAL(pcb->remote_ip)) {
return ERR_VAL;
}
/* 使用这路UDP控制块中配置的远端IP和远端端口号 */
return udp_sendto(pcb, p, &pcb->remote_ip, pcb->remote_port);
}
udp_sendto()
:指定远端IP和远端端口号的UDP发送。
struct udp_pcb *pcb
:负责本次发送的UDP控制块。
struct pbuf *p
:需要发送的数据的pbuf。
ip_addr_t *dst_ip
:远端IP地址。
u16_t dst_port
:远端端口号地址。
传入参数校验。
还需要指定本地网卡:
ip4_route_src()
去路由匹配。这个匹配逻辑可以参考前面IP章节。然后调用udp_sendto_if()
发送出去。
/**
@ingroup udp_raw
Send data to a specified address using UDP.
*
@param pcb UDP PCB used to send the data.
@param p chain of pbuf's to be sent.
@param dst_ip Destination IP address.
@param dst_port Destination UDP port.
*
dst_ip & dst_port are expected to be in the same byte order as in the pcb.
*
If the PCB already has a remote address association, it will
be restored after the data is sent.
*
@return lwIP error code (@see udp_send for possible error codes)
*
@see udp_disconnect() udp_send()
*/
err_t
udp_sendto(struct udp_pcb *pcb, struct pbuf *p,
const ip_addr_t *dst_ip, u16_t dst_port)
{
struct netif netif;
/ 参数校验 */
LWIP_ERROR("udp_sendto: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto: invalid pbuf", p != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto: invalid dst_ip", dst_ip != NULL, return ERR_ARG);
/* UDP控制块本地IP类型和目标IP类型要一致 */
if (!IP_ADDR_PCB_VERSION_MATCH(pcb, dst_ip)) {
return ERR_VAL;
}
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_send\n"));
if (pcb->netif_idx != NETIF_NO_INDEX) {
/* 如果已经绑定了网卡,则直接使用该网卡发送UDP报文 /
netif = netif_get_by_index(pcb->netif_idx);
} else { / 没有绑定网卡就需要匹配 /
#if LWIP_MULTICAST_TX_OPTIONS / 多播TX功能 /
netif = NULL;
if (ip_addr_ismulticast(dst_ip)) {
/ 如果UDP报文的目的IP地址是多播地址,则使用多播网卡来发送 /
if (pcb->mcast_ifindex != NETIF_NO_INDEX) {
netif = netif_get_by_index(pcb->mcast_ifindex);
}
#if LWIP_IPV4
else
#if LWIP_IPV6
if (IP_IS_V4(dst_ip))
#endif / LWIP_IPV6 /
{
/ 如果当前UDP指定的多播地址不是任意也不是广播,就需要通过路由去匹配 /
if (!ip4_addr_isany_val(pcb->mcast_ip4) &&
!ip4_addr_eq(&pcb->mcast_ip4, IP4_ADDR_BROADCAST)) {
/ 通过UDP本地IP和多播IP去匹配本地网卡 /
netif = ip4_route_src(ip_2_ip4(&pcb->local_ip), &pcb->mcast_ip4);
}
}
#endif / LWIP_IPV4 */
}
if (netif == NULL) /* 还没有指定网卡 /
#endif / LWIP_MULTICAST_TX_OPTIONS /
{
/ 通过UDP本地IP和目的IP去匹配网卡 */
netif = ip_route(&pcb->local_ip, dst_ip);
}
}
if (netif == NULL) {
/* 找不到适合发送的当前UDP报文的网卡,则丢弃 /
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: No route to "));
ip_addr_debug_print(UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, dst_ip);
LWIP_DEBUGF(UDP_DEBUG, ("\n"));
UDP_STATS_INC(udp.rterr);
return ERR_RTE;
}
/ 通过以下API实现发送UDP报文 /
#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
return udp_sendto_if_chksum(pcb, p, dst_ip, dst_port, netif, have_chksum, chksum);
#else / LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP /
return udp_sendto_if(pcb, p, dst_ip, dst_port, netif);
#endif / LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
}
udp_sendto_if()
:确定UDP本地IP地址,然后调用udp_sendto_if_src()
UDP发送数据基函数进行组包。
struct udp_pcb *pcb
:负责本次发送的UDP控制块。
struct pbuf *p
:需要发送的数据的pbuf。
ip_addr_t *dst_ip
:远端IP地址。
u16_t dst_port
:远端端口号地址。
struct netif *netif
:指定发送UDP报文的网卡。
参数校验。
确定本地IP:
/**
@ingroup udp_raw
Send data to a specified address using UDP.
The netif used for sending can be specified.
*
This function exists mainly for DHCP, to be able to send UDP packets
on a netif that is still down.
*
@param pcb UDP PCB used to send the data.
@param p chain of pbuf's to be sent.
@param dst_ip Destination IP address.
@param dst_port Destination UDP port.
@param netif the netif used for sending.
*
dst_ip & dst_port are expected to be in the same byte order as in the pcb.
*
@return lwIP error code (@see udp_send for possible error codes)
*
@see udp_disconnect() udp_send()
*/
err_t
udp_sendto_if(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;
/* 参数校验 */
LWIP_ERROR("udp_sendto_if: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if: invalid pbuf", p != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if: invalid dst_ip", dst_ip != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if: invalid netif", netif != NULL, return ERR_ARG);
if (!IP_ADDR_PCB_VERSION_MATCH(pcb, dst_ip)) {
return ERR_VAL;
}
/* PCB本地地址是IP_ANY_ADDR还是多播? /
#if LWIP_IPV6
if (IP_IS_V6(dst_ip)) {
if (ip6_addr_isany(ip_2_ip6(&pcb->local_ip)) ||
ip6_addr_ismulticast(ip_2_ip6(&pcb->local_ip))) {
src_ip = ip6_select_source_address(netif, ip_2_ip6(dst_ip));
if (src_ip == NULL) {
/ 没有找到合适的源地址 /
return ERR_RTE;
}
} else {
/ 使用UDP PCB本地IPv6地址作为源地址,如果仍然有效 /
if (netif_get_ip6_addr_match(netif, ip_2_ip6(&pcb->local_ip)) < 0) {
/ 地址无效 /
return ERR_RTE;
}
src_ip = &pcb->local_ip;
}
}
#endif / LWIP_IPV6 /
#if LWIP_IPV4 && LWIP_IPV6
else
#endif / LWIP_IPV4 && LWIP_IPV6 /
#if LWIP_IPV4
if (ip4_addr_isany(ip_2_ip4(&pcb->local_ip)) ||
ip4_addr_ismulticast(ip_2_ip4(&pcb->local_ip))) {
/ 如果UDP控制块本地IP没有指定,或者指定的是多播地址,则使用指定的网卡的IP作为UDP本地IP即可 /
src_ip = netif_ip_addr4(netif);
} else { / /
/ 检查UDP PCB本地IP地址是否正确,如果netif->ip_addr已更改,这可能是旧地址 /
if (!ip4_addr_cmp(ip_2_ip4(&(pcb->local_ip)), netif_ip4_addr(netif))) {
/ UDP本地IP和指定网卡的IP不匹配,不发送。 /
return ERR_RTE;
}
/ 确认使用的源IP /
src_ip = &pcb->local_ip;
}
#endif / LWIP_IPV4 /
#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
return udp_sendto_if_src_chksum(pcb, p, dst_ip, dst_port, netif, have_chksum, chksum, src_ip);
#else / LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP /
return udp_sendto_if_src(pcb, p, dst_ip, dst_port, netif, src_ip);
#endif / LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
}
UDP接收处理数据,南向是通过udp_input()
API给IP层收到UDP数据报后上交到UDP协议处理。
udp_input()
:
struct pbuf *p
:收到UDP报文的pbuf。
struct netif *inp
:收到该UDP报文的网卡。
参数校验。
报文校验。
匹配UDP PCB:通过IP和端口号确保该UDP报文得到某个应用程序。遍历UDP PCB udp_pcbs
:
UDP PCB 本地端口、IP和UDP报文目的端口和IP匹配:端口一致且IP匹配:
SOF_BROADCAST
选项。这个IP是全广播地址或者和当前UDP PCB IP处于同一个子网。本地可以匹配成功。uncon_pcb
变量中,有更适合且未连接的UDP PCB适配本次UDP报文的,更新到uncon_pcb
中。UDP PCB 远端端口、IP和UDP报文源端口和IP匹配:端口一致且IP匹配:
uncon_pcb
。上述都匹配成功后,UDP PCB即可匹配成功,当前 UDP 报文是给我们的。
校验和校验:
pbuf偏移头部,指向UDP数据区。即是用户数据。
如果开启了SOF_REUSEADDR
选项:则把当前UDP PCB包复制转发到所有能匹配成功的UDP PCB。
把数据回调到上层应用:pcb->recv()
/**
Process an incoming UDP datagram.
*
Given an incoming UDP datagram (as a chain of pbufs) this function
finds a corresponding UDP PCB and hands over the pbuf to the pcbs
recv function. 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;
LWIP_UNUSED_ARG(inp);
LWIP_ASSERT_CORE_LOCKED(); /* 确保在内核锁内 /
/ 参数校验 */
LWIP_ASSERT("udp_input: invalid pbuf", p != NULL);
LWIP_ASSERT("udp_input: invalid netif", inp != NULL);
PERF_START;
UDP_STATS_INC(udp.recv);
/* 检查最小长度(UDP首部) /
if (p->len < UDP_HLEN) {
/ drop short packets /
LWIP_DEBUGF(UDP_DEBUG,
("udp_input: short UDP datagram (%"U16_F" bytes) discarded\n", p->tot_len));
UDP_STATS_INC(udp.lenerr);
UDP_STATS_INC(udp.drop);
MIB2_STATS_INC(mib2.udpinerrors);
pbuf_free(p);
goto end;
}
/ 提取UDP首部 */
udphdr = (struct udp_hdr *)p->payload;
/* 检查是否是广播包 */
broadcast = ip_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif());
LWIP_DEBUGF(UDP_DEBUG, ("udp_input: received datagram of length %"U16_F"\n", p->tot_len));
src = lwip_ntohs(udphdr->src); /* UDP报文的源端口号 / dest = lwip_ntohs(udphdr->dest); / UDP报文的目的端口号 */
udp_debug_print(udphdr);
/* 打印相关信息 */
LWIP_DEBUGF(UDP_DEBUG, ("udp ("));
ip_addr_debug_print_val(UDP_DEBUG, *ip_current_dest_addr());
LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F") <-- (", lwip_ntohs(udphdr->dest)));
ip_addr_debug_print_val(UDP_DEBUG, *ip_current_src_addr());
LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F")\n", lwip_ntohs(udphdr->src)));
pcb = NULL;
prev = NULL;
uncon_pcb = NULL;
/* 遍历UDP PCB列表以找到匹配的PCB。
匹配pcb:连接到远程端口和ip地址优先。
如果没有找到完全匹配的,那么与本地端口和ip地址匹配的第一个未连接的pcb将获得数据报 /
for (pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) {
/ 每次遍历都打印PCB本地和远端IP地址和端口号 */
LWIP_DEBUGF(UDP_DEBUG, ("pcb ("));
ip_addr_debug_print_val(UDP_DEBUG, pcb->local_ip);
LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F") <-- (", pcb->local_port));
ip_addr_debug_print_val(UDP_DEBUG, pcb->remote_ip);
LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F")\n", pcb->remote_port));
/* 匹配UDP PCB 本地端口、IP和UDP报文目的端口和IP /
if ((pcb->local_port == dest) &&
(udp_input_local_match(pcb, inp, broadcast) != 0)) {
if ((pcb->flags & UDP_FLAGS_CONNECTED) == 0) {
if (uncon_pcb == NULL) {
/ 第一个未连接的匹配PCB /
uncon_pcb = pcb;
#if LWIP_IPV4
} else if (broadcast && ip4_current_dest_addr()->addr == IPADDR_BROADCAST) {
/ 全局广播地址(仅对IPv4有效;之前检查过匹配) /
if (!IP_IS_V4_VAL(uncon_pcb->local_ip) || !ip4_addr_cmp(ip_2_ip4(&uncon_pcb->local_ip), netif_ip4_addr(inp))) {
/ uncon_pcb 与收到数据netif不匹配,则需要重新检查此PCB /
if (IP_IS_V4_VAL(pcb->local_ip) && ip4_addr_cmp(ip_2_ip4(&pcb->local_ip), netif_ip4_addr(inp))) {
/ 更新uncon_pcb /
uncon_pcb = pcb;
}
}
#endif / LWIP_IPV4 /
}
/ 支持SOF_REUSEADDR选项功能。
因为如果没有开启这个功能,那前面两个if的匹配逻辑就能找到唯一一个符合要求的uncon_pcb。
如果支持SOF_REUSEADDR功能,UDP PCB中就可能存在多个匹配成功未连接的PCB,这样选第一个即可(靠近链表尾,即是老的) /
#if SO_REUSE
else if (!ip_addr_isany(&pcb->local_ip)) {
/ 更加倾向于有指定本地IP未连接的PCB /
uncon_pcb = pcb;
}
#endif / SO_REUSE */
}
/* 匹配UDP PCB 远端端口、IP和UDP报文源端口和IP /
if ((pcb->remote_port == src) &&
(ip_addr_isany_val(pcb->remote_ip) ||
ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()))) {
/ 第一个完全匹配的PCB /
if (prev != NULL) {
/ 将PCB移动到udp_pcbs的前面,以便下次更快地找到它 */
prev->next = pcb->next;
pcb->next = udp_pcbs;
udp_pcbs = pcb;
} else {
UDP_STATS_INC(udp.cachehit);
}
break;
}
}
prev = pcb; /* 遍历下一个UDP PCB /
}
/ 没有找到完全匹配的PCB,就使用未连接的匹配PCB */
if (pcb == NULL) {
pcb = uncon_pcb;
}
/* 最终检查当前UDP报文是不是给我们的 /
if (pcb != NULL) {
for_us = 1; / UDP PCB匹配成功,是给我们的 /
} else { / UDP PCB匹配不成功 /
#if LWIP_IPV6
if (ip_current_is_v6()) {
/ 检查下当前UDP报文的目的IP是不是给我们的 /
for_us = netif_get_ip6_addr_match(inp, ip6_current_dest_addr()) >= 0;
}
#endif / LWIP_IPV6 /
#if LWIP_IPV4
if (!ip_current_is_v6()) {
/ 检查下当前UDP报文的目的IP是不是给我们的 /
for_us = ip4_addr_cmp(netif_ip4_addr(inp), ip4_current_dest_addr());
}
#endif / LWIP_IPV4 */
}
if (for_us) { /* 当前UDP包是给我们的 /
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_input: calculating checksum\n"));
/ 校验和校验 /
#if CHECKSUM_CHECK_UDP
IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_UDP) {
#if LWIP_UDPLITE
if (ip_current_header_proto() == IP_PROTO_UDPLITE) { / UDP LITE协议:总长度字段就是需要进行校验和的数据长度 /
u16_t chklen = lwip_ntohs(udphdr->len);
if (chklen < sizeof(struct udp_hdr)) {
if (chklen == 0) {
/ 对于UDP-Lite,校验和长度为0表示对整个报文的校验和(参考RFC 3828章3.1) /
chklen = p->tot_len;
} else {
/ 至少UDP-Lite头必须被校验和覆盖!(再次参考RFC 3828第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 /
{ / UDP协议 /
if (udphdr->chksum != 0) { / 校验和字段不为0,则说明要进行校验和计算 /
/ 加上伪首部,一起进行校验和 /
if (ip_chksum_pseudo(p, IP_PROTO_UDP, p->tot_len,
ip_current_src_addr(),
ip_current_dest_addr()) != 0) {
goto chkerr;
}
}
}
}
#endif / CHECKSUM_CHECK_UDP /
if (pbuf_remove_header(p, UDP_HLEN)) { / pbuf指向UDP数据区,即是用户数据 /
/ Can we cope with this failing? Just assert for now */
LWIP_ASSERT("pbuf_remove_header failed\n", 0);
UDP_STATS_INC(udp.drop);
MIB2_STATS_INC(mib2.udpinerrors);
pbuf_free(p);
goto end;
}
if (pcb != NULL) { /* 如果已成功匹配PCB /
MIB2_STATS_INC(mib2.udpindatagrams);
#if SO_REUSE && SO_REUSE_RXTOALL / SOF_REUSEADDR选项功能 /
/ 如果设置了SOF_REUSEADDR选项功能,则说明可能存在多个匹配成功的PBC,都需要把当前UDP报文拷贝传递过去 /
if (ip_get_option(pcb, SOF_REUSEADDR) &&
(broadcast || ip_addr_ismulticast(ip_current_dest_addr()))) {
/ 如果SOF_REUSEADDR在第一次匹配时设置,则将广播或组播数据包传递给所有组播pcb */
struct udp_pcb *mpcb;
for (mpcb = udp_pcbs; mpcb != NULL; mpcb = mpcb->next) {
if (mpcb != pcb) { /* 跳过前面匹配成功的PCB(后面会处理) /
/ 比较PCB本地IP地址+端口号 和 UDP目的IP地址+端口号 /
if ((mpcb->local_port == dest) &&
(udp_input_local_match(mpcb, inp, broadcast) != 0)) {
/ 将一个包的副本传递给所有本地匹配 */
if (mpcb->recv != NULL) {
struct pbuf *q;
/* 拷贝UDP报文 /
q = pbuf_clone(PBUF_RAW, PBUF_POOL, p);
if (q != NULL) {
/ 回调到用户层 /
mpcb->recv(mpcb->recv_arg, mpcb, q, ip_current_src_addr(), src);
}
}
}
}
}
}
#endif / SO_REUSE && SO_REUSE_RXTOALL */
if (pcb->recv != NULL) {
/* 把数据回调到对应UDP PCB的应用程序 /
pcb->recv(pcb->recv_arg, pcb, p, ip_current_src_addr(), src);
} else {
/ 没有recv功能注册,那就得释放pbuf! */
pbuf_free(p);
goto end;
}
} else {
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_input: not for us.\n"));
#if LWIP_ICMP || LWIP_ICMP6
/* 没有找到匹配项,发送ICMP目的端口不可达,除非目的地址是广播/组播 /
if (!broadcast && !ip_addr_ismulticast(ip_current_dest_addr())) {
/ 将pbuf数据区指针移回IP头 /
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 /
UDP_STATS_INC(udp.proterr);
UDP_STATS_INC(udp.drop);
MIB2_STATS_INC(mib2.udpnoports);
pbuf_free(p);
}
} else { / 当前UDP报文不是给我们的,丢弃 /
pbuf_free(p);
}
end:
PERF_STOP("udp_input");
return;
#if CHECKSUM_CHECK_UDP
chkerr:
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("udp_input: UDP (or UDP Lite) datagram discarded due to failing checksum\n"));
UDP_STATS_INC(udp.chkerr);
UDP_STATS_INC(udp.drop);
MIB2_STATS_INC(mib2.udpinerrors);
pbuf_free(p);
PERF_STOP("udp_input");
#endif / CHECKSUM_CHECK_UDP */
}
UDP层初始化接口:
void udp_init (void);
南向供IP层使用:
void udp_input (struct pbuf *p, struct netif *inp);
UDP RAW相关接口分析:北向,供用户使用
struct udp_pcb * udp_new (void);
struct udp_pcb * udp_new_ip_type(u8_t type);
void udp_remove (struct udp_pcb *pcb);
err_t udp_bind (struct udp_pcb *pcb, const ip_addr_t *ipaddr,
u16_t port);
void udp_bind_netif (struct udp_pcb *pcb, const struct netif* netif);
err_t udp_connect (struct udp_pcb *pcb, const ip_addr_t *ipaddr,
u16_t port);
void udp_disconnect (struct udp_pcb *pcb);
void udp_recv (struct udp_pcb *pcb, udp_recv_fn recv,
void *recv_arg);
err_t udp_sendto_if (struct udp_pcb *pcb, struct pbuf *p,
const ip_addr_t *dst_ip, u16_t dst_port,
struct netif *netif);
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);
err_t udp_sendto (struct udp_pcb *pcb, struct pbuf *p,
const ip_addr_t *dst_ip, u16_t dst_port);
err_t udp_send (struct udp_pcb *pcb, struct pbuf *p);
手机扫一扫
移动阅读更方便
你可能感兴趣的文章