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

扯谈网络编程之自己实现ping的更多相关文章

  1. 扯谈网络编程之Tcp SYN flood洪水攻击

    简单介绍 TCP协议要经过三次握手才干建立连接: (from wiki) 于是出现了对于握手过程进行的攻击.攻击者发送大量的SYN包,server回应(SYN+ACK)包,可是攻击者不回应ACK包,这 ...

  2. 浅谈TCP/IP网络编程中socket的行为

    我认为,想要熟练掌握Linux下的TCP/IP网络编程,至少有三个层面的知识需要熟悉: 1. TCP/IP协议(如连接的建立和终止.重传和确认.滑动窗口和拥塞控制等等) 2. Socket I/O系统 ...

  3. Linux 网络编程基础(4) -- Ping 的C代码实现

    1.背景 在进行网络编程的时候,通常使用的协议有TCP协议,UDP协议.这些协议在简历套接字之初需要制定套接字的类型,比如TCP应当设置为 SOCK_STREAM, UDP对应的套接字应当设置为SOC ...

  4. 脑残式网络编程入门(五):每天都在用的Ping命令,它到底是什么?

    本文引用了公众号纯洁的微笑作者奎哥的技术文章,感谢原作者的分享. 1.前言   老于网络编程熟手来说,在测试和部署网络通信应用(比如IM聊天.实时音视频等)时,如果发现网络连接超时,第一时间想到的就是 ...

  5. Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理

    Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...

  6. 浅谈JAVA中如何利用socket进行网络编程(二)

    转自:http://developer.51cto.com/art/201106/268386.htm Socket是网络上运行的两个程序间双向通讯的一端,它既可以接受请求,也可以发送请求,利用它可以 ...

  7. 浅谈C#网络编程(一)

    阅读目录: 基础 Socket编程 多线程并发 阻塞式同步IO 基础 在现今软件开发中,网络编程是非常重要的一部分,本文简要介绍下网络编程的概念和实践. Socket是一种网络编程接口,它是对传输层T ...

  8. 浅谈iOS网络编程之一入门

    计算机网络,基本上可以抽象是端的通信.实际在通讯中会用到不同的设备,不同的硬件中,为了能友好的传输信息,那么建立一套规范就十分必要了.先来了解一些基本概念 了解网络中传输的都是二进制数据流.  2.了 ...

  9. 浅谈JAVA中如何利用socket进行网络编程(一)

    转自:http://developer.51cto.com/art/201106/268385.htm Socket是网络上运行的两个程序间双向通讯的一端,它既可以接受请求,也可以发送请求,利用它可以 ...

随机推荐

  1. Microsoft Win32 Programmer's Reference.chm

    实在是太棒了,感谢这位网友: http://download.csdn.net/detail/tgyd6800/9632351

  2. Linux服务器使用SSH的命令(有详细的参数解释)

    前一阵远程维护Linux服务器,使用的是SSH,传说中的secure shell. 登陆:ssh [hostname] -u user 输入密码:***** 登陆以后就可以像控制自己的机器一样控制它了 ...

  3. Taxonomy of class loader problems encountered when using Jakarta Commons Logging(转)

    Acknowledgments I would like to thank Jacob Kjome for reviewing early drafts of this document. His c ...

  4. perl 改变对象属性

    [root@wx03 test]# cat Critter.pm package Critter; #unshift(@INC,"/root/test"); #use messag ...

  5. 【Nginx笔记】nginx配置文件具体解释

    本文主要对nginx的配置做重点说明,关于nginx的其他基本概念.建议參考官网描写叙述.这里推荐Nginx Beginner's Guide这篇文档.对刚開始学习的人高速认识nginx非常有帮助. ...

  6. crm使用FetchXml聚合查询

    /* 创建者:菜刀居士的博客  * 创建日期:2014年07月08号  */ namespace Net.CRM.FetchXml {     using System;     using Micr ...

  7. AVL树----java

                                                                                        AVL树----java AVL ...

  8. [置顶] Jsp中的table多表头导出excel文件

    首先引入两份JS:copyhtmltoexcel.js以及 tableToExcel.js /* * 默认转换实现函数,如果需要其他功能,需自行扩展 * 参数: * tableID : HTML中Ta ...

  9. 给线程发送消息让它执行不同的处理(自己建立消息循环,非常有意思) good

    unit Unit2; interface usesSystem.Classes, Windows, Messages; constWM_DO = WM_USER + 1; typeTDemoThre ...

  10. asp.net 检查文件夹和文件是否存在

    原文  asp.net 检查文件夹和文件是否存在 允许 path 参数指定相对或绝对路径信息. 相对路径信息被解释为相对于当前工作目录. 检查该目录是否存在之前,从 path 参数的末尾移除尾随空格. ...