#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/time.h>
#include<arpa/inet.h>
#include<netdb.h>
#define ICMP_SIZE (sizeof(struct icmp))
#define ICMP_ECHO 0
#define ICMP_ECHOREPLY 0
#define BUF_SIZE 1024
#define NUM 5 //报文发送次数
#define UCHAR unsigned char
#define USHORT unsigned short
#define UINT unsigned int
struct icmp
{
UCHAR type; //类型
UCHAR code; //代码
USHORT checksum; //校验和
USHORT id; //序列号
USHORT sequence; //序号
struct timeval timestamp; //时间戳
};
//ip首部数据结构
struct ip
{
//主机字节序判断
//#if __BYTE_ORDER == __LITTLE_ENDINA
#if __BYTE_ORDER == __LITTLE_ENDIAN
UCHAR hlen:4; //首部长度
UCHAR version:4; //版本
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
UCHAR version:4;
UCHAR hlen:4;
#endif
UCHAR tos; //服务类型
USHORT len; //总长度
USHORT id; //标识符
USHORT offset; //标志和片偏移
UCHAR ttl; //生存时间
UCHAR protocol; //协议
USHORT checksum; //校验和
struct in_addr ipsrc; //32位源ip地址
struct in_addr ipdst; //32位目的ip地址
};
char buf[BUF_SIZE] = {0};
USHORT checkSum(USHORT *, int); //计算校验和
float timediff(struct timeval *, struct timeval *); //计算时间差
void pack(struct icmp *, int); //封装一个ICMP报文
int unpack(char *, int , char *); //对接收的IP保温进行解包
int main(int argc, char *argv[])
{
struct hostent *host;
struct icmp sendicmp;
struct sockaddr_in from;
struct sockaddr_in to;
int fromlen = 0;
int sockfd;
int nsend = 0;
int nreceived = 0;
int i, n;
in_addr_t inaddr;
memset(&from, 0, sizeof(struct sockaddr_in));
memset(&to, 0, sizeof(struct sockaddr_in));
if (argc < 2)
{
printf("use : %s hostname/IP address \n", argv[0]);
exit(1);
}
//生成原始套接字
if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
{
printf("socket() error\n");
exit(1);
}
//设置目的地址信息
to.sin_family = AF_INET;
//判断域名还是ip地址(好像没实现)
if (inaddr = inet_addr(argv[1]) == INADDR_NONE)
{
//域名
if ((host = gethostbyname(argv[1])) == NULL)
{
printf("gethostbyname() error \n");
exit(1);
}
to.sin_addr = *(struct in_addr *)host->h_addr_list[0];
}
else
{
//ip地址
to.sin_addr.s_addr = inaddr;
}
//输出域名ip地址信息
printf("ping %s (%s) : %d bytes of data.\n", argv[1], inet_ntoa(to.sin_addr),
(int)ICMP_SIZE);
//循环发送接收报文
for (i = 0; i < NUM; i++)
{
nsend++;
memset(&sendicmp, 0, ICMP_SIZE);
pack(&sendicmp, nsend);
if (sendto(sockfd, &sendicmp, ICMP_SIZE, 0, (struct sockaddr*)&to, sizeof(to)) == -1)
{
printf("sendto() error\n");
continue;
}
if ((n = recvfrom(sockfd, buf, BUF_SIZE, 0, (struct sockaddr *) &from, &fromlen)) < 0)
{
printf("recvfrom() error\n");
continue;
}
nreceived++;
if (unpack(buf, n, inet_ntoa(from.sin_addr)) == -1)
{
printf("unpack() error \n");
}
sleep(1);
}
printf("--- %s ping statistics ---\n", argv[1]);
printf("%d packets transmitted, %d received, %%%d packet loss\n", nsend, nreceived,
(nsend - nreceived) / nsend * 100);
return 0;
}
USHORT checkSum(USHORT *addr, int len)
{
UINT sum = 0;
while(len > 1)
{
sum += *addr++;
len -= 2;
}
//处理剩下的一个字节
if (len == 1)
{
sum += *(UCHAR *)addr;
}
//将32位的高16位和低16位相加
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return (USHORT) ~sum;
}
float timediff(struct timeval *begin, struct timeval *end)
{
int n;
n = (end->tv_sec - begin->tv_sec) * 100000
+ (end->tv_usec - begin->tv_usec);
//转为毫秒返回
return (float) (n / 1000);
}
void pack(struct icmp *icmp, int sequence)
{
icmp->type = ICMP_ECHO;
icmp->code = 0;
icmp->checksum = 0;
icmp->id = getpid();
icmp->sequence = sequence;
gettimeofday(&icmp->timestamp, 0);
icmp->checksum = checkSum((USHORT *)icmp, ICMP_SIZE);
}
int unpack(char *buf, int len, char *addr)
{
int i, ipheadlen;
struct ip * ip;
struct icmp * icmp;
float rtt;
struct timeval end;
ip = (struct ip *) buf;
//计算ip首部长度,即ip首部的长度标识乘4
ipheadlen = ip->hlen << 2;
//越过ip首部,指向icmp报文
icmp = (struct icmp *)(buf + ipheadlen);
//icmp报文总长度
len -= ipheadlen;
if (len < 8)
{
printf("ICMP packets\'s length is less than 8 \n");
return -1;
}
if (icmp->type != ICMP_ECHOREPLY ||
icmp->id != getpid())
{
printf("ICMP packets are not send by us \n");
return -1;
}
gettimeofday(&end, 0);
rtt = timediff(&icmp->timestamp, &end);
printf("%d bytes form %s : icmp_seq=%u ttl=%d rtt=%fms \n",
len, addr, icmp->sequence, ip->ttl, rtt);
return 0;
}