欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

模拟实现Ping

程序员文章站 2022-07-13 23:47:18
...

PING (Packet Internet Groper),因特网包探索器,用于测试网络连接量的程序

Ping发送一个ICMP(Internet Control Messages Protocol)即因特网信报控制协议;

回声请求消息给目的地并报告是否收到所希望的ICMPecho (ICMP回声应答)。它是用来检查网络是否通畅或者网络连接速度的命令。

      作为一个生活在网络上的管理员或者黑客来说,ping命令是第一个必须掌握的DOS命令

    它所利用的原理是这样的:利用网络上机器IP地址的唯一性,给目标IP地址发送一个数据包,再要求对方返回一个同样大小的数据包来确定两台网络机器是否连接相通,时延是多少。

ping指的是端对端连通,通常用来作为可用性的检查, 但是某些病毒木马会强行大量远程执行ping命令抢占你的网络资源,导致系统变慢,网速变慢。严禁ping入侵作为大多数防火墙的一个基本功能提供给用户进行选择。通常的情况下你如果不用作服务器或者进行网络测试,可以放心的选中它,保护你的电脑。

大家在编写代码的时候可以先ping一下百度或google看一下它的具体响应格式是怎样的,这样方便敲程序的时候有较清晰的思路

代码块:(解释都在注释里)

#include<stdio.h>
#include<sys/time.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<netinet/in.h>
#include<string.h>
#include<netinet/ip_icmp.h>
#include<netinet/ip.h>


#define BUF_SZ 1024
#define PACK_LEN 56

int sendnum=0;//发包的编号
int recvnum=0;
char sendpack[BUF_SZ];//包的大小
char recvpack[BUF_SZ];
struct sockaddr_in from;//收来自哪里的包

//以毫秒为单位求出间隔时间  
//timeval 两个参数 tv_sec 秒   tv_usec 微秒
long diftime(const struct timeval*end,const struct timeval*_begin ){
	long ret=(end->tv_sec - _begin->tv_sec)*1000+(end->tv_usec-_begin->tv_usec)/1000;
  
	return ret;
}

unsigned short chksum(unsigned short *addr,int len)//校验和
{
	unsigned int ret=0;//先置成0

	while(len>1){//二进制求和
		ret+=*addr++;
		len-=2;
	}
  //如果是奇数会剩下一个
	if(len==1){
		ret+=*(unsigned char*)addr;
	}
  //高16位加上 低16位
	ret=(ret>>16)+(ret&0xffff);
	ret+=(ret>>16);//处理溢出情况
  //取反
	return (unsigned short )~ret;
}

int pack(int num,pid_t pid)
{
  memset(sendpack,0x00,sizeof(sendpack));//先清零
  //强转成要发送的icmp格式
	struct icmp*p=(struct icmp*)sendpack;
  //填写数据 
	p->icmp_type=ICMP_ECHO;//8
	p->icmp_code=0;//校验码
	p->icmp_cksum=0;//校验和
	p->icmp_seq=num;//序号
	p->icmp_id=pid;//进程id

	struct timeval tval;
  //计时
	gettimeofday(&tval,NULL);
  //数据拷贝
	memcpy((void*)p->icmp_data,(void*)&tval,sizeof(tval));
	p->icmp_cksum=chksum((unsigned short*)sendpack,PACK_LEN+8);

	return PACK_LEN+8;
}

void unpack(char*buf,int len ,pid_t pid)//解包函数
{
  //ip数据包强转 求出ip头部大小
	struct ip *pip=(struct ip*)buf;
  //强转成icmp头部
	struct icmp *picmp=(struct icmp*)(buf+(pip->ip_hl<<2));//ip头部的大小乘4

  struct timeval end;
  gettimeofday(&end,NULL);
	printf("%d bytes from %s : icmp_seq=%d ttl=%d time=%d ms\n",PACK_LEN,inet_ntoa(from.sin_addr),\
        picmp->icmp_seq,pip->ip_ttl,diftime(&end,(struct timeval*)(picmp->icmp_data)));
}

void recv_packet(int sfd,pid_t pid)
{
	memset(recvpack,0x00,sizeof(recvpack));
	int r;
  socklen_t len=sizeof(from);
  recvnum++;
	r=recvfrom(sfd,recvpack,BUF_SZ,0,(struct sockaddr*)&from,&len);//读取数据到recvpack
	unpack(recvpack,r,pid);//解包
}

void send_packet(int sfd,pid_t pid,struct sockaddr_in ad)
{
	sendnum++;//编号
	int r=pack(sendnum,pid);//走包
  //发包    对象不知道,不能用write
	sendto(sfd,sendpack,r,0,(struct sockaddr*)&ad,sizeof(ad));
}

int main(int argc,char*argv[])
{
	if(argc!=2)
		return 1;
	struct in_addr addr;//32位的IPV4的ip地址
	struct sockaddr_in ad;//发送的地址
  //如果传的不是ip地址,是域名或主机名的话
	if((addr.s_addr=inet_addr(argv[1]))==INADDR_NONE){
    //用域名或者主机名获取地址
		struct hostent*pent=gethostbyname(argv[1]);
		if(pent==NULL){
			perror("gethostbyname error");
			exit(1);
		}
    //地址拷贝
		memcpy((char*)&addr,(char*)pent->h_addr,pent->h_length);
	}
	//原始套接字SOCK_RAW
	int sfd=socket(PF_INET,SOCK_RAW,IPPROTO_ICMP);
	if(sfd==-1){
		perror("socket error");
		exit(1);
	}

	ad.sin_family=AF_INET;
	ad.sin_addr=addr;
	pid_t pid=getpid();
  printf("pid= %d ",pid);
	printf("PING  %s (%s) %d bytes of data.\n",argv[1],inet_ntoa(ad.sin_addr),PACK_LEN);

	while(1){
		send_packet(sfd,pid,ad);//发包
		recv_packet(sfd,pid);//收包
		sleep(1);
	}
	printf("%s \n",inet_ntoa(addr));
	/*struct timeval begin;
	gettimeofday(&begin,NULL);//微秒级别

	sleep(1);
	struct timeval end;
	gettimeofday(&end,NULL);

	long ret=diftime(&end,&begin);
	//int ret=(end.tv_sec - begin.tv_sec)*1000+(end.tv_sec-begin.tv_sec)/1000;

	printf("ret :%d \n",ret);*/

}