lwIP 介紹_3 協議


協議

我的博客
本文原文


lwIP 是一模塊化的框架,支持很多的協議,大部分代碼可以為了精簡代碼刪除。

鏈路與網絡協議

ARP: 地址解析協議

地址解析協議 ARP: Address Resolution Protocol 是鏈路層協議,用來轉換本機硬件地址 (即 MAC 地址) 與 IP 地址。

支持 ARP 的網絡接口會令 etharp_output 處理所有外發的包,並將會在它的 netif 結構體中設置 flag 標識來使能免費 ARP (gratuitous ARP)。並將維護一個最近的 IP 地址以及相關的硬件地址表,如果外發的包不能匹配任何已知的硬件地址,代碼將會執行一個 ARP 請求來解析正確的地址。

一個應用程序員不應該直接調用這個協議,因為如果一切工作正常,那么這個過程是透明的。有幾個標識能夠確定 ARP 表的大小、包是如何在掛起的 ARP 請求后排隊的,以及 ARP 代碼是否需要從到來的包中學習 MAC-IP 地址綁定關系。這些選項可以在 lwipopts.h 頭文件中配置。

IPv4

現今使用的主流的網絡層協議。

IPv4 (Internet Protocol version 4) 是現今廣為使用的網絡協議。它具有如下特性:

  • 盡可能發送,網絡設備盡可能將要發送的包發送到目標點,然而不能確保百分百完成,所有的中間媒介都會盡可能完成這個任務
  • 不能保證發送或重傳質量,目標點不能通知主機它接受到了數據包
  • 接受到包的順序可能與發送包的順序不同,一旦包發送到網絡上,它們可能會經過完全不同的路由。因此包到達目標點的順序是不能保證的
  • 主機可能會收到重復的包

針對應用向的 IPv4

操作地址

一個 IPv4 地址由 32 比特位長的字串組成,可以通過 . 划分成格式 127.0.0.1。在 lwIP 中,IP 地址由 struct ip_addr 結構體持有,它持有無符號的 32 比特位長的子。因此,設置一個 IP 地址,可以使用如下的代碼:

#include <lwip/ip_addr.h>
struct ip_addr local;
IP4_ADDR(&local, 127.0.0.1); // 設置回環地址為 127.0.0.1

其他會實用到 ip_addr 結構體的如下:

  • IP_ADDR_ANY 任意地址,比如,如果你想要監聽一個 TCP 端口,但是不希望綁定到一個指定的地址
  • ip_addr_set(dest, src) 從一個結構體復制地址到另一個結構體
  • ip_addr_cmp(addr1, addr2) 比較兩個地址是否相同
  • ip4_addr1(ipaddr) IP 地址的第一個字節,比如 192.168.133.144 中的 192
  • ip4_addr2(ipaddr) IP 地址的第二個字節,比如 192.168.133.144 中的 168
  • ip4_addr3(ipaddr) IP 地址的第三個字節,比如 192.168.133.144 中的 133
  • ip4_addr4(ipaddr) IP 地址的第四個字節,比如 192.168.133.144 中的 144
主機與網絡字節順序

因為大端、小端的架構不同,必須確定結構體中的值是按照主機中的字序排布的還是網絡字節序排布的。

網絡字節序是以大端排布的。你的主機平台可能也是大端,這樣你的代碼無需考慮這些技術細節。但是,如果你為一台小端處理器 (比如 IntelAMDARM 等) 重新編譯了你的程序,那么你的代碼可能會出現一些難以確定的問題。

因此,下面的一些主機到網絡的字節轉換函數可能會有幫助,這些函數如下:

  • netval = htonl(hostval)32 比特位主機字序轉換為網絡字序
  • netval = htons(hostval)16 比特位主機字序轉換為網絡字序
  • hostval = ntohl(netval)32 比特位網絡字序轉換為主機字序
  • hostval = ntohs(netval)16 比特位網絡字序轉換為主機字序

lwIP 在大部分情況下,會自動為你注意所有的內部網絡協議結構體。在你手動設置或比較 IP 地址時,需要注意這一點;或者,如果你將結構體串起到包中,並發送到另一個系統上時需要注意這一點。如果你的目標的字序與你主機的字序不同,那么你必須按照既定的標准,保證你這邊的字序與對方的字序一致(你這邊的16位與對方的16位一致)。

注意一點ip_addr->addr 是網絡字序排布的,u32_t someip 是主機字序排布的。

  • IN_CLASSA 宏檢查一個 IP 地址是否是一個 A 類地址,它操作 32 比特值,因此是主機字節序
  • IP_ADDR_ANY 宏是 struct ip_addr 結構體,它的 IP 地址為網絡字節序
  • ip_addr_cmp 宏比較兩個保存為 struct ip_addr 中的 IP 地址,因此它是網絡字節序

下面一些實例:

struct ip_addr ip, otherip;

ip.addr = 0x7f000001;       /* BAD,大端下為 127.0.0.1,小端下為 1.0.0.127 */
IP4_ADDR(&ip, 127.0.0.1);   /* GOOD */
ip_addr_set(&otherip, &ip); /* GOOD,網絡字序分配給網絡字序 */
otherip.addr = ip.addr;     /* BAD,可以工作,但是最好實用宏 */

if(ip.addr == 0x7f000001);  /* BAD,在大端上 true,小端上 false */
if(ip.addr == 0x0100007f);  /* BAD,在小端上 true,大端上 false */

if(ntohl(ip.addr) == 0x7f000001);   /* GOOD */
if(ip_addr_cmp(&ip, &otherip));     /* GOOD */
為接口分配地址

一個網絡接口只能具有一個 IP 地址。在 lwIP 中,支持三種方法為接口分配 IP 地址:

  • 靜態 IP: netifIP 地址可以通過 netif_addnetif_set_addrnetif_set_ipaddr 進行初始化
  • DHCP 動態獲取: DHCP 是一個可選協議,可從 DHCP 服務器上獲取動態 IP
  • AUTOIP: AUTOIP 是一個可選協議,可以從本地子網中挑選一個 IP 地址,而不需要通過服務器為其分配地址

IPv6

IPv4 的后繼,將 IP 地址擴展為 128 比特。

應用角度的 IPv6

IPv6 的支持現在已經添加到 lwIP 中。在 v1.4.x 以上版本的 lwIP 可以在 IPv4IPv6 之間進行選擇,但不是兩者同時使用。雙協議棧的使用是當前開發版本中的一個特性,可能會在 v1.5.0 版本發布。

ICMP

IP 的控制協議。

ICMP 支持

ICMP 應用支持三個協議:

  • Echo Replyping,客戶端響應 ping 請求,響應 IP 包中的數據
  • 目的地不可達,指示設備不能轉發 IP 數據包。比如,如果一個包尋址到的設備需求一個本設備不支持的協議,就會報這個錯誤
  • 超時,指示設備因為包的生命周期 Time To Live TTL 到達 0,而丟棄了數據包

應用角度的 ICMP

ICMP 信息由 lwIP 協議棧自身處理或生成。因此一個應用不應該與 ICMP 的代碼進行交互。如果用戶希望生成自己的 ping,那么可以組織一個包作為 IP 包,通過 IP 模塊進行發送。

IGMP

IP 進行的多播協議。

傳輸層協議

UDP

沒有可靠握手機制的套接字協議。

TCP

具有握手,可以進行流控的協議。

其他

DHCP

在帶有服務器時的 IP 地址獲取方法。

應用角度的 DHCP

為了使能 DHCP,你必須確保編譯、鏈接到 DHCP。你可以通過在 lwipopts.h 頭文件中將 LWIP_DHCP 值設置為 1 來使能這一功能,這會添加指向 dhcp 結構體的字段到 netif 中。dhcp 結構體會在 dhcp_start() 中進行分配。另外,LWIP_UDP 必不能設置為 0,因為 DHCP 是運行在 UDP 上的協議。

在一個簡單的設置中,在初始化接口之后,可以簡單的調用:

dhcp_start(&mynetif);

之后,為了正確處理動態 IP 使用時限,DHCP 需要調用一系列的計時函數。在 1.4.0 版本之后,你只需要調用一個單獨的函數,來處理協議棧中的所有的定時器,因此添加下面的函數到你的 main 循環中是等效的:

sys_check_timeouts();

之后,你需要檢查你的接口 ->dhcp->state == DHCP_BOUND,就可以了。

對於 lwIP 2.0,你需要調用:

dhcp_supplied_address(const struct netif *netif)

如果需要處理比較復雜的網絡變化環境,比如,一個移動設備可能會不停的更換鏈接到的網絡,那么你需要告知 DHCP 函數這一情況。這通常可以通過調用 dhcp_network_changed() 函數實現。因為協議棧中的 AUTOIP 以及 IGMP 協議也需要關注這個情況,所以正確的調用方法是:

netif_set_link_up(&mynetif);
netif_set_link_down(&mynetif);

這兩個函數需要與接口的 link 狀態變化綁定。

下面是詳細信息,為了在一個接口上使用 DHCP,簡單實用如下函數:

  • dhcp_start() 為一個接口啟動 DHCP 配置
  • dhcp_renew() 強制刷新之前的時限
  • dhcp_release() 釋放 DHCP 時限,通常在 dhcp_stop() 之前調用
  • dhcp_stop() 為一個接口停止 DHCP 配置
  • dhcp_inform() 告知服務器我們的 IP 地址

注意: 這些函數是 lwIP 的內核函數。它並不會受到並發訪問的保護。在多線程環境中,它們可能只在核心線程調用 (即,tcp-ip 線程)。在其他線程調用時,使用在 netifapi.c 中定義的對應版本的 netifapi_dhcp_*() 函數。

一個選擇是利用 PHY 自協商的特點。大部分 PHY 在連接狀態 link 發生改變時生成中斷。對 PHY 連接狀態發生改變的中斷處理傳遞給 tcp-ip 線程。之后在 HandlePhyInterrupt,如果 linkup 態,進行任何必要的硬件寄存器調整,來適配子協商的速度,之后調用 dhcp_start。如果 linkdown 態,首先調用 netif_set_down(),前面提到的其他的 dhcp 函數可能就不需要了。

AUTOIP

沒有服務器時的 IP 地址選擇方法。

應用角度的 AUTOIP

為了使能 AUTOIP,需要配置 lwipopts.h 頭文件中的 LWIP_AUTOIP

在一個接口上使用 AUTOIP,簡單實用如下命令:

  • autoip_start() 使用一個新的 IP 另接口 up
  • autoip_stop() 使接口 down

注意: 這些函數是 lwIP 的內核函數。不會受到並發訪問的保護。在多線程的環境下,工作類似 DHCP

SNMP

用來監控網絡條件。

PPP

用來在兩個節點之間直接創建鏈接。

應用角度的 PPP

有兩種使用 PPP 的方式,一種是通過 PPPoE (PPP over Ethernet),一種是 PPP over seriallwIP 支持在線程環境下運行,這個情況下 PPP 是一個單獨的任務,與 lwIP 主線程相分離。lwIP 也支持在 main 循環中運行,在 main 函數中調用 lwIP 的函數。

串行下的 PPP

為了設置 PPP 通過串行鏈接,你需要提供串行 IO 功能。

無論是線程環境還是 main 循環函數環境,都需要應用 sio_write()

/**
 * Writes to the serial device.
 * 
 * @param fd serial device handle
 * @param data pointer to data to send
 * @param len length (in bytes) of data to send
 * @return number of bytes actually sent
 * 
 * @note This function will block until all data can be sent.
 */
u32_t sio_write(sio_fd_t fd, u8_t *data, u32_t len);
任務支持

除了 sio_write(),還需要應用 sio_read()sio_read_abort() 以及 linkStatusCB。前兩個函數是靜態鏈接的,最后一個函數通過函數指針定義 (因此名稱可以不同,也可以動態創建)。

函數 sio_read()pppInputThread 中重復調用。它會阻塞等待,直到超時或請求緩沖區被填充,會從串行設備上讀取數據。如果調用了 sio_read_abort() 那么必須廢棄掉 sio_read()。在 sio_readsio_read_abort 之間這樣的軟連接關系必須被開發者自己實現,可以通過全局變量實現,可以通過 RTOS 的事件實現等。超時時長需要較小,大小只需要數據足夠有 1 字節寬度滿足 PPP 協議棧響應即可。2 毫秒的時長就已經足夠了。

/**
 * Reads from the serial device.
 * 
 * @param fd serial device handle
 * @param data pointer to data buffer for receiving
 * @param len maximum length (in bytes) of data to receive
 * @return number of bytes actually received - may be 0 if aborted by sio_read_abort
 * 
 */
u32_t sio_read(sio_fd_t fd, u8_t *data, u32_t len);

sio_read_abort() 函數必須要讓 sio_read 馬上退出。這是在 tcpip_thread 中實現的,大部分情況是在結束一個 PPP 會話時執行 (終止、鏈接斷開或顯式關閉)。

/**
 * Reads from the serial device.
 * 
 * @param fd serial device handle
 * @param data pointer to data buffer for receiving
 * @param len maximum length (in bytes) of data to receive
 * @return number of bytes actually received - may be 0 if aborted by sio_read_abort
 * 
 */
u32_t sio_read(sio_fd_t fd, u8_t *data, u32_t len);
回調函數

下面的回調函數:

void (*linkStatusCB)(void *ctx, int errCode, void *arg)

會在下面事件下調用:

  • 鏈接終止,errCode0arg 為空
  • 接口 uperrCodePPPERR_NONEarg 是指向 ppp_addrs 結構體,這個結構體中包含 IP 地址
  • 接口 downerrCodePPPERR_CONNECTarg 為空

errCode 可以為下面的情況:

#define PPPERR_NONE      0 /* 無錯誤 */
#define PPPERR_PARAM    -1 /* 無效參數 */
#define PPPERR_OPEN     -2 /* 不能開啟 PPP 會話 */
#define PPPERR_DEVICE   -3 /* 無效的 IO 設備 */
#define PPPERR_ALLOC    -4 /* 不能分配資源 */
#define PPPERR_USER     -5 /* 用戶打斷 */
#define PPPERR_CONNECT  -6 /* 鏈接丟失 */
#define PPPERR_AUTHFAIL -7 /* 認證失敗 */
#define PPPERR_PROTOCOL -8 /* 協議不匹配 */

ctx 指針是用戶可選定義的指針,是調用 pppOverSerialOpen 函數的參數,可以指向用戶定義的數據。

無任務支持

你需要在你的主線程中接收串行數據,之后你的主線程調用:

pppos_input(int pd, u_char *data, int len);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM