[原创]C语言模拟Ping
最近在学习TCP/IP协议。在以前套接字基础上又学习了原始套接字的使用。并喜欢上了这个东西-因为它功能太强大了。下面是我用原始套接字实现的Ping.exe命令
//Ping模拟 By RedIce
//E-mail:redice@see.xidian.
//http://redice.1.
#include <winsock2.h> //Winsock API头文件
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib,"ws2_32.lib") //Winsock API连接库文件
/*IP头 结构*/
//BYTE<=>unsigned char,自定义类型
//USHORT<=>unsigned short,自定义类型
//UINT<=>unsigned int,自定义类型
//ULONG<=>unsigned long,自定义类型
typedef struct iphdr{
BYTE h_len:4; //首部长度指的是IP层头部占32 bit字的数目
//(也就是IP层头部包含多少个4字节,实际字节数4*hlen),
BYTE version:4; //IP版本号
BYTE tos; //服务类型TOS
USHORT total_len; //IP包总长度
USHORT ident; //标识
USHORT frag_and_flags; //标志位
BYTE ttl; //生存时间
BYTE proto; //协议
USHORT checksum; //IP首部校验和
UINT sourceIP; //源IP地址(32位)
UINT destIP; //目的IP地址(32位)
}IpHeader;
/*ICMP头 结构*/
typedef struct _ihdr{
BYTE i_type; //类型 发出的ICMP为8(ICMP_ECHO_REQUEST),接受到的ICMP为0
BYTE i_code; //代码
USHORT i_cksum; //ICMP包校验和
USHORT i_id; //识别号(一般用进程号作为标识号)
USHORT i_seq; //报文序列号(一般设置为0)
ULONG timestamp;//时间戳
}IcmpHeader;
USHORT checksum(USHORT *,int); //函数声明:计算ICMP包校验和
void usage();//函数声明:使用帮助
void main(int argc,char *argv[])
{
char *ICMP_DEST_IP;//目标主机IP
WSADATA wsaData;
struct sockaddr_in dest,from;//地址结构
int datasize; //ICMP报文大小
int ret;//API函数 返回值
int i;//循环计数器
int attachsize=32;//ICMP数据包附加字节数,本程序默认为32字节
int n=4;//发送数据包个数,本程序默认发送4个ICMP数据包
int timeout;//延迟
DWORD packetrecv=0;//收到的数据包
DWORD mintime=0;//用时最短时间
DWORD maxtime=0;//用时最长时间
DWORD averagetime=0;//平均用时
DWORD lostpercent=0;//丢包率
DWORD start;//发送ICMP包起始时间
char *icmp_data;//ICMP包
char *recvbuf;//ICMP应答接收缓冲区
int fromlen=sizeof(from);//地址结构体的大小
SOCKET sockRaw;//原始套接字
char *attachdata;//ICMP包附加数据
PHOSTENT hostinfo;//主机信息(域名->IP)
//读取命令行参数
if(1==argc)//如果仅有一个默认的命令行参数则显示程序说明
//默认的第一个命令行参数为本程序的路径
{
usage();
return;//退出程序
}
else //如果有多个命令行参数
{
for(i=1;i<=argc-1;i++)
{
if(strstr(argv[i],"-n"))//"-n" 指定发送ICMP数据包个数
{
n=atoi(argv[i+1]);
i++;
}
if(strstr(argv[i],"-l"))//"-l" 指定每个包附加数据的大小
{
attachsize=atoi(argv[i+1]);
i++;
}
if(strstr(argv[i],"-t"))//"-t" 死亡之Ping
{
n=999999;
}
if(strstr(argv[i],"?"))
{
usage();
return;//退出程序
}
}
ICMP_DEST_IP=argv[argc-1];
}
//初始化Socket
ret=WSAStartup(MAKEWORD(2,2), //Socket版本号
&wsaData //指向WSADATA数据结构的指针
);
if(ret!=0)//WSAStartup调用成功返回0
{
printf("初始化Socket出错!\n");
return;//退出程序
}
//域名解析
hostinfo=gethostbyname(ICMP_DEST_IP);
if(NULL==hostinfo)
{
printf("无法解析主机%s的IP地址!",ICMP_DEST_IP);
WSACleanup(); //中止Windows Sockets DLL的使用,释放资源
return;
}
else
{
ICMP_DEST_IP=inet_ntoa(*(struct in_addr*)*hostinfo->h_addr_list);
}
//创建原始套接字
sockRaw=socket(AF_INET,//协议族(AF_INET: TCP_IP)
SOCK_RAW,//套接字类型(原始套接字)
IPPROTO_ICMP//协议类型(ICMP协议)
);
if(INVALID_SOCKET==sockRaw)//socket调用失败返回INVALID_SOCKET,反之返回套接字句柄
{
printf("创建原始套接字出错!\n");
WSACleanup(); //中止Windows Sockets DLL的使用,释放资源
return;//退出程序
}
//设置目的IP
memset(&dest,0,sizeof(dest));
dest.sin_addr.S_un.S_addr=inet_addr(ICMP_DEST_IP);
dest.sin_family=AF_INET;
attachdata=(char*)malloc(attachsize);
memset(attachdata,0,attachsize);//ICMP包数据部分 填充attachsize字节0
datasize=sizeof(IcmpHeader)+attachsize;//ICMP数据包总大小(头+体)
icmp_data=(char*)malloc(datasize);//根据上面计算的结果为ICMP包数据分配内存
recvbuf=(char*)malloc(200);//
memset(icmp_data,0,datasize);
//ICMP数据包封装
((IcmpHeader *)icmp_data)->i_type=8;
((IcmpHeader *)icmp_data)->i_code=0;
((IcmpHeader *)icmp_data)->i_id=(USHORT)GetCurrentProcessId();
((IcmpHeader *)icmp_data)->timestamp=GetTickCount();
((IcmpHeader *)icmp_data)->i_seq=0;
memcpy(icmp_data+sizeof(IcmpHeader),attachdata,attachsize);//ICMP包数据部分 填充32字节0
((IcmpHeader *)icmp_data)->i_cksum=0;
//计算ICMP包校验和
((IcmpHeader *)icmp_data)->i_cksum=checksum((USHORT *)icmp_data,datasize);
timeout=1000;
//设置发送延迟
setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,sizeof(timeout));
timeout=2000;
//设置接受延迟
setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(timeout));
printf("Pinging %s with %d bytes of data:\n\n",ICMP_DEST_IP,attachsize);
for(i=1;i<=n;i++)
{ start=GetTickCount();
//发送第i个ICMP数据包
ret=sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr *) &dest,sizeof(dest));
if(SOCKET_ERROR==ret)
{
printf("发送ICMP数据包%d出错!\n",i);
continue;
}
//等待接收返回的ICMP数据包
while(1)
{
if((GetTickCount()-start)>=1000)
{
printf("Request timed out.\n");
break;
}
memset(recvbuf,0,200);
ret=recvfrom(sockRaw,recvbuf,200,0,(struct sockaddr *) &from,&fromlen);
if(SOCKET_ERROR==ret)
{
printf("接收数据包%d出错!\n",i);
break;
}
else
{
//显示返回ICMP包的信息
unsigned short iphdrlen;//IP头长度
IcmpHeader *icmphdr;
DWORD timeuse;//数据包发送到接收用时
int attachlen;//返回的ICMP数据包中数据段的大小
timeuse=(GetTickCount()-start);
if(0==packetrecv)
{
mintime=timeuse;
maxtime=timeuse;
}
if(timeuse>maxtime) maxtime=timeuse;
if(timeuse<mintime) mintime=timeuse;
averagetime=averagetime+timeuse;
iphdrlen=((IpHeader *)recvbuf)->h_len *4;
icmphdr=(IpHeader *)(recvbuf+iphdrlen);
attachlen=ret-iphdrlen-sizeof(IcmpHeader);
printf("Reply from %s: bytes=%d time=%d ms TTL=%d\n",ICMP_DEST_IP,attachlen,timeuse,((IpHeader *)recvbuf)->ttl);
packetrecv++;
break;
}
}
}
//显示统计信息
lostpercent=100*((float)(n-packetrecv)/(float)n);//计算丢包率
averagetime=(float)averagetime/(float)packetrecv;//计算平均用时
printf("\nPing statistics for %s:\n",ICMP_DEST_IP);
printf(" Packets: Sent = %d, Received = %d, Lost = %d (%d %s loss),\n",n,packetrecv,lostpercent,lostpercent,"%");
printf("Approximate round trip times in milli-seconds:\n");
printf(" Minimum = %dms, Maximum = %dms, Average = %dms",maxtime,mintime,averagetime);
free(recvbuf);
free(icmp_data);
closesocket(sockRaw);
WSACleanup();
}
//计算ICMP数据包校验和
USHORT checksum(USHORT *buffer,int size)
{
unsigned long cksum=0;
while(size>1)
{
cksum+=*buffer++;
size-=sizeof(USHORT);
}
if(size)
{
cksum+=*(UCHAR *)buffer;
}
cksum=(cksum>>16)+(cksum & 0xffff);
cksum+=(cksum>>16);
return (USHORT) (~cksum);
}
//使用说明
void usage()
{
printf("Ping程序模拟\n\n");
printf("By RedIce\n");
printf("E-mail:redice@see.xidian.\n");
printf("http://redice.1.\n\n");
printf("Usage: checknet.exe [-n count] [-l size] target_name \n\n");
printf("Options:\n");
printf(" -n count 发送ICMP数据包的个数,默认为4个.\n");
printf(" -l size 每个ICMP包附加的数据字节数,默认为32字节.\n\n");
}
//这是运行后的效果(嘿嘿,还真相Ping程序,哈哈)
[[italic] 本帖最后由 redice 于 2007-12-23 01:58 编辑 [/italic]]