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;
- }
- }
- }
- }
复制代码 来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |