主要分析原始碼實現。
原始碼部分,本章節也只分析協定實現部分和最原始的南北介面。
北向協定棧介面和通訊端介面的封裝後面有獨立章節分析。
即是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資料區。
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_RAND
。/**
* Initialize this module.
*/
void
udp_init(void)
{
#ifdef LWIP_RAND
udp_port = UDP_ENSURE_LOCAL_PORT_RANGE(LWIP_RAND());
#endif /* 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_REUSE
且兩個UDP控制塊都設定了SO_REUSEADDR
功能,則不用對比了,直接支援複用。這裡需要注意是否開啟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_bind()
進行繫結。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,
* using the bound address to make a more informed decision when possible. */
if (IP_IS_V6(&pcb->remote_ip) &&
ip6_addr_lacks_zone(ip_2_ip6(&pcb->remote_ip), IP6_UNKNOWN)) {
ip6_addr_select_zone(ip_2_ip6(&pcb->remote_ip), ip_2_ip6(&pcb->local_ip));
}
#endif /* LWIP_IPV6 && LWIP_IPV6_SCOPES */
/* 註冊遠端埠號 */
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()
:
/**
* @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。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_bind()
進行繫結。ip_output_if_src()
轉發出去。/** @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)
{
#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP /* 需要計算校驗和 */
return udp_sendto_if_src_chksum(pcb, p, dst_ip, dst_port, netif, 0, 0, src_ip); /* 校驗和欄位先填入0,後面會計算 */
}
/** 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);