测试环境: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;
}