4. ARP协议详解:ARP请求/响应、ARP缓存表、免费ARP、在LwIP中的实现
各位同学,咱们今天来啃一块硬骨头——ARP协议。说实话,很多做嵌入式网络开发的朋友,对ARP的理解就停留在“IP转MAC”这个层面。但实际项目中,ARP搞不好,网络直接瘫痪。我当年调试一个物联网网关,就因为ARP缓存表没处理好,整栋楼的设备都连不上服务器,被老板骂得狗血淋头。今天咱们就把ARP彻底讲透。
4.1 为什么需要ARP?
你想想看,网络通信最终依赖的是MAC地址。IP地址是逻辑地址,它帮你找到目标设备所在的网段。但到了局域网内部,交换机只认MAC地址。所以,当你的设备知道目标IP(比如192.168.1.100),却不知道它的MAC地址时,就需要ARP来“搭桥”。
说白了,ARP就是一个“寻人启事”协议。它在局域网里广播:“谁是192.168.1.100?请把你的MAC地址告诉我!”
4.2 ARP请求与响应:一问一答
ARP的工作流程其实很简单,就两步:
- ARP请求:源设备广播一个ARP请求包。这个包的目标MAC地址填的是全F(FF:FF:FF:FF:FF:FF),表示“所有人注意”。包里面包含了源IP、源MAC,以及目标IP。
- ARP响应:目标设备收到请求后,发现目标IP是自己,就单播回复一个ARP响应包。里面带上自己的MAC地址。
这里有个细节要注意:ARP请求是广播,但ARP响应是单播。为什么?因为请求方已经在请求包里把自己的MAC地址告诉对方了,响应方可以直接回复。
ARP报文结构(以太网环境)
+----------------+----------------+----------------+----------------+
| 硬件类型 (2字节) | 协议类型 (2字节) | 硬件地址长度(1) | 协议地址长度(1) |
+----------------+----------------+----------------+----------------+
| 操作码 (2字节) | 源MAC地址 (6字节) | 源IP地址 (4字节) |
+----------------+----------------+----------------+----------------+
| 目标MAC地址 (6字节) | 目标IP地址 (4字节) |
+----------------+----------------+----------------+----------------+
操作码:1表示请求,2表示响应。这个字段很重要,接收方靠它区分是请求还是响应。
4.3 ARP缓存表:别每次都广播
如果每次发数据都要广播一次ARP请求,那网络效率就太低了。所以每个设备都维护着一张ARP缓存表,记录着IP地址和MAC地址的对应关系。
我见过不少新手工程师,在嵌入式设备里把ARP缓存表设得特别大,结果内存爆了。其实嵌入式设备,缓存表一般就10~20条记录就够了。为什么?因为你的设备通常只跟少数几个设备通信(比如网关、服务器)。
| IP地址 | MAC地址 | 状态 | 生存时间 |
|---|---|---|---|
| 192.168.1.1 | 00:11:22:33:44:55 | RESOLVED | 120秒 |
| 192.168.1.100 | AA:BB:CC:DD:EE:FF | PENDING | 3秒 |
状态字段很关键:
- RESOLVED:已经解析成功,可以直接用。
- PENDING:正在等待响应,还没拿到MAC地址。
- STALE:过期了,需要重新验证。
生存时间(TTL)一般建议设120秒左右。太短了频繁广播,太长了MAC地址变了都不知道。
避坑指南:我曾经在一个项目里,把ARP缓存TTL设成了600秒。结果网关换了网卡,MAC地址变了,我的设备还拿着旧MAC发了10分钟的数据包,全部丢包。从那以后,我习惯把TTL控制在120秒以内。
4.4 免费ARP:我来了,都给我更新
免费ARP(Gratuitous ARP)是个很有意思的东西。它跟普通的ARP请求长得一模一样,但目标IP填的是自己的IP。说白了,就是设备主动告诉所有人:“我的IP是XXX,我的MAC是YYY,你们都给我记好了!”
免费ARP的典型应用场景:
- IP地址冲突检测:设备上线时发一个免费ARP。如果收到响应,说明有人用了同样的IP,赶紧报错。
- MAC地址变更通知:网卡换了,或者虚拟机迁移了,发个免费ARP让交换机更新MAC表。
- 网关冗余切换:VRRP等协议里,主备切换后发免费ARP,让所有设备知道网关MAC变了。
注意:免费ARP不能滥用。我曾经见过一个设备每5秒发一次免费ARP,直接把局域网里其他设备的ARP缓存表刷爆了。正常情况,设备上线时发一次就够了。
4.5 在LwIP中的实现
LwIP(Lightweight IP)是嵌入式领域最常用的TCP/IP协议栈。它的ARP实现非常精简,咱们来看看核心代码。
4.5.1 ARP表结构
struct etharp_entry {
struct etharp_entry *next; // 链表指针
struct etharp_entry *prev;
ip_addr_t ipaddr; // IP地址
struct eth_addr ethaddr; // MAC地址
u8_t state; // 状态:ETHARP_STATE_EMPTY, ETHARP_STATE_PENDING, ETHARP_STATE_STABLE
u8_t time; // 生存时间计数器
u8_t ctime; // 创建时间
};
LwIP的ARP表是一个静态数组,默认大小是10个条目。为什么用静态数组?因为嵌入式设备不能动态分配内存,怕内存碎片。我个人觉得这个设计很务实。
4.5.2 ARP请求发送流程
err_t etharp_request(struct netif *netif, const ip_addr_t *ipaddr) {
struct etharp_hdr *hdr;
struct pbuf *p;
// 1. 分配pbuf
p = pbuf_alloc(PBUF_RAW, sizeof(struct etharp_hdr), PBUF_RAM);
if (p == NULL) {
return ERR_MEM;
}
// 2. 填充ARP头部
hdr = (struct etharp_hdr *)p->payload;
hdr->hwtype = htons(1); // 以太网
hdr->proto = htons(0x0800); // IP协议
hdr->hwlen = 6;
hdr->protolen = 4;
hdr->opcode = htons(ARP_REQUEST); // 操作码:请求
// 3. 填充源MAC和源IP
MEMCPY(&hdr->shwaddr, netif->hwaddr, ETH_HWADDR_LEN);
SMEMCPY(&hdr->sipaddr, &netif->ip_addr, sizeof(ip_addr_t));
// 4. 目标MAC填0,目标IP填目标地址
memset(&hdr->dhwaddr, 0, ETH_HWADDR_LEN);
SMEMCPY(&hdr->dipaddr, ipaddr, sizeof(ip_addr_t));
// 5. 通过以太网层发送(广播)
etharp_output(netif, p, (const ip_addr_t *)&ip_addr_broadcast);
return ERR_OK;
}
这段代码逻辑很清晰。注意第4步,目标MAC填0,因为还不知道嘛。第5步用广播地址发送,让所有人都能收到。
4.5.3 ARP响应处理
static err_t etharp_raw(struct netif *netif, const struct eth_addr *ethaddr,
const struct eth_addr *dest, const ip_addr_t *ipaddr,
const ip_addr_t *destipaddr, u16_t opcode) {
// ... 类似请求的填充过程,但opcode设为ARP_REPLY
// 目标MAC填请求方的MAC,目标IP填请求方的IP
// 单播发送
}
响应处理的关键点:
- 操作码改成
ARP_REPLY - 目标MAC和IP从请求包里提取出来
- 用单播方式发送,不是广播
4.5.4 缓存表更新策略
LwIP的ARP缓存更新遵循一个原则:谁主动谁更新。
- 收到ARP响应时,把对方的IP-MAC映射加入缓存表。
- 收到ARP请求时,如果请求的是自己的IP,也把对方的映射加入缓存表(这叫“偷听”)。
- 缓存表满了怎么办?LwIP会踢掉最旧的那个条目。
我的经验:LwIP默认的ARP表只有10个条目。如果你的设备要跟很多设备通信,记得在lwipopts.h里把ARP_TABLE_SIZE改大。我一般设成20,再大就浪费内存了。
4.6 常见问题与避坑
- ARP请求超时:LwIP默认重试5次,每次间隔1秒。如果5次都没响应,就返回超时。我建议在应用层也加个超时处理,别死等。
- ARP欺骗:局域网里如果有人恶意发送伪造的ARP响应,你的设备就会把数据发给错误的地方。LwIP没有防ARP欺骗的机制,需要自己在应用层加校验。
- 代理ARP:有些路由器会替其他设备回复ARP请求。如果你的设备跟路由器不在同一个网段,可能会遇到这个问题。
我曾经踩过的坑:在一个项目里,设备A和设备B通过一个交换机直连。设备A发ARP请求找设备B,但设备B的MAC地址变了。设备A的ARP缓存里还留着旧MAC,结果发了半天数据都丢了。后来我加了个机制:如果连续3次发送失败,就主动清除ARP缓存,重新请求。
4.7 小结
ARP协议看着简单,但实际项目中坑不少。记住几个要点:
- ARP请求是广播,响应是单播
- 缓存表别太大,TTL别太长
- 免费ARP用于IP冲突检测和MAC变更通知
- LwIP的ARP实现很精简,但该有的功能都有
下一章咱们讲ICMP协议,就是那个ping命令用的协议。到时候你会发现,ICMP和ARP经常一起出现,配合着用才能把网络调通。