lwip-2.1.3/src/core/ipv4/autoip.c
AutoIP 通常指的是在動態網絡環境中,系統自動為其網絡接口分配、管理和使用一個IP地址的技術,而無需依賴中心化的服務器(如DHCP服務器)或手動靜態配置
需要使用autoip,需要在opt.h頭文件當中使能LWIP_AUTOIP這個宏,通常也會使能LWIP_DHCP_AUTOIP_COOP這個宏
/*
------------------------------------
---------- AUTOIP options ----------
------------------------------------
*/
/**
* @defgroup lwip_opts_autoip AUTOIP
* @ingroup lwip_opts_ipv4
* @{
*/
/**
* LWIP_AUTOIP==1: Enable AUTOIP module.
*/
#if !defined LWIP_AUTOIP || defined __DOXYGEN__
#define LWIP_AUTOIP 0
#endif
#if !LWIP_IPV4
/* disable AUTOIP when IPv4 is disabled */
#undef LWIP_AUTOIP
#define LWIP_AUTOIP 0
#endif /* !LWIP_IPV4 */
/**
* LWIP_DHCP_AUTOIP_COOP==1: Allow DHCP and AUTOIP to be both enabled on
* the same interface at the same time.
*/
#if !defined LWIP_DHCP_AUTOIP_COOP || defined __DOXYGEN__
#define LWIP_DHCP_AUTOIP_COOP 0
#endif
在使用netif_init初始化網絡接口的的時候,在調用netif_add之後,如果有啓用DHCP和auto IP功能,lwIP 內核就會同時啓動DHCP和AutoIP客户端(如果DHCP失敗),lwip遵循先DHCP再autoIP的原則,首先使用DHCP去獲取IP,如果有超時之類的情況導致獲取失敗,則會啓動autoIP功能。
一旦AutoIP啓動,lwIP會隨機地從鏈路本地地址塊 169.254.1.0 到 169.254.254.255 中選擇一個地址作為候選地址。可以通過查看IP是169.254.x.x還是192.168.x.x來判斷使用了DHCP還是autoIP。
為了避免候選地址衝突,autoIP會使用發送arp協議來完成地址衝突檢測,在autoip_start調用autoip_start_probing之後,會調用autoip_arp_probe來發送一個arp_request來判斷隨機到的IP是否衝突(request的源IP為0.0.0.0),在發送完成之後會等待看是否有arp_reply,如果有説明該IP已被使用,如果沒有表示未發生衝突,收到reply後通過autoip_arp_reply去處理
PROBING 狀態下的衝突判斷: 如果收到的 ARP 包中,源IP地址 (sipaddr) 等於我們正在探測的地址 (llipaddr),或者目標IP地址 (dipaddr) 等於 llipaddr 且源MAC地址不是自己的,都視為衝突。
ANNOUNCING/BOUND 狀態下的衝突判斷*: 如果收到的 ARP 包中,源IP地址等於我們已綁定的 llipaddr,但源MAC地址不是自己的,説明有另一個設備也使用了這個地址,需要處理衝突。
autoip_arp_reply的調用是由etharp_input觸發的,那麼數據是如何從底層到上層的?etharp_input是由ethernet_input中調用
網卡驅動(屬於底層硬件抽象層)在收到數據後,必須主動地將收到的數據包注入到 lwIP 協議棧中。這個注入的入口點,通常就是 ethernet_input() 函數。
而lwip提供了一個標準的接口給用於給上層傳輸數據netif->input(),這個回調函數在網絡接口初始化netif_init的時候通過netif_add註冊
lwip_init - -- netif_init --- netif_add --- netif_input
當底層網卡驅動收到數據之後,通過DMA的方式將數據放到一個緩衝區當中,當數據存入之後,網卡硬件會向MCU發出一個硬件中斷,之後MCU響應中斷,找到驅動對應的中斷服務程序(ISR),在中斷服務程序當中將數據傳入到隊列當中,通過任務或者信號量機制通知上層的任務來處理數據,這個任務會調用netif_input()即ethernet_input()去將數據處理。
/*
* Handles every incoming ARP Packet, called by etharp_input().
*
* @param netif network interface to use for autoip processing
* @param hdr Incoming ARP packet
*/
void
autoip_arp_reply(struct netif *netif, struct etharp_hdr *hdr)
{
struct autoip *autoip = netif_autoip_data(netif);
LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE, ("autoip_arp_reply()\n"));
if ((autoip != NULL) && (autoip->state != AUTOIP_STATE_OFF)) {
/* when ip.src == llipaddr && hw.src != netif->hwaddr
*
* when probing ip.dst == llipaddr && hw.src != netif->hwaddr
* we have a conflict and must solve it
*/
ip4_addr_t sipaddr, dipaddr;
struct eth_addr netifaddr;
SMEMCPY(netifaddr.addr, netif->hwaddr, ETH_HWADDR_LEN);
/* Copy struct ip4_addr_wordaligned to aligned ip4_addr, to support compilers without
* structure packing (not using structure copy which breaks strict-aliasing rules).
*/
IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&sipaddr, &hdr->sipaddr);
IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&dipaddr, &hdr->dipaddr);
if (autoip->state == AUTOIP_STATE_PROBING) {
/* RFC 3927 Section 2.2.1:
* from beginning to after ANNOUNCE_WAIT
* seconds we have a conflict if
* ip.src == llipaddr OR
* ip.dst == llipaddr && hw.src != own hwaddr
*/
if ((ip4_addr_cmp(&sipaddr, &autoip->llipaddr)) ||
(ip4_addr_isany_val(sipaddr) &&
ip4_addr_cmp(&dipaddr, &autoip->llipaddr) &&
!eth_addr_cmp(&netifaddr, &hdr->shwaddr))) {
LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE | LWIP_DBG_LEVEL_WARNING,
("autoip_arp_reply(): Probe Conflict detected\n"));
autoip_restart(netif);
}
} else {
/* RFC 3927 Section 2.5:
* in any state we have a conflict if
* ip.src == llipaddr && hw.src != own hwaddr
*/
if (ip4_addr_cmp(&sipaddr, &autoip->llipaddr) &&
!eth_addr_cmp(&netifaddr, &hdr->shwaddr)) {
LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE | LWIP_DBG_LEVEL_WARNING,
("autoip_arp_reply(): Conflicting ARP-Packet detected\n"));
autoip_handle_arp_conflict(netif);
}
}
}
}
發生衝突後需要處理衝突,使用autoip_handle_arp_conflict函數去處理衝突,lwip實現的plan B
從下面代碼可以看出在首次衝突的時候不撤退,採用defend防禦策略,將lastconflict置為DEFEND_INTERVAL * AUTOIP_TICKS_PER_SECOND,在倒計時期間(lastconflict>0)如果再發生衝突則防禦失敗,程序會選擇撤退(autoip_restart)並換一個地址重新開始。倒計時結束都未發送衝突則防禦成功,lastconflict為0,lastconflict在 autoip_tmr() 中每100毫秒遞減,防禦成功或失敗後重置
/**
* Handle a IP address conflict after an ARP conflict detection
*/
static void
autoip_handle_arp_conflict(struct netif *netif)
{
struct autoip *autoip = netif_autoip_data(netif);
/* RFC3927, 2.5 "Conflict Detection and Defense" allows two options where
a) means retreat on the first conflict and
b) allows to keep an already configured address when having only one
conflict in 10 seconds
We use option b) since it helps to improve the chance that one of the two
conflicting hosts may be able to retain its address. */
if (autoip->lastconflict > 0) {
/* retreat, there was a conflicting ARP in the last DEFEND_INTERVAL seconds */
LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("autoip_handle_arp_conflict(): we are defending, but in DEFEND_INTERVAL, retreating\n"));
/* Active TCP sessions are aborted when removing the ip addresss */
autoip_restart(netif);
} else {
LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("autoip_handle_arp_conflict(): we are defend, send ARP Announce\n"));
autoip_arp_announce(netif);
autoip->lastconflict = DEFEND_INTERVAL * AUTOIP_TICKS_PER_SECOND;
}
}
如未發生衝突,之後會進行 autoip_arp_announce發送無償arp。目的是為了正式向網絡中宣告“這個 IP 地址現在屬於這個 MAC 地址”,刷新其他設備的 ARP 緩存,並作為最後一道衝突檢測的防線。
先介紹autoip的核心結構體
/** AutoIP state information per netif */
struct autoip
{
/** the currently selected, probed, announced or used LL IP-Address */
// 當前autoIP模塊選擇、探測、宣告的邏輯鏈路層的IP地址
ip4_addr_t llipaddr;
/** current AutoIP state machine state */
// 當前autoIP狀態機的狀態
u8_t state;
/** sent number of probes or announces, dependent on state */
// 記錄當前狀態下發送的報文次數
u8_t sent_num;
/** ticks to wait, tick is AUTOIP_TMR_INTERVAL long */
// Time To Wait 的縮寫,是一個倒計時器,表示距離下一次操作還需要等待多少個定時器週期
u16_t ttw;
/** ticks until a conflict can be solved by defending */
// 衝突防禦倒計時器。用於記錄在一次衝突發生後,進入防禦狀態的剩餘時間
u8_t lastconflict;
/** total number of probed/used Link Local IP-Addresses */
// 記錄已經嘗試過的鏈路本地地址的總數
u8_t tried_llipaddr;
};
autoIP的核心是autoip_tmr狀態機,狀態機函數
34行當TTW到0的時候需要進行下一步操作,此時判斷髮送probe的次數是否到達PROBE_NUM次(通常為3),如果到達三次,表示已發送足夠數量的Probe,未檢測到衝突。切換狀態為AUTOIP_STATE_
ANNOUNCING並將候選地址 (autoip->llipaddr) 正式設置到網絡接口上 (netif->ip_addr)。此時,設備已經可以使用這個 IP 地址了。下面的else則表示發送次數還不夠。
65行進入AUTOIP_STATE_ANNOUNCING狀態,在這個狀態,地址已被綁定,設備通過發送 ARP Announcement 來向網絡正式宣告它的存在,當發送次數達到ANNOUNCE_NUM後切換狀態為AUTOIP_STATE_BOUND
void
autoip_tmr(void)
{
struct netif *netif;
/* loop through netif's */
NETIF_FOREACH(netif) {
struct autoip *autoip = netif_autoip_data(netif);
/* only act on AutoIP configured interfaces */
if (autoip != NULL) {
if (autoip->lastconflict > 0) {
autoip->lastconflict--;
}
LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE,
("autoip_tmr() AutoIP-State: %"U16_F", ttw=%"U16_F"\n",
(u16_t)(autoip->state), autoip->ttw));
if (autoip->ttw > 0) {
autoip->ttw--;
}
switch (autoip->state) {
case AUTOIP_STATE_PROBING:
if (autoip->ttw == 0) {
if (autoip->sent_num >= PROBE_NUM) {
/* Switch to ANNOUNCING: now we can bind to an IP address and use it */
autoip->state = AUTOIP_STATE_ANNOUNCING;
autoip_bind(netif);
/* autoip_bind() calls netif_set_addr(): this triggers a gratuitous ARP
which counts as an announcement */
autoip->sent_num = 1;
autoip->ttw = ANNOUNCE_WAIT * AUTOIP_TICKS_PER_SECOND;
LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("autoip_tmr(): changing state to ANNOUNCING: %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
ip4_addr1_16(&autoip->llipaddr), ip4_addr2_16(&autoip->llipaddr),
ip4_addr3_16(&autoip->llipaddr), ip4_addr4_16(&autoip->llipaddr)));
} else {
autoip_arp_probe(netif);
LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE, ("autoip_tmr() PROBING Sent Probe\n"));
autoip->sent_num++;
if (autoip->sent_num == PROBE_NUM) {
/* calculate time to wait to for announce */
autoip->ttw = ANNOUNCE_WAIT * AUTOIP_TICKS_PER_SECOND;
} else {
/* calculate time to wait to next probe */
autoip->ttw = (u16_t)((LWIP_AUTOIP_RAND(netif) %
((PROBE_MAX - PROBE_MIN) * AUTOIP_TICKS_PER_SECOND) ) +
PROBE_MIN * AUTOIP_TICKS_PER_SECOND);
}
}
}
break;
case AUTOIP_STATE_ANNOUNCING:
if (autoip->ttw == 0) {
autoip_arp_announce(netif);
LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE, ("autoip_tmr() ANNOUNCING Sent Announce\n"));
autoip->ttw = ANNOUNCE_INTERVAL * AUTOIP_TICKS_PER_SECOND;
autoip->sent_num++;
if (autoip->sent_num >= ANNOUNCE_NUM) {
autoip->state = AUTOIP_STATE_BOUND;
autoip->sent_num = 0;
autoip->ttw = 0;
LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
("autoip_tmr(): changing state to BOUND: %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
ip4_addr1_16(&autoip->llipaddr), ip4_addr2_16(&autoip->llipaddr),
ip4_addr3_16(&autoip->llipaddr), ip4_addr4_16(&autoip->llipaddr)));
}
}
break;
default:
/* nothing to do in other states */
break;
}
}
}
}