【前言】Ping 是用于测试网络连接量的程序,利用它可以检查网络是否能够连通。Ping程序的实现原理非常简单,即发送一个ICMP回声请求消息给目的地并报告是否收到所希望的ICMP回声应答。本文采用C语言编写一个ing功能的程序。
0.ICMP协议简介
ICMP是(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。
ICMP协议是一种面向连接的协议,用于传输出错报告控制信息。它是一个非常重要的协议,它对于安全具有极其重要的意义。
它是族的一个子协议,属于网络层协议,主要用于在主机与路由器之间传递控制信息,包括报告错误、交换受限控制和状态信息等。当遇到IP数据无法访问目标、IP路由器无法按当前的传输速率转发数据包等情况时,会自动发送ICMP消息。
ICMP提供一致易懂的出错报告信息。发送的出错报文返回到发送原数据的设备,因为只有发送设备才是出错报文的逻辑接受者。发送设备随后可根据ICMP报文确定发生错误的类型,并确定如何才能更好地重发失败的数据报。但是ICMP唯一的功能是报告问题而不是纠正错误,纠正错误的任务由发送方完成。
我们在网络中经常会使用到ICMP协议,比如我们经常使用的用于检查网络通不通的命令(Linux和Windows中均有),这个“Ping”的过程实际上就是ICMP协议工作的过程。还有其他的网络命令如的Tracert命令也是基于ICMP协议的。
ICMP报文分为两种,一是错误报告报文,二是查询报文。每个ICMP报头均包含类型、编码和校验和这三项内容,长度为8位,8位和16位,其余选项则随ICMP的功能不同而不同。这两种ICMP类型报头格式如下:
1.程序源代码
/********************************************************
* 功能:实现PING功能
* 环境: GCC-4.2.4
* 作者:YSQ-NJUST,yushengqiangyu@163.com
* 备注:自由软件,主要用于学习、交流、共享。
*******************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define E_FAILD_FD -1
#define ICMP_DATA_LEN 20 /* ICMP负载长度 */
#define ICMP_ECHO_MAX 4 /* ECHO-REQUEST报文发送次数 */
#define ICMP_REQUEST_TIMEOUT 2 /* 超时时间间隔 */
/* ICMP报文发送与接收缓存 */
static unsigned char aucSendBuf[1024 * 1024] = {0};
static unsigned char aucRecvBuf[1024 * 1024] = {0};
/* 结构体定义 */
typedef struct tagIcmpStatic
{
unsigned int uiSendPktNum;
unsigned int uiRcvPktNum;
float fMinTime;
float fMaxTime;
float fArgTime;
}ICMP_STATIC_S;
/* 全局数据结构 */
ICMP_STATIC_S g_stPktStatic; /* ICMP报文统计 */
struct timeval stSendTime = {0}; /* ECHO-REQUEST报文发送时间 */
struct timeval stRcvTime ={0}; /* ECHO-REPLY报文接收时间 */
/* 输出报文统计信息 */
void showStatic(const ICMP_STATIC_S *pstStInfo)
{
unsigned int uiSend, uiRecv;
uiSend = pstStInfo->uiSendPktNum;
uiRecv = pstStInfo->uiRcvPktNum;
printf("\n***PING Statistics***");
printf("\nPackets:Send = %u,Recveived = %u,Lost = %u", uiSend, uiRecv, uiSend - uiRecv);
printf("\nTime:Minimum = %.1fms,Maximum = %.1fms,Average=%.2fms\n", pstStInfo->fMinTime, pstStInfo->fMaxTime, pstStInfo->fArgTime);
}
/* 计算时间差,返回时间以毫秒为单位 */
unsigned int timeSub(const struct timeval *pstOut, const struct timeval *pstIn)
{
unsigned int uiSec = 0;
int iUsec = 0;
uiSec = pstOut->tv_sec - pstIn->tv_sec;
iUsec = pstOut->tv_usec - pstIn->tv_usec;
if (0 > iUsec)
{
iUsec += 1000000;
uiSec--;
}
return uiSec * 1000 + (unsigned int)(iUsec / 1000);
}
/* 校验和计算 */
unsigned short calcIcmpChkSum(const void *pPacket, int iPktLen)
{
unsigned short usChkSum = 0;
unsigned short *pusOffset = NULL;
pusOffset = (unsigned short *)pPacket;
while(1 < iPktLen)
{
usChkSum += *pusOffset++;
iPktLen -= sizeof(unsigned short);
}
if (1 == iPktLen)
{
usChkSum += *((char *)pusOffset);
}
usChkSum = (usChkSum >> 16) + (usChkSum & 0xffff);
usChkSum += (usChkSum >>16);
return ~usChkSum;
}
/* ICMP报文填充 */
int newIcmpEcho(const int iPacketNum, const int iDataLen)
{
struct icmp *pstIcmp = NULL;
memset(aucSendBuf, 0, sizeof(aucSendBuf));
pstIcmp = (struct icmp *)aucSendBuf;
pstIcmp->icmp_type = ICMP_ECHO;
pstIcmp->icmp_code = 0;
pstIcmp->icmp_seq = htons((unsigned short)iPacketNum);
pstIcmp->icmp_id = htons((unsigned short)getpid());
pstIcmp->icmp_cksum = 0;
pstIcmp->icmp_cksum = calcIcmpChkSum(pstIcmp, iDataLen + 8);
return iDataLen + 8;
}
/* 解析ECHO-REPLY响应报文 */
int parseIcmp(const struct sockaddr_in *pstFromAddr, char *pRecvBuf, const int iLen)
{
int iIpHeadLen = 0;
int iIcmpLen = 0;
struct ip *pstIp = NULL;
struct icmp *pstIcmpReply = NULL;
pstIp = (struct ip *)pRecvBuf;
iIpHeadLen = pstIp->ip_hl << 2;
pstIcmpReply = (struct icmp *)(pRecvBuf + iIpHeadLen);
/* 报文长度非法 */
iIcmpLen = iLen - iIpHeadLen;
if (8 > iIcmpLen)
{
printf("[Error]Bad ICMP Echo-reply\n");
return -1;
}
/* 报文类型非法 */
if ((pstIcmpReply->icmp_type != ICMP_ECHOREPLY) ||
(pstIcmpReply->icmp_id != htons((unsigned short)getpid())))
{
return -1;
}
sleep(1);
printf("%d bytes reply from %s: icmp_seq=%u Time=%dms TTL=%d\n", iIcmpLen, inet_ntoa(pstFromAddr->sin_addr), ntohs(pstIcmpReply->icmp_seq), timeSub(&stRcvTime, &stSendTime), pstIp->ip_ttl);
return 1;
}
/* Echo响应报文接收 */
void recvIcmp(const int fd)
{
int iRet = 0;
int iRecvLen = 0;
unsigned int uiInterval = 0;
socklen_t fromLen = sizeof(struct sockaddr_in);
struct sockaddr_in stFromAddr = {0};
/* 清空接收缓存,并准备接收响应报文 */
memset(aucRecvBuf, 0, 1024 * 1024);
iRecvLen = recvfrom(fd, (void *)aucRecvBuf, sizeof(aucRecvBuf), 0, (struct sockaddr *)&stFromAddr,&fromLen);
gettimeofday(&stRcvTime, NULL);
if (0 > iRecvLen)
{
if (EAGAIN == errno)
{
/* 请求超时 */
printf("Request time out.\n");
g_stPktStatic.fMaxTime = ~0;
}
else
{
/* 接收数据包出错 */
perror("[Error]ICMP Receive");
}
return;
}
/* 获取统计参数 */
g_stPktStatic.uiRcvPktNum++;
uiInterval = timeSub(&stRcvTime, &stSendTime);
g_stPktStatic.fArgTime = (g_stPktStatic.fArgTime * (g_stPktStatic.uiSendPktNum - 1) + uiInterval)/g_stPktStatic.uiSendPktNum;
if (uiInterval < g_stPktStatic.fMinTime)
{
g_stPktStatic.fMinTime = uiInterval;
}
if (uiInterval > g_stPktStatic.fMaxTime)
{
g_stPktStatic.fMaxTime = uiInterval;
}
/* 解析ICMP响应报文 */
iRet = parseIcmp(&stFromAddr, (char *)aucRecvBuf, iRecvLen);
if (0 > iRet)
{
return;
}
}
/* 发送ICMP报文 */
void sendIcmp(const int fd, const struct sockaddr_in *pstDestAddr)
{
unsigned char ucEchoNum = 0;
int iPktLen = 0;
int iRet = 0;
while(ICMP_ECHO_MAX > ucEchoNum)
{
iPktLen = newIcmpEcho(ucEchoNum, ICMP_DATA_LEN);
/* 记录发送起始时间 */
g_stPktStatic.uiSendPktNum++;
gettimeofday(&stSendTime, NULL);
/* 发送ICMP-ECHO报文 */
iRet = sendto(fd, aucSendBuf, iPktLen, 0, (struct sockaddr *)pstDestAddr, sizeof(struct sockaddr_in));
if(0 > iRet)
{
perror("Send ICMP Error");
continue;
}
/* 等待接收响应报文 */
recvIcmp(fd);
ucEchoNum++;
}
}
/*主函数*/
int main(int argc, char *argv[])
{
int iRet = 0;
int iRcvBufSize = 1024 * 1024;
int fd = E_FAILD_FD;
in_addr_t stHostAddr;
struct timeval stRcvTimeOut = {0};
struct hostent *pHost = NULL;
struct sockaddr_in stDestAddr = {0};
struct protoent *pProtoIcmp = NULL;
g_stPktStatic.uiSendPktNum = 0;
g_stPktStatic.uiRcvPktNum =0;
g_stPktStatic.fMinTime = 1000000.0;
g_stPktStatic.fMaxTime = -1.0;
g_stPktStatic.fArgTime = 0.0;
/* 参数判断 */
if (2 > argc)
{
printf("\nUsage:%s hostname/IP address\n", argv[0]);
return -1;
}
/* 获取ICMP协议类型 */
pProtoIcmp = getprotobyname("icmp");
if (NULL == pProtoIcmp)
{
perror("[Error]Get ICMP Protoent Structrue");
return -1;
}
/* 创建ICMP使用的SOCKET */
fd = socket(PF_INET, SOCK_RAW, pProtoIcmp->p_proto);
if (0 > fd)
{
perror("[Error]Init Socket");
return -1;
}
/* ROOT权限回收 */
iRet = setuid(getuid());
if (0 > iRet)
{
perror("[Error]Setuid");
close(fd);
return -1;
}
/* 设置SOCKET选项 */
stRcvTimeOut.tv_sec = ICMP_REQUEST_TIMEOUT;
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &iRcvBufSize, sizeof(iRcvBufSize));
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &stRcvTimeOut, sizeof(struct timeval));
/* 地址解析 */
stHostAddr = inet_addr(argv[1]);
if (INADDR_NONE == stHostAddr)
{
/* 根据主机名称解析IP地址 */
pHost = gethostbyname(argv[1]);
if (NULL == pHost)
{
perror("[Error]Host Name Error");
close(fd);
return -1;
}
memcpy((char *)&stDestAddr.sin_addr, (char *)(pHost->h_addr), pHost->h_length);
}
else
{
memcpy((char *)&stDestAddr.sin_addr, (char *)&stHostAddr, sizeof(stHostAddr));
}
printf("\nPING %s(%s): %d bytes in ICMP packets\n", argv[1], inet_ntoa(stDestAddr.sin_addr), ICMP_DATA_LEN);
/* 发送ICMP报文 */
sendIcmp(fd, &stDestAddr);
/* 输出统计信息 */
showStatic(&g_stPktStatic);
/* 关闭FD */
close(fd);
return 0;
}
2.编译运行
sudo gcc -g -Wall -o myping ping.c
chmod u+x
myping ./myping |
3.运行结果示意
PING (119.75.217.56): 20 bytes in ICMP packets
28 bytes reply from 119.75.217.56: icmp_seq=0 Time=27ms TTL=128
28 bytes reply from 119.75.217.56: icmp_seq=1 Time=23ms TTL=128
28 bytes reply from 119.75.217.56: icmp_seq=2 Time=30ms TTL=128
28 bytes reply from 119.75.217.56: icmp_seq=3 Time=31ms TTL=128
***PING Statistics***
Packets:Send = 4,Recveived = 4,Lost = 0
Time:Minimum = 23.0ms,Maximum = 31.0ms,Average=27.75ms |