目录

前言

主要简述TCPIP协议族相关的。

ARP协议源码在etharp.c和etharp.h中,也是本次笔记的主要内容。

ARP源码实现的重要数据结构:

  • ARP缓存表。
  • ARP报文。

原文:李柱明博客

8.1 IP地址与MAC地址

TCP/IP协议的网络层有自己的IP地址。

单看网络层,传输数据包时只需要知道目标主机的IP地址即可。

但是网络层数据包下传到链路层时,链路层需要知道下一个节点的MAC地址,才能发包。

为了实现网络层对MAC地址无感,又能实现数据包收发,就需要把IP地址和MAC地址绑定。

一个网卡,有IP地址,而网卡对接物理设备时,物理设备有MAC地址,可以把IP地址和网卡设备MAC地址绑定。

而有时候,IP地址可能是动态的,即是当前网卡设备根据需求被赋予不同的IP,所以IP地址与MAC地址映射也需要动态才能更好地把网络层和链路层分割。

8.2 ARP协议简介

地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。

主机A知道主机B的ip地址,但是在二层链路,也就是数据链路层,是通过mac地址进行转发的,通过ARP协议实现IP和MAC地址绑定。

ARP协议有静态获取和动态获取:

  • 静态获取:即是手动配置ARP映射表。
  • 动态获取:主机通过ARP协议主机获取、主机维护ARP映射表。

8.3 ARP协议报文

ARP请求和应答分组的格式如图:

以太网首部:

  • 目的MAC(6):链路层的数据帧的下一个目标结点设备的MAC。当携带ARP请求报文时,以太网目的地址MAC为广播地址:0xFFFFFF。
  • 源MAC(6):当前设备的MAC。
  • 帧类型(2):为0x0806时,表示ARP报文。

ARP报文:

  • 硬件类型(2):硬件地址的类型。

    • 为1即表示以太网地址。
    • 其它还能表示令牌环地址等。
  • 协议类型(2):表示硬件地址要映射的协议地址类型。

    • 0x0800表示IP地址。
    • 其它还能表示ICMP/IGMP等。
  • 硬件地址长度(1):硬件地址的长度,以字节为单位。

    • 以太网上IP地址的ARP请求或应答:该字段为MAC地址的长度,6。
  • 协议地址长度(1):

    • 以太网上IP地址的ARP请求或应答:该字段为IP地址长度,4。
  • OP字段(2):操作字段。

    • 1:ARP请求。
    • 2:ARP应答。
    • 3:RARP请求。
    • 4:RARP应答。
  • 发送端以太网地址(6)。

  • 发送端IP地址(4)。

  • 目的以太网地址(6)。

  • 目的IP地址(4)。

小笔记:

  • 看到上述的报文简述后,会发现以太网的数据帧首部和ARP请求数据报中都有发送端的硬件地址。这个很正常,因为两者所处的OSI层级不一样,前者是链路层处理的数据帧头需要的硬件地址。后者是网络层处理的ARP请求数据报中的重要信息。
  • ARP请求有些信息会留空,如ARP请求时,目的端的硬件地址不需要填充。而协议中保留,是为了实现ARP请求和ARP应答的报文字段一致。

ARP简要交互:

对于一个ARP请求来说,除目的端硬件地址外的所有其他的字段都有填充值。

当系统收到一份目的端为本机的ARP请求报文后,它就把硬件地址填进去,然后用两个目的端地址分别替换两个发送端地址,并把操作字段置为2(ARP应答),最后把它发送回去。

ARP交互报文例子图,wireshark分析:

8.4 ARP缓存表

8.4.1 ARP缓存表简介

每台主机或路由器在其内存中具有一个ARP缓存表(ARP table),这张表包含IP地址到MAC地址的映射关系。

网络层的IP数据包需要经过链路层转发时,可以直接查询缓存表是否有这个IP映射的MAC。

如果有,目标链路层数据帧的目标MAC就直接使用这个MAC,就能转发了。

如果没有,通过ARP协议,往链路层局域网内广播一下,询问下有没有这个IP对应的结点设备,如果有就把这个IP对应的链路层设备的MAC返回到这个主机,然后这个主机的链路层使用这个MAC发送数据帧出去。而且可以把这个MAC及其对应的IP保存到自己的ARP缓存表中,方便下次直接使用。当然,这个映射也有过期检查,需要超时机制维护。

8.4.2 LWIP中的缓存表

lwip的缓存表:static struct etharp_entry arp_table[ARP_TABLE_SIZE];

ARP_TABLE_SIZE默认为10,即是默认能缓存10条ARP映射记录。

8.4.3 ARP缓存表数据结构

struct etharp_entry

struct etharp_entry {
#if ARP_QUEUEING
/* 指向此ARP表项上挂起的数据包队列的指针. */
struct etharp_q_entry *q;
#else /* ARP_QUEUEING */
/* 指向此ARP表项上的单个挂起的数据包队列的指针 */
struct pbuf *q;
#endif /* ARP_QUEUEING */
/* 目标IP地址 */
ip4_addr_t ipaddr;
/* 当前ARP映射记录对应网卡信息 */
struct netif *netif;
/* 目标IP对应的MAC地址 */
struct eth_addr ethaddr;
/* 当前netry的生存时间 */
u16_t ctime;
/* 当前netry的状态信息 */
u8_t state;
};

8.4.4 ARP缓存表数据缓冲队列

struct etharp_q_entry *q;

这个字段用于指向缓存表的数据包缓冲队列。

在IP层发送一个数据包时,会先在ARP映射表中查找与目的IP地址对应的MAC地址,这样才能封装以太网帧,才能在链路层把数据包发送出去。

但是如果IP层发送一个数据包时,在ARP映射表中查不到对应的硬件地址MAC,就发送一个ARP请求包,在请求过程中,把这个IP层的数据包缓存到这个队列q先,直到请求成功后,获取这个IP层数据包IP对应的MAC地址或请求失败为止。

对于PBUFF_ERFPBUF_POOLPBUF_RAM类型的数据包是不允许直接挂到ARP entry的挂起缓存队列上的,因为内核等待目标主机的ARP应答期间,这些数据有可能会被上层改动,所以LwIP需要将这些pbuf数据包拷贝到新的空间,等待发送。

这个队列的数据结构:

  • 在memp.h的MEMP_ARP_QUEUE内存池中有这个数据结构的内存资源。共有MEMP_NUM_ARP_QUEUE个,默认为30个。
#if ARP_QUEUEING
struct etharp_q_entry {
struct etharp_q_entry *next; /* 下一个节点 */
struct pbuf *p; /* pbuf */
};
#endif /* ARP_QUEUEING */

8.4.5 ARP缓存表entry状态信息

u8_t state;

/** ARP states */
enum etharp_state {
ETHARP_STATE_EMPTY = 0, /* 空闲态 */
ETHARP_STATE_PENDING, /* pending态 */
ETHARP_STATE_STABLE, /* 有效态 */
ETHARP_STATE_STABLE_REREQUESTING_1, /* 有效过渡态1 */
ETHARP_STATE_STABLE_REREQUESTING_2 /* 有效过渡态2 */
#if ETHARP_SUPPORT_STATIC_ENTRIES
, ETHARP_STATE_STATIC /* 静态entry */
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
};

当前netry的状态信息:

  • ETHARP_STATE_EMPTY:空状态。当前entry资源无效,可以被填充使用。
  • ETHARP_STATE_PENDING:PENDING态。当前entry正在ARP请求,但是还没收到ARP响应。
  • ETHARP_STATE_STABLE:有效态。当前entry记录的IP地址与MAC地址映射有效。
  • ETHARP_STATE_STABLE_REREQUESTING_1:有效过渡态1。就是为了防止entry块过期前频繁发起ARP请求。
  • ETHARP_STATE_STABLE_REREQUESTING_2:有效过渡态2。
  • ETHARP_STATE_STATIC:静态条目。手动配置的ARP映射,一直有效。

当表项是ETHARP_STATE_STABLE的时候又发送一个ARP请求包,那么表项状态会暂时被设置为THARP_STATE_STABLE_REREQUESTING_1,然后被设置为ETHARP_STATE_STABLE_REREQUESTING_2状态,这些是一个过渡状态,当收到ARP应答后,表项又会被设置为ETHARP_STATE_STABLE,这样能保持表项的有效。

比如,每个IP层的数据包都会先遍历ARP缓存表,如果找到有效的条目后,直接使用该条目,然后会继续调用etharp_output_to_arp_index()把数据发送出去,源码如下:

  • 在发送IP包时检查当前被使用的ARP entry是否块过期,如果快过期,需要发起ARP请求更新当前条目,更新有两种级别:

    • ARP_AGE_REREQUEST_USED_UNICAST:默认在条目超时前30秒,发起单播级别的ARP请求,减少不必要的广播。
    • ARP_AGE_REREQUEST_USED_BROADCAST:默认在条目超时前15秒,发起广播级别的ARP请求。
/* 为避免由于ARP表项超时导致稳定使用的连接中断,需要在ARP表项过期前重新请求,更新ARP缓存表 */
#define ARP_AGE_REREQUEST_USED_UNICAST (ARP_MAXAGE - 30)
#define ARP_AGE_REREQUEST_USED_BROADCAST (ARP_MAXAGE - 15) /* Just a small helper function that sends a pbuf to an ethernet address in the arp_table specified by the index 'arp_idx'. */
static err_t
etharp_output_to_arp_index(struct netif *netif, struct pbuf *q, netif_addr_idx_t arp_idx)
{
LWIP_ASSERT("arp_table[arp_idx].state >= ETHARP_STATE_STABLE",
arp_table[arp_idx].state >= ETHARP_STATE_STABLE);
/* 在entry快过期前,发起ARP请求进行更新。
为了防止在这段时间频繁发起ARP请求,所以引入有效过渡态。
这里为有效态才能发起ARP请求。 */
if (arp_table[arp_idx].state == ETHARP_STATE_STABLE) {
if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_BROADCAST) {
/* 使用广播级别(过期前15秒),发起标准ARP请求 */
if (etharp_request(netif, &arp_table[arp_idx].ipaddr) == ERR_OK) {
arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1; /* 更新为有效过渡态1 */
}
} else if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_UNICAST) {
/* 发出单播ARP请求(过期前30秒),以防止不必要的广播 */
if (etharp_request_dst(netif, &arp_table[arp_idx].ipaddr, &arp_table[arp_idx].ethaddr) == ERR_OK) {
arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1; /* 更新为有效过渡态1 */
}
}
}
/* IP层发送数据 */
return ethernet_output(netif, q, (struct eth_addr *)(netif->hwaddr), &arp_table[arp_idx].ethaddr, ETHTYPE_IP);
}

8.4.6 ARP缓存表超时处理

ARP缓存表每条映射记录都是有有效期的(静态除外),在struct etharp_entryu16_t ctime;这个字段中记录这点钱entry的生存时间。

ARP超时处理函数是etharp_tmr(),是一个周期定时函数。

相关宏:

ARP_TMR_INTERVAL:该函数的节拍为ARP_TMR_INTERVAL,默认为1000,即是1秒跑一次。

ARP_MAXAGE:用于限制ARP条目最大生存时间,默认为300,且节拍为1秒,所以ARP的entry默认最大生存时间是5分钟。

ARP_MAXPENDING:限制ARP请求响应超时,也是重发ARP请求的次数,默认为5,且节拍为1秒,所以ARP请求响应默认超时为5秒。

/**
* 清除ARP表中过期的表项
* 该函数应该每隔ARP_TMR_INTERVAL毫秒(1秒)调用一次,以使ARP表项过期
*/
void
etharp_tmr(void)
{
int i; LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_timer\n"));
/* 遍历、删除ARP表中过期的表项 */
for (i = 0; i < ARP_TABLE_SIZE; ++i) {
u8_t state = arp_table[i].state;
if (state != ETHARP_STATE_EMPTY /* 跳过空闲态的entry */
#if ETHARP_SUPPORT_STATIC_ENTRIES
&& (state != ETHARP_STATE_STATIC) /* 跳过静态的entry */
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
) {
arp_table[i].ctime++; /* 记录当前entry的生存时间 */
if ((arp_table[i].ctime >= ARP_MAXAGE) ||
((arp_table[i].state == ETHARP_STATE_PENDING) &&
(arp_table[i].ctime >= ARP_MAXPENDING))) { /* entry生存时间超时或者ARP请求超时都要清空本entry */
/* pending or stable entry has become old! */
LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_timer: expired %s entry %d.\n",
arp_table[i].state >= ETHARP_STATE_STABLE ? "stable" : "pending", i));
/* 清理刚刚过期的条目 */
etharp_free_entry(i);
} else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING_1) {
/* 过渡态1更新到过渡态2,为了防止上层在两秒能发起多次ARP更新请求 */
arp_table[i].state = ETHARP_STATE_STABLE_REREQUESTING_2;
} else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING_2) {
/* 恢复到有效态。允许ARP */
arp_table[i].state = ETHARP_STATE_STABLE;
} else if (arp_table[i].state == ETHARP_STATE_PENDING) {
/* 还没收到ARP响应,重发ARP请求 */
etharp_request(arp_table[i].netif, &arp_table[i].ipaddr);
}
}
}
}

8.4.7 ARP缓存表entry更新

能触发ARP缓存表entry更新的情况:

  1. ARP超时处理。把过期或者ARP请求超时的arp entry删除。

  2. IP层发送数据。经过ARP协议时:

    • arp entry更新:在对应arp entry快过期前发起ARP请求(更新arp entry)。
    • arp entry新建:ARP缓存表中没有找到对应entry时,新建一个。

新建arp entry逻辑:

调用etharp_find_entry()函数获取一个entry。

该函数的参数u8_t flags;表示申请arp entry资源的方式:

  • ETHARP_FLAG_TRY_HARD:允许覆盖已有arp entry。

    • 在需要新建arp entry,且没有发现空闲的arp entry时,强制覆盖一条。优先被覆盖的顺序:empty entry > oldest stable entry > oldest pending entry without queued packets > oldest pending entry with queued packets
  • ETHARP_FLAG_FIND_ONLY:只读模式。如果ARP缓存表中没有匹配IP(和网卡)的arp entry,则返回失败,数据包处理终止。

8.5 ARP协议超时机制框图

8.6 ARP收发报文数据流图

8.7 ARP报文组包源码实现

8.7.1 ARP报文数据结构

/** the ARP message, see RFC 826 ("Packet format") */
struct etharp_hdr {
PACK_STRUCT_FIELD(u16_t hwtype); /* 硬件类型 */
PACK_STRUCT_FIELD(u16_t proto); /* 协议类型 */
PACK_STRUCT_FLD_8(u8_t hwlen); /* 硬件地址长度 */
PACK_STRUCT_FLD_8(u8_t protolen); /* 协议地址长度 */
PACK_STRUCT_FIELD(u16_t opcode); /* 操作字段 */
PACK_STRUCT_FLD_S(struct eth_addr shwaddr); /* 源硬件地址 */
PACK_STRUCT_FLD_S(struct ip4_addr_wordaligned sipaddr); /* 源协议地址 */
PACK_STRUCT_FLD_S(struct eth_addr dhwaddr); /* 目标硬件地址 */
PACK_STRUCT_FLD_S(struct ip4_addr_wordaligned dipaddr); /* 目标协议地址 */
} PACK_STRUCT_STRUCT;

8.7.2 ARP报文组建发送函数(基函数)

ARP请求包是通过etharp_raw()函数进行组包和发送的,然后通过封装该函数得出不同需求的ARP请求函数供给上层使用。

/**
* Send a raw ARP packet (opcode and all addresses can be modified)
*
* @param netif the lwip network interface on which to send the ARP packet
* @param ethsrc_addr the source MAC address for the ethernet header
* @param ethdst_addr the destination MAC address for the ethernet header
* @param hwsrc_addr the source MAC address for the ARP protocol header
* @param ipsrc_addr the source IP address for the ARP protocol header
* @param hwdst_addr the destination MAC address for the ARP protocol header
* @param ipdst_addr the destination IP address for the ARP protocol header
* @param opcode the type of the ARP packet
* @return ERR_OK if the ARP packet has been sent
* ERR_MEM if the ARP packet couldn't be allocated
* any other err_t on failure
*/
static err_t
etharp_raw(struct netif *netif, const struct eth_addr *ethsrc_addr,
const struct eth_addr *ethdst_addr,
const struct eth_addr *hwsrc_addr, const ip4_addr_t *ipsrc_addr,
const struct eth_addr *hwdst_addr, const ip4_addr_t *ipdst_addr,
const u16_t opcode)
{
struct pbuf *p;
err_t result = ERR_OK;
struct etharp_hdr *hdr; LWIP_ASSERT("netif != NULL", netif != NULL); /* 链路层组包,为ARP报文申请内存资源 */
p = pbuf_alloc(PBUF_LINK, SIZEOF_ETHARP_HDR, PBUF_RAM);
if (p == NULL) { /* 内存资源申请失败 */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
("etharp_raw: could not allocate pbuf for ARP request.\n"));
ETHARP_STATS_INC(etharp.memerr);
return ERR_MEM;
}
LWIP_ASSERT("check that first pbuf can hold struct etharp_hdr",
(p->len >= SIZEOF_ETHARP_HDR)); hdr = (struct etharp_hdr *)p->payload;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_raw: sending raw ARP packet.\n"));
hdr->opcode = lwip_htons(opcode); /* 操作字段 */ LWIP_ASSERT("netif->hwaddr_len must be the same as ETH_HWADDR_LEN for etharp!",
(netif->hwaddr_len == ETH_HWADDR_LEN)); SMEMCPY(&hdr->shwaddr, hwsrc_addr, ETH_HWADDR_LEN); /* 源MAC字段 */
SMEMCPY(&hdr->dhwaddr, hwdst_addr, ETH_HWADDR_LEN); /* 目标MAC字段 */
IPADDR_WORDALIGNED_COPY_FROM_IP4_ADDR_T(&hdr->sipaddr, ipsrc_addr); /* 源IP字段 */
IPADDR_WORDALIGNED_COPY_FROM_IP4_ADDR_T(&hdr->dipaddr, ipdst_addr); /* 目标IP字段 */ hdr->hwtype = PP_HTONS(LWIP_IANA_HWTYPE_ETHERNET); /* 硬件类型 */
hdr->proto = PP_HTONS(ETHTYPE_IP); /* 协议类型 */
hdr->hwlen = ETH_HWADDR_LEN; /* 硬件地址长度 */
hdr->protolen = sizeof(ip4_addr_t); /* 协议地址长度 */ /* 发送ARP包 */ #if LWIP_AUTOIP
/* 如果我们源IP为本地链路的IP,说明本ARP报文为ARP探测包,用于检查当前链路这个目标IP是否被占用了。所以需要用以太网帧的目标MAC需要设置为广播MAC(参见RFC3927第2.5节最后一段)*/
if (ip4_addr_islinklocal(ipsrc_addr)) {
ethernet_output(netif, p, ethsrc_addr, &ethbroadcast, ETHTYPE_ARP); /* 发送以太网帧 */
} else
#endif /* LWIP_AUTOIP */
{ /* 非ARP探测包,可按指定MAC封装以太网首部的目标MAC */
ethernet_output(netif, p, ethsrc_addr, ethdst_addr, ETHTYPE_ARP); /* 发送以太网帧 */
}
/* lwip状态记录 */
ETHARP_STATS_INC(etharp.xmit);
/* 释放当前ARP报文资源 */
pbuf_free(p);
p = NULL;
return result;
}

8.7.3 发送ARP请求包

etharp_request_dst()

  • 发起ARP请求。
  • 可指定目标MAC。
/**
* Send an ARP request packet asking for ipaddr to a specific eth address.
* Used to send unicast request to refresh the ARP table just before an entry times out.
*
* @param netif the lwip network interface on which to send the request
* @param ipaddr the IP address for which to ask
* @param hw_dst_addr the ethernet address to send this packet to
* @return ERR_OK if the request has been sent
* ERR_MEM if the ARP packet couldn't be allocated
* any other err_t on failure
*/
static err_t
etharp_request_dst(struct netif *netif, const ip4_addr_t *ipaddr, const struct eth_addr *hw_dst_addr)
{
return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, hw_dst_addr,
(struct eth_addr *)netif->hwaddr, netif_ip4_addr(netif), &ethzero,
ipaddr, ARP_REQUEST); /* ARP请求 */
}

etharp_request_dst()

  • 发起ARP标准请求包。(广播包)
/**
* Send an ARP request packet asking for ipaddr.
*
* @param netif the lwip network interface on which to send the request
* @param ipaddr the IP address for which to ask
* @return ERR_OK if the request has been sent
* ERR_MEM if the ARP packet couldn't be allocated
* any other err_t on failure
*/
err_t
etharp_request(struct netif *netif, const ip4_addr_t *ipaddr)
{
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_request: sending ARP request.\n"));
return etharp_request_dst(netif, ipaddr, &ethbroadcast);
}

8.7.4 发送ARP IP探测包

etharp_acd_probe()

  • 发起ARP IP探测。
  • 用于发送探测消息进行地址冲突检测。
/**
* Send an ARP request packet probing for an ipaddr.
* Used to send probe messages for address conflict detection.
*
* @param netif the lwip network interface on which to send the request
* @param ipaddr the IP address to probe
* @return ERR_OK if the request has been sent
* ERR_MEM if the ARP packet couldn't be allocated
* any other err_t on failure
*/
err_t
etharp_acd_probe(struct netif *netif, const ip4_addr_t *ipaddr)
{
return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, &ethbroadcast,
(struct eth_addr *)netif->hwaddr, IP4_ADDR_ANY4, &ethzero,
ipaddr, ARP_REQUEST);
}

8.7.5 发送ARP IP宣告包

etharp_acd_announce()

  • 发起ARP IP宣告。
  • 用于发送地址冲突检测的公告消息。
/**
* Send an ARP request packet announcing an ipaddr.
* Used to send announce messages for address conflict detection.
*
* @param netif the lwip network interface on which to send the request
* @param ipaddr the IP address to announce
* @return ERR_OK if the request has been sent
* ERR_MEM if the ARP packet couldn't be allocated
* any other err_t on failure
*/
err_t
etharp_acd_announce(struct netif *netif, const ip4_addr_t *ipaddr)
{
return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, &ethbroadcast,
(struct eth_addr *)netif->hwaddr, ipaddr, &ethzero,
ipaddr, ARP_REQUEST);
}

8.8 数据包发送分析

8.8.1 数据发包处理简述(ARP相关)

主要分析ARP协议层的处理。

IP层数据包通过ip4_output()函数传递到ARP协议处理。通过ARP协议获得目标IP主机的MAC地址才能封装以太网帧,在链路层转发。

etharp_output()收到IP层发来的数据包后按一下逻辑分支处理:

  • 对于广播或者多播的数据包:调用ethernet_output()函数直接把数据包丢给网卡即可。

    • MAC的多播地址范围:01:00:5E:00:00:00 —— 01:00:5E:7F:FF:FF
  • 对于单播数据包:

    • 遍历ARP缓存表:遍历时,可以从当前网卡上次发送数据包使用的arp entry开始查起,找到就调用etharp_output_to_arp_index()把IP数据包转交给链路层转发。

      • etharp_output_to_arp_index()概述里面会更新维护ARP缓存表当前arp entry。
    • 发起ARP请求:如果缓存表中没有当前IP数据包目标IP映射的MAC地址,就需要调用etharp_query(),把IP数据包转交给ARP协议处理。

      • etharp_query()会发起ARP请求,在ARP请求过程中,把这些IP层的数据包保存到当前ARP条目的entry的挂起缓存队列中。直到收到ARP响应或者ARP请求超时为止。

对于PBUFF_ERFPBUF_POOLPBUF_RAM类型的数据包是不允许直接挂到ARP entry的挂起缓存队列上的,因为内核等待目标主机的ARP应答期间,这些数据有可能会被上层改动,所以LwIP需要将这些pbuf数据包拷贝到新的空间,等待发送。

8.8.2 etharp_output():IP数据包是否ARP协议处理

解析和填写以太网地址头为传出IP数据包。

/**
* Resolve and fill-in Ethernet address header for outgoing IP packet.
*
* For IP multicast and broadcast, corresponding Ethernet addresses are selected and the packet is transmitted on the link.
*
* For unicast addresses, the packet is submitted to etharp_query(). In case the IP address is outside the local network, the IP address of the gateway is used.
*
* @param netif The lwIP network interface which the IP packet will be sent on.
* @param q The pbuf(s) containing the IP packet to be sent.
* @param ipaddr The IP address of the packet destination.
*
* @return
* - ERR_RTE No route to destination (no gateway to external networks),
* or the return type of either etharp_query() or ethernet_output().
*/
err_t
etharp_output(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr)
{
const struct eth_addr *dest;
struct eth_addr mcastaddr;
const ip4_addr_t *dst_addr = ipaddr;
/* tcpip内核锁上锁检查 */
LWIP_ASSERT_CORE_LOCKED();
/* 参数校验 */
LWIP_ASSERT("netif != NULL", netif != NULL);
LWIP_ASSERT("q != NULL", q != NULL);
LWIP_ASSERT("ipaddr != NULL", ipaddr != NULL); if (ip4_addr_isbroadcast(ipaddr, netif)) {
/* 目标IP为广播地址 */
/* 目标MAC也设置为广播地址:FF-FF-FF-FF-FF-FF-FF */
dest = (const struct eth_addr *)&ethbroadcast;
} else if (ip4_addr_ismulticast(ipaddr)) {
/* 目标IP为多播地址 */
/* 目标MAC也设置为多播地址:01:00:5E:00:00:00 —— 01:00:5E:7F:FF:FF */
mcastaddr.addr[0] = LL_IP4_MULTICAST_ADDR_0;
mcastaddr.addr[1] = LL_IP4_MULTICAST_ADDR_1;
mcastaddr.addr[2] = LL_IP4_MULTICAST_ADDR_2;
mcastaddr.addr[3] = ip4_addr2(ipaddr) & 0x7f;
mcastaddr.addr[4] = ip4_addr3(ipaddr);
mcastaddr.addr[5] = ip4_addr4(ipaddr);
dest = &mcastaddr;
} else { /* 目标IP为单播地址 */
netif_addr_idx_t i;
/* 判断目标IP是否和原IP主机处于同一个子网上,如果不是,则修改要查找MAC的的IP为当前网卡的网关IP */
if (!ip4_addr_net_eq(ipaddr, netif_ip4_addr(netif), netif_ip4_netmask(netif)) &&
!ip4_addr_islinklocal(ipaddr)) { /* 不是同一个子网,也不是本地链路地址,需要把数据包转发到网关 */
#if LWIP_AUTOIP
struct ip_hdr *iphdr = LWIP_ALIGNMENT_CAST(struct ip_hdr *, q->payload);
/* 根据RFC 3297 2.6.2章(转发规则),如果IP地址为本地链路地址(169.254.0.0/16),这样的IP数据包是不能通过路由器转发的 */
if (!ip4_addr_islinklocal(&iphdr->src)) /* 源IP地址不是本地链路地址 */
#endif /* LWIP_AUTOIP */
{
#ifdef LWIP_HOOK_ETHARP_GET_GW
/* 网关钩子函数,可以自定义选择网关 */
dst_addr = LWIP_HOOK_ETHARP_GET_GW(netif, ipaddr);
if (dst_addr == NULL)
#endif /* LWIP_HOOK_ETHARP_GET_GW */
{
/* 查看网卡是否有默认网关 */
if (!ip4_addr_isany_val(*netif_ip4_gw(netif))) {
/* 获取网卡默认网关 */
dst_addr = netif_ip4_gw(netif);
} else { /* 没找到有效网关 */
/* 没有路由到目的地错误(缺省网关丢失) */
return ERR_RTE;
}
}
}
}
#if LWIP_NETIF_HWADDRHINT
if (netif->hints != NULL) {
/* 网卡中上次发包时使用的arp entry优先遍历 */
netif_addr_idx_t etharp_cached_entry = netif->hints->addr_hint;
if (etharp_cached_entry < ARP_TABLE_SIZE) {
#endif /* LWIP_NETIF_HWADDRHINT */
if ((arp_table[etharp_cached_entry].state >= ETHARP_STATE_STABLE) && /* arp entry有效 */
#if ETHARP_TABLE_MATCH_NETIF
(arp_table[etharp_cached_entry].netif == netif) && /* arp entry对应网卡匹配 */
#endif
(ip4_addr_eq(dst_addr, &arp_table[etharp_cached_entry].ipaddr))) { /* 找到目标IP的MAC映射 */
ETHARP_STATS_INC(etharp.cachehit); /* 记录lwip arp相关状态 */
/* 发送IP数据包 */
return etharp_output_to_arp_index(netif, q, etharp_cached_entry);
}
#if LWIP_NETIF_HWADDRHINT
}
}
#endif /* LWIP_NETIF_HWADDRHINT */ /* 如果网卡指定先遍历的arp entry没有找到合法映射,就需要遍历ARP缓存表 */
for (i = 0; i < ARP_TABLE_SIZE; i++) {
if ((arp_table[i].state >= ETHARP_STATE_STABLE) && /* arp entry有效 */
#if ETHARP_TABLE_MATCH_NETIF
(arp_table[i].netif == netif) && /* 网卡匹配对应 */
#endif
(ip4_addr_eq(dst_addr, &arp_table[i].ipaddr))) { /* 目标IP对应 */
/* 找到目标IP的MAC映射 */
/* 把当前arp entry索引保存到网卡中,以便下次快速遍历 */
ETHARP_SET_ADDRHINT(netif, i);
/* 发送IP数据包 */
return etharp_output_to_arp_index(netif, q, i);
}
}
/* 在ARP缓存表中没有找到对应的ARP entry,就需要发起ARP 请求 */
return etharp_query(netif, dst_addr, q);
} /* 对于广播或组播的数据包,直接知道了目标硬件字段的MAC地址,可以直接往链路层发送 */
return ethernet_output(netif, q, (struct eth_addr *)(netif->hwaddr), dest, ETHTYPE_IP);
}

8.8.3 etharp_output_to_arp_index():需要维护arp entry的IP数据包转发函数

etharp_output_to_arp_index()这个函数实现维护arp entry超时前更新,然后把数据包通过ethernet_output()转交给链路层。

其源码参考前面缓存表状态。

8.8.4 etharp_query():需要发起ARP请求的IP数据包转发函数

如果IP数据包为单播包,且在 ARP 缓存表中没有找到对应的MAC地址,就需要调用etharp_query()函数发起ARP请求处理。

主要内容:

  • IP地址校验。只能是有效的单播IP地址。
  • 找到可操作的arp entry。
  • 发起ARP请求。
  • 拷贝pbuf保存到arp entry缓存队列。
/**
* Send an ARP request for the given IP address and/or queue a packet.
*
* If the IP address was not yet in the cache, a pending ARP cache entry
* is added and an ARP request is sent for the given address. The packet
* is queued on this entry.
*
* If the IP address was already pending in the cache, a new ARP request
* is sent for the given address. The packet is queued on this entry.
*
* If the IP address was already stable in the cache, and a packet is
* given, it is directly sent and no ARP request is sent out.
*
* If the IP address was already stable in the cache, and no packet is
* given, an ARP request is sent out.
*
* @param netif The lwIP network interface on which ipaddr
* must be queried for.
* @param ipaddr The IP address to be resolved.
* @param q If non-NULL, a pbuf that must be delivered to the IP address.
* q is not freed by this function.
*
* @note q must only be ONE packet, not a packet queue!
*
* @return
* - ERR_BUF Could not make room for Ethernet header.
* - ERR_MEM Hardware address unknown, and no more ARP entries available
* to query for address or queue the packet.
* - ERR_MEM Could not queue packet due to memory shortage.
* - ERR_RTE No route to destination (no gateway to external networks).
* - ERR_ARG Non-unicast address given, those will not appear in ARP cache.
*
*/
err_t
etharp_query(struct netif *netif, const ip4_addr_t *ipaddr, struct pbuf *q)
{
struct eth_addr *srcaddr = (struct eth_addr *)netif->hwaddr;
err_t result = ERR_MEM;
int is_new_entry = 0;
s16_t i_err;
netif_addr_idx_t i; /* 如果是广播、组播或者无效的单播,不需要发起ARP请求,不用在继续往下处理 */
if (ip4_addr_isbroadcast(ipaddr, netif) ||
ip4_addr_ismulticast(ipaddr) ||
ip4_addr_isany(ipaddr)) {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: will not add non-unicast IP address to ARP cache\n"));
return ERR_ARG;
} /* 按规则找到arp entry进行操作 */
i_err = etharp_find_entry(ipaddr, ETHARP_FLAG_TRY_HARD, netif); if (i_err < 0) { /* 没找到能用的arp entry */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: could not create ARP entry\n"));
if (q) {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: packet dropped\n"));
ETHARP_STATS_INC(etharp.memerr);
}
return (err_t)i_err;
}
LWIP_ASSERT("type overflow", (size_t)i_err < NETIF_ADDR_IDX_MAX);
i = (netif_addr_idx_t)i_err; /* 找到合法的arp entry */ if (arp_table[i].state == ETHARP_STATE_EMPTY) { /* 是新的arp entry,做下标记 */
is_new_entry = 1; /* 标记下当前为新的arp entry */
arp_table[i].state = ETHARP_STATE_PENDING; /* 更新为pending态 */
/* 保存网卡到arp entry中 */
arp_table[i].netif = netif;
} /* arp请求中或者arp entry有效即可往下走 */
LWIP_ASSERT("arp_table[i].state == PENDING or STABLE",
((arp_table[i].state == ETHARP_STATE_PENDING) ||
(arp_table[i].state >= ETHARP_STATE_STABLE))); /* do we have a new entry? or an implicit query request? */
if (is_new_entry || (q == NULL)) { /* 新的arp entry或者隐式ARP请求都需要重新发起ARP请求 */
/* 发送ARP请求 */
result = etharp_request(netif, ipaddr);
if (result != ERR_OK) {
/* ARP请求发送失败 */
/* 该故障可能只是暂时的,而且在etharp_tmr()周期定时函数中会重新发出ARP请求,所以这里先跳过 */
} else {
/* ARP请求发送成功 */
if ((arp_table[i].state == ETHARP_STATE_PENDING) && !is_new_entry) {
/* 隐式ARP请求,发送请求成功,重置下ctime,不让其快过期 */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: reset ctime for entry %"S16_F"\n", (s16_t)i));
arp_table[i].ctime = 0;
}
}
if (q == NULL) {
return result; /* 隐式ARP请求的话,不需要往下保存数据了 */
}
} LWIP_ASSERT("q != NULL", q != NULL); if (arp_table[i].state >= ETHARP_STATE_STABLE) { /* 当前条目已经有效 */
/* 更新下当前网卡发送IP数据包时使用的arp entry,方便下次优先遍历 */
ETHARP_SET_ADDRHINT(netif, i);
/* 可以直接把IP数据包交给链路层转发 */
result = ethernet_output(netif, q, srcaddr, &(arp_table[i].ethaddr), ETHTYPE_IP);
} else if (arp_table[i].state == ETHARP_STATE_PENDING) { /* ARP请求中 */
/* 需要把当前IP数据包放到当前arp entry的缓存队列中 */
struct pbuf *p;
int copy_needed = 0;
/* 如果q这个pbuf链中包含一个可改动的pbuf(即是PBUFF_ERF、PBUF_POOL、PBUF_RAM),都需要拷贝整个pbuf链到arp entry的缓存队列中. */
p = q;
while (p) {
LWIP_ASSERT("no packet queues allowed!", (p->len != p->tot_len) || (p->next == NULL));
if (PBUF_NEEDS_COPY(p)) { /* 遍历pbuf链中各个pbuf,是否含有可改动的pbuf */
copy_needed = 1; /* 需要拷贝整个pbuf链 */
break;
}
p = p->next;
}
if (copy_needed) {
/* 将整个包复制到新的pbuf中 */
p = pbuf_clone(PBUF_LINK, PBUF_RAM, q);
} else {
/* 引用旧的pbuf就足够了 */
p = q;
pbuf_ref(p);
} if (p != NULL) {
#if ARP_QUEUEING
struct etharp_q_entry *new_entry;
/* 申请一个新的 arp entry queue 节点资源 */
new_entry = (struct etharp_q_entry *)memp_malloc(MEMP_ARP_QUEUE);
if (new_entry != NULL) {
unsigned int qlen = 0; /* 记录队列节点数 */
new_entry->next = NULL;
new_entry->p = p; /* 把IP层需要发送的数据包先放到当前arp entry queue节点 */
if (arp_table[i].q != NULL) {
/* 队列已经存在,将新条目插入到队列尾 */
struct etharp_q_entry *r;
r = arp_table[i].q;
qlen++;
while (r->next != NULL) {
r = r->next;
qlen++;
}
r->next = new_entry;
} else {
/* 队列不存在,当前节点放到队列首部 */
arp_table[i].q = new_entry;
}
#if ARP_QUEUE_LEN
if (qlen >= ARP_QUEUE_LEN) { /* 队列超员后,需要丢弃旧报文,为新报文腾位 */
struct etharp_q_entry *old;
old = arp_table[i].q;
arp_table[i].q = arp_table[i].q->next; /* 删除最老的报文 */
pbuf_free(old->p); /* 释放该报文资源 */
memp_free(MEMP_ARP_QUEUE, old); /* 回收该节点资源 */
}
#endif
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: queued packet %p on ARP entry %"U16_F"\n", (void *)q, i));
result = ERR_OK; /* ARP请求处理完毕 */
} else { /* ARP缓存节点MEMP_ARP_QUEUE内存池没有资源了 */
pbuf_free(p); /* 释放拷贝的的pbuf资源 */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: could not queue a copy of PBUF_REF packet %p (out of memory)\n", (void *)q));
result = ERR_MEM; /* 返回内存不足 */
}
#else /* ARP_QUEUEING */ /* arp entry缓存队列只能是单包配置 */
/* 对于每个ARP请求总是只排队一个包,释放之前排队的包 */
if (arp_table[i].q != NULL) {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: dropped previously queued packet %p for ARP entry %"U16_F"\n", (void *)q, (u16_t)i));
pbuf_free(arp_table[i].q);
}
arp_table[i].q = p;
result = ERR_OK;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: queued packet %p on ARP entry %"U16_F"\n", (void *)q, (u16_t)i));
#endif /* ARP_QUEUEING */
} else { /* 拷贝pbuf链表时PBUF_RAM内存堆空间不足 */
ETHARP_STATS_INC(etharp.memerr);
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: could not queue a copy of PBUF_REF packet %p (out of memory)\n", (void *)q));
result = ERR_MEM; /* 返回内存不足 */
}
}
return result; /* 搞完 */
}

8.8.5 etharp_find_entry():查找可被新建的arp entry

  • 优先寻找匹配IP(和网卡)的arp entry。
  • 然后再寻找最不重要的arp entry。优先被覆盖的顺序:empty entry > oldest stable entry > oldest pending entry without queued packets > oldest pending entry with queued packets
/**
* Search the ARP table for a matching or new entry.
*
* If an IP address is given, return a pending or stable ARP entry that matches
* the address. If no match is found, create a new entry with this address set,
* but in state ETHARP_EMPTY. The caller must check and possibly change the
* state of the returned entry.
*
* If ipaddr is NULL, return a initialized new entry in state ETHARP_EMPTY.
*
* In all cases, attempt to create new entries from an empty entry. If no
* empty entries are available and ETHARP_FLAG_TRY_HARD flag is set, recycle
* old entries. Heuristic choose the least important entry for recycling.
*
* @param ipaddr IP address to find in ARP cache, or to add if not found.
* @param flags See @ref etharp_state
* @param netif netif related to this address (used for NETIF_HWADDRHINT)
*
* @return The ARP entry index that matched or is created, ERR_MEM if no
* entry is found or could be recycled.
*/
static s16_t
etharp_find_entry(const ip4_addr_t *ipaddr, u8_t flags, struct netif *netif)
{
s16_t old_pending = ARP_TABLE_SIZE; /* pending态没有阻塞数据最老的arp entry */
s16_t old_stable = ARP_TABLE_SIZE; /* 有效态最老的arp entry */
s16_t empty = ARP_TABLE_SIZE;
s16_t i = 0;
s16_t old_queue = ARP_TABLE_SIZE; /* pending态有阻塞数据最老的arp entry */
/* 对应的age */
u16_t age_queue = 0, age_pending = 0, age_stable = 0; LWIP_UNUSED_ARG(netif); /* 防止编译警告 */ /**
* a) do a search through the cache, remember candidates
* b) select candidate entry
* c) create new entry
*/ /* a) in a single search sweep, do all of this
* 1) remember the first empty entry (if any)
* 2) remember the oldest stable entry (if any)
* 3) remember the oldest pending entry without queued packets (if any)
* 4) remember the oldest pending entry with queued packets (if any)
* 5) search for a matching IP entry, either pending or stable
* until 5 matches, or all entries are searched for.
*/ for (i = 0; i < ARP_TABLE_SIZE; ++i) { /* 遍历ARP缓存表 */
u8_t state = arp_table[i].state;
if ((empty == ARP_TABLE_SIZE) && (state == ETHARP_STATE_EMPTY)) {
/* 检索到空闲的arp entry */
LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_find_entry: found empty entry %d\n", (int)i));
/* 记住第一个空闲的arp entry */
empty = i;
} else if (state != ETHARP_STATE_EMPTY) { /* 当前遍历到的arp entry不是空闲态 */
LWIP_ASSERT("state == ETHARP_STATE_PENDING || state >= ETHARP_STATE_STABLE",
state == ETHARP_STATE_PENDING || state >= ETHARP_STATE_STABLE);
if (ipaddr && ip4_addr_eq(ipaddr, &arp_table[i].ipaddr) /* 匹配当前arp entry的IP */
#if ETHARP_TABLE_MATCH_NETIF
&& ((netif == NULL) || (netif == arp_table[i].netif)) /* 匹配当前arp entry的网卡 */
#endif /* ETHARP_TABLE_MATCH_NETIF */
) {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: found matching entry %d\n", (int)i));
/* 已经找到符合要求,且已经存在的arp entry了 */
return i;
}
if (state == ETHARP_STATE_PENDING) { /* peding态 */
if (arp_table[i].q != NULL) { /* 有阻塞数据包 */
if (arp_table[i].ctime >= age_queue) { /* 当前arp entry更老 */
/* 更新值 */
old_queue = i;
age_queue = arp_table[i].ctime;
}
} else
{ /* 没有阻塞数据包 */
if (arp_table[i].ctime >= age_pending) { /* 当前arp entry更老 */
/* 更新值 */
old_pending = i;
age_pending = arp_table[i].ctime;
}
}
} else if (state >= ETHARP_STATE_STABLE) { /* 有效态 */
#if ETHARP_SUPPORT_STATIC_ENTRIES
/* 不要处理静态的arp entry */
if (state < ETHARP_STATE_STATIC)
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
{
if (arp_table[i].ctime >= age_stable) { /* 当前arp entry更老 */
/* 更新值 */
old_stable = i;
age_stable = arp_table[i].ctime;
}
}
}
}
} /* 已经遍历ARP缓存表完毕,没找到匹配IP已有的arp entry。 */ if (((flags & ETHARP_FLAG_FIND_ONLY) != 0) || /* 只读模式,没有匹配的arp entry就直接退出 */
((empty == ARP_TABLE_SIZE) && ((flags & ETHARP_FLAG_TRY_HARD) == 0))) { /* 非只读模式,但是没找到空闲arp entry,又不能覆盖,也直接退出 */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: no empty entry found and not allowed to recycle\n"));
return (s16_t)ERR_MEM;
} /* b) choose the least destructive entry to recycle:
* 1) empty entry
* 2) oldest stable entry
* 3) oldest pending entry without queued packets
* 4) oldest pending entry with queued packets
*
* { ETHARP_FLAG_TRY_HARD is set at this point }
*/ if (empty < ARP_TABLE_SIZE) { /* 找到空闲的arp entry */
i = empty;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting empty entry %d\n", (int)i));
} else { /* 没找到空闲的arp entry */
if (old_stable < ARP_TABLE_SIZE) { /* 2) 先找最老的有效态arp entry */
/* 回收最老的有效态arp entry */
i = old_stable;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting oldest stable entry %d\n", (int)i));
/* stable态arp entry上不应该存在排队的数据包 */
LWIP_ASSERT("arp_table[i].q == NULL", arp_table[i].q == NULL);
} else if (old_pending < ARP_TABLE_SIZE) { /* 3) 再找没有阻塞数据的pending态的arp entry */
/* 回收这条arp entry */
i = old_pending;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting oldest pending entry %d (without queue)\n", (int)i));
} else if (old_queue < ARP_TABLE_SIZE) { /* 4) 最后才找有阻塞数据的pending态的arp entry */
/* 回收这条arp entry */
i = old_queue;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting oldest pending entry %d, freeing packet queue %p\n", (int)i, (void *)(arp_table[i].q)));
} else { /* 没找到 */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: no empty or recyclable entries found\n"));
return (s16_t)ERR_MEM;
} LWIP_ASSERT("i < ARP_TABLE_SIZE", i < ARP_TABLE_SIZE);
/* 找到需要需要回收的arp entry,将其回收 */
etharp_free_entry(i);
} /* 确保当前arp entry已经为空闲态,且索引不超限制 */
LWIP_ASSERT("i < ARP_TABLE_SIZE", i < ARP_TABLE_SIZE);
LWIP_ASSERT("arp_table[i].state == ETHARP_STATE_EMPTY",
arp_table[i].state == ETHARP_STATE_EMPTY); if (ipaddr != NULL) {
/* 保存IP到新建arp entry */
ip4_addr_copy(arp_table[i].ipaddr, *ipaddr);
}
arp_table[i].ctime = 0; /* 重置age */
#if ETHARP_TABLE_MATCH_NETIF
arp_table[i].netif = netif; /* 保存网卡到新建arp entry */
#endif /* ETHARP_TABLE_MATCH_NETIF */
return (s16_t)i; /* 返回找到符合要求的arp entry */
}

8.9 数据包接收分析

主要分析ARP协议层的处理。

通过前面分析了以太网链路层收到数据帧后j由ethernet_input(),如果是一个合法的以太网数据帧,并且协议是ARP类型,就会上传到etharp_input()处理。

etharp_input()主要内容是:

  1. 检查ARP报文。

  2. ARP请求报文:如果是请求报文的目标IP和本地的匹配,就组装ARP响应报文并发送出去。

  3. ARP响应报文:

    1. 更新ARP缓存表。
    2. 把阻塞在arp entry缓存队列的IP数据报发送出去。
    3. 释放pbuf。因为ARP报文到此已经处理完毕。

etharp_input()

/**
* Responds to ARP requests to us. Upon ARP replies to us, add entry to cache
* send out queued IP packets. Updates cache with snooped address pairs.
*
* Should be called for incoming ARP packets. The pbuf in the argument
* is freed by this function.
*
* @param p The ARP packet that arrived on netif. Is freed by this function.
* @param netif The lwIP network interface on which the ARP packet pbuf arrived.
*
* @see pbuf_free()
*/
void
etharp_input(struct pbuf *p, struct netif *netif)
{
struct etharp_hdr *hdr;
ip4_addr_t sipaddr, dipaddr;
u8_t for_us, from_us; LWIP_ASSERT_CORE_LOCKED(); LWIP_ERROR("netif != NULL", (netif != NULL), return;); hdr = (struct etharp_hdr *)p->payload; /* 把ARP报文拿出来 */ /* RFC 826 "Packet Reception": */
/* 检查ARP包的合法性 */
if ((hdr->hwtype != PP_HTONS(LWIP_IANA_HWTYPE_ETHERNET)) || /* 硬件地址类型只支持以太网类型 */
(hdr->hwlen != ETH_HWADDR_LEN) || /* 硬件地址长度检查(MAC类型为6) */
(hdr->protolen != sizeof(ip4_addr_t)) || /* 协议地址长度检查。目前只支持IPV4 */
(hdr->proto != PP_HTONS(ETHTYPE_IP))) /* 协议地址类型,IPv4类型 */
{ /* ARP报文校验不通过 */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
("etharp_input: packet dropped, wrong hw type, hwlen, proto, protolen or ethernet type (%"U16_F"/%"U16_F"/%"U16_F"/%"U16_F")\n",
hdr->hwtype, (u16_t)hdr->hwlen, hdr->proto, (u16_t)hdr->protolen));
/* 数据包丢弃记录 */
ETHARP_STATS_INC(etharp.proterr);
ETHARP_STATS_INC(etharp.drop);
/* 释放pbuf */
pbuf_free(p);
return;
}
/* ARP报文校验通过,记录下 */
ETHARP_STATS_INC(etharp.recv); #if LWIP_ACD
/* We have to check if a host already has configured our ip address and
* continuously check if there is a host with this IP-address so we can
* detect collisions.
* acd_arp_reply ensures the detection of conflicts. It will handle possible
* defending or retreating and will make sure a new IP address is selected.
* etharp_input does not need to handle packets that originate "from_us".
*/
acd_arp_reply(netif, hdr); /* IP冲突处理。AUTOIP协议范畴,如果开启了AUTOIP,每一个收到的ARP包都会先经过这个处理。 */
#endif /* LWIP_ACD */ IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&sipaddr, &hdr->sipaddr); /* 把ARP报文的源IP地址提前出来 */
IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&dipaddr, &hdr->dipaddr); /* 把ARP报文的目标IP地址提前出来 */ if (ip4_addr_isany_val(*netif_ip4_addr(netif))) { /* 主机网卡没有配置IP地址 */
for_us = 0; /* 这个ARP包不是给我们的 */
from_us = 0; /* 也不是从我们这里发出去再被转回来的 */
} else { /* 主机网卡已经被配置了IP,可以继续分析ARP报文 */
/* ARP报文是否是指向我们的 */
for_us = (u8_t)ip4_addr_eq(&dipaddr, netif_ip4_addr(netif));
/* 收到的这个ARP报文是否是从我们这里发出的 */
from_us = (u8_t)ip4_addr_eq(&sipaddr, netif_ip4_addr(netif));
} /* 如果这个ARP报文是给我们的:
-> 更新ARP缓存表;
-> 如果是一个ARP请求报文,那就组装ARP响应报文回去。
-> 如果是一个ARP响应报文,那就把当前arp entry中的缓存队列数据发出去。
如果这个ARP报文不是给我们的:
-> 如果缓存表中有空闲的arp entry,我们也可以保存这个IP-MAC映射 */
etharp_update_arp_entry(netif, &sipaddr, &(hdr->shwaddr),
for_us ? ETHARP_FLAG_TRY_HARD : ETHARP_FLAG_FIND_ONLY); /* 更新ARP缓存表 */ /* 检查当前ARP报文的类型 */
switch (hdr->opcode) {
case PP_HTONS(ARP_REQUEST): /* ARP请求 */
/* ARP request. 如果是询问我们这个IP映射的MAC地址,那么就组装ARP响应报文回去 */
LWIP_DEBUGF (ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: incoming ARP request\n"));
if (for_us && !from_us) { /* 这个ARP请求是询问我们的 */
/* 发送ARP响应 */
etharp_raw(netif,
(struct eth_addr *)netif->hwaddr, &hdr->shwaddr,
(struct eth_addr *)netif->hwaddr, netif_ip4_addr(netif),
&hdr->shwaddr, &sipaddr,
ARP_REPLY);
} else if (ip4_addr_isany_val(*netif_ip4_addr(netif))) { /* 我们还没有配置IP */
/* { for_us == 0 and netif->ip_addr.addr == 0 } */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: we are unconfigured, ARP request ignored.\n"));
} else { /* 这个ARP请求不是给我们的 */
/* { for_us == 0 and netif->ip_addr.addr != 0 } */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: ARP request was not for us.\n"));
}
break;
case PP_HTONS(ARP_REPLY):
/* ARP reply. 已经更新了ARP缓存表 */
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: incoming ARP reply\n"));
break;
default:
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: ARP unknown opcode type %"S16_F"\n", lwip_htons(hdr->opcode)));
ETHARP_STATS_INC(etharp.err); /* 收到的ARP报文异常,记录下 */
break;
}
/* ARP报文处理完毕,释放资源 */
pbuf_free(p);
}

8.10 LWIP ARP一图笔记

【lwip】08-ARP协议一图笔记及源码实现的更多相关文章

  1. IOS懒人笔记应用源码

    这个源码是懒人笔记应用源码,也是一个已经上线的apple应用商店的应用,懒人笔记iOS客户端源码,支持语音识别,即将语音转化成文本文字,所用语音识别类库为讯飞语音类库. 懒人笔记是一款为懒人设计的笔记 ...

  2. Hadoop学习笔记(9) ——源码初窥

    Hadoop学习笔记(9) ——源码初窥 之前我们把Hadoop算是入了门,下载的源码,写了HelloWorld,简要分析了其编程要点,然后也编了个较复杂的示例.接下来其实就有两条路可走了,一条是继续 ...

  3. Cognitive Graph for Multi-Hop Reading Comprehension at Scale(ACL2019) 阅读笔记与源码解析

    论文地址为:Cognitive Graph for Multi-Hop Reading Comprehension at Scale github地址:CogQA 背景 假设你手边有一个维基百科的搜索 ...

  4. cesium 实现风场图效果(附源码下载)

    前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. 内 ...

  5. [转]OpenTK学习笔记(1)-源码、官网地址

    OpenTK源码下载地址:https://github.com/opentk/opentk OpenTK使用Nuget安装命令:OpenTK:Install-Package OpenTK -Versi ...

  6. 笔记-twisted源码-import reactor解析

    笔记-twisted源码-import reactor解析 1.      twisted源码解析-1 twisted reactor实现原理: 第一步: from twisted.internet ...

  7. 【lwip】07-链路层收发以太网数据帧源码分析

    目录 前言 7.1 链路层概述 7.2 MAC地址的基本概念 7.3 以太网帧结构 7.4 以太网帧结构 7.5 以太网帧报文数据结构 7.6 发送以太网数据帧 7.7 接收以太网数据帧 7.8 虚拟 ...

  8. Selenium3笔记-WebDriver源码初探

    Selenium3 有哪些变化? 其实相对于与Selenium2,Selenium3没有做太多的改动.下面给出官方的文档说明,供参考. 参考文档:https://seleniumhq.wordpres ...

  9. 浅析 <路印协议--Loopring> 及整体分析 Relay 源码

    作者:林冠宏 / 指尖下的幽灵 前序: 路印协议功能非常之多及强大,本文只做入门级别的分析. 理论部分请细看其白皮书,https://github.com/Loopring/whitepaper 实际 ...

随机推荐

  1. 048_末晨曦Vue技术_处理边界情况之使用$root访问根实例

    处理边界情况之使用$root访问根实例 点击打开视频教程 在每个 new Vue 实例的子组件中,其根实例可以通过 $root property 进行访问. 例如,在这个根实例中: src\main. ...

  2. [CSharpTips]判断两条线段是否相交

    判断两条线段是否相交 主要用到了通过向量积的正负判断两个向量位置关系 向量a×向量b(×为向量叉乘),若结果小于0,表示向量b在向量a的顺时针方向:若结果大于0,表示向量b在向量a的逆时针方向:若等于 ...

  3. spring-aop-事务-注解开发-代理

    1.spring + mybatis: Aop流程: 提前定义好几个用于Aop的类 前置通知:新建MyBeForeAdvice类 实现 MethodBeforeAdvice,并实现其方法 后置通知:新 ...

  4. Java反射(重要)

    全文内容 1: 获取字节码文件对象的三种方式 2: 获取公有,私有方法,并调用构造方法,成员方法 3: 获取并调用私有成员变量 4: 如何为实例对象的成员变量赋值 5: 文末有一些注意 tea1类代码 ...

  5. RabbitMQ 入门系列:9、扩展内容:死信队列:真不适合当延时队列。

    系列目录 RabbitMQ 入门系列:1.MQ的应用场景的选择与RabbitMQ安装. RabbitMQ 入门系列:2.基础含义:链接.通道.队列.交换机. RabbitMQ 入门系列:3.基础含义: ...

  6. 禁止mysql自动更新

    每到00:00时,MySQL弹出小黑框 这是mysql在自动检测更新 右键"此电脑",点击"管理" 依此操作即可

  7. 第七十三篇:解决Vue组件中的样式冲突

    好家伙, 1.组件之间的样式冲突 默认情况下,写在.vue组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题. 举个例子: 我们在Left.vue的组件中添加样式 <templat ...

  8. 使用.Net对图片进行裁剪、缩放、与加水印

    图片的裁剪.缩放.与加水印,是任何系统经常要用到的功能,它们现已集成到IUtility工具中,使用十分简便.(具体代码将在文末给出,支持.NET/.NET Framework/.NET Core) 现 ...

  9. KingbaseES 函数稳定性与SQL性能

    背景:客户现场的一次艰苦的调优过程(https://www.cnblogs.com/kingbase/p/16015834.html),让我觉得非常有必要让数据库用户了解函数的不同稳定性属性,及其对于 ...

  10. Filebeat Nginx Module 自定义字段

    Filebeat Nginx Module 自定义字段 一.修改/usr/local/nginx/conf/nginx.conf中 log_format access '$remote_addr - ...