在网络层,源主机与目的主机之间是通过IP地址来唯一标识的。但是以太网是通过一个48bit的MAC地址来标识不同的网络通信设备的。那么IP数据包最终需要在物理网络上进行发送,就必须将IP地址转换为目标主机对应的MAC地址。

ARP协议被用来解决上述问题。为了实现在IP地址和MAC之间的转换,ARP协议引入了ARP缓存表的概念。ARP缓存表中存放了最近获得周围其他主机IP地址到MAC地址之间的映射记录。

系统初始化时,ARP缓存表是空的(静态绑定除外)。此时(调用netif_set_up时),会向外界广播一个自己的地址信息,称为无回报ARP请求。其他主机接收到ARP数据包之后,会更新ARP缓存表。

当主机A要与主机B通信时:

第1步:主机A在ARP缓存中,检查与主机B的IP地址相匹配的MAC地址。

第2步:如果主机A在ARP缓存中没有找到映射,它将在本地网络上广播ARP请求帧。本地网络上的每台主机都接收到ARP请求并且检查是否与自己的IP地址匹配。如果发现请求的IP地址与自己的IP地址不匹配,它将丢弃ARP请求。

第3步:主机B确定ARP请求中的IP地址与自己的IP地址匹配,则将主机A的IP地址和MAC地址映射添加到本地ARP缓存中。

第4步:主机B将包含其MAC地址的ARP回复消息直接发送回主机A。

第5步:当主机A收到从主机B发来的ARP回复消息时,会用主机B的IP和MAC地址映射更新ARP缓存。主机B的MAC地址确定后,主机A就能通过IP地址和主机B通信了。

注:ARP缓存是有生存期的,一般为20分钟。生存期结束后,将再次重复上面的过程。

IP数据包从源主机到达最终目的主机的过程中,该IP数据包可能会经过中间物理网络中多种网络设备的转发,在每一次转发过程中都会涉及到地址转换的问题。在非最后一步转发中,当转发主机和目的主机不在同一个局域网中时,即便知道目的主机的MAC地址,两者也不能直接通信,必须经过路由转发才可以。所以此时,发送主机通过ARP协议获得的将不是目的主机的真实MAC地址,而是一台可以通往局域网外的路由器的MAC地址。在数据转发的最后一步,分组必将经过最后一条物理路线到达它的目的站,发送主机这时将目的主机IP地址映射为目标MAC地址。

ARP报文格式

/* 以太网头部 */
struct eth_hdr {PACK_STRUCT_FIELD(struct eth_addr dest);    //目的MAC地址PACK_STRUCT_FIELD(struct eth_addr src);    //源MAC地址PACK_STRUCT_FIELD(u16_t type);          //帧类型(IP:0x0800、ARP:0x0806)
} PACK_STRUCT_STRUCT;
/* ARP头部 */
struct etharp_hdr {PACK_STRUCT_FIELD(u16_t hwtype);              //硬件地址类型(以太网:1)PACK_STRUCT_FIELD(u16_t proto);               //映射协议地址类型(IP:0x0800)PACK_STRUCT_FIELD(u16_t _hwlen_protolen);     //硬件地址长度+协议地址长度PACK_STRUCT_FIELD(u16_t opcode);              //操作字段(ARP请求:1、ARP应答:2)PACK_STRUCT_FIELD(struct eth_addr shwaddr);   //源MAC地址PACK_STRUCT_FIELD(struct ip_addr2 sipaddr); //源IP地址PACK_STRUCT_FIELD(struct eth_addr dhwaddr);  //目的MAC地址PACK_STRUCT_FIELD(struct ip_addr2 dipaddr);    //目的IP地址
} PACK_STRUCT_STRUCT;
/* 帧类型 */
#define ETHTYPE_ARP         0x0806        //ARP
#define ETHTYPE_IP          0x0800        //IP
#define ETHTYPE_VLAN        0x8100        //VLAN
#define ETHTYPE_PPPOEDISC   0x8863        //PPPOEDISC
#define ETHTYPE_PPPOE       0x8864        //PPPOE
/* ARP数据类型(操作字段OP) */
#define ARP_REQUEST    1    //ARP请求
#define ARP_REPLY      2    //ARP应答

前面说到网络接口启动的时候,要向外界发送一个无回报ARP请求,用来通知网络中的其它主机。在分析网络接口管理的时候遇到过,代码如下:

/* 使能网络接口 */
void netif_set_up(struct netif *netif)
{/* 设置网络接口使能标志位 */if (!(netif->flags & NETIF_FLAG_UP )) {netif->flags |= NETIF_FLAG_UP;/* 广播无回报ARP */if (netif->flags & NETIF_FLAG_ETHARP) {etharp_gratuitous(netif);}}
}

下面从ARP发送开始,一步一步分析无回报ARP请求是如何发送的

/* 组建并发送ARP(请求/响应)数据包 */
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 struct ip_addr *ipsrc_addr, const struct eth_addr *hwdst_addr, const struct ip_addr *ipdst_addr, const u16_t opcode)
{struct pbuf *p;err_t result = ERR_OK;u8_t k;struct eth_hdr *ethhdr;struct etharp_hdr *hdr;/* 为ARP请求申请内存空间 */p = pbuf_alloc(PBUF_RAW, SIZEOF_ETHARP_PACKET, PBUF_RAM);if (p == NULL) {return ERR_MEM;}/* 以太网头部指针 */ethhdr = p->payload;/* ARP头部指针 */hdr = (struct etharp_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR);/* 操作字段 */hdr->opcode = htons(opcode);/* 源MAC地址和目的MAC地址 */k = ETHARP_HWADDR_LEN;while(k > 0) {k--;hdr->shwaddr.addr[k] = hwsrc_addr->addr[k];hdr->dhwaddr.addr[k] = hwdst_addr->addr[k];ethhdr->dest.addr[k] = ethdst_addr->addr[k];ethhdr->src.addr[k]  = ethsrc_addr->addr[k];}/* 源IP地址、目的IP地址 */hdr->sipaddr = *(struct ip_addr2 *)ipsrc_addr;hdr->dipaddr = *(struct ip_addr2 *)ipdst_addr;/* 硬件地址类型、协议地址类型 */hdr->hwtype = htons(HWTYPE_ETHERNET);hdr->proto = htons(ETHTYPE_IP);/* 硬件地址长度、协议地址长度 */hdr->_hwlen_protolen = htons((ETHARP_HWADDR_LEN << 8) | sizeof(struct ip_addr));/* 帧类型 */ethhdr->type = htons(ETHTYPE_ARP);/* 发送数据包 */result = netif->linkoutput(netif, p);/* 释放数据包空间 */pbuf_free(p);p = NULL;return result;
}

ARP请求,是通过调用 etharp_raw函数实现的。ARP头部中目的MAC地址全0,表示MAC地址待填充。

/* 广播MAC地址 */
const struct eth_addr ethbroadcast = {{0xff,0xff,0xff,0xff,0xff,0xff}};
/* 待填充MAC地址 */
const struct eth_addr ethzero = {{0,0,0,0,0,0}};/* 广播一个ARP请求 */
err_t etharp_request(struct netif *netif, struct ip_addr *ipaddr)
{return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, &ethbroadcast, (struct eth_addr *)netif->hwaddr, &netif->ip_addr, &ethzero, ipaddr, ARP_REQUEST);
}

无回报ARP请求的原理是:将自身IP作为目的IP发送出去,这样就不会有任何主机响应,但是其它主机接收到后会更新ARP缓存表

/* 广播一个无回报ARP请求 */
#define etharp_gratuitous(netif) etharp_request((netif), &(netif)->ip_addr)

前面说到当主机发送数据包时,需要先查找ARP缓存表来获取目的主机MAC地址。下面来具体分析ARP缓存表的数据结构体,以及ARP缓存表的建立、查找和删除。

ARP表项数据结构

/* ARP表项 */
struct etharp_entry {struct etharp_q_entry *q;     //待发送数据包缓存链表struct ip_addr ipaddr;        //IP地址struct eth_addr ethaddr;      //MAC地址enum etharp_state state;      //ARP表项状态u8_t ctime;                   //时间信息struct netif *netif;          //网络接口指针
};
/* ARP缓存表 */
static struct etharp_entry arp_table[ARP_TABLE_SIZE];

ARP缓存表项状态

/* ARP表项状态 */
enum etharp_state {ETHARP_STATE_EMPTY = 0,    //空ETHARP_STATE_PENDING,      //挂起,已发送ARP请求还未得到响应ETHARP_STATE_STABLE        //已建立
};

发送IP数据包之前,需要查ARP缓存表,如果在ARP缓存表中没有找到相应表项。则先发送ARP请求,并将数据包暂时缓存起来,得到ARP响应之后再发送。ARP提供了etharp_q_entry 结构体,用于管理这些数据包。

/* 未建立ARP表项之前,待发送IP数据包管理结构体 */
struct etharp_q_entry {struct etharp_q_entry *next;struct pbuf *p;
};

ARP缓存表项是有时限的,超过时限这将该ARP缓存表项删除。一般情况下,已经建立的表项为20分钟,处于挂起状态的表项为10秒钟。通过一个定时器回调函数etharp_tmr来进行计时处理。

/* 已建立表项寿命 (240 * 5) seconds = 20 minutes */
#define ARP_MAXAGE 240
/* 挂起表项寿命 (2 * 5) seconds = 10 seconds */
#define ARP_MAXPENDING 2/* ARP定时器回调函数(周期5秒) */
void etharp_tmr(void)
{u8_t i;/* 遍历ARP缓存表 */for (i = 0; i < ARP_TABLE_SIZE; ++i) {/* ARP缓存表时间加一 */arp_table[i].ctime++;/* 已建立表项和挂起表项超时 */if (((arp_table[i].state == ETHARP_STATE_STABLE) && (arp_table[i].ctime >= ARP_MAXAGE)) ||((arp_table[i].state == ETHARP_STATE_PENDING)  && (arp_table[i].ctime >= ARP_MAXPENDING))) {/* ARP表项待发送数据包缓存链表不为空 */if (arp_table[i].q != NULL) {/* 释放待发送数据包缓存链表 */free_etharp_q(arp_table[i].q);arp_table[i].q = NULL;}/* 设置ARP表项状态为空 */      arp_table[i].state = ETHARP_STATE_EMPTY;}}
}/* 释放ARP表项待发送数据包缓存链表 */
static void free_etharp_q(struct etharp_q_entry *q)
{struct etharp_q_entry *r;/* 遍历待发送数据包缓存链表 */while (q) {r = q;q = q->next;/* 释放待发送数据包 */pbuf_free(r->p);/* 释放待发送数据包管理结构体 */memp_free(MEMP_ARP_QUEUE, r);}
}

ARP缓存表的建立和查找都是基于find_entry实现的。下面先从find_entry开始,一步一步分析

/* 匹配ARP缓存表时不允许回收表项 */
#define ETHARP_TRY_HARD         1
/* 匹配ARP缓存表时不建立新表项 */
#define ETHARP_FIND_ONLY    2/* 通过IP地址查找ARP缓存表,如果不存在则按一定规则建立新表项 */
/* 建立新表项的规则:1.在空表项处 2.删除已建立的最老表项 3.删除挂起且没有缓存待发送数据包的最老表项 4.删除挂起且有缓存待发送数据包的最老表项  */
static s8_t find_entry(struct ip_addr *ipaddr, u8_t flags)
{s8_t old_pending = ARP_TABLE_SIZE, old_stable = ARP_TABLE_SIZE;s8_t empty = ARP_TABLE_SIZE;u8_t i = 0, age_pending = 0, age_stable = 0;s8_t old_queue = ARP_TABLE_SIZE;u8_t age_queue = 0;if (ipaddr) {/* 最新一次访问的表项为已建立态 */if (arp_table[etharp_cached_entry].state == ETHARP_STATE_STABLE) {/* IP地址和表项IP地址匹配 */if (ip_addr_cmp(ipaddr, &arp_table[etharp_cached_entry].ipaddr)) {return etharp_cached_entry;}}}/* 遍历所有ARP表项,匹配到表项直接返回 */for (i = 0; i < ARP_TABLE_SIZE; ++i) {/* 记录第一个空表项下标 */if ((empty == ARP_TABLE_SIZE) && (arp_table[i].state == ETHARP_STATE_EMPTY)) {empty = i;}/* 该表项为挂起态 */else if (arp_table[i].state == ETHARP_STATE_PENDING) {/* IP地址和表项IP地址匹配 */if (ipaddr && ip_addr_cmp(ipaddr, &arp_table[i].ipaddr)) {/* 将当前表项记录为最新一次访问表项 */etharp_cached_entry = i;return i;} /* 该表项待发送数据包缓冲区不为空 */else if (arp_table[i].q != NULL) {/* 记录最老的挂起态且待发送数据包缓冲区不为空的表项下标 */if (arp_table[i].ctime >= age_queue) {old_queue = i;age_queue = arp_table[i].ctime;}}/* 该表象待发送数据包缓冲区为空 */else {/* 记录最老的挂起态且待发送数据包缓冲区为空的表项下标 */if (arp_table[i].ctime >= age_pending) {old_pending = i;age_pending = arp_table[i].ctime;}}        }/* 该表项为已建立态 */else if (arp_table[i].state == ETHARP_STATE_STABLE) {/* IP地址和表项IP地址匹配 */if (ipaddr && ip_addr_cmp(ipaddr, &arp_table[i].ipaddr)) {/* 将当前表项记录为最新一次访问表项 */etharp_cached_entry = i;return i;} /* 记录最老的已建立态表项下标 */else if (arp_table[i].ctime >= age_stable) {old_stable = i;age_stable = arp_table[i].ctime;}}}/* 该IP没有匹配到ARP表项,且没有空表项,且不允许删除老表项。或者不允许建立新表项 */if (((empty == ARP_TABLE_SIZE) && ((flags & ETHARP_TRY_HARD) == 0)) || ((flags & ETHARP_FIND_ONLY) != 0)) {return (s8_t)ERR_MEM;}/* 存在空表项 */if (empty < ARP_TABLE_SIZE) {i = empty;}/* 存在已建立态的表项 */else if (old_stable < ARP_TABLE_SIZE) {i = old_stable;} /* 存在挂起态且待发送数据包缓冲区为空的表项 */else if (old_pending < ARP_TABLE_SIZE) {i = old_pending;} /* 存在挂起态且待发送数据包缓冲区不为空的表项 */else if (old_queue < ARP_TABLE_SIZE) {i = old_queue;/* 释放待发送数据包缓存链表 */free_etharp_q(arp_table[i].q);arp_table[i].q = NULL;} /* 不存在可以删除的表项 */else {return (s8_t)ERR_MEM;}/* 删除该表项 */arp_table[i].state = ETHARP_STATE_EMPTY;/* 设置为新的表项 */if (ipaddr != NULL) {ip_addr_set(&arp_table[i].ipaddr, ipaddr);}arp_table[i].ctime = 0;etharp_cached_entry = i;      /* 将当前表项记录为最新一次访问表项 */return (err_t)i;
}

更新(不存在则建立)ARP缓存表。当ARP缓存表从挂起转为建立的时候,需要发送原先缓存的待发送数据包。

/* 更新(不存在则建立)ARP缓存表 */
static err_t update_arp_entry(struct netif *netif, struct ip_addr *ipaddr, struct eth_addr *ethaddr, u8_t flags)
{s8_t i;u8_t k;/* 地址不能为广播地址、组播地址、不确定地址 */if (ip_addr_isany(ipaddr) || ip_addr_isbroadcast(ipaddr, netif) || ip_addr_ismulticast(ipaddr)) {return ERR_ARG;}/* 通过IP地址查找ARP缓存表,如果不存在则按一定规则建立新表项 */i = find_entry(ipaddr, flags);/* 未找到且建立失败 */if (i < 0)return (err_t)i;/* 设置该表项为已建立态 */arp_table[i].state = ETHARP_STATE_STABLE;/* 绑定网络接口 */arp_table[i].netif = netif;/* 绑定MAC地址 */k = ETHARP_HWADDR_LEN;while (k > 0) {k--;arp_table[i].ethaddr.addr[k] = ethaddr->addr[k];}/* 时间信息置0 */arp_table[i].ctime = 0;/* 遍历数据包缓冲区链表 */while (arp_table[i].q != NULL) {struct pbuf *p;struct etharp_q_entry *q = arp_table[i].q;arp_table[i].q = q->next;p = q->p;/* 释放待发送数据包管理结构体 */memp_free(MEMP_ARP_QUEUE, q);/* 发送数据 */etharp_send_ip(netif, p, (struct eth_addr*)(netif->hwaddr), ethaddr);/* 释放待发送数据包 */pbuf_free(p);}return ERR_OK;
}

查找ARP缓存表,也是基于find_entry实现。

/* 查找ARP缓存表,如果不存在不建立新表项 */
s8_t etharp_find_addr(struct netif *netif, struct ip_addr *ipaddr, struct eth_addr **eth_ret, struct ip_addr **ip_ret)
{s8_t i;/* 查找ARP缓存表,如果不存在不建立新表项 */i = find_entry(ipaddr, ETHARP_FIND_ONLY);/* 如果该表项为已建立态 */if((i >= 0) && arp_table[i].state == ETHARP_STATE_STABLE) {/* 返回IP地址和MAC地址 */*eth_ret = &arp_table[i].ethaddr;*ip_ret = &arp_table[i].ipaddr;/* 返回表项下标 */return i;}return -1;
}

分析完ARP缓存表的查找之后,继续来分析IP数据包是怎么借用功能将数据发送出去的

/* 经过ARP功能填充头部,发送IP数据包 */
err_t etharp_output(struct netif *netif, struct pbuf *q, struct ip_addr *ipaddr)
{struct eth_addr *dest, mcastaddr;/* 向前调整出以太网头部空间 */if (pbuf_header(q, sizeof(struct eth_hdr)) != 0) {return ERR_BUF;}/* 目的MAC地址置NULL */dest = NULL;/* IP地址是广播地址 */if (ip_addr_isbroadcast(ipaddr, netif)) {/* 设置目的MAC地址为广播MAC地址 */dest = (struct eth_addr *)&ethbroadcast;} /* IP地址是组播地址 */else if (ip_addr_ismulticast(ipaddr)) {/* 设置目的MAC地址为组播MAC地址 */mcastaddr.addr[0] = 0x01;mcastaddr.addr[1] = 0x00;mcastaddr.addr[2] = 0x5e;mcastaddr.addr[3] = ip4_addr2(ipaddr) & 0x7f;mcastaddr.addr[4] = ip4_addr3(ipaddr);mcastaddr.addr[5] = ip4_addr4(ipaddr);dest = &mcastaddr;} /* IP地址为单播IP地址 */else {/* IP地址不在当前网段 */if (!ip_addr_netcmp(ipaddr, &(netif->ip_addr), &(netif->netmask))) {/* 网络接口网关地址不为0 */if (netif->gw.addr != 0) {/* 将目的IP地址改为网关IP地址 */ipaddr = &(netif->gw);} /* 网关地址为0,返回错误 */else {return ERR_RTE;}}/* 查找ARP缓存表,并发送IP数据包 */return etharp_query(netif, ipaddr, q);}/* 广播或组播(已知目的MAC地址),发送IP数据包 */return etharp_send_ip(netif, q, (struct eth_addr*)(netif->hwaddr), dest);
}/* 已知目的MAC地址(查ARP表或MAC地址可推算(广播/组播)),发送IP数据包 */
static err_t etharp_send_ip(struct netif *netif, struct pbuf *p, struct eth_addr *src, struct eth_addr *dst)
{struct eth_hdr *ethhdr = p->payload;u8_t k;/* 设置以太网头部源MAC地址和目的MAC地址 */k = ETHARP_HWADDR_LEN;while(k > 0) {k--;ethhdr->dest.addr[k] = dst->addr[k];ethhdr->src.addr[k]  = src->addr[k];}/* 设置硬件地址类型 */ethhdr->type = htons(ETHTYPE_IP);/* 发送数据包 */return netif->linkoutput(netif, p);
}

同样分析完ARP缓存表的更新之后,继续来分析收到数据包(IP数据包或ARP数据包)后的更新步骤

/* 以太网数据包输入处理 */
err_t ethernet_input(struct pbuf *p, struct netif *netif)
{struct eth_hdr* ethhdr;u16_t type;/* 以太网头部指针 */ethhdr = p->payload;/* 帧类型 */type = htons(ethhdr->type);/* 判断数据包帧类型 */switch (type) {/* IP数据包 */case ETHTYPE_IP:/* 收到IP数据包,更新ARP缓存表 */etharp_ip_input(netif, p);/* 向后调整剥掉以太网头部 */if(pbuf_header(p, -(s16_t)SIZEOF_ETH_HDR)) {pbuf_free(p);p = NULL;} else {/* IP数据包输入处理 */ip_input(p, netif);}break;/* ARP数据包 */case ETHTYPE_ARP:/* ARP数据包输入处理 */etharp_arp_input(netif, (struct eth_addr*)(netif->hwaddr), p);break;default:pbuf_free(p);p = NULL;break;}return ERR_OK;
}
/* 收到IP数据包,更新ARP缓存表 */
void etharp_ip_input(struct netif *netif, struct pbuf *p)
{struct eth_hdr *ethhdr;struct ip_hdr *iphdr;/* 以太网头部指针 */ethhdr = p->payload;/* IP头部指针 */iphdr = (struct ip_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR);/* 该IP不在当前网段,直接返回 */if (!ip_addr_netcmp(&(iphdr->src), &(netif->ip_addr), &(netif->netmask))) {return;}/* 更新ARP缓存表 */update_arp_entry(netif, &(iphdr->src), &(ethhdr->src), 0);
}
/* ARP数据包输入处理 */
void etharp_arp_input(struct netif *netif, struct eth_addr *ethaddr, struct pbuf *p)
{struct etharp_hdr *hdr;struct eth_hdr *ethhdr;struct ip_addr sipaddr, dipaddr;u8_t i;u8_t for_us;/* ARP数据包长度过短 */if (p->len < SIZEOF_ETHARP_PACKET) {/* 释放该数据包 */pbuf_free(p);return;}/* 以太网头部 */ethhdr = p->payload;/* ARP头部 */hdr = (struct etharp_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR);/* 不符合ARP数据包格式 */if ((hdr->hwtype != htons(HWTYPE_ETHERNET)) || (hdr->_hwlen_protolen != htons((ETHARP_HWADDR_LEN << 8) | sizeof(struct ip_addr))) || (hdr->proto != htons(ETHTYPE_IP)) || (ethhdr->type != htons(ETHTYPE_ARP)))  {/* 释放该数据包 */pbuf_free(p);return;}/* 取出源IP地址和目的IP地址 */SMEMCPY(&sipaddr, &hdr->sipaddr, sizeof(sipaddr));SMEMCPY(&dipaddr, &hdr->dipaddr, sizeof(dipaddr));/* 该网络接口没有配置IP地址 */if (netif->ip_addr.addr == 0) {for_us = 0;} /* 判断该ARP数据是不是发给自己的 */else {for_us = ip_addr_cmp(&dipaddr, &(netif->ip_addr));}/* 该ARP数据是发给自己的 */if (for_us) {/* 更新ARP缓存表。如果不存在则建立新表项,但是如果表已满,则建立失败 */update_arp_entry(netif, &sipaddr, &(hdr->shwaddr), ETHARP_TRY_HARD);} else {/* 更新ARP缓存表。如果不存在则建立新表项,如果表已满,则按一定规则回收旧表项再建立新表项 */update_arp_entry(netif, &sipaddr, &(hdr->shwaddr), 0);}/* 判断数据包是ARP请求还是ARP响应 */switch (htons(hdr->opcode)) {/* ARP请求 */case ARP_REQUEST:/* 该ARP数据是发给自己的 */if (for_us) {/* 构建ARP响应包 */hdr->opcode = htons(ARP_REPLY);/* 目的IP */hdr->dipaddr = hdr->sipaddr;/* 源IP */SMEMCPY(&hdr->sipaddr, &netif->ip_addr, sizeof(hdr->sipaddr));/* 源MAC地址、目的MAC地址 */i = ETHARP_HWADDR_LEN;while(i > 0) {i--;hdr->dhwaddr.addr[i] = hdr->shwaddr.addr[i];ethhdr->dest.addr[i] = hdr->shwaddr.addr[i];hdr->shwaddr.addr[i] = ethaddr->addr[i];ethhdr->src.addr[i] = ethaddr->addr[i];}/* 发送ARP响应 */netif->linkoutput(netif, p);} /* 该网络接口没有配置IP */else if (netif->ip_addr.addr == 0) {} /* 不是发给自己的 */else {}break;/* ARP响应 */case ARP_REPLY:break;default:break;}/* 释放ARP数据包 */pbuf_free(p);
}

总结一下ARP的处理流程

LwIP之ARP协议相关推荐

  1. lwip之ARP协议概念

    什么是ARP协议 ARP(Address Resolution Protocol)协议是地址转换协议.负责将IP地址转换为MAC地址.在OSI参考模型中属于网络层,和IP协议同属于同一层,但是在逻辑上 ...

  2. LWIP(chapter2) ARP协议与源码解析

    在TCP/IP 协议中 两台网络设备传输数据,设备的唯一标识符是IP地址.人的标识符很多,区分一个人通过姓名,身份证号码 但是TCP/IP 中只能通过IP地址.IP数据 包的传输必须依赖于物理层,也就 ...

  3. lwIP ARP协议分析

    ARP 协议分析 总的来说,lwip将链路层ethernet的协议分组格式分为ether和etherarp 分开处理.ip分组先进入etharp_ip_input更新一下arp表项,然后直接进入 ne ...

  4. 《lwip学习6》-- ARP协议

    初始ARP 地址解析协议(Address Resolution Protocol, ARP)是通过解析 IP 地址得到数据链路层地址的,是一个在网络协议包中极其重要的网络传输协议,它与网卡有着极其密切 ...

  5. TCP/IP详解学习笔记(3)-IP协议,ARP协议,RARP协议

    把这三个协议放到一起学习是因为这三个协议处于同一层,ARP协议用来找到目标主机的Ethernet网卡Mac地址,IP则承载要发送的消息.数据链路层可以从ARP得到数据的传送信息,而从IP得到要传输的数 ...

  6. 通信原理之IP协议,ARP协议 (三)

    把这三个协议放到一起学习是因为这三个协议处于同一层,ARP协议用来找到目标主机的Ethernet网卡Mac地址,IP则承载要发送的消息.数据链路层可以从ARP得到数据的传送信息,而从IP得到要传输的数 ...

  7. 4.3.7 ARP协议

    4.3.7 ARP协议 在一个网段内 不在一个网段内

  8. 网络工程:2.1.ARP协议与PC间通信

    ARP协议功能:通过解析网路层IPV4地址来找寻数据链路层MAC地址的一个网络传输协议 ARP协议格式 每台带ARP协议设备表格式  1.PC间正常通信原则一(双方PC能相互访问) ① 需求:1.1. ...

  9. ARP协议详解之ARP动态与静态条目的生命周期

    ARP协议详解之ARP动态与静态条目的生命周期 ARP动态条目的生命周期 动态条目随时间推移自动添加和删除. q  每个动态ARP缓存条目默认的生命周期是两分钟.当超过两分钟,该条目会被删掉.所以,生 ...

最新文章

  1. 偷盖茨、奥巴马 Twitter 的黑客被抓了,年轻到你想不到!
  2. mysql忘记密码怎么改
  3. [Javascipt] Immediately-Invoker 2
  4. Java nginx 双向ssl_使用Nginx实现HTTPS双向验证的方法
  5. 23.2.3 高速缓存依赖性(1)
  6. java math rint_Java Math.rint() 方法
  7. python概率密度函数参数估计_EM算法求高斯混合模型参数估计——Python实现
  8. Mysql 学习笔记2
  9. 我有文章了,但也不想搞学术了
  10. jsp登录密码加密_[源码和文档分享]基于JSP和MYSQL数据库的在线购物网站的设计与实现...
  11. accessibility-service 高版本无法编译_今天我把APP的编译速度缩短了近5倍
  12. 95-235-070-源码-task-OneInputStreamTask
  13. mysql无法输入数据库_关于mysql数据库无法录入中文的问题
  14. WPF捕获事件即使这个事件被标记为Handled
  15. 主键和外键举例_数据库-主键和外键及其约束
  16. ISO26262解析(六)——硬件集成测试
  17. 小学教训计算机培训的简单内容,小学计算机教育随笔
  18. android xp,安卓手机刷xp系统装机版xp
  19. 手机usb外接摄像头 diy红外夜视仪
  20. OCR技术3-大批量生成文字训练集

热门文章

  1. Qt文档阅读笔记-Qt4 Lower-Level API扩展Qt Applications(Qt4中Plugin的使用)解析与实例
  2. Java高级语法笔记-抽象类
  3. C/C++ OpenCV中值滤波双边滤波
  4. php输出1到10的和,php通过排列组合实现1到9数字相加都等于20的方法
  5. java 获取子类实际的类型名_Java 泛型类 以及 泛型类获取子类的具体参数类型 以及 获取子类型具体参数的实例...
  6. vb6.0服务器组件安装失败,win10安装vb6.0总是失败怎么办?win10 vb6.0安装不了的解决办法...
  7. kf真空接头标准尺寸_大连高速旋转接头商家,什么是旋转接头_上海汉洲国际贸易...
  8. then 微信小程序_微信小程序 引入es6 promise
  9. 计算机隐藏用户设置,Win10电脑怎么设置隐藏账户?电脑如何设置隐藏账户?
  10. (数据库系统概论|王珊)第四章数据库安全性-第二、三、四、五、六节:数据库安全性控制