Linux下操作ARP表项


        ARP缓存表arp_tbl由协议栈在运行期间自动维护,包括邻居新建,更新,回收等。同时,TCP/IP协议栈的实现中也提供了三个命令,可以用来由用户维护arp_tbl,这三个命令分别是SIOCDARP(删除arp_tbl中的一个邻居),SIOCSARP(设置arp_tbl中的一个邻居), SIOCGARP(获取arp_tbl中的一个邻居)。用户使用系统调用ioctl来传递这三个命令,命令参数是结构体struct arpreq,其定义如下:

struct arpreq {
     struct sockaddr   arp_pa;           //协议地址
     struct sockaddr   arp_ha;           //硬件地址
     int               arp_flags;        //标志位
     struct sockaddr   arp_netmask;      //网络掩码(只用于代理ARP)
     char              arp_dev[16];      //对应的网络设备接口的名称。
 };



        使用SIOCDARP命令时,只要在struct arpreq结构体填入arp_pa,arp_dev,内核会根据arp_pa中的邻居IP地址从myarp_tbl的哈希表hash_buckets中找出该邻居,并把它的状态更新为NUD_FAILED(失败),则在下一次的arp垃圾回收中,该邻居就会被会收掉。


        使用SIOCSARP命令时,需要设置struct arpreq的arp_pa,arp_ha,arp_flags。如果arp_tbl中已存在该邻居,会更新该邻居,否则新建一个邻居,其状态为 NUD_STALE,如果arp_flags中有ATF_PERM,状态再加上NUD_PERMANENT。


        使用SIOCGARP时,只需要arp_pa,从arp_tbl中找到该邻居并返回,不过,一般要查看arp缓存,并不使用SIOCGARP命令,而是从proc/net/arp文件中取。



        struct arpreq的arp_flags上的标志有如下一些:


#define ATF_COM         0x02    //已完成的邻居(成员ha有效,且含有正确的MAC地址)
#define ATF_PERM        0x04    //永久性的邻居(邻居状态有NUD_PERMANENT)
#define ATF_PUBL        0x08    //发布该邻居
#define ATF_USETRAILERS 0x10    //不是非常清楚
#define ATF_NETMASK     0x20    //仅用于代理ARP
#define ATF_DONTPUB     0x40    //不处理该邻居


关于arp表项的状态机示意图如下:()

centos 查看arp表 arp表 linux_socket



示例代码:


#include <stdio.h> 
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>
#include <string.h>



/*******************************************************************************
* 函 数 名	:	arpDel
* 负 责 人	:	
* 创建日期	:	
* 函数功能	:	LINUX删除ARP表项
* 输入参数	:	
                   char *ifname     :   网络接口名,如eth0
                   char *ipStr      :   需要删除的IP,点分十进制串
* 输出参数	:
* 返 回 值	:
				0		:	成功
				-1		:	失败
* 调用关系	: 
* 其	它	: 
* 修改记录	:	
*******************************************************************************/
int arpDel(char *ifname, char *ipStr)
{
	if(ifname == NULL || ipStr == NULL)
	{
		printf("para is null.\n");
		return -1;
	}

	struct arpreq req;
	struct sockaddr_in *sin;
	int ret = 0;
	int sock_fd = 0;

	memset(&req, 0, sizeof(struct arpreq));
	sin = (struct sockaddr_in *)&req.arp_pa;
	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = inet_addr(ipStr);
	//arp_dev长度为[16],注意越界
	strncpy(req.arp_dev, ifname, 15);

	sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sock_fd < 0)
	{
		printf("get socket error.\n");
		return -1;
	}

	ret = ioctl(sock_fd, SIOCDARP, &req);
	if(ret < 0)
	{
		printf("ioctl error.\n");
		close(sock_fd);
		return -1;
	}

	close(sock_fd);
	return 0;
}




/*******************************************************************************
* 函 数 名	:	arpGet
* 负 责 人	:	
* 创建日期	:	
* 函数功能	:	LINUX获取ARP表项
* 输入参数	:	
                   char *ifname     :   网络接口名,如eth0
                   char *ipStr      :   需要删除的IP,点分十进制串
* 输出参数	:
* 返 回 值	:
				0		:	成功
				-1		:	失败
* 调用关系	: 
* 其	它	: 
* 修改记录	:	
*******************************************************************************/
int arpGet(char *ifname, char *ipStr)
{
	if(ifname == NULL || ipStr == NULL)
	{
		printf("para is null.\n");
		return -1;
	}

	struct arpreq req;
	struct sockaddr_in *sin;
	int ret = 0;
	int sock_fd = 0;

	memset(&req, 0, sizeof(struct arpreq));

	sin = (struct sockaddr_in *)&req.arp_pa;
	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = inet_addr(ipStr);

	//arp_dev长度为[16],注意越界
	strncpy(req.arp_dev, ifname, 15);

	sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sock_fd < 0)
	{
		printf("get socket error.\n");
		return -1;
	}

	ret = ioctl(sock_fd, SIOCGARP, &req);
	if(ret < 0)
	{
		printf("ioctl error.\n");
		close(sock_fd);
		return -1;
	}

	unsigned char *hw = (unsigned char *)req.arp_ha.sa_data;
	printf("%#x-%#x-%#x-%#x-%#x-%#x\n", hw[0], hw[1], hw[2], hw[3], hw[4], hw[5]);
	printf("%#x\n", req.arp_flags);
	close(sock_fd);
	return 0;
}





int getHwAddr(char *buff, char *mac)
{
	if( buff == NULL || mac == NULL )
	{
		return -1;
	}

	int i = 0;
	unsigned int p[6];

	if(sscanf(mac, "%x:%x:%x:%x:%x:%x", &p[0], &p[1], &p[2], &p[3], &p[4], &p[5]) < 6)
	{
		return -1;
	}

	for(i = 0; i < 6; i ++)
	{
		buff[i] = p[i];
	}

	return 0;
}



/*******************************************************************************
* 函 数 名	:	arpSet
* 负 责 人	:	
* 创建日期	:	
* 函数功能	:	LINUX增加ARP表项
* 输入参数	:	
                   char *ifname     :   网络接口名,如eth0
                   char *ipStr      :   IP,点分十进制串
                   char *mac        :   MAC地址,如00:a1:b2:c3:d4:e5
* 输出参数	:
* 返 回 值	:
				0		:	成功
				-1		:	失败
* 调用关系	: 
* 其	它	: 
* 修改记录	:	
*******************************************************************************/
int arpSet(char *ifname, char *ipStr, char *mac)
{
	if(ifname == NULL || ipStr == NULL || mac == NULL)
	{
		printf("para is null.\n");
		return -1;
	}

	struct arpreq req;
	struct sockaddr_in *sin;
	int ret = 0;
	int sock_fd = 0;

	memset(&req, 0, sizeof(struct arpreq));
	sin = (struct sockaddr_in *)&req.arp_pa;
	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = inet_addr(ipStr);
	//arp_dev长度为[16],注意越界
	strncpy(req.arp_dev, ifname, 15);
	req.arp_flags = ATF_PERM | ATF_COM;
    
	if(getHwAddr((char *)req.arp_ha.sa_data, mac) < 0)
	{
		printf("get mac error.\n");
		return -1;
	}

	sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sock_fd < 0)
	{
		printf("get socket error.\n");
		return -1;
	}

	ret = ioctl(sock_fd, SIOCSARP, &req);
	if(ret < 0)
	{
		printf("ioctl error.\n");
		close(sock_fd);
		return -1;
	}

	close(sock_fd);
	return 0;
}



int main(int argc, char *argv[])
{
	printf("---------------------------------------\n");
	arpSet("eth0", "5.5.5.5", "00:a3:b4:c5:d6:e7");
	printf("---------------------------------------\n");
	arpGet("eth0", "5.5.5.5");
	printf("---------------------------------------\n");
	printf("retvalue=%d\n", arpDel("eth0", "5.5.5.5"));
	printf("---------------------------------------\n");
	arpGet("eth0", "5.5.5.5");

	return 0;
}