声明下,因为对底层协议不是很了解,如有错误,麻烦指正,谢谢!
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)。
设备存在网络层地址(即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