现在的位置: 首页 > 综合 > 正文

通过ICMP协议测试目标主机是否在线

2018年04月02日 ⁄ 综合 ⁄ 共 3268字 ⁄ 字号 评论关闭
/*
	AliveChecker.h
	author: zhouciming@163.com
*/

#ifndef __ALIVE_CHECKER_H__
#define __ALIVE_CHECKER_H__

typedef unsigned short	ushort;
typedef unsigned long	ulong;

class CPing
{
public:
	CPing();
	~CPing();

	bool ping(ulong destIP, int timeout = 10000);

protected:
	bool init();
	ushort getCheckSum(ushort* addr, int len);	// 计算校验和

private:
	int m_sockfd;
};

#endif

/*
	AliveChecker.cpp
	author: zhouciming@163.com
*/

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netinet/ip_icmp.h>
#include "AliveChecker.h"

#define INVALID_SOCKET	-1
#define PACKET_SIZE		1024

CPing::CPing()
{
	m_sockfd = INVALID_SOCKET;
}

CPing::~CPing()
{
	if(m_sockfd != INVALID_SOCKET)
		close(m_sockfd);
}

bool CPing::init()		// 初始化socket fd
{
	if(m_sockfd != INVALID_SOCKET)
		close(m_sockfd);

	m_sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if (m_sockfd < 0)
	{
		perror("socket");
		return false;
	}
	return true;
}

// 向目标地址发送一个icmp数据包, 并接收响应来判断目标主机是否在线
bool CPing::ping(ulong destIP, int time_out)	// time_out: 超时时间(单位: 毫秒)
{
	if(m_sockfd == INVALID_SOCKET && init() == false)
		return false;
	
	struct timeval timeout;

	timeout.tv_sec = time_out / 1000;			// 秒
	timeout.tv_usec = (time_out % 1000) * 1000;	// 微秒
	if (setsockopt(m_sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) == -1)
	{
		perror("setsockopt");
		return false;
	}
	
	struct icmp* icmp;
	struct timeval *tval;
	char sendpacket[PACKET_SIZE] = {0};
	pid_t pid = getpid();

	icmp = (struct icmp*)sendpacket;
	icmp->icmp_type = ICMP_ECHO;	//回显请求
	icmp->icmp_code = 0;
	icmp->icmp_cksum = 0;
	icmp->icmp_seq = 0;
	icmp->icmp_id = pid;
	tval = (struct timeval*)icmp->icmp_data;
	gettimeofday(tval, NULL);		// 获取当前时间
	icmp->icmp_cksum = getCheckSum((ushort*)icmp, sizeof(struct icmp));

	struct sockaddr_in addr;
	
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = destIP;	// 设置目标地址

	// 发送ICMP数据包
	int n = sendto(m_sockfd, (char*)&sendpacket, sizeof(struct icmp), 0, (struct sockaddr*)&addr, sizeof(addr));
	if (n < 1)
	{
		return false;
	}
	
	while(1)	// 可能会接收到其他ping的应答消息, 所以这里用循环
	{
		int maxfds;
		fd_set readfds;
		struct sockaddr_in from;
		int fromlen = sizeof(from);
		char recvBuf[PACKET_SIZE] = {0};

		FD_ZERO(&readfds);
		FD_SET(m_sockfd, &readfds);
		maxfds = m_sockfd + 1;

		n = select(maxfds, &readfds, NULL, NULL, &timeout);
		if (n <= 0)
		{
			return false;
		}

		n = recvfrom(m_sockfd, recvBuf, sizeof(recvBuf), 0, (struct sockaddr *)&from, (socklen_t *)&fromlen);
		if (n < 1)
		{
			break;
		}

		ulong fromIP = from.sin_addr.s_addr;
		
		if (fromIP != destIP)	// 不是目标主机的echo信息
		{
			return false;
		}

		struct ip* iph = (struct ip *)recvBuf;
		icmp = (struct icmp *)(recvBuf + (iph->ip_hl << 2));

		if (icmp->icmp_type == ICMP_ECHOREPLY && icmp->icmp_id == pid)   // ICMP_ECHOREPLY回显应答
		{
			return true;
		}
	}
	return false;
}

ushort CPing::getCheckSum(ushort* addr, int len)
{
	int sum = 0;
	ushort *w = addr;
	ushort answer = 0;

	while(len > 1)
	{
		sum += *w++;
		len -= 2;
	}
	if( len == 1)
	{
		*(unsigned char *)(&answer) = *(unsigned char *)w;
		sum += answer;
	}

	sum = (sum >> 16) + (sum & 0xffff);
	sum += (sum >> 16);
	answer = ~sum;

	return answer;	
}

/*
	main.cpp
	author: zhouciming@163.com
*/

#include <stdio.h>
#include <arpa/inet.h>
#include "AliveChecker.h"

void test(const char* ip)
{
	CPing ping;
	in_addr_t destIP = inet_addr(ip);
	
	if(ping.ping(destIP))
	{
		printf("ping successful\n");
	}
	else
	{
		printf("ping failed\n");
	}
}

int main(int argc, char* argv[])
{
	if(argc < 2)
	{
		printf("Usage: %s <destIP>\n", argv[0]);
		return 1;
	}
	else
	{
		const char* ip = argv[1];
		test(ip);
	}

	return 0;
}

Makefile:

#CROSS = arm-hisiv100nptl-linux-
CXX = $(CROSS)g++
RM=rm -f
CFLAGS = -Wall -Os -DLINUX

ALL=a

all: $(ALL)

a: main.cpp AliveChecker.cpp
	$(CXX) $(CFLAGS) -o $@ $^

clean:
	$(RM) *.o $(ALL)

注意:以上编译出来的程序必须以root用户执行,因为ICMP协议用到了SOCK_RAW(原始套接字)。

抱歉!评论已关闭.