ICMP协议的PING程序

本文详细解析了ping命令的工作原理及其实现过程,包括发送和接收ICMP包、计算延迟等关键步骤。
#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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值