linux使用ICMP实现ping和traceroute

本文档展示了一段C++代码,用于实现ICMP协议的Ping和Traceroute功能。代码中包含了SocketIcmp类,用于发送和接收ICMP数据包,并使用SelectPoller类进行事件监听。程序首先通过DNS解析获取目标主机的IP地址,然后进行Ping测试,最后执行Traceroute追踪路由。

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

测试环境:ubuntu20.04

/**
 * @file sockIcmp.cpp
 * @author robinfox (390017268@qq.com)
 * @brief
 * @version 0.1
 * @date 2022-05-16
 *
 * @copyright Copyright (c) 2022
 *
 * gcc sockIcmp.cpp -lpthread -lstdc++  -w -g -o icmp
 */

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <netdb.h>
#include <string.h>

#include <bits/stdc++.h>
#include "MyTimer.h"

using namespace std;
using namespace oneapm;

#define PACKET_SIZE 4096
#define ERROR 0
#define SUCCESS 1

class Info
{
public:
	Info(const string &s) : type(0), state(0), seq(0), maxMs(0), minMs(0), avgMs(0), url(s)
	{
		bzero(&addr, sizeof(addr));
		timer.start();
	}
	int type;
	int state;
	int err;
	struct sockaddr_in addr;
	int ttl;  // ping return ttl
	int seq;

	// trace route 3 times
	double maxMs;
	double minMs;
	double avgMs;

	string url;
	string cname;

	std::deque<string> ipv4;  // ip array
	std::deque<string> alias; // names
	std::deque<string> hops;  // trace route hops

	Timer timer;

	Info() = delete;
	Info(const Info &) = delete;
};

using InfoPtr = std::shared_ptr<Info>;

class SocketIcmp
{
public:
	static InfoPtr nameToIp4(const string &s);

public:
	SocketIcmp();
	virtual ~SocketIcmp();

	int init();
	int sendIcmp(InfoPtr &info, int ttl, int len=128);
	int recvIcmp(InfoPtr &info);

	int sockfd;

private:
	
	char sendpacket[PACKET_SIZE];
	char recvpacket[PACKET_SIZE];
	pid_t pid;

private:
	unsigned short cal_chksum(unsigned short *addr, int len);
};

using SockIcmpPtr = std::shared_ptr<SocketIcmp>;

class SelectPoller
{
public:
	int poll_once();
	int init();
	SockIcmpPtr getSock() { return sock; }

private:
	SockIcmpPtr sock = nullptr;
	int maxfds = 0;
	fd_set readfds;
};

///////////////////////////////////////////////////////////////

static thread_local char t_resolveBuffer[64 * 1024];
InfoPtr SocketIcmp::nameToIp4(const string &url)
{
	char ip[INET_ADDRSTRLEN];
	char *ptr, **pptr;

	struct hostent host;
	struct hostent *phost = NULL;
	int herrno = 0;
	memset(&host, 0, sizeof(host));

	InfoPtr info = std::make_shared<Info>(url);
	int ret = gethostbyname_r(url.c_str(), &host, t_resolveBuffer, sizeof(t_resolveBuffer), &phost, &herrno);

	if (ret == 0 && phost != NULL)
	{
		// assert(he->h_addrtype == AF_INET && he->h_length == sizeof(uint32_t));
		// out->addr_.sin_addr = *reinterpret_cast<struct in_addr*>(he->h_addr);
		// printf("official hostname: %s\n", phost->h_name);
		info->cname = phost->h_name;

		for (pptr = phost->h_aliases; *pptr != NULL; pptr++)
		{
			// printf("\talias: %s\n", *pptr);
			info->alias.emplace_back(*pptr);
		}

		switch (phost->h_addrtype)
		{
		case AF_INET:
			pptr = phost->h_addr_list;
			/// h_addr_list[0]
			info->addr.sin_addr = *reinterpret_cast<struct in_addr *>(phost->h_addr);
			info->addr.sin_family = AF_INET;
			for (; *pptr != NULL; pptr++)
			{
				inet_ntop(phost->h_addrtype, *pptr, ip, sizeof(ip));
				// printf("\taddress: %s\n", ip);
				info->ipv4.emplace_back(ip);
			}

			info->state = 1;
			break;
		default:
			// printf("unknow address type.");
			info->state = -1;
			info->err = herrno;
			break;
		}
	}
	else
	{
		info->state = -1;
		info->err = herrno;
	}

	return info;
}

SocketIcmp::SocketIcmp()
{
	sockfd = -1;
}

SocketIcmp::~SocketIcmp()
{
	if (sockfd != -1)
		close(sockfd);
}

int SocketIcmp::init()
{
	// 取得PID,作为Ping的Sequence ID
	pid = getpid();

	// 取得socket  。  如果没加sudo 这里会报错
	sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if (sockfd < 0)
	{
		printf("create socket error\n");
		return errno;
	}

	int timeout = 3000;

	struct timeval timeo;
	// 设定TimeOut时间
	timeo.tv_sec = timeout / 1000;
	timeo.tv_usec = timeout % 1000;

	if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo)) == -1)
	{
		// printf("ip:%s,setsockopt error\n", ips);
		return ERROR;
	}
}

// icmp header 效验算法
unsigned short SocketIcmp::cal_chksum(unsigned short *addr, int len)
{
	int nleft = len;
	int sum = 0;
	unsigned short *w = addr;
	unsigned short answer = 0;

	while (nleft > 1)
	{
		sum += *w++;
		nleft -= 2;
	}

	if (nleft == 1)
	{
		*(unsigned char *)(&answer) = *(unsigned char *)w;
		sum += answer;
	}

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

	return answer;
}

int SocketIcmp::sendIcmp(InfoPtr &info, int ttl, int len)
{
	// 设定Ping包
	memset(sendpacket, 0, sizeof(sendpacket));

	struct timeval *tval;

	struct ip *iph;
	struct icmp *icmp;

	icmp = (struct icmp *)sendpacket;
	icmp->icmp_type = ICMP_ECHO; //回显请求
	icmp->icmp_code = 0;
	icmp->icmp_cksum = 0;
	icmp->icmp_seq = info->seq++;
	icmp->icmp_id = pid;

	tval = (struct timeval *)icmp->icmp_data;
	gettimeofday(tval, NULL);
	icmp->icmp_cksum = cal_chksum((unsigned short *)icmp, len+8); //校验

	if (ttl < 1)
		ttl = 60;
	setsockopt(sockfd, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl)); // set ttl  on all sockets
	int n;
	// 发包 。可以把这个发包挪到循环里面去。
	//printf("%s \n", inet_ntoa(info->addr.sin_addr));
	n = sendto(sockfd, (char *)&sendpacket, len+8, 0, (struct sockaddr *)&(info->addr), sizeof(info->addr));
	if (n < 1)
	{
		printf("sendto error\n");
		return errno;
	}
	else
	{
		//printf("send icmp packet len = %d \n", n);
	}

	return n;
}

int SocketIcmp::recvIcmp(InfoPtr &info)
{
	char ip[INET_ADDRSTRLEN];

	struct sockaddr_in fromAddr;
	memset(recvpacket, 0, sizeof(recvpacket));
	int fromlen = sizeof(fromAddr);
	int n = recvfrom(sockfd, recvpacket, sizeof(recvpacket), 0, (struct sockaddr *)&fromAddr, (socklen_t *)&fromlen);
	//printf("recvfrom Len:%d\n", n);
	if (n < 1)
	{
		info->hops.emplace_back("");
		return 0;
	}

	struct ip *iph;
	struct icmp *icmp;

	iph = (struct ip *)recvpacket;
	int ttl = iph->ip_ttl;
	//printf("ttl = %d ", ttl);



	inet_ntop(fromAddr.sin_family, (void *)&(fromAddr.sin_addr), ip, sizeof(ip));

	string from_ip(ip);
	// (char *)inet_ntoa(fromAddr.sin_addr);
	// 判断是否是自己Ping的回复
	//printf("recv icmp from ip = %s \n", from_ip.c_str());
	info->hops.emplace_back(from_ip);

	if (fromAddr.sin_addr.s_addr == info->addr.sin_addr.s_addr)
	{
		//printf("find dest ip addr %s\n", from_ip.c_str());
		info->state = 2;
		info->ttl = ttl;
	}
	else
	{
		info->hops.emplace_back(from_ip);
	}

	

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

	//printf("icmp->icmp_type:%d,icmp->icmp_id:%d\n", icmp->icmp_type, icmp->icmp_id);
	// // 判断Ping回复包的状态
	// if (icmp->icmp_type == ICMP_ECHOREPLY && icmp->icmp_id == pid) // ICMP_ECHOREPLY回显应答
	// {
	// 	// 正常就退出循环
	// 	printf("icmp packet is ok\n");
	// }
	// else
	// {
	// 	// 否则继续等
	// 	printf("recv a wront packet\n");
	// }

	return fromlen;
}

/////////////////////////////////////////////////////////////////////////////
int SelectPoller::poll_once()
{
	// 设定TimeOut时间,这次才是真正起作用的
	FD_ZERO(&readfds);
	FD_SET(sock->sockfd, &readfds);
	maxfds = sock->sockfd + 1;

	// 设定TimeOut时间
	int timeout = 3000;
	struct timeval timeo;

	timeo.tv_sec = timeout / 1000;
	timeo.tv_usec = timeout % 1000;

	int n = select(maxfds, &readfds, NULL, NULL, &timeo);
	if (n < 0)
	{
		// printf("ip:%s,Time out error\n", ips);
		// close(sockfd);
		return errno;
	}

	return n;
}

int SelectPoller::init()
{
	this->sock = std::make_shared<SocketIcmp>();
	return this->sock->init();
}
////////////////////////////////////////////////////

SelectPoller poller;
// send nTimes packets
int ping(InfoPtr &info, int nTimes = 5)
{
	SockIcmpPtr sock = poller.getSock();
	for (int i  =0; i<nTimes; i++)
	{
		info->timer.start();
		sock->sendIcmp(info, 64);
		int n = poller.poll_once();
		//printf("select ret n = %d\n", n);
		if (n > 0)
		{
			n = sock->recvIcmp(info);
			double delta = info->timer.stop_delta_ms();
			printf("ttl(60-h) = %d  time = %f\n", info->ttl, delta);
		}
		else{
			printf("ping timeout\n");

		}
		
	}
	
}

int trace(InfoPtr &info)
{
	SockIcmpPtr sock = poller.getSock();

	for (int i = 1; i < 32; i++)
	{

		sock->sendIcmp(info, i);
		int n = poller.poll_once();
		// printf("select ret n = %d\n", n);
		if (n > 0)
		{
			n = sock->recvIcmp(info);
			if (n > 0)
			{
				int index = info->hops.size();
				if (index > 1)
					printf("%d\t%s \n", i, info->hops[index-1].c_str());
				else
					printf("error");
			}
			
		}
		else
		{
			printf("%d\t................\n", i);
		}

		if (info->state == 2)
		{
			printf("end\n");
			break;
		}
	}
}

void worker(char * url)
{
	InfoPtr info = SocketIcmp::nameToIp4(url);

	printf("\n--------------dns info-------------------\n");
	printf("official name: %s \n", info->cname.c_str());
	for (const auto &str : info->alias)
	{
		printf("alias name: %s \n", str.c_str());
	}

	for (const auto &str : info->ipv4)
	{
		printf("ip addr: %s \n", str.c_str());
	}

	printf("--------------ping info-------------------\n");
	// ping it
	ping(info);

	printf("--------------trace info-------------------\n");
	info->state = 1;
	info->hops.clear();
	// trace it
	trace(info);
}


int main()
{
	char url[200];
	poller.init();

	while (1)
	{

		printf("Please input icmp hostname:");
		scanf("%s", url);
		if (strlen(url) == 4 && 0 == strcmp(url, "exit"))
			break;

		worker(url);
	}
	
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值