扯谈网络编程之自己实现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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Navicat 15 for orca激活码_通用破解码

    Navicat 15 for orca激活码_通用破解码,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月17日
    185
  • python2 nonlocal_python unboundlocalerror

    python2 nonlocal_python unboundlocalerror在廖雪峰的官网上看到一个很有意思题目。关于闭包的,有兴趣的朋友可以看一下这里,做一下这个题目,当然需要一点闭包的知识。下面我简述一下:利用闭包返回一个计数器函数,每次调用它返回递增整数。#修改下面这个函数defcreateCounter():defcounter():passreturncounter#测试:counterA=createCounter()print(counter…

    2022年9月6日
    7
  • 正弦波放大电路与移相电路设计

    正弦波放大电路与移相电路设计原文地址 http www docin com p 613242880 html 相关文章 1 正弦波放大电路 http zhidao baidu com link url lE4Ox8xWoSal 7C9ZIbVzmGJ4 j2efLU2 Z

    2025年9月6日
    1
  • GB28181协议_gb28181版本

    GB28181协议_gb28181版本国家为了规范安防行业的设备平台互联互通,在2012年出台了GB/T-28181的第一版标准:这个版本我13年在工作中开发IPC设备使用过,当时觉得协议对流媒体的规范还是比较好(H.264+G711封装成PS流,再经过RTP协议进行实时传输),但是控制方面有很多不足,相对于当前流行的ONVIF协议在控制信令上还是有很多不足。去公安…

    2025年8月25日
    4
  • win10怎么更新显卡驱动_win10系统AMD显卡驱动安装失败的解决方法

    win10怎么更新显卡驱动_win10系统AMD显卡驱动安装失败的解决方法我们在操作win10系统电脑的时候,win10系统AMD显卡驱动安装失败的问题对于我们来说其实是比较少见的,一般情况下的正常使用都不会遇到win10系统AMD显卡驱动安装失败的状况。但在win10系统中如果由于我们个人的不当操作导致win10系统AMD显卡驱动安装失败问题出现的话,我们应该怎样去处理解决呢?其实解决这个问题的方法并不复杂,我们只需要1、开始按钮点右键选“设备管理器”,找到显示适…

    2022年6月5日
    126
  • CPU监控_安卓cpu实时监控

    CPU监控_安卓cpu实时监控一,关键概念1,ContextSwitches(上下文切换)2,TherunQueue(运行队列)3,CPU的使用率二,CPU监控2.1,健康状态下CPU基准线2.2,vmstat使用2.2.1

    2022年8月4日
    6

发表回复

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

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