大家好,又见面了,我是全栈君。
ping是基于ICMP(Internet Control Message Protocol)协议实现的。而ICMP协议是在IP层实现的。
ping实际上是发起者发送一个Echo Request(type = 8)的,远程主机回应一个Echo Reply(type = 0)的过程。
为什么用ping不能測试某一个port
刚開始接触网络的时候,可能非常多人都有疑问,怎么用ping来測试远程主机的某个特定port?
事实上假设看下ICMP协议,就能够发现ICMP里根本没有port这个概念,也就根本无法实现測试某一个port了。
ICMP协议的包格式(来自wiki):
Bits 0–7 | Bits 8–15 | Bits 16–23 | Bits 24–31 | |
---|---|---|---|---|
IP Header (20 bytes) | Version/IHL | Type of service | Length | |
Identification | flags and offset | |||
Time To Live (TTL) | Protocol | Checksum | ||
Source IP address | ||||
Destination IP address | ||||
ICMP Header (8 bytes) | Type of message | Code | Checksum | |
Header Data | ||||
ICMP Payload (optional) | Payload Data |
Echo Request的ICMP包格式(from wiki):
00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Type = 8 | Code = 0 | Header Checksum | |||||||||||||||||||||||||||||
Identifier | Sequence Number | ||||||||||||||||||||||||||||||
Data |
Ping怎样计算请问耗时
在ping命令的输出上,能够看到有显示请求的耗时。那么这个耗时是怎么得到的呢?
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=6.28 ms
从Echo Request的格式里,看到不时间相关的东东,可是由于是Echo,即远程主机会原样返回Data数据。所以Ping的发起方把时间放到了Data数据里,当得到Echo Reply里,取到发送时间。再和当前时间比較,就能够得到耗时了。
当然,还有其他的思路,比方记录每个包的发送时间。当得到返回时。再计算得到时间差,但显然这种实现太复杂了。
Ping怎样区分不同的进程?
我们都知道本机IP。远程IP,本机port,远程port,四个元素才干够确定唯的一个信道。而ICMP里没有port,那么一个ping程序怎样知道哪些包才是发给自己的?或者说操作系统怎样差别哪个Echo Reply是要发给哪个进程的?
实际上操作系统不能差别,全部的本机IP。远程IP同样的ICMP程序都能够接收到同一份数据。
程序自己要依据Identifier来区分究竟一个ICMP包是不是发给自己的。在Linux下,Ping发出去的Echo Request包里Identifier就是进程pid,远程主机会返回一个Identifier同样的Echo Reply包。
能够接以下的方法简单验证:
启动系统自带的ping程序,查看其pid。
设定自己实现的ping程序的identifier为上面得到的pid。然后发Echo Request包。
能够发现系统ping程序会接收到远程主机的回应。
自己实现ping
自己实现ping要用到rawsocket,在linux下须要root权限。
网上有非常多实现的程序,可是有非常多地方不太对的。自己总结实现了一个(最好用g++编绎):
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <errno.h> #include <netinet/ip.h> #include <unistd.h> #include <sys/types.h> #include <sys/time.h> unsigned short csum(unsigned short *ptr, int nbytes) { register long sum; unsigned short oddbyte; register short answer; sum = 0; while (nbytes > 1) { sum += *ptr++; nbytes -= 2; } if (nbytes == 1) { oddbyte = 0; *((u_char*) &oddbyte) = *(u_char*) ptr; sum += oddbyte; } sum = (sum >> 16) + (sum & 0xffff); sum = sum + (sum >> 16); answer = (short) ~sum; return (answer); } inline double countMs(timeval before, timeval after){ return (after.tv_sec - before.tv_sec)*1000 + (after.tv_usec - before.tv_usec)/1000.0; } #pragma pack(1) struct EchoPacket { u_int8_t type; u_int8_t code; u_int16_t checksum; u_int16_t identifier; u_int16_t sequence; timeval timestamp; char data[40]; //sizeof(EchoPacket) == 64 }; #pragma pack() void ping(in_addr_t source, in_addr_t destination) { static int sequence = 1; static int pid = getpid(); static int ipId = 0; char sendBuf[sizeof(iphdr) + sizeof(EchoPacket)] = { 0 }; struct iphdr* ipHeader = (iphdr*)sendBuf; ipHeader->version = 4; ipHeader->ihl = 5; ipHeader->tos = 0; ipHeader->tot_len = htons(sizeof(sendBuf)); ipHeader->id = htons(ipId++); ipHeader->frag_off = htons(0x4000); //set Flags: don't fragment ipHeader->ttl = 64; ipHeader->protocol = IPPROTO_ICMP; ipHeader->check = 0; ipHeader->saddr = source; ipHeader->daddr = destination; ipHeader->check = csum((unsigned short*)ipHeader, ipHeader->ihl * 2); EchoPacket* echoRequest = (EchoPacket*)(sendBuf + sizeof(iphdr)); echoRequest->type = 8; echoRequest->code = 0; echoRequest->checksum = 0; echoRequest->identifier = htons(pid); echoRequest->sequence = htons(sequence++); gettimeofday(&(echoRequest->timestamp), NULL); u_int16_t ccsum = csum((unsigned short*)echoRequest, sizeof(sendBuf) - sizeof(iphdr)); echoRequest->checksum = ccsum; struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(0); sin.sin_addr.s_addr = destination; int s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (s == -1) { perror("socket"); return; } //IP_HDRINCL to tell the kernel that headers are included in the packet if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, "1",sizeof("1")) < 0) { perror("Error setting IP_HDRINCL"); exit(0); } sendto(s, sendBuf, sizeof(sendBuf), 0, (struct sockaddr *) &sin, sizeof(sin)); char responseBuf[sizeof(iphdr) + sizeof(EchoPacket)] = {0}; struct sockaddr_in receiveAddress; socklen_t len = sizeof(receiveAddress); int reveiveSize = recvfrom(s, (void*)responseBuf, sizeof(responseBuf), 0, (struct sockaddr *) &receiveAddress, &len); if(reveiveSize == sizeof(responseBuf)){ EchoPacket* echoResponse = (EchoPacket*) (responseBuf + sizeof(iphdr)); //TODO check identifier == pid ? if(echoResponse->type == 0){ struct timeval tv; gettimeofday(&tv, NULL); in_addr tempAddr; tempAddr.s_addr = destination; printf("%d bytes from %s : icmp_seq=%d ttl=%d time=%.2f ms\n", sizeof(EchoPacket), inet_ntoa(tempAddr), ntohs(echoResponse->sequence), ((iphdr*)responseBuf)->ttl, countMs(echoResponse->timestamp, tv)); }else{ printf("response error, type:%d\n", echoResponse->type); } }else{ printf("error, response size != request size.\n"); } close(s); } int main(void) { in_addr_t source = inet_addr("192.168.1.100"); in_addr_t destination = inet_addr("192.168.1.1"); for(;;){ ping(source, destination); sleep(1); } return 0; }
安全相关的一些东东:
死亡之Ping http://zh.wikipedia.org/wiki/%E6%AD%BB%E4%BA%A1%E4%B9%8BPing
虽然是非常老的漏洞。可是也能够看出协议栈的实现也不是那么的靠谱。
Ping flood http://en.wikipedia.org/wiki/Ping_flood
server关闭ping服务,默认是0,是开启:
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
总结:
在自己实现的过程中。发现有一些蛋疼的地方,如
协议文档不够清晰,得重复对比。
有时候一个小地方处理不正确,非常难查bug。即使程序能正常工作,但也并不代表它是正确的。
用wireshark能够非常方便验证自己写的程序有没有问题。
參考:
http://en.wikipedia.org/wiki/Ping_(networking_utility)
http://en.wikipedia.org/wiki/ICMP_Destination_Unreachable
http://tools.ietf.org/pdf/rfc792.pdf
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/116388.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...