扯谈网络编程之自己实现ping

ping是基于ICMP(Internet Control Message Protocol)协议实现的,而ICMP协议是在IP层实现的。

ping实际上是发起者发送一个Echo Request(type = 8)的,远程主机回应一个Echo Reply(type = 0)的过程。

为什么用ping不能测试某一个端口

刚开始接触网络的时候,可能很多人都有疑问,怎么用ping来测试远程主机的某个特定端口?

其实如果看下ICMP协议,就可以发现ICMP里根本没有端口这个概念,也就根本无法实现测试某一个端口了。

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,本机端口,远程端口,四个元素才可以确定唯的一个信道。而ICMP里没有端口,那么一个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);
	}

	while(true){
		sendto(s, sendBuf, sizeof(sendBuf), 0, (struct sockaddr *) &sin, sizeof(sin));
		usleep(100);
	}

	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

总结:

在自己实现的过程中,发现有一些蛋疼的地方,如

协议文档不够清晰,得反复对照;

有时候一个小地方处理不对,很难查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,布布扣,bubuko.com

时间: 2024-10-13 13:11:10

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

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

简介 TCP协议要经过三次握手才能建立连接: (from wiki) 于是出现了对于握手过程进行的攻击.攻击者发送大量的FIN包,服务器回应(SYN+ACK)包,但是攻击者不回应ACK包,这样的话,服务器不知道(SYN+ACK)是否发送成功,默认情况下会重试5次(tcp_syn_retries).这样的话,对于服务器的内存,带宽都有很大的消耗.攻击者如果处于公网,可以伪造IP的话,对于服务器就很难根据IP来判断攻击者,给防护带来很大的困难. 攻与防 攻击者角度 从攻击者的角度来看,有两个地方可以

浅谈网络编程

目录 网络编程 C/S架构和B/S架构 网络协议(osi七层协议) tcp/ip五层协议分析 物理层 数据链路层 网络层 传输层 应用层 socket 网络编程 C/S架构和B/S架构 C:client 客户端 S:server 服务端 如:电脑上装的qq,微信等等 B:browser 浏览器 S:server 服务端 如:京东,天猫,7k7k等等 B/S架构的本质也是C/S S 端: ? 1.有固定的ip ? 2.稳定一直在运行,支持并发 网络协议(osi七层协议) 互联网协议按照功能不同分为

网络编程的基本概念

网络编程是编写服务器程序不可或缺的部分. 高性能的服务器程序一定都会有好的网络处理程序,下面就来谈一谈网络编程的基本概念. 凡是网络编程,几乎都离不开socket.socket分为阻塞与非阻塞两种.阻塞的socket在调用读写接口是,会一直阻塞到socket上有可读数据,或者socket可以写入数据.而非阻塞socket,则会立即返回,不管socket是否可以读写.举个简单的例子,你去咖啡店买咖啡,发现前面排了很多人,对于阻塞模型来说,就是你会排队一直等待.非阻塞模型,就是你看到要排队,你就走了

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

我认为,想要熟练掌握Linux下的TCP/IP网络编程,至少有三个层面的知识需要熟悉: . TCP/IP协议(如连接的建立和终止.重传和确认.滑动窗口和拥塞控制等等) . Socket I/O系统调用(重点如read/write),这是TCP/IP协议在应用层表现出来的行为. . 编写Performant, Scalable的服务器程序.包括多线程.IO Multiplexing.非阻塞.异步等各种技术. 关于TCP/IP协议,建议参考Richard Stevens的<TCP/IP Illust

《网络编程》原始套接字 ---ping程序实现

概述 基于字节流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)不可以访问传输层协议,只是对应用层的报文进行操作,传输层的数据报格式都是由系统提供的协议栈实现,用户只需要填充相应的应用层报文,由系统完成底层报文首部的填充并发送.原始套接字(SOCK_RAW)可以访问位于基层的传输层协议,原始套接字没有端口号. 原始套接字(SOCK_RAW)是一种不同于 SOCK_STREAM.SOCK_DGRAM 的套接字,它实现于系统核心.原始套接字使进程可以读与写 ICMP.IGMP

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

1.背景 在进行网络编程的时候,通常使用的协议有TCP协议,UDP协议.这些协议在简历套接字之初需要制定套接字的类型,比如TCP应当设置为 SOCK_STREAM, UDP对应的套接字应当设置为SOCK_DGRAM.但是这些套接字并非能够提供网络所需的全部功能,我们还需要其他的套接字,比如原始套接字OCK_RAW.原始 套接字可以提供SOCK_STREAM和SOCK_DGRAM所不及的能力.比如: (1)有了原始套接字,进程可以读取ICMPV4.ICMPV6.IGMP等的分组.正如ping所使用

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

Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO https://blog.csdn.net/column/details/21963.html 部分代码会放在我的的Github:https://github.com/h2pl/ 浅谈 Linux

浅谈iOS网络编程之一入门

计算机网络,基本上可以抽象是端的通信.实际在通讯中会用到不同的设备,不同的硬件中,为了能友好的传输信息,那么建立一套规范就十分必要了.先来了解一些基本概念 了解网络中传输的都是二进制数据流.  2.了解网络编程概念. 认识网络: // 网络概念 <1> 经常见到的: 网卡/网线/IP地址/子网掩码/路由地址/DNS服务器地址 作用? // <2> 容易忽略的:MAC地址/数据/数据包 // <3> 网络编程的概念:客户端/服务器/请求/响应/数据流 // 网络是数据交互

也谈服务器端网络编程

这里主要说一下自己看的书和一些心得体会,把自己的学习路线梳理一下,也是作为自己这段时间的学习总结. 因为从事的都是服务器端开发和学习,所以本文内容主要在该范围内,假定读者经过了系统的CS学习(DS.OS.组成原理等核心课程). First,我的学习书籍路线参考如下内容: 1 A:<Linux鸟哥私房菜>(第三版): 2 3 B:<Linux程序设计>(第四版 中文版): 4 5 C:<TCP/IP详解v1>(英文版): 6 7 D:C/C++语言相关: 8 9 E:&l