概述
socket编码通讯的时候,目的地址一般都是域名或者对端的IP地址,如果是域名先有一个域名解析的过程以获取到对端的IP地址,在以太网通讯中,IP属于三层通讯,往下的第二层是MAC地址,如果是局域网通讯就需要根据IP地址查询到MAC地址,填充第二层的数据包。
arp缓存就是为了方便快速查找mac地址而设计的,通常有以太网协议栈自己维护的,所以并不需要我们主动参与。
系统提供的命令和接口
Linux系统提供了一些操作命令和接口文件,方便用户查看、操作arp缓存,命令包括arp、ip neigh等,还有接口文件 /proc/net/arp,简单说明如下。
arp命令操作
Linux中用命令 arp 来实现arp缓存的查询、和增删操作,命令具体使用方式通过 arp -h 或者 man arp,可以查看,比如:
- arp -an 可以查看当前 arp 缓存的记录
- arp -i 可以查看指定网卡的 arp 缓存的记录
- arp -d 192.168.1.x 可以删除一条记录
- arp -s 192.168.1.x aa:bb:cc:ee:dd:11 temp 可以新增一条临时记录,当然arp命令会判断host(192.168.1.x)是否合理(可以匹配到某一张网卡的网段)。
ip neigh 命令操作
事实上 arp 命令已经被标记为 obsolete , 推荐使用 ip neigh 命令来代替 arp 命令,虽然我觉得 arp 命令更加直观简单。
同样可以用 ip neigh help 来查看使用方法:
# ip neigh help
Usage: ip neigh { add | del | change | replace }
{ ADDR [ lladdr LLADDR ] [ nud STATE ] | proxy ADDR } [ dev DEV ]
[ router ] [ extern_learn ] [ protocol PROTO ]
ip neigh { show | flush } [ proxy ] [ to PREFIX ] [ dev DEV ] [ nud STATE ]
[ vrf NAME ]
STATE := { permanent | noarp | stale | reachable | none |
incomplete | delay | probe | failed }proc 文件查看
另外,还可以通过文件查看当前缓存情况:
# cat /proc/net/arp
IP address HW type Flags HW address Mask Device
172.18.89.100 0x1 0x2 aa:bb:cc:ee:dd:11 * ens34
172.18.89.1 0x1 0x2 00:50:56:c0:00:01 * ens34
172.18.145.254 0x1 0x2 00:50:56:e3:75:35 * ens33
172.18.145.2 0x1 0x2 00:50:56:ea:60:d5 * ens33ioctl 代码支持 arp 的相关操作
实际我们代码中,如果要查看、修改 arp 缓存,我们可以利用system或者popen,加上上面的命令来完成编码,但是形式上有点丑陋。
另外,也可以通过 ioctl 函数来完成对 arp 缓存的操作,包括查询、增加(修改)和删除,下面逐一说明。
先看几个结构体和宏定义:
#define SIOCDARP 0x8953 /* delete ARP table entry */
#define SIOCGARP 0x8954 /* get ARP table entry */
#define SIOCSARP 0x8955 /* set ARP table entry */
#define ATF_COM 0x02 /* completed entry (ha valid) */
#define ATF_PERM 0x04 /* permanent entry */
#define ATF_PUBL 0x08 /* publish entry */
#define ATF_USETRAILERS 0x10 /* has requested trailers */
#define ATF_NETMASK 0x20 /* want to use a netmask (only for proxy entries) */
#define ATF_DONTPUB 0x40 /* don't answer this addresses */
struct arpreq {
struct sockaddr arp_pa; /* protocol address */
struct sockaddr arp_ha; /* hardware address */
int arp_flags; /* flags */
struct sockaddr arp_netmask; /* netmask (only for proxy arps) */
char arp_dev[IFNAMSIZ];
};查询
ioctl 可以根据ip地址查询mac地址(可惜不能根据 mac 查询 ip):
static int do_test_ioctol_case0()
{
const char *dev = "enp4s0";
const char *host = "192.168.3.101";
int sockfd = -1;
struct arpreq arpreqest;
struct sockaddr_in *arp_pa = (struct sockaddr_in *)&arpreqest.arp_pa;
unsigned char *mac = NULL;
// 一个必要的 socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 明确要获取哪一张网卡的信息,用网卡的名称来指示
memset(&arpreqest, 0, sizeof(arpreqest));
strncpy(arpreqest.arp_dev, dev, IFNAMSIZ);
arp_pa->sin_family = AF_INET;
arp_pa->sin_addr.s_addr = inet_addr(host);
if(ioctl(sockfd, SIOCGARP, &arpreqest) < 0)
{
if(ENXIO == errno)
{
// 设备名称不对,设备不存在
printf("Failed to find host(%s)\n", host);
}
else if(ENODEV == errno)
{
// 找不到这条记录
printf("Failed to find dev(%s)\n", dev);
}
else
{
// 其他错误
printf("Failed to exec(SIOCGARP): %m\n");
}
goto out;
}
if(arpreqest.arp_flags & ATF_COM)
{
// 有完整的记录,就表示有mac地址
mac = (unsigned char *)&arpreqest.arp_ha.sa_data[0];
printf("Host(%s)'s hardware address is: %02x:%02x:%02x:%02x:%02x:%02x", host, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
if(arpreqest.arp_flags & ATF_PERM)
{
printf("\t%s", "Permanent");
}
if(arpreqest.arp_flags & ATF_PUBL)
{
printf("\t%s", "Publish");
}
if(arpreqest.arp_flags & ATF_USETRAILERS)
{
printf("\t%s", "Has requested trailers");
}
if(arpreqest.arp_flags & ATF_NETMASK)
{
printf("\t%s", "Use a netmask");
}
if(arpreqest.arp_flags & ATF_DONTPUB)
{
printf("\t%s", "Don's answer this addresses");
}
printf("\n");
out:
close(sockfd);
return 0;
}增加
增加记录的代码和查询的比较类似,需要注意的是查询只写 ip 地址,增加需要填写 ip和mac 地址:
static int do_test_ioctol_case1()
{
const char *dev = "enp4s0";
const char *host = "192.168.3.102";
const char *macstr = "11:22:33:aa:bb:ce";
int sockfd = -1;
struct arpreq arpreqest;
struct sockaddr_in *arp_pa = (struct sockaddr_in *)&arpreqest.arp_pa;
unsigned char mac[6] = {0};
// 一个必要的 socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 明确要获取哪一张网卡的信息,用网卡的名称来指示
memset(&arpreqest, 0, sizeof(arpreqest));
strncpy(arpreqest.arp_dev, dev, IFNAMSIZ);
// arpreqest.arp_flags = ATF_PERM | ATF_COM;
arpreqest.arp_flags = ATF_COM;
arp_pa->sin_family = AF_INET;
arp_pa->sin_addr.s_addr = inet_addr(host);
sscanf(macstr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
memcpy((unsigned char*)arpreqest.arp_ha.sa_data, mac, 6);
// get_hw_addr((unsigned char*)arpreqest.arp_ha.sa_data, mac);
if(ioctl(sockfd, SIOCSARP, &arpreqest) < 0)
{
if(ENXIO == errno)
{
// 设备名称不对,设备不存在
printf("Failed to find host(%s)\n", host);
}
else if(ENODEV == errno)
{
// 找不到这条记录
printf("Failed to find dev(%s)\n", dev);
}
else
{
// 其他错误
printf("Failed to exec(SIOCGARP): %m\n");
}
goto out;
}
printf("\n");
out:
close(sockfd);
return 0;
}删除
删除的代码可以和增加的代码几乎完全一样,ioctl 的第二个参数不一样而已:
static int do_test_ioctol_case2()
{
const char *dev = "enp4s0";
const char *host = "192.168.3.102";
// const char *macstr = "11:22:33:aa:bb:ce";
int sockfd = -1;
struct arpreq arpreqest;
struct sockaddr_in *arp_pa = (struct sockaddr_in *)&arpreqest.arp_pa;
// unsigned char mac[6] = {0};
// 一个必要的 socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 明确要获取哪一张网卡的信息,用网卡的名称来指示
memset(&arpreqest, 0, sizeof(arpreqest));
strncpy(arpreqest.arp_dev, dev, IFNAMSIZ);
// arpreqest.arp_flags = ATF_PERM | ATF_COM;
// arpreqest.arp_flags = ATF_COM;
arp_pa->sin_family = AF_INET;
arp_pa->sin_addr.s_addr = inet_addr(host);
// 删除的时候,可以不设置mac地址,只设置IP地址。但是不能确ip地址。
// sscanf(macstr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
// memcpy((unsigned char*)arpreqest.arp_ha.sa_data, mac, 6);
if(ioctl(sockfd, SIOCDARP, &arpreqest) < 0)
{
if(ENXIO == errno)
{
// 设备名称不对,设备不存在
printf("Failed to find host(%s)\n", host);
}
else if(ENODEV == errno)
{
// 找不到这条记录
printf("Failed to find dev(%s)\n", dev);
}
else
{
// 其他错误
printf("Failed to exec(SIOCGARP): %m\n");
}
goto out;
}
printf("\n");
out:
close(sockfd);
return 0;
}重要结构体回顾:
struct arpreq
struct arpreq {
struct sockaddr arp_pa; /* protocol address */
struct sockaddr arp_ha; /* hardware address */
int arp_flags; /* flags */
struct sockaddr arp_netmask; /* netmask (only for proxy arps) */
char arp_dev[IFNAMSIZ];
};这个结构体中,不管哪个操作都需要正确设置 arp_dev。
struct sockaddr arp_pa 不管什么操作,这个用于设置IP地址,需要注意的是记得设置协议类型(比如IPV4用AT_INET)。
struct sockaddr arp_ha 设置操作,设置mac地址;查询操作,返回的mac地址保存于这个结构体中;删除操作,该字段不起作用。
int arp_flags 有以下几个可选项
#define ATF_COM 0x02 /* completed entry (ha valid) */ #define ATF_PERM 0x04 /* permanent entry */ #define ATF_PUBL 0x08 /* publish entry */ #define ATF_USETRAILERS 0x10 /* has requested trailers */ #define ATF_NETMASK 0x20 /* want to use a netmask (only for proxy entries) */ #define ATF_DONTPUB 0x40 /* don't answer this addresses */