【lwip】11-UDP協議&源碼分析

前言

主要分析源碼實現。

源碼部分,本章節也只分析協議實現部分和最原始的南北介面。

北向協議棧介面和套接字介面的封裝後面有獨立章節分析。

即是UDP RAW介面。

友鏈:

11.1 傳輸層說明

IP協議只能完成數據報在互聯網主機之間的傳遞,交付。

而傳輸層主要負責向兩個主機中進程之間的通訊提供服務。

傳輸層的幾個重要的任務:

  1. 為兩個通訊的進程提供連接機制。即是傳輸層需要識別兩個正在通訊的進程。
  2. 傳輸層應該提供數據傳輸服務。在數據發送端,傳輸層將用戶數據進行組裝遞交給IP層發送出去。在接收端,等待屬於同一應用程式的所有數據單元到達,然後解析轉交給用戶。
  3. 為了提供可靠的傳輸服務,傳輸層還可以提供流量控制、數據確認等,保證兩個主機的應用程式之間數據的有效性。

11.2 UDP協議簡介

UDP 是 User Datagram Protocol 的簡稱,中文名是用戶數據報協議。

11.3 UDP特點

UDP特點:

  1. 無連接、不可靠。
  2. 儘可能提供交付數據服務,出現差錯直接丟棄,無回饋。
  3. 面向報文,發送方的 UDP 拿到上層數據直接添加個 UDP 首部,然後進行校驗後就遞交給IP 層,而接收的一方在接收到 UDP 報文後簡單進行校驗,然後直接去除數據遞交給上層應用。
  4. 支援一對一,一對多,多對一,多對多的交互通訊。
  5. 速度快,UDP 沒有 TCP 的握手、確認、窗口、重傳、擁塞控制等機制,UDP 是一個無狀態的傳輸協議,所以它在傳遞數據時非常快,即使在網路擁塞的時候 UDP 也不會降低發送的數據。

11.4 UDP埠號

UDP 報文協議根據對應的埠號傳遞到目標主機的應用執行緒的。

傳輸層到應用層的唯一標識是通過埠號決定的,兩個執行緒之間進行通訊必須用埠號進行識別。

使用「IP 地址 + 埠號」來區分主機不同的執行緒。

範圍:[0,65535],因為只有2位元組:

  1. 埠號小於256的定義為常用埠,伺服器一般都是通過常用埠號來識別的。任何TCP/IP實現所提供的服務都用[1,1023]之間的埠號,是由ICANN來管理的;
  2. 埠號從[1024, 49151]是被註冊的埠,也成為「用戶埠」,被IANA指定為特殊服務使用。
  3. 大多數TCP/IP實現給臨時埠號分配[1024, 5000]之間的埠號。
  4. 大於5000的埠號是為其他伺服器預留的。
  5. 客戶端只需保證該埠號在本機上是唯一的就可以了。客戶端埠號因存在時間很短暫又稱臨時埠號

常見的UDP協議埠號:




說明
0 保留
7 echo 報文回送伺服器埠
53 DNS 域名伺服器埠
69 TFT 中小型文件傳輸協議埠
123 NTP 網路時問協議埠
是用來同步網路中各個電腦時問的協議
161 SNMP 簡單網路管理協議埠

11.5 UDP報文

UDP 報文也被稱為用戶數據報。

UDP報文封裝:

  • UDP的數據區就是用戶程式的數據了。

UDP報文格式:

源埠號:用戶發送數據的進程鎖綁定的本地埠號。

  • 用戶進程調用UDP相關業務時,可以不配置源埠號。若不配置,內部會自動適配一個臨時埠號。

目的埠號:遠端主機用戶進程接收數據綁定的埠號。

總長度:是UDP數據報的總長度:UDP首部+UDP數據區。

  • 這個欄位有點冗餘,因為在IP報文中就包含了IP首部和IP總長度,這樣能計算出UDP數據報的長度。所以,UDP LITE就把這個欄位改為需要進行校驗和的UDP報文數據長度(從UDP首部算起)。
  • 對於UDP LITE協議,這個欄位為0時,表示對整個UDP報文進行校驗和計算。參考RFC 3828 chap. 3.1
  • 對於UDP LITE協議,這個欄位要麼為0,要麼不少於UDP報文首部長度(即是校驗和至少要涵蓋UDP首部)。參考RFC 3828 chap. 3.1

檢驗和:UDP協議為UDP偽首部+UDP首部+UDP數據區所有數據都加入校驗和。UDP LITE協議為「總長度」指定的長度加入校驗和,從UDP偽首部算起,再加上偽首部校驗和。

  • 填入0時,表示不進行校驗和。而在實際計算校驗和得到的結果剛好為0時,則向校驗和欄位填入0FFFF。

    • 填入0XFFFF的可行性證明:如果校驗和結果為0,即是其它數據的和為0XFFFF。而對端繼續校驗和時,就是0XFFFF+0XFFFF,結果還是0XFFFF。

UDP LITE:UDP協議的校驗和是UDP首部和UDP數據區,如果數據區很多數據,一個校驗失敗就丟棄了,代價有點大,所以衍生出UDP LITE。只校驗UDP報文前面指定數據長度的數據。一般用於實時適配、實時通話等這些要求通訊速度快,可靠性要求不高的業務中。

11.6 UDP偽首部和校驗和

UDP校驗和的計算包括了三部分:UDP偽首部+UDP首部+UDP數據區。

UDP偽首部包含IP首部一些欄位。其目的是讓UDP驗證數據是否已經正確到達目的地。

UDP偽首部只參與校驗,不參與實際發送。

偽首部中UDP總長度和UDP首部的總長度欄位一致。

11.7 wireshark報文分析

11.8 UDP數據結構

參考udp.cudp.h文件

11.8.1 UDP首部

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;

11.9 UDP控制塊

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

11.10 埠號相關

11.10.1 埠號範圍

#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))

11.10.2 埠號初始值

UDP的埠號由全局值udp_port累加管理。

其初始值有兩次初始:第一次是變數賦值,第二次是調用udp_init()進行隨機初始。

變數初始值:

/* last local UDP port */
static u16_t udp_port = UDP_LOCAL_PORT_RANGE_START;

隨機初始化:

  • 需要開啟LWIP隨機宏LWIP_RAND
/**
 * Initialize this module.
 */
void
udp_init(void)
{
#ifdef LWIP_RAND
  udp_port = UDP_ENSURE_LOCAL_PORT_RANGE(LWIP_RAND());
#endif /* LWIP_RAND */
}

11.10.3 udp_new_port()埠號申請

埠號申請是有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; /* 申請成功 */
}

11.11 UDP控制塊操作函數

UDP控制塊的操作函數相對簡單,因為沒有流量控制、沒有確認機制等等。

11.11.1 udp_new():新建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;
}

11.11.2 udp_remove():刪除UDP控制塊

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

11.11.3 udp_bind():綁定控制塊

當UDP服務於應用程式時,數據流需要底層和應用層進行對接,就需要把UDP控制塊綁定到本地IP和埠號。

綁定控制塊時需要注意的是:

  1. 檢查是否有PCB已經綁定了當前IP和埠號。
  2. 當前PCB有沒有已經插入了udp_pcbs鏈表。

小筆記:在沒有設置SOF_REUSEADDR選項功能時,需要確保一個UDP報文最多只能到達一個應用程式。即是一個網路介面中的一個埠號。需要注意的是任意IP。

udp_bind()

  • struct udp_pcb *pcb:需要綁定本地IP和埠號的UDP控制塊。

  • ip_addr_t *ipaddr:UDP控制塊需要綁定的本地IP地址。

    • 如果為NULL,則綁定本地IP為全0的IP。即表示本地任意IP都可。
    • 如果不為空,則綁定指定的本地IP。
  • u16_t port:UDP控制塊需要綁定的本地埠號。

    • 如果為0,則綁定由內部調用udp_new_port()隨機生成埠號。
    • 如果不為0,則綁定指定的埠號。
  • 先檢查下當前UDP控制塊有沒有插入了udp_pcbs鏈表,因為綁定成功後,需要插入該鏈表。已經插入了,就不需要重複操作。

  • 檢查綁定的IP地址。傳入為空,則賦值為全0的IP地址。

  • 檢查綁定的埠號。

    • 如果為0,則調用udp_new_port()生成一個並綁定。

    • 如果不為0,則遍歷udp_pcbs鏈表,判斷是否有其它UDP控制塊重複使用這個埠號。確保一個UDP報文最多只有一個應用程式去向。相同條件:埠號相同且IP報文能到達這個服務。IP報文能否到達這個服務,可以通過以下判斷(其一即符合要求):重複埠號的UDP控制塊綁定的IP對比當前UDP控制塊需要綁定的IP。

      1. 兩個IP一致。
      2. 任一IP為全0。(出現萬能IP)
      3. 如果開啟了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; /* 綁定成功 */
}

11.11.4 udp_bind_netif():綁定網卡

udp控制塊也可以綁定指定網卡。

udp_bind_netif()

  • udp_pcb *pcb:需要綁定網卡的UDP控制塊。

  • struct netif *netif:UDP控制塊需要綁定的網卡。

    • 為NULL時,表示解綁。
  • 獲取網卡索引,綁定到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; /* 取消綁定 */
  }
}

11.11.5 udp_connect():連接控制

(本地行為)

UDP協議是沒有連接狀態的,但是為什麼UDP可以調用udp_connect()這個函數?有什麼用?

  • 調用這個是為了這個UDP控制塊本地長期綁定一個遠端IP和埠號,減少後面重複綁定和解綁的步驟。

先了解下UDP sendto() 函數傳輸數據過程 :

  1. 第 1 階段:向 UDP 控制塊註冊目標 IP 和埠號。
  2. 第 2 階段:傳輸數據。
  3. 第 3 階段:刪除 UDP 控制塊中註冊的目標地址資訊。

如果需要頻繁發送,那第一階段和第三階段是重複多餘的,所以可以使用 已連接(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,
   * 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;
}

11.11.6 udp_disconnect():斷開連接

(本地行為)

就是本地註銷遠端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);
}

11.11.7 udp_recv():控制塊註冊接收函數

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

11.11.8 udp_netif_ip_addr_changed():更新UDP控制塊本地IP

當底層網卡在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); /* 更新 */
      }
    }
  }
}

11.12 UDP發送數據

注意校驗和相關宏:

  • 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層的介面函數。上面的函數都是必須經過該函數實現的。

    • 主要分析該函數。其它函數看看就好了。

11.12.1 udp_sendto_if_src():UDP發送數據基函數

先分析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 @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;
}

11.12.2 udp_send():UDP發送數據函數

從用戶的角度看,用戶前期配置好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:遠端埠號地址。

  • 傳入參數校驗。

  • 還需要指定本地網卡:

    • 如果UDP控制塊已經綁定了本地網卡,則直接調用該網卡即可。
    • 否則,需要根據遠端IP地址,用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:

    • 如果UDP控制塊沒有指定本地IP,則獲取指定的網卡的IP作為UDP本地IP。
    • 如果UDP控制塊指定了本地IP,則這個IP必須和指定網卡的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 */
}

11.13 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匹配:

      • 當前UDP PCB沒有指定本地IP,或UDP報文的目的IP就是指向當前UDP PCB的IP。本地可以匹配成功。
      • 如果UDP報文對應的目的IP是一個廣播地址,且當前UDP設置了SOF_BROADCAST選項。這個IP是全廣播地址或者和當前UDP PCB IP處於同一個子網。本地可以匹配成功。
      • 如果UDP PCB 本地埠、IP和UDP報文目的埠和IP匹配成功後,但是該UDP PCB還沒有處於連接狀態,則可以記錄到uncon_pcb變數中,有更適合且未連接的UDP PCB適配本次UDP報文的,更新到uncon_pcb中。
    • UDP PCB 遠端埠、IP和UDP報文源埠和IP匹配:埠一致且IP匹配:

      • UDP PCB遠端IP隨意或者就是當前UDP報文的源IP。遠端匹配成功。
      • 如果UDP PCB 遠端埠、IP和UDP報文源埠和IP匹配失敗,則可以使用UDP PCB 本地埠、IP和UDP報文目的埠和IP匹配但是未連接的UDP PCBuncon_pcb
    • 上述都匹配成功後,UDP PCB即可匹配成功,當前 UDP 報文是給我們的。

  • 校驗和校驗:

    • UDP協議:校驗和欄位為0,不用校驗。校驗和欄位不為0,則全部校驗。
    • UDP LITE協議:UDP報文的總長度欄位值即為需要進行校驗和計算的數據長度。注意:長度欄位為0表示整個報文校驗。(參考RFC 3828章3.1)
  • pbuf偏移頭部,指向UDP數據區。即是用戶數據。

  • 如果開啟了SOF_REUSEADDR選項:則把當前UDP PCB包複製轉發到所有能匹配成功的UDP PCB。

    • 如果沒有開啟該選項,當前UDP報文就只遞交給第一個匹配成功的DUP 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 */
}

11.14 UDP RAW介面編程

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

11.15 個人部落格