ping的实现和代码分析

一.介绍

ping命令是用来查看网络上另一个主机系统的网络连接是否正常的一个工具。ping命令的工作原理是:向网络上的另一个主机系统发送ICMP报文,如果指定系统得到了报文,它将把报文一模一样地传回给发送者,这有点象潜水艇声纳系统中使用的发声装置。
例如,在Linux终端上执行ping

如下:

二.分析

由上面的执行结果可以看到,ping命令执行后显示出被测试系统主机名和相应IP地址、返回给当前主机的ICMP报文顺序号、ttl生存时间和往返时间rtt(单位是毫秒,即千分之一秒)。要写一个模拟ping命令,这些信息有启示作用。要真正了解ping命令实现原理,就要了解ping命令所使用到的TCP/IP协议。 ICMP(Internet
Control Message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方。ICMP协议是IP层的一个协议,但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。由于IP层协议是一种点对点的协议,而非端对端的协议,它提供无连接的数据报服务,没有端口的概念,因此很少使用bind()和connect()函数,若有使用也只是用于设置IP地址。

明白了工作原理,我们就可以写我们自己的ping命令:myping

1.头文件和定义函数以及变量:

/*
 *     作者:     greenday
  *    名称:     myping
 *     程序应用:  ping命令是向目的主机发送ICMP报文,检验本地主机和远程的目的主机是否连接
 *     日期:     2014.11.22
 *
*/

 /*ICMP必须使用原始套接字进行设计,要手动设置IP的头部和ICMP的头部并行校验*/
/***********主函数*********************************************
myping.c*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/time.h>
#include <stdio.h>
#include <string.h> /* bzero */
#include <netdb.h>
#include <pthread.h>
//保存发送包的状态值
typedef struct pingm_pakcet{
	struct timeval tv_begin;     //发送时间
	struct timeval tv_end;       //接收到的时间
	short seq;                   //序列号
	int flag;          //1,表示已经发送但是没有接收到回应,0,表示接收到回应
}pingm_pakcet;
static pingm_pakcet *icmp_findpacket(int seq);
static unsigned short icmp_cksum(unsigned char *data, int len);
static struct timeval icmp_tvsub(struct timeval end, struct timeval begin);
static void icmp_statistics(void);
static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv,int length);
static int icmp_unpack(char *buf,int len);
static void *icmp_recv(void *argv);
static void icmp_sigint(int signo);
static void icmp_usage();

static pingm_pakcet pingpacket[128];
#define K 1024
#define BUFFERSIZE 72                            //发送缓冲区的大小
static unsigned char send_buff[BUFFERSIZE];
static unsigned char recv_buff[2*K];             //防止接收溢出,设置大一些
static struct sockaddr_in dest;                  //目的地址
static int rawsock = 0;                          //发送和接收线程需要的socket描述符
static pid_t pid;                                //进程PID
static int alive = 0;                            //是否接收到退出信号
static short packet_send = 0;                    //已经发送的数据包数量
static short packet_recv = 0;                    //已经接收的数据包数量
static char dest_str[80];                        //目的主机字符串
static struct timeval tv_begin, tv_end, tv_interval;

2.计算发送和接收的时间

static void icmp_usage()
{
	//ping加IP地址或者域名
	printf("ping aaa.bbb.ccc.ddd\n");
}
/*终端信号处理函数SIGINT*/
static void icmp_sigint(int signo)
{
	alive = 0;
	gettimeofday(&tv_end,NULL);
	tv_interval = icmp_tvsub(tv_end, tv_begin);

	return;
}

3.统计数据结果

/*统计数据结果函数******************************************
打印全部ICMP发送的接收统计结果*/
 static void icmp_statistics(void)
 {
 	long time = (tv_interval.tv_sec * 1000) + (tv_interval.tv_usec/1000);
 	printf("--- %s ping statistics ---\n", dest_str);
 	printf("%d packets transmitted, %d received, %d%c packet loss, time %ld ms\n",
 		packet_send,packet_recv,(packet_send-packet_recv)*100/packet_send,'%',time);
 }
 /*************查找数组中的标识函数***********************
 查找合适的包的位置
 当seq为1时,表示查找空包
 其他值表示查找seq对应的包*/
 static pingm_pakcet *icmp_findpacket(int seq)
 {
 	int i;
 	pingm_pakcet *found = NULL;
 	//查找包的位置
 	if(seq == -1){
 		for(i=0;i<128;i++){
 			if(pingpacket[i].flag == 0){
 				found = &pingpacket[i];
 				break;
 			}
 		}
 	}
 	else if(seq >= 0){
 		for(i =0 ;i< 128;i++){
 			if(pingpacket[i].seq == seq){
 				found = &pingpacket[i];
 				break;
 			}
 		}
 	}
 	return found;
 }

4.校验和函数

/*************校验和函数*****************************
TCP/IP协议栈使用的校验算法是比较经典的,对16位的数据进行累加计算,并返回计算结果,

CRC16校验和计算icmp_cksum
参数:
 	data:数据
       len:数据长度
返回值:
    计算结果,short类型
*/
static unsigned short icmp_cksum(unsigned char *data, int len)
{
	int sum = 0;   //计算结果
	int odd = len & 0x01;  //是否为奇数
    /*将数据按照2字节为单位累加起来*/
    while(len & 0xfffe){
    	sum += *(unsigned short*)data;
    	data += 2;
    	len -= 2;
    }
    /*判断是否为奇数个数据,若ICMP报头为奇数个字节,会剩下最后一个字节*/
    if(odd){
    	unsigned short tmp = ((*data)<<8)&0xff00;
    	sum += tmp;
    }
    sum = (sum >> 16) + (sum & 0xffff);   //高地位相加
    sum += (sum >> 16);                    //将溢出位加入

    return ~sum;                           //返回取反值
}

5.ICMP头部校验打包和拆包

/**********进行ICMP头部校验********************/
//设置ICMP报头
static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv, int length)
{
	unsigned char i = 0;
	//设置报头
	icmph->icmp_type = ICMP_ECHO;   //ICMP回显请求
	icmph->icmp_code = 0;           //code的值为0
	icmph->icmp_cksum = 0;          //先将cksum的值填为0,便于以后的cksum计算
	icmph->icmp_seq = seq;          //本报的序列号
	icmph->icmp_id = pid & 0xffff;  //填写PID
	for(i=0; i< length; i++)
		icmph->icmp_data[i] = i;   //计算校验和
	icmph->icmp_cksum = icmp_cksum((unsigned char*)icmph, length);
}

/*解压接收到的包,并打印信息*/
static int icmp_unpack(char *buf, int len)
{
	int i,iphdrlen;
	struct ip *ip = NULL;
	struct icmp *icmp = NULL;
	int rtt;

	ip = (struct ip *)buf;            //IP报头
	iphdrlen = ip->ip_hl * 4;         //IP头部长度
	icmp = (struct icmp *)(buf+iphdrlen);  //ICMP段的地址
	len -= iphdrlen;
	//判断长度是否为ICMP包
	if(len < 8){
		printf("ICMP packets\'s length is less than 8\n");
		return -1;
	}
	//ICMP类型为ICMP_ECHOREPLY并且为本进程的PID
	if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid)){
		struct timeval tv_interval,tv_recv,tv_send;
		//在发送表格中查找已经发送的包,按照seq
		pingm_pakcet *packet = icmp_findpacket(icmp->icmp_seq);
		if(packet == NULL)
			return -1;
		packet->flag = 0;          //取消标志
		tv_send = packet->tv_begin;  //获取本包的发送时间

		gettimeofday(&tv_recv,NULL);  //读取此时间,计算时间差
		tv_interval = icmp_tvsub(tv_recv,tv_send);
		rtt = tv_interval.tv_sec * 1000 + tv_interval.tv_usec/1000;
		/*打印结果包含
		  ICMP段的长度
		  源IP地址
		  包的序列号
		  TTL
		  时间差
		*/
		printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n",
			len,inet_ntoa(ip->ip_src),icmp->icmp_seq,ip->ip_ttl,rtt);
		packet_recv ++;              //接收包数量加1
	}
	else {
		return -1;
	}
}

6.计算时间差函数

/************计算时间差time_sub************************
参数:
	end:接收到时间
	begin:开始发送的时间
返回值:
 	使用的时间
*/
static struct timeval icmp_tvsub(struct timeval end, struct timeval begin)
{
	struct timeval tv;
	//计算差值
	tv.tv_sec = end.tv_sec - begin.tv_sec;
	tv.tv_usec = end.tv_usec - begin.tv_usec;
	//如果接收的时间的usec值小于发送时的usec,从uesc域借位
	if(tv.tv_usec < 0){
		tv.tv_sec --;
		tv.tv_usec += 1000000;
	}

	return tv;
}

7.发送报文函数

//**********发送报文***************************
static void *icmp_send(void *argv)
{
	//保存程序开始发送数据的时间
	gettimeofday(&tv_begin, NULL);
	while(alive){
		int size = 0;
		struct timeval tv;
		gettimeofday(&tv, NULL);     //当前包的发送时间
		//在发送包状态数组中找到一个空闲位置
		pingm_pakcet *packet = icmp_findpacket(-1);
		if(packet){
			packet->seq = packet_send;
			packet->flag = 1;
			gettimeofday(&packet->tv_begin,NULL);
		}
		icmp_pack((struct icmp *)send_buff,packet_send,&tv, 64);
		//打包数据
		size = sendto(rawsock, send_buff,64,0,(struct sockaddr *)&dest, sizeof(dest));
		if(size < 0){
			perror("sendto error");
			continue;
		}
		packet_send ++;
		//每隔1s发送一个ICMP回显请求包
		sleep(1);
	}
}

8.接收目的主机的回复函数

//***********接收ping目的主机的回复***********
static void *icmp_recv(void *argv)
{
	//轮询等待时间
	struct timeval tv;
	tv.tv_usec = 200;
	tv.tv_sec = 0;
	fd_set readfd;
	//当没有信号发出一直接收数据
	while(alive){
		int ret = 0;
		FD_ZERO(&readfd);
		FD_SET(rawsock,&readfd);
		ret = select(rawsock+1,&readfd,NULL,NULL,&tv);
		switch(ret)
		{
			case -1:
				//错误发生
				break;
			case 0:
				//超时
				break;
			default :
				{
					//收到一个包
					int fromlen = 0;
					struct sockaddr from;
					//接收数据
					int size = recv(rawsock,recv_buff,sizeof(recv_buff),0);
					if(errno == EINTR){
						perror("recvfrom error");
						continue;
					}
                                        //解包
					ret = icmp_unpack(recv_buff,size);
					if(ret == 1){
						continue;
					}
				}
				break;
		}
	}
}

9.设置ICMP头部(程序中不需要,这里只是作为了解)

/**********设置ICMP发送报文的头部*********************************
   回显请求的ICMP报文
   */
/*struct icmp
{
	u_int8_t icmp_type;   //消息类型
	u_int8_t icmp_code;   //消息类型的子码
	u_int16_t icmp_cksum;   //校验和
	union
	{
		struct ih_idseq    //显示数据报
	    {
	    	u_int16_t icd_id;  //数据报ID
	    	u_int16_t icd_seq;  //数据报的序号
	    }ih_idseq;
	}icmp_hun;
#define icmp_id icmp_hun.ih_idseq.icd_id;
#define icmp_seq icmp_hun.ih_idseq.icd_seq;
	union
	{
		u_int8_t id_data[1];    //数据
	}icmp_dun;
#define icmp_data icmp_dun.id_data;
}; */

10.主函数

//主程序
int main(int argc, char const *argv[])
{
	struct hostent *host = NULL;
	struct protoent *protocol = NULL;
	char protoname[] = "icmp";
	unsigned long inaddr = 1;
	int size = 128*K;

	if(argc < 2)                     //参数是否数量正确
	{
		icmp_usage();
		return -1;
	}
                                           //获取协议类型
	protocol = getprotobyname(protoname);
	if(protocol == NULL)
	{
		perror("getprotobyname()");
		return -1;
	}
	                                       //复制目的地址字符串
	memcpy(dest_str, argv[1],strlen(argv[1])+1);
	memset(pingpacket, 0, sizeof(pingm_pakcet) * 128);
                                           //socket初始化
	rawsock = socket(AF_INET, SOCK_RAW, protocol->p_proto);
	if(rawsock < 0){
		perror("socket");
		return -1;
	}

	pid = getuid();                       //为与其他线程区别,加入pid
                                          //增大接收缓冲区,防止接收包被覆盖
	setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
	bzero(&dest, sizeof(dest));
                                          //获取目的地址的IP地址
	dest.sin_family = AF_INET;
                                          //输入的目的地址为字符串IP地址
	inaddr = inet_addr(argv[1]);
	if(inaddr == INADDR_NONE){             //输入的是DNS地址
		host = gethostbyname(argv[1]);
		if(host == NULL){
			perror("gethostbyname");
			return -1;
		}
		                                    //将地址复制到dest
		memcpy((char *)&dest.sin_addr, host->h_addr, host->h_length);
	}                                       //IP地址字符串
	else {
		memcpy((char *)&dest.sin_addr, &inaddr,sizeof(inaddr));
	}
                                           //打印提示
	inaddr = dest.sin_addr.s_addr;
	printf("PING %s (%ld.%ld.%ld.%ld) 56(84) bytes of data.\n",
		dest_str,(inaddr&0x000000ff)>>0,(inaddr&0x0000ff00)>>8,(inaddr&0x00ff0000)>>16,(inaddr&0xff000000)>>24);
                                           //截取信号SIGINT,将icmp_sigint挂接上
    signal(SIGINT,icmp_sigint);

    /*发送数据并接收回应
	建立两个线程,一个用于发数据,另一个用于接收响应数据,主程序等待两个线程运行完毕后再进行
	下一步,最后对结果进行统计并打印
	*/
	alive = 1;                                     //初始化可运行
	pthread_t send_id, recv_id;                    //建立两个线程,用于发送和接收
	int err = 0;
	err = pthread_create(&send_id, NULL, icmp_send, NULL); //发送
	if(err <  0){
		return -1;
	}
	err = pthread_create(&recv_id, NULL, icmp_recv, NULL); //接收
	if(err < 0){
		return -1;
	}
                                  //等待线程结束
	pthread_join(send_id, NULL);
	pthread_join(recv_id, NULL);
                                  //清理并打印统计结果
	close(rawsock);
	icmp_statistics();
	return 0;
}

三.程序运行方法

由于在程序中用到了<pthread.h>多线程,所以在编译时要加上-lpthread,并且想要运行的话要在root权限下,一般的用户是没有权限的

此程序编译方法为

gcc -o myping myping.c -lpthread

运行:

./myping www.baidu.com

结果为:

时间: 2024-12-27 23:22:30

ping的实现和代码分析的相关文章

2018-2019-2 20165114《网络对抗技术》Exp4 恶意代码分析

Exp4 恶意代码分析 目录 一.实验目标 (1)监控你自己系统的运行状态,看有没有可疑的程序在运行. (2)分析一个恶意软件,就分析Exp2或Exp3中生成后门软件:分析工具尽量使用原生指令或sysinternals,systracer套件. (3)假定将来工作中你觉得自己的主机有问题,就可以用实验中的这个思路,先整个系统监控看能不能找到可疑对象,再对可疑对象进行进一步分析,好确认其具体的行为与性质. 二.基础问题回答 (1)如果在工作中怀疑一台主机上有恶意代码,但只是猜想,所有想监控下系统一

java代码分析及分析工具

java代码分析及分析工具 一个项目从搭建开始,开发的初期往往思路比较清晰,代码也比较清晰.随着时间的推移,业务越来越复杂.代码也就面临着耦合,冗余,甚至杂乱,到最后谁都不敢碰. 作为一个互联网电子商务网站的业务支撑系统,业务复杂不言而喻.从09年开始一直沿用到现在,中间代码经过了多少人的手,留下了多少的坑,已经记不清楚了,谁也说不清了. 代码的维护成本越来越高.代码已经急需做调整和改善.最近项目组专门设立了一个小组,利用业余时间做代码分析的工作,目标对核心代码进行分析并进行设计重构. 代码分析

Java静态代码分析工具Infer

Java静态代码分析工具Infer 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 一.Infer介绍 Infer是Facebook最新开源的静态程序分析工具,用于在发布移动应用之前对代码进行分析,找出潜在的问题.目前Facebook使用此工具分析Facebook的App,包括Android.iOS.Facebook Messenger和Instagram等. Facebook称该工具帮助其每个月检查出应用潜在的数百个Bug,例如一些空指针访问.资源

$*和[email&#160;protected]之间区别代码分析

#!/bin/bash set 'apple pie' pears peaches for i in $*           /*单引号被去掉,循环单个字符输出*/ do echo $i done [[email protected] Ex_14.02-14.31]# sh 14-14-1 apple pie pears peaches -------------------------------------------------------------- #!/bin/bash set

《linux 内核完全剖析》 keyboard.S 部分代码分析(key_map)

keyboard.S 部分代码分析(key_map) keyboard中间有这么一段,我一开始没看明白,究竟啥意思 key_map: .byte 0,27 .ascii "1234567890-=" .byte 127,9 .ascii "qwertyuiop[]" .byte 13,0 .ascii "asdfghjkl;'" .byte '`,0 .ascii "\\zxcvbnm,./" .byte 0,'*,0,32

20145234黄斐《网络对抗技术》实验四,恶意代码分析

恶意代码 概述 恶意代码是指故意编制或设置的.对网络或系统会产生威胁或潜在威胁的计算机代码.最常见的恶意代码有计算机病毒(简称病毒).特洛伊木马(简称木马).计算机蠕虫(简称蠕虫).后门.逻辑炸弹等. 特征: 恶意的目的,获取靶机权限.用户隐私等 本身是计算机程序,可以执行,并作用于靶机 通过执行发生作用,一般来说不运行是没问题的 恶意代码分析 在大多数情况下,进行恶意代码分析时,我们将只有恶意代码的可执行文件本身,而这些文件并不是我们人类可读的.为了了解这些文件的意义,你需要使用各种工具和技巧

20145326蔡馨熠《网络对抗》——恶意代码分析

20145326蔡馨熠<网络对抗>--恶意代码分析 1.实验后回答问题 (1)如果在工作中怀疑一台主机上有恶意代码,但只是猜想,所以想监控下系统一天天的到底在干些什么.请设计下你想监控的操作有哪些,用什么方法来监控.. 需要监控什么? 系统中各种程序.文件的行为. 还需要注意是否会出现权限更改的行为. 注册表. 是否有可疑进程. 如果有网络连接的情况,需要注意这个过程中的IP地址与端口. 用什么来监控? 最先想到的肯定是使用wireshark抓包了,再进行进一步分析. Sysinternals

代码分析—“CA0052 没有选择要分析的目标”(VS2012)

情况: 1.未采用代码分析时程序正常编译 2.采用代码分析,会提示"没有选择分析目标"或"未加载制定版本的程序集"...的错误 分析: 是由于代码分析依赖程序集的强签名,包括版本 解决方案: 1.修改代码分析工具的配置项: FxCopCmd.exe.config里节点AssemblyReferenceResolveMode的Value值StrongName修改为StrongNameIgnoringVersion或None 2.修改当前分析的项目: .csproj增加

常用 Java 静态代码分析工具的分析与比较

转载自: http://www.oschina.net/question/129540_23043 简介: 本文首先介绍了静态代码分析的基本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBugs,PMD,Jtest),最后从功能.特性等方面对它们进行分析和比较,希望能够帮助 Java 软件开发人员了解静态代码分析工具,并选择合适的工具应用到软件开发中. 引言 在 Java 软件开发过程中,开发团队往往要花费大量的时间和精力发现并修改代