扯谈网络编程之自己实现ping
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的更多相关文章
- 扯谈网络编程之Tcp SYN flood洪水攻击
简单介绍 TCP协议要经过三次握手才干建立连接: (from wiki) 于是出现了对于握手过程进行的攻击.攻击者发送大量的SYN包,server回应(SYN+ACK)包,可是攻击者不回应ACK包,这 ...
- 浅谈TCP/IP网络编程中socket的行为
我认为,想要熟练掌握Linux下的TCP/IP网络编程,至少有三个层面的知识需要熟悉: 1. TCP/IP协议(如连接的建立和终止.重传和确认.滑动窗口和拥塞控制等等) 2. Socket I/O系统 ...
- Linux 网络编程基础(4) -- Ping 的C代码实现
1.背景 在进行网络编程的时候,通常使用的协议有TCP协议,UDP协议.这些协议在简历套接字之初需要制定套接字的类型,比如TCP应当设置为 SOCK_STREAM, UDP对应的套接字应当设置为SOC ...
- 脑残式网络编程入门(五):每天都在用的Ping命令,它到底是什么?
本文引用了公众号纯洁的微笑作者奎哥的技术文章,感谢原作者的分享. 1.前言 老于网络编程熟手来说,在测试和部署网络通信应用(比如IM聊天.实时音视频等)时,如果发现网络连接超时,第一时间想到的就是 ...
- Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理
Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...
- 浅谈JAVA中如何利用socket进行网络编程(二)
转自:http://developer.51cto.com/art/201106/268386.htm Socket是网络上运行的两个程序间双向通讯的一端,它既可以接受请求,也可以发送请求,利用它可以 ...
- 浅谈C#网络编程(一)
阅读目录: 基础 Socket编程 多线程并发 阻塞式同步IO 基础 在现今软件开发中,网络编程是非常重要的一部分,本文简要介绍下网络编程的概念和实践. Socket是一种网络编程接口,它是对传输层T ...
- 浅谈iOS网络编程之一入门
计算机网络,基本上可以抽象是端的通信.实际在通讯中会用到不同的设备,不同的硬件中,为了能友好的传输信息,那么建立一套规范就十分必要了.先来了解一些基本概念 了解网络中传输的都是二进制数据流. 2.了 ...
- 浅谈JAVA中如何利用socket进行网络编程(一)
转自:http://developer.51cto.com/art/201106/268385.htm Socket是网络上运行的两个程序间双向通讯的一端,它既可以接受请求,也可以发送请求,利用它可以 ...
随机推荐
- 项目优化经验分享(六)SVN冲突和处理
上一篇博客我们分享了新增需求的确定思想<站在全局看问题>.今天我们来分享项目开发中SVN冲突的解决经验:SVN冲突和处理! 引言 开发过项目的人都知道,公司开发一个项目都会使用到版本号控制 ...
- 让office2003和office2010共存的方法【转】
前段时间由于工作需要安装office2010,每次打开word都会弹出安装配置界面,反之亦然.于是我在网上找了不少资料.也试了不少方法,终于试用了以下方法得以解决,以下来源于网络. 电脑上同时安装了O ...
- 快速安装多系统(xp与win7)
具体方法: 1.利用pe安装xp系统 2.xp下,空出一个分区,用于安装win7 3.进入pe下,安装win7系统到空出的分区 4.win7正常启动后,会覆盖原来xp的启动方式 5.再次进入pe,利用 ...
- 52. 模版和设计元素——Lotus Notes的代码重用
不论是理论上还是实用上,代码重用都是编程的一个重要议题.可以从两个角度来讨论代码重用. 一是逻辑上代码以怎样的方式被重用.既可以通过面向对象的思想普及以来耳熟能详的继承的方式.比如先建了一个车的基类, ...
- Android滑动菜单框架完全解析,教你如何一分钟实现滑动菜单特效
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/8744400 之前我向大家介绍了史上最简单的滑动菜单的实现方式,相信大家都还记得.如 ...
- Effective C++_笔记_条款00_基本术语
(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 下面是每一位C++程序员都应该了解的C++词汇. 1 C++中 ...
- metasploit学习之ms03_026
傻瓜式利用ms03_026_dcom: Matching Modules ================ Name Disclosure Date Rank Description ---- --- ...
- jquery 动态添加和删除 ul li列表
今天需要实现一个jquery动态添加和删除 ul li列表中的li行,自己简单的实现乐一个,分享一下 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML ...
- mysql基础:列类型--整型
mysql列类型--字符串 http://blog.csdn.net/jk110333/article/details/9342301 mysql列类型--时间和日期 http://blog. ...
- Swift - 使用set,get确保索引加减在正常的范围内
通过类的计算属性set和get,我们可以对索引的加减进行保护.下面是一个样例,索引index初始值是0,有效范围是0~2.不管是index++还是index--,索引都是一直在这个范围能循环遍历. 1 ...