ICMP协议的PING程序

时间:2022-12-05 05:05:40
#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <errno.h>

#define PACKET_SIZE 4096
#define MAX_WAIT_TIME 5
#define MAX_NO_PACKETS 3

char sendpacket[PACKET_SIZE];
char recvpacket[PACKET_SIZE];
int sockfd,datalen=56;
int nsend = 0, nreceived = 0;
struct sockaddr_in dest_addr;
struct sockaddr_in from;
struct timeval tvrecv;
pid_t pid;

void send_packet();
void recv_packet();
void statistics(int signo);
unsigned short cal_chksum(unsigned short *addr, int len);
int pack(int pack_no);
int unpack(char *buf, int len);

void statistics(int signo)
{
printf("\n-------------PING statistics-------------\n");
printf("%d packets transmitted, %d received, %%%d lost\n",nsend,nreceived,(nsend-nreceived)/nsend*100);
close(sockfd);
exit(1);
}

void tv_sub(struct timeval *out, struct timeval *in)
{
if((out->tv_usec -= in->tv_usec) < 0){
--out->tv_sec;
out->tv_usec+=1000000;
}
out->tv_usec -= in->tv_sec;;
}
unsigned short 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 pack(int pack_no)
{
int packsize;
struct icmp *icmp;
struct timeval *tval;

icmp = (struct icmp *)sendpacket;
icmp->icmp_type = ICMP_ECHO;
icmp->icmp_code = 0;
icmp->icmp_cksum = 0;
icmp->icmp_seq = pack_no;
icmp->icmp_id = pid;

packsize = 8 + datalen;
tval = (struct timeval *)icmp->icmp_data;
gettimeofday(tval,NULL);//记录发包时间
icmp->icmp_cksum = cal_chksum((unsigned short *)icmp,packsize);
return packsize;
}

void send_packet()
{
int packtsize;
while(nsend < MAX_NO_PACKETS){//ping的次数
nsend++;
packtsize = pack(nsend);//填充icmp
if(sendto(sockfd,sendpacket,packtsize,0,(struct sockaddr *)&dest_addr,sizeof(dest_addr)) < 0){
perror("sendto error");
continue;
}
sleep(1);
}
}

void recv_packet()
{
int n,fromlen;
extern int errno;
signal(SIGALRM,statistics);//收到信号,跳转statistics函数
fromlen = sizeof(from);//接收包的长度
while(nreceived < 10){
alarm(MAX_WAIT_TIME);//定义5秒
if((n = recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(struct sockaddr *)&from,&fromlen)) < 0){//读取包
if(errno == EINTR) continue;
perror("recvfrom error");
continue;
}
gettimeofday(&tvrecv,NULL);//记录收到包的时间
if(unpack(recvpacket,n) == -1) continue;//调用unpack解包,传入包和长度n
nreceived++;
}
}

int unpack(char *buf,int len)
{
int iphdrlen;
struct ip *ip;
struct icmp *icmp;
struct timeval *tvsend;
double rtt;
ip = (struct ip *)buf;
iphdrlen = (ip->ip_hl)*4;//求IP包头长度
icmp = (struct icmp *)(buf + iphdrlen);
len -= iphdrlen;//减去IP包头,直接指向icmp报文
if(len < 8){
printf("ICMP packets \'s length is less than 8\n");
return -1;
}
if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id ==pid)){//判断icmp包是否问应答包,比对发的包和接收的包是否对应
tvsend = (struct timeval *)icmp->icmp_data;
tv_sub(&tvrecv,tvsend);//计算时间差
rtt = tvrecv.tv_sec*1000 + tvrecv.tv_usec/1000;
printf("%d bytes from %s:icmp_seq=%u tll=%d rtt=%.3fms \n",
len,inet_ntoa(from.sin_addr),
icmp->icmp_seq,ip->ip_ttl,rtt);
} else return -1;

return 0;
}

int main(int argc, char *argv[])
{
struct hostent *host;
struct protoent *protocol;
int size = 50*1024;

if(argc < 2){
printf("usage:%s hostname/IP address\n",argv[0]);
exit(1);
}

if((protocol = getprotobyname("icmp")) == NULL){
perror("unknow protocol icmp");
exit(1);
}

if((sockfd = socket(AF_INET,SOCK_RAW,protocol->p_proto)) < 0){
perror("socket error");
exit(2);
}

setuid(getuid());
setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size));
bzero(&dest_addr,sizeof(dest_addr));
dest_addr.sin_family = AF_INET;

if((host = gethostbyname(argv[1])) == NULL){
perror("gethostbyname error");
exit(1);
}

dest_addr.sin_addr = *((struct in_addr *)host->h_addr);
pid = getpid();
printf("PING %s(%s):%d bytes data in ICMP packets.\n",argv[1],
inet_ntoa(dest_addr.sin_addr),datalen);
send_packet();
recv_packet();
statistics(SIGALRM);

return 0;
}