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

扯谈网络编程之自己实现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

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

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/116388.html原文链接:https://javaforall.net

(0)
上一篇 2022年1月22日 上午6:00
下一篇 2022年1月22日 上午7:00


相关推荐

  • java: 找不到符号「建议收藏」

    java: 找不到符号「建议收藏」java:找不到符号符号:类TypeInformationTestBase位置:程序包org.apache.flink.api.common.typeutils程序报这种错误,不是代码造成的。解决办法:0、首先要统一编码格式:file–>settings–>fileencodings1、右键–>maven–>reloadproject2、build或rebuild3、flile–>inv..

    2022年7月8日
    68
  • MOS电平转换电路 stm32的I2C电平转换电路 IIC电平转换电路

    MOS电平转换电路 stm32的I2C电平转换电路 IIC电平转换电路电平转换电路 通常用在 IIC 总线电压的转换 例如像 stm32 单片机的 I2C 总线电压一般为 3 3V 而这条总线上挂载的从机外设 可能电平电压是 5V 的 这时候就需要进行电平转换 可以使用专用的电平转换芯片 成本高 而用 MOS 做电平转换也能实现同样的功能 电路图中 3V3 由芯片侧的电源提供 VIN 由外设电源提供 如果外设的总线电压为 5V 那么 VIN 就由 5V 供电 如果为 3 3V 那 VIN 就由 3 3V 供电 所以这个电路通常放置于外部设备的电路中 如果放在主机侧 需要考虑外设

    2026年3月26日
    3
  • 23种常用设计模式的UML类图

    23种常用设计模式的UML类图23种常用设计模式的UML类图本文UML类图参考《HeadFirst设计模式》(源码)与《设计模式:可复用面向对象软件的基础》(源码)两书中介绍的设计模式与UML图。整理常用设计模式的类图,一

    2022年6月30日
    31
  • .gitkeep

    .gitkeep

    2021年10月20日
    71
  • alternatives命令用法

    alternatives命令用法alternatives 命令用法

    2026年3月19日
    2
  • PyCharm快捷键大全(windows+mac)

    PyCharm快捷键大全(windows+mac)Pycharm 有着丰富且强大的快捷键组合 如果能熟练掌握常见快捷键的使用 那么绝对能提高你代码的编写效率和质量 之前写过一篇 Pycharm 常用的 10 个 windows 快捷键 Pycharm 最

    2026年3月27日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号