ARP原理及双网卡设备arp、ping查询问题

本文介绍了ARP地址解析协议的作用,详细解释了ARP如何在发送数据前查询目的MAC地址,包括同一子网和不同子网的情况。讨论了双网卡设备在同网段时,ping操作收到不同MAC回应的问题,并提出了疑问。此外,还提到了ARP的其他用途,如检测IP冲突。最后,展示了用于测试的`arpping.c`代码片段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

声明下,因为对底层协议不是很了解,如有错误,麻烦指正,谢谢!

1、先来简单了解下ARP地址解析协议的用途

设备存在网络层地址(即IP地址)和链路层地址(即MAC地址),MAC地址是适配器(即网卡)出场就固定了的,以16进制格式表示:“XX:XX:XX:XX:XX:XX”,48位。IP地址是人为划分的,以点分十进制表示:“192.168.1.1”,32位。

IP地址是可以变动的,好比人的姓名,可以有多个别名;而MAC地址就好比人的身份证是固定的。发送数据就好比找陌生人一样,首先要确认是不是你所要找的人,最终是查看身份证号是否匹配,而不是查看他的姓名是否匹配。所以一台设备往另一台设备发送数据,必须要确认对端的MAC地址是目的地址。但就如生活中我们只叫人的姓名,而不喊他的身份证号一样,因为好记;在上层编码发送数据的时候,也只记得对端的IP地址,而不知道它的MAC地址,这时候就需要使用ARP协议来查询自己所要找的IP地址对应的MAC地址了。

总结:内核以太网驱动,必须知道目的端的硬件地址才能发送数据


2、接着来说下ARP的操作过程。在发送ARP请求前,都会在本机的ARP缓冲中查看是否已经存在了所要查询的IP对应的MAC地址,因为ARP缓存是自动建立的(别人发送据过来,本机收到都会记录该数据包的源IP地址和其MAC地址到ARP缓存中),如果在本机查到了,则不需要发送ARP请求了。下面说下没有找到的情况(一般ARP的一条记录的缓存时间是20分钟),分两种情况:目的端在同一子网和目的端在不同子网。

(注:ARP包是广播发送的,即电缆上的所有以太网接口都要接收广播的数据帧,即你把两台不同网段的设备用交换机接在一起,都能收到对方的ARP请求和应答。不过并不能发送数据,所以正常情况同一子网都是同网段的设备,不同网段的设备通过路由器隔开。所以可以说,ARP只为同一个子网上的节点解析IP地址)

虽然ARP是链路层数据包,但设备在询问目的IP并发送ARP请求时,是经过第三层网络层的路由表来决定的。
1、同一子网
目的IP经路由表查询得知是在同一子网的,适配器在链路层帧中封装这个ARP分组,用MAC广播地址作为帧的目的地址,目的IP作为帧的目的地址,将该帧传输进子网。目的IP收到该广播包后单播回应ARP包。发送端接收到该ARP回应包后,自动在ARP缓存中增加一条对应记录,最后将数据包发送到目的设备。

2、不同子网
目的IP经路由表查询得知不是在同一个子网的,则之后数据是默认发送给网关的,一般也就是路由器。此时适配器在链路层帧中封装这个ARP分组,用MAC广播地址作为帧的目的地址,网关的IP地址作为帧的目的地址,将该帧传输进子网,最后得到的是网关的MAC地址。而后数据发送给路由器,路由器再在另一子网,询问目的IP的MAC地址。可见在不同网段间传输数据包,目的IP是始终不变的,目的MAC地址会改为中间经过的路由器MAC地址。

在《计算机网络自顶向下方法》中有如下这段话
“包含该ARP查询的帧能被子网上的所有其他适配器接收到,并且(由于广播地址)每个适配器都把在该帧里的ARP分组向上传递给节点中的ARP模块。每个节点检查它的IP地址是否与ARP分组中的目的IP地址相匹配。(至多)一个匹配的节点给查询节点发送回一个带有所希望映射的相应ARP分组”


3、问题来了  (目前没有搞明白,在这里记录下,后面想明白了再来修改,或清楚的跟我讲解下,谢谢)

一台双网卡设备,有线接入路由器LAN口,无线接入该路由器热点,PC机也接入该路由器LAN口,并设有设备有线IP和无线IP同网段的IP地址(即PC都能访问有线和无线IP)。
现在的现象是,PC机ping无线IP,会得到两个ARP回应包(对应有线MAC和无线MAC);但PC机ping有线IP,则只得到一个ARP回应包(对应有线MAC)。

对于ping无线IP的情况,一开始我是这么理解的:
PC机发送ARP包后,有线网口和无线网口都接收到该ARP包,然后都从协议栈查询是否有所要查询的IP。因为有线和无线使用同一个协议栈,即从有线网卡进来的ARP也可以查询到无线的IP地址,并回应PC该无线IP地址对应的MAC地址是有线网卡的。


按这样的思考逻辑,在ping设备有线IP的时候,也应该会有两个ARP回应包,然而只有一个,显然上面的思考是有问题。
然后就是实际抓包,发现设备ping无线IP的时候,在设备里用tcpdump抓无线网卡的包,并没有抓到相应的ARP包,这就不明白,PC得到的无线网卡的ARP包是谁回应的,难道是路由器回应的。如果是路由器回应的,就感觉好解释,为啥ping有线包的时候只有一个ARP包回应,因为路由器里没有有线IP的记录,不过这完全是自己的猜测了...


4、说下ARP的其他用途
1)检测是否有IP冲突,即arp查询自己的IP,如有回应,则说明冲突了
2)发送ARP请求时,让所有接收到的主机更新其MAC地址

附加:可以用PING命令进行测试ARP包是二层协议数据包。两台同网段的设备A,B接在同个交换机下,将要ping的那台B设备的路由表删掉,在A设备ping B设备,虽然无回应,但抓包可以看到是有ARP回应的。因为PING命令首先判断目的IP在同网段的,发送了ARP包,并得到了回应,但发送的ICMP包,虽然B设备接收到了,但没有路由表能出数据包导致无回应。


5、最后下面代码来至busybox\networking\udhcp\arpping.c,可以进行实际测试。在执行前,先执行arp -d命令清空arp缓存,执行程序后,通过arp -a查看得到的arp缓存。

/*
 * arpping.c
 *
 * Mostly stolen from: dhcpcd - DHCP client daemon
 * by Yoichi Hariguchi <yoichi@fore.com>
 */

#include <time.h>
#include <sys/socket.h>
#include <netinet/if_ether.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include "dhcpd.h"
#include "arpping.h"
#include "common.h"

/* args:	yiaddr - what IP to ping
 *		ip - our ip
 *		mac - our arp address
 *		interface - interface to use
 * retn:	1 addr free
 *		0 addr used
 *		-1 error
 */

/* FIXME: match response against chaddr */
int arpping(uint32_t yiaddr, uint32_t ip, uint8_t *mac, char *interface)
{

	int	timeout = 2;
	int	optval = 1;
	int	s;			/* socket */
	int	rv = 1;			/* return value */
	struct sockaddr addr;		/* for interface name */
	struct arpMsg	arp;
	fd_set		fdset;
	struct timeval	tm;
	time_t		prevTime;


	if ((s = socket (PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP))) == -1) {
#ifdef IN_BUSYBOX
		LOG(LOG_ERR, bb_msg_can_not_create_raw_socket);
#else
		LOG(LOG_ERR, "Could not open raw socket");
#endif
		return -1;
	}

	if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)) == -1) {
		LOG(LOG_ERR, "Could not setsocketopt on raw socket");
		close(s);
		return -1;
	}

	/* send arp request */
	memset(&arp, 0, sizeof(arp));
	memcpy(arp.h_dest, MAC_BCAST_ADDR, 6);		/* MAC DA */
	memcpy(arp.h_source, mac, 6);			/* MAC SA */
	arp.h_proto = htons(ETH_P_ARP);			/* protocol type (Ethernet) */
	arp.htype = htons(ARPHRD_ETHER);		/* hardware type */
	arp.ptype = htons(ETH_P_IP);			/* protocol type (ARP message) */
	arp.hlen = 6;					/* hardware address length */
	arp.plen = 4;					/* protocol address length */
	arp.operation = htons(ARPOP_REQUEST);		/* ARP op code */
	memcpy(arp.sInaddr, &ip, sizeof(ip));		/* source IP address */
	memcpy(arp.sHaddr, mac, 6);			/* source hardware address */
	memcpy(arp.tInaddr, &yiaddr, sizeof(yiaddr));	/* target IP address */

	memset(&addr, 0, sizeof(addr));
	strcpy(addr.sa_data, interface);
	if (sendto(s, &arp, sizeof(arp), 0, &addr, sizeof(addr)) < 0)
		rv = 0;

	/* wait arp reply, and check it */
	tm.tv_usec = 0;
	prevTime = uptime();
	while (timeout > 0) {
		FD_ZERO(&fdset);
		FD_SET(s, &fdset);
		tm.tv_sec = timeout;
		if (select(s + 1, &fdset, (fd_set *) NULL, (fd_set *) NULL, &tm) < 0) {
			DEBUG(LOG_ERR, "Error on ARPING request: %m");
			if (errno != EINTR) rv = 0;
		} else if (FD_ISSET(s, &fdset)) {
			if (recv(s, &arp, sizeof(arp), 0) < 0 ) rv = 0;
			if (arp.operation == htons(ARPOP_REPLY) &&
			    memcmp(arp.tHaddr, mac, 6) == 0 &&
			    *((uint32_t *) arp.sInaddr) == yiaddr) {
				DEBUG(LOG_INFO, "Valid arp reply receved for this address");
				rv = 0;
				break;
			}
		}
		timeout -= uptime() - prevTime;
		prevTime = uptime();
	}
	close(s);
	DEBUG(LOG_INFO, "%salid arp replies for this address", rv ? "No v" : "V");
	return rv;
}

对应的arpping.h

/*
 * arpping .h
 */

#ifndef ARPPING_H
#define ARPPING_H

#include <netinet/if_ether.h>
#include <net/if_arp.h>
#include <net/if.h>
#include <netinet/in.h>

struct arpMsg {
	/* Ethernet header */
	u_char   h_dest[6];			/* destination ether addr */
	u_char   h_source[6];			/* source ether addr */
	u_short  h_proto;			/* packet type ID field */

	/* ARP packet */
	uint16_t htype;				/* hardware type (must be ARPHRD_ETHER) */
	uint16_t ptype;				/* protocol type (must be ETH_P_IP) */
	uint8_t  hlen;				/* hardware address length (must be 6) */
	uint8_t  plen;				/* protocol address length (must be 4) */
	uint16_t operation;			/* ARP opcode */
	uint8_t  sHaddr[6];			/* sender's hardware address */
	uint8_t  sInaddr[4];			/* sender's IP address */
	uint8_t  tHaddr[6];			/* target's hardware address */
	uint8_t  tInaddr[4];			/* target's IP address */
	uint8_t  pad[18];			/* pad for min. Ethernet payload (60 bytes) */
} ATTRIBUTE_PACKED;

/* function prototypes */
int arpping(uint32_t yiaddr, uint32_t ip, uint8_t *arp, char *interface);

#endif

### 配置Linux Ubuntu双网卡在同一网段共存 对于Ubuntu Linux系统中的双网卡配置,在同一网段中共存的关键在于正确设置IP地址分配策略以及路由规则,防止冲突并确保通信顺畅。当两块网卡接入相同子网时,应避免自动获取相同的IP地址范围造成混乱。 #### 修改网络接口配置文件 编辑`/etc/netplan/*.yaml` 文件(具体名称可能不同),以适应特定环境需求: ```bash network: version: 2 ethernets: enp0s3: dhcp4: no addresses: - 192.168.1.100/24 gateway4: 192.168.1.1 nameservers: addresses: [8.8.8.8, 8.8.4.4] enp0s8: dhcp4: no addresses: - 192.168.1.101/24 ``` 上述示例中,假设两个物理网口分别为`enp0s3` 和 `enp0s8` ,两者被赋予了相邻但不重复的静态IP地址[^1]。 #### 应用Netplan配置变更 保存修改后的YAML文件,并执行命令应用新的网络配置: ```bash sudo netplan apply ``` 这一步骤将使新设定生效,允许操作系统识别更新过的网络参数。 #### 设置永久ARP忽略选项 为了避免潜在的MAC地址冲突问题,可以通过调整内核参数来控制系统的ARP行为。创建或编辑 `/etc/sysctl.d/50-custom.conf` 文件加入如下内容: ```bash net.ipv4.conf.all.arp_ignore = 1 net.ipv4.conf.default.arp_ignore = 1 net.ipv4.conf.enp0s3.arp_announce = 2 net.ipv4.conf.enp0s8.arp_announce = 2 ``` 之后运行 sysctl 命令加载这些更改: ```bash sudo sysctl --system ``` 此操作有助于减少因多路径冗余带来的不确定性,提高稳定性[^2]。 #### 测试连通性 完成以上步骤后,建议使用 ping 工具测试各设备间的可达性和响应时间;同时利用 iperf 或其他工具评估带宽性能指标,验证是否达到预期效果[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值