ping在类unix下的实现

  1. 参考链接:http://www.jb51.cc/article/p-ehzxwqqp-zr.html
  2. http://blog.csdn.net/petershina/article/details/8571562
  3. http://www.cnblogs.com/noble/p/4144139.html
  4. http://www.jb51.cc/article/p-qxsxpcrq-tm.html
  1. ping程序使用原始套接字,发送以太网首部+ip首部+icmp首部+56字节的数据
  2. 在发送时将icmp标志设置为进程pid,同时icmp序列号由1递增,数据部分为发送时的时间,在icmp应答报文中将请求回显报文中的标志位和数据原封不动的返回,便于计算往返时延rtt。
  3. icmp报头+数据 检验采用循环二进制反码求和
  4. 流程:创建通信的套接字-->将地址、端口信息与套接字绑定-->构建IP包头与ICMP包头-->发送构建的数据包-->接收对方主机的回应-->给出程序反馈信息
  5. 编译时要加上-lpthread
    包含头文件了,仅能说明有了线程函数的声明, 但是还没有实现, 加上-lpthread是在链接阶段,链接这个库
  6. 写注释时注意别在注释外面写全角空格和英文,编译器不识别
  7. protoent结构:
    struct protoent{
    	char *p_name; //网络协议名
    	char **p_aliases;//别名
    	int p_proto;//网络协议编号
    } 
    
  8. ip结构
    typedef struct _ip_hdr  
    {  
        #if LITTLE_ENDIAN  
        unsigned char ihl:4;     //首部长度  
        unsigned char version:4,//版本   
        #else  
        unsigned char version:4,//版本  
        unsigned char ihl:4;     //首部长度  
        #endif  
        unsigned char tos;       //服务类型  
        unsigned short tot_len;  //总长度  
        unsigned short id;       //标志  
        unsigned short frag_off; //分片偏移  
        unsigned char ttl;       //生存时间  
        unsigned char protocol;  //协议  
        unsigned short chk_sum;  //检验和  
        struct in_addr srcaddr;  //源IP地址  
        struct in_addr dstaddr;  //目的IP地址  
    }ip_hdr; 
    
  9. icmp结构
    typedef struct _icmphdr{
    unsigned char i_type; //8位类型,本实验用 8: ECHO 0:ECHO REPLY
    unsigned char i_code; //8位代码,本实验置零
    unsigned short i_cksum; //16位校验和,从TYPE开始,直到最后一位用户数据,如果为字节数为奇数则补充一位
    unsigned short i_id ; //识别号(一般用进程号作为识别号),用于匹配ECHO和ECHO REPLY包
    unsigned short i_seq ; //报文序列号,用于标记ECHO报文顺序
    unsigned int timestamp; //时间戳
    }ICMP_HEADER;
  10. 实现代码:
    #include<stdio.h>
    #include<stdlib.h>
    #include<signal.h>
    #include<unistd.h>
    #include<netinet/ip_icmp.h>
    #include<netdb.h>/*名字与地址转换*/
    #include<string.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<sys/time.h>
    #include<netinet/in.h>/*字节排序*/
    #include<arpa/inet.h>/*字符串与网络字节序转化*/
    #include<pthread.h>
    
    
    struct sockaddr_in dst_addr;
    struct sockaddr_in recv_addr;
    
    struct timeval tvrecv;
    char icmp_pkt[1024]={0};
    char recv_pkt[1024]={0};
    
    int sockfd=0,bytes=56,nsend_pkt = 0,nrecv_pkt = 0;
    pid_t pid;
    
    void statistics();
    int in_chksum(unsigned short * buf,int size);/*检验和算法*/
    int pack(int send_pkt);/*设置icmp报头*/
    void *send_ping();/*发送三个icmp报文*/
    int unpack(char *recv_pkt,int size);/*剥去icmp报头*/
    void *recv_ping();/*接受所有icmp报文*/
    void tv_sub(struct timeval *out,struct timeval *in);/*两个time_val结构相减*/
    
    /*struct protoent{
    	char *p_name; //网络协议名
    	char **p_aliases;//别名
    	int p_proto;//网络协议编号
    } */
    int main(int argc,char *argv[])
    {
    	int size = 50 * 1024;
    	int errno=-1;
    	int ttl=64;
    	void *tret;
    	pthread_t send_id,recv_id;
    	struct in_addr ipv4_addr;
    	struct hostent *ipv4_host;
    	struct protoent *protocol = NULL;
    
    	if(argc < 2 )
    	{
    		printf("Usage : ./ping <host>\n");
    		exit(1);
    	}
    	
    	if((protocol = getprotobyname("icmp")) == NULL)/*不可重入,线程不安全*/
    	{
    		printf("unkown protocol\n");
    		exit(1);
    	}
     /*使用原始套接字,只有root权限才可以创建,以太网首部+ip首部+icmp首部+56字节的数据,直接使用底层协议*/
    	if((sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto)) < 0)
    	{
    		printf("socket fail\n");
    		exit(1);
    	}
    	/*回收root权限,设置当前实际用户权限。*/
    	setuid(getuid());
    	/*扩大套件字接受缓冲区到50k,避免缓冲区溢出的可能性,若无意中ping一个广播或多播地址,可能会收到大量应答*/
    	setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size));
    	/*指定为IPPROTO_IP级别,制定外出TTL。*/
    	setsockopt(sockfd,IPPROTO_IP,IP_MULTICAST_TTL,&ttl,sizeof(ttl));
       /*指定存活时间*/
    	setsockopt(sockfd,IP_TTL,sizeof(ttl));
    /*memset(&dst_addr,sizeof(dst_addr));*/
         bzero(&dst_addr,sizeof(dst_addr));
    	 dst_addr.sin_family=AF_INET;
    	 errno=inet_aton(argv[1],&ipv4_addr);
    
    	 if(errno == 0)
    	 {
    		 ipv4_host = gethostbyname(argv[1]);
    		 if(ipv4_host == NULL)
    		 {
    			 printf("connect: Invalid argument\n");
    			 return -1;
    		 }
    		 dst_addr.sin_addr=*((struct in_addr  *)ipv4_host->h_addr);
    	 }
    	 else
    	 {
    		 dst_addr.sin_addr= ipv4_addr;
    	 }
    
    	 pid=getpid();
    
    	 printf("PING %s (%s) %d bytes of data.\n",argv[1],inet_ntoa(dst_addr.sin_addr),bytes);
         signal(SIGINT,statistics);
    
    	 if((errno=pthread_create(&send_id,NULL,send_ping,NULL)) !=0)
    	 {
    		 printf("send_ping thread fail\n");
    		 exit(1);
    	 }
    
        if((errno=pthread_create(&recv_id,recv_ping,NULL)) !=0)
    	 {
    		 printf("recv_ping thread fail\n");
    		 exit(1);
    	 }
    
    	
    	 if((errno =pthread_join(send_id,&tret)) !=0)
    	 {
    		 printf("can't join with thread send");
    		 exit(1);
    	 }
    	
    	 if((errno =pthread_join(recv_id,&tret)) != 0)
    	 {
    		 printf("can't join with thread recv");
    		 exit(1);
    	 }
    
    	 return 0;
    }
    /*在收到终端信号时,输出ping 的检测状态 */   
    void statistics()
    {
    	printf("\n--- %s ping statistics ---\n",inet_ntoa(dst_addr.sin_addr));
    	printf("%d packets transmitted,%d recvived,%.3f%c packet loss\n",nsend_pkt,nrecv_pkt,(float)100*(nsend_pkt-nrecv_pkt)/nsend_pkt,'%');
    	close(sockfd);
    
    	exit(0);
    }
    /*校验和算法 icmp首部+数据*/
    int in_chksum(unsigned short *buf,int size)
    {
         int nleft=size;
    	 int sum=0;
    	 unsigned short *w=buf;
    	 unsigned short ans=0;
    	 /*将ICMP二进制数据以两个字节相加*/
    	 while(nleft > 1)
    	 {
    		 sum+=*w; /*相加16bits*/
    		 w++;  /* 移动sizeof(short)2个大小的字节*/
    		 nleft-=2;
    	 }
    	 /*若只剩下一个字节的数据,则将最后一个字节视为两个字节的高字节,低字节视为0,继续相加*/
    	 if(nleft == 1)
    	 {
    		 *(unsigned char *)(&ans)= *(unsigned char *)w;
    		 sum+=ans;
    	 }
    	 /*将高16位与低16位相加,可能产生进位,故第二次移位相加*/
    	 sum=(sum>>16)+(sum & 0xFFFF);
    	 sum+=(sum>>16);
    	 ans=~sum;
    	 return ans;
    }
    
    int pack(int send_pkt)
    {
       struct icmp *pkt= (struct icmp  *)icmp_pkt;
       struct timeval *time=NULL;
    
       pkt->icmp_type = ICMP_ECHO;
       pkt->icmp_code = 0;
       pkt->icmp_cksum= 0;
       pkt->icmp_seq= htons(send_pkt);
       pkt->icmp_id = pid;
       time = (struct timeval *)pkt->icmp_data;
       gettimeofday(time,NULL);/*记录发送时间*/
       pkt->icmp_cksum=in_chksum((unsigned short *)pkt,bytes + 8);/*进行校验*/
      
       return bytes + 8;
    }
    
    void *send_ping()
    {
    	int send_bytes=0;
    	int ret=-1;
    	while(1)
    	{
           nsend_pkt++;
    	   send_bytes= pack(nsend_pkt);
    
           ret=sendto(sockfd,icmp_pkt,send_bytes,(struct sockaddr *)&dst_addr,sizeof(dst_addr));
    	   if(ret == -1)
    		   printf("send fail \n");
    	   sleep(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_sec-=in->tv_sec;
    }
    
    int unpack(char *recv_pkt,int size)
    {
    	struct iphdr *ip= NULL;
    	int iphdrlen;
    	struct icmp *icmp;
    	struct timeval *tvsend;
    	double rtt;
    	ip = (struct iphdr *)recv_pkt;
    	iphdrlen = ip->ihl<<2;/*求ip报头的长度,单位为4个字节*/
    	icmp = (struct icmp *)(recv_pkt + iphdrlen);
    
    	size-=iphdrlen;
    	if(size <8)/*小于icmp报头长度则不合理*/
    	{
    		printf("ICMP size is less than 8\n");
    		exit(1);
    	}
    	if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid))
    	{
    		tvsend = (struct timeval *)icmp->icmp_data;
    		tv_sub(&tvrecv,tvsend);
    		rtt = tvrecv.tv_sec * 1000 + (double)tvrecv.tv_usec /(double)1000;
    		printf("%d byte from %s: icmp_seq = %d ttl=%d rtt=%.3fms\n",size,inet_ntoa(recv_addr.sin_addr),ntohs(icmp->icmp_seq),ip->ttl,rtt);
    	}
    		else
    		{
    			return -1;
    		}
    		return 0;
    }
    void *recv_ping()
    {
    	fd_set rd_set;
    	struct timeval time;
    	time.tv_sec = 5;
    	time.tv_usec =0;
        int ret = 0,nread =0,recv_len =0;
        recv_len= sizeof(recv_addr);
    	while(1)
    	{
    		FD_ZERO(&rd_set);
    		FD_SET(sockfd,&rd_set);
    		ret=select(sockfd+1,&rd_set,&time);
    
    		if(ret <= 0)
    			continue;
    			else if(FD_ISSET(sockfd,&rd_set))
    			{
    				nread=recvfrom(sockfd,recv_pkt,sizeof(recv_pkt),(struct sockaddr *)&recv_addr,(socklen_t *)&recv_len);
    				if(nread < 0)
    					continue;
    				gettimeofday(&tvrecv,NULL);
    				if(unpack(recv_pkt,nread) == -1)
    				  continue;
    				  nrecv_pkt++;
    			}
    	}
    }
运行结果:

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


用的openwrt路由器,家里宽带申请了动态公网ip,为了方便把2280端口映射到公网,发现经常被暴力破解,自己写了个临时封禁ip功能的脚本,实现5分钟内同一个ip登录密码错误10次就封禁这个ip5分钟,并且进行邮件通知使用步骤openwrt为19.07.03版本,其他版本没有测试过安装bashmsmtpopkg
#!/bin/bashcommand1&command2&wait从Shell脚本并行运行多个程序–杨河老李(kviccn.github.io)
1.先查出MAMP下面集成的PHP版本cd/Applications/MAMP/bin/phpls-ls 2.编辑修改.bash_profile文件(没有.bash_profile文件的情况下回自动创建)sudovim~/.bash_profile在文件的最后输入以下信息,然后保存退出exportPATH="/Applications/MAMP/bin/php/php7.2.20/b
1、先输入locale-a,查看一下现在已安装的语言2、若不存在如zh_CN之类的语言包,进行中文语言包装:apt-getinstalllanguage-pack-zh-hans3、安装好后我们可以进行临时修改:然后添加中文支持: locale-genzh_CN.UTF-8临时修改> export LC_ALL='zh_CN.utf8'> locale永久
BashPerlTclsyntaxdiff1.进制数表示Languagebinaryoctalhexadecimalbash2#[0~1]0[0~7]0x[0~f]or0X[0~f]perl0b[0~1]0[0~7]0x[0~f]tcl0b[0~1]0o[0~7]0x[0~f]bashdifferentbaserepresntationreference2.StringlengthLanguageStr
正常安装了k8s后,使用kubect工具后接的命令不能直接tab补全命令补全方法:yum-yinstallbash-completionsource/usr/share/bash-completion/bash_completionsource<(kubectlcompletionbash)echo"source<(kubectlcompletionbash)">>~/.bashrc 
参考这里启动jar包shell脚本修改过来的#!/bin/bash#默认应用名称defaultAppName='./gadmin'appName=''if[[$1&&$1!=0]]thenappName=$1elseappName=$defaultAppNamefiecho">>>>>>本次重启的应用:$appName<
#一个数字的行#!/bin/bashwhilereadlinedon=`echo$line|sed's/[^0-9]//g'|wc-L`if[$n-eq1]thenecho$linefidone<1.txt#日志切割归档#!/bin/bashcd/data/logslog=1.logmv_log(){[-f$1]&&mv$1$2
#文件增加内容#!/bin/bashn=0cat1.txt|whilereadlinedon=[$n+1]if[$n-eq5]thenecho$lineecho-e"#Thisisatestfile.\n#Testinsertlineintothisfile."elseecho$linefidone#备份/etc目录#
# su - oraclesu: /usr/bin/ksh: No such file or directory根据报错信息:显示无法找到文件 /usr/bin/ksh果然没有该文件,但是发现存在文件/bin/ksh,于是创建了一个软连接,可以规避问题,可以成功切换到用户下,但无法执行系统自带命令。$. .bash_profile-ksh: .: .b
history显示历史指令记录内容,下达历史纪录中的指令主要的使用方法如果你想禁用history,可以将HISTSIZE设置为0:#exportHISTSIZE=0使用HISTIGNORE忽略历史中的特定命令下面的例子,将忽略pwd、ls、ls-ltr等命令:#exportHISTIGNORE=”pwd:ls:ls-ltr:”使用HIS
一.命令历史  1.history环境变量:    HISTSIZE:输出的命令历史条数,如history的记录数    HISTFILESIZE:~/.bash_history保存的命令历史记录数    HISTFILLE:历史记录的文件路径    HISTCONTROL:     ignorespace:忽略以空格开头的命令
之前在网上看到很多师傅们总结的linux反弹shell的一些方法,为了更熟练的去运用这些技术,于是自己花精力查了很多资料去理解这些命令的含义,将研究的成果记录在这里,所谓的反弹shell,指的是我们在自己的机器上开启监听,然后在被攻击者的机器上发送连接请求去连接我们的机器,将被攻击者的she
BashOne-LinersExplained,PartI:Workingwithfileshttps://catonmat.net/bash-one-liners-explained-part-oneBashOne-LinersExplained,PartII:Workingwithstringshttps://catonmat.net/bash-one-liners-explained-part-twoBashOne-LinersExplained,PartII
Shell中变量的作用域:在当前Shell会话中使用,全局变量。在函数内部使用,局部变量。可以在其他Shell会话中使用,环境变量。局部变量:默认情况下函数内的变量也是全局变量#!/bin/bashfunctionfunc(){a=99}funcecho$a输出>>99为了让全局变量变成局部变量
1、多命令顺序执行;  命令1;命令2  多个命令顺序执行,命令之间没有任何逻辑联系&&  命令1&&命令2  逻辑与,当命令1正确执行,才会执行命令2||  命令1||命令2  逻辑或,当命令1执行不正确,才会执行命令2例如:ls;date;cd/home/lsx;pwd;who ddif=输入文件of=输
原博文使用Linux或者unix系统的同学可能都对#!这个符号并不陌生,但是你真的了解它吗?首先,这个符号(#!)的名称,叫做"Shebang"或者"Sha-bang"。Linux执行文件时发现这个格式,会把!后的内容提取出来拼接在脚本文件或路径之前,当作实际执行的命令。 Shebang这个符号通常在Unix系统的脚本
1、历史命令history[选项][历史命令保存文件]选项:-c:  清空历史命令-w:  把缓存中的历史命令写入历史命令保存文件 ~/.bash_historyvim/etc/profile中的Histsize可改存储历史命令数量历史命令的调用使用上、下箭头调用以前的历史命令使用“!n”重复执行第n条历史
目录1.Shell脚本规范2.Shell脚本执行3.Shell脚本变量3.1环境变量3.1.1自定义环境变量3.1.2显示与取消环境变量3.1.3环境变量初始化与对应文件的生效顺序3.2普通变量3.2.1定义本地变量3.2.2shell调用变量3.2.3grep调用变量3.2.4awk调用变量3.3
   http://www.voidcn.com/blog/wszzdanm/article/p-6145895.html命令功能:显示登录用户的信息命令格式:常用选项:举例:w显示已经登录的用户及正在进行的操作[root@localhost~]#w 11:22:01up4days,21:22, 3users, loadaverage:0.00,0.00,0.00USER