请结合附件:Ping的实现原理与ping.cpp的内容,编写一个程序,使其能够实现简单的ping的功能,即判断目标网站是否可以连接,然后通过Wireshark进行抓包分析其ICMP协议,指出哪个数据包是ping的请求(request),哪个数据包是对这个请求的回应(reply)(如果reply数据包存在的话)。
相关知识:
ICMP (Internet Control Message Protocol) Internet控制报文协议,它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息(即提供TCP/IP网络上设备、服务协议及路由器可用性的信息),ICMP允许主机或路由器报告差错情况和提供有关异常情况的报告,目的是为了更有效地转发IP数据报和提高交付成功的机会。 注:控制消息指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。 ICMP的两个主要应用是Ping和Traceroute。 需要编写程序,实现简单的ping的功能,主要需要了解以下内容: 要点1:ICMP协议相关知识 ICMP不是高层协议,而是IP层的协议。ICMP报文是装在IP数据报中,作为其中的数据部分。 ①ICMP报文的格式 ★ICMP协议的类型码与代码根据不同情况,各自取值。8bits类型和8bits代码字段:一起决定了ICMP报文的类型,常见的有: A.类型8、代码0:回射请求。 B.类型0、代码0:回射应答。 C.类型11、代码0:超时。 ★ICMP报文的前4个字节是统一的格式,共有三个字段:即类型、代码和检验和。接着的4个字节的内容与ICMP的类型有关。(这8个字节为ICMP报文的首部) ②ICMP报文的种类 从类型值来看ICMP报文可分为二大类: A.差错报告报文;
B.询问报文。
要点2:IP地址的点分十进制表示 机器中存放的IP地址是32位二进制代码。以8位为一组,采用点分十进制记法可以进一步提高可读性,如图所示: 在实验代码中,使用到的两个函数inet_addr()和Inet_ntoa()完成的就是32位二进制表示的IP地址与点分十进制记法之间的转换: ①inet_addr函数(点分十进制->32位二进制)需要一个字符串作为其参数,该字符串指定了以点分十进制格式表示的IP地址(例如:192.168.0.16)。而且inet_addr函数会返回一个适合分配给S_addr的u_long类型的数值。 ②Inet_ntoa函数(32位二进制->点分十进制)会完成相反的转换,它接受一个in_addr结构体类型的参数并返回一个以点分十进制格式表示的IP地址字符串 注:在vs2013以上的版本会出现这个两个无法使用的错误信息及相对应的解决方法如图所示: 这两个函数是随IPv6出现的新函数,对于IPv4地址和IPv6地址都适用。对比与使用方法可参见:https://blog.csdn.net/gujintong1110/article/details/45390653 要点3:校验和的计算(具体实现可参见参考代码ping.cpp) 在发送数据时,为了计算数据包的检验和。应该按如下步骤: 1.把校验和字段设置为0; 2.把需要校验的数据看成以16位为单位的数字组成,依次进行二进制反码求和; 3.把得到的结果存入校验和字段中 在接收数据时,计算数据包的检验和相对简单,按如下步骤: 1.把首部看成以16位为单位的数字组成,依次进行二进制反码求和,包括校验和字段; 2.检查计算出的校验和的结果是否为0; 3.如果等于0,说明被整除,校验和正确。否则,校验和就是错误的,协议栈要抛弃这个数据包。 注:IP、ICMP、UDP和TCP报文头都有检验和字段,大小都是16bit这四种报文的校验和算法一样,但是在作用范围存在不同: IP校验和只校验20字节的IP报头; ICMP校验和覆盖整个报文(ICMP报头+ICMP数据); UDP和TCP校验和不仅覆盖整个报文,而且还有12个字节的IP伪首部,包括源IP地址(4字节)、目的IP地址(4字节)、协议(2字节)、TCP/UDP包长(2字节)。 另外UDP、TCP数据报的长度可以为奇数字节,所以在计算校验和时需要在最后增加填充字节0(填充字节只是为了计算校验和,可以不被传送) 要点4:两个关键函数sendto()和recvfrom() ①sendto()用于已连接的数据报或流式套接口发送数据。 对于sendto()函数,成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。 ②recvfrom()用于从(已连接)套接口上接收数据,并捕获数据发送源的地址 对于recvfrom()函数,成功则返回接收到的字符数,失败则返回-1,错误原因存于errno中。 注:可以通过WSAGetLastError()得到错误代码,对应MSDN参考文档即可知道错误的原因。 具体解析可参见:https://blog.csdn.net/qq_32744005/article/details/51772694
------------------------------------------------------------------------------------------------------------------------------------------------- 鉴于之前网络编程课就已经做过ping的实验了,所以这个代码就不用打了,直接贴上来,偷懒一下...------------------------------------------------------------------------------------------------------------------------------------------------- 代码:
#define _CRT_SECURE_NO_WARNINGS #define _WINSOCK_DEPRECATED_NO_WARNINGS #include #include #pragma comment(lib, "WS2_32")
typedef struct _IPHeader { unsigned char iphVerLen; unsigned char ipTOS; unsigned short ipLength; unsigned short ipID; unsigned short ipFlags; unsigned char ipTTL; unsigned char ipProtocol; USHORT ipChecksum; ULONG ipSource; ULONG ipDestination; } IPHeader, *PIPHeader; typedef struct icmp_hdr { unsigned char icmp_type; unsigned char icmp_code; unsigned short icmp_checksum; unsigned short icmp_id; unsigned short icmp_sequence; unsigned long icmp_timestamp; } ICMP_HDR, *PICMP_HDR;
typedef struct _EchoRequest { ICMP_HDR icmphdr; char cData[32]; }ECHOREQUEST, *PECHOREQUEST;
#define REQ_DATASIZE 32 typedef struct _EchoReply { IPHeader iphdr; ECHOREQUEST echoRequest; }ECHOREPLAY, *PECHOREPLAY;
USHORT checksum(USHORT* buff, int size) { u_long cksum = 0; while (size > 1) { cksum = cksum + *buff; buff = buff + 1; size = size - sizeof(USHORT); } if (size == 1) { USHORT u = 0; u = (USHORT)(*(UCHAR*)buff); cksum = cksum + u; }
cksum = (cksum >> 16) + (cksum & 0x0000ffff); cksum = cksum + (cksum >> 16); u_short answer = (u_short)(~cksum); return (answer); } int main() { WSADATA wsaData; WORD version = MAKEWORD(2, 2); int ret = WSAStartup(version, &wsaData); if (ret != 0) { printf(" 加载Winsock库错误! \n"); return 0; } char szDestIp[100]; printf("输入所要连接的外网地址:\n"); scanf("%s", szDestIp);
SOCKET sRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
SOCKADDR_IN dest; dest.sin_family = AF_INET; dest.sin_port = htons(0); dest.sin_addr.S_un.S_addr = inet_addr(szDestIp); ECHOREQUEST echoReq;
echoReq.icmphdr.icmp_type = 8; echoReq.icmphdr.icmp_code = 0; echoReq.icmphdr.icmp_id = (USHORT)GetCurrentProcessId(); echoReq.icmphdr.icmp_checksum = 0; echoReq.icmphdr.icmp_sequence = 0; memset(&echoReq.cData, 'E', 32); USHORT nSeq = 0; SOCKADDR_IN from; int nLen = sizeof(from);
int fail = 0; int success = 0;
int i = 0; int a[4];
int isFail = 0;
int timeQ; while (TRUE) { static int nCount = 0; int nRet; if (nCount++ == 4) break; echoReq.icmphdr.icmp_checksum = 0; echoReq.icmphdr.icmp_timestamp = GetTickCount(); echoReq.icmphdr.icmp_sequence = nSeq++; echoReq.icmphdr.icmp_checksum = checksum((USHORT*)&echoReq, sizeof(echoReq)); nRet = sendto(sRaw, (char*)&echoReq, sizeof(echoReq), 0, (SOCKADDR *)&dest, sizeof(dest)); if (nRet == SOCKET_ERROR) {
printf(" sendto() failed: %d \n", WSAGetLastError()); system("pause"); return 0;
}
ECHOREPLAY echoReply; nRet = recvfrom(sRaw, (char*)&echoReply, sizeof(ECHOREPLAY), 0, (sockaddr*)&from, &nLen); if (nRet == SOCKET_ERROR) { if (WSAGetLastError() == WSAETIMEDOUT) { printf(" timed out\n"); printf("时间超时\n"); fail++; continue; } printf(" recvfrom() failed: %d\n", WSAGetLastError());
isFail = 1;
printf("来自172.16.4.42的回复:无法访问目标主机\n");
success++; continue; } if (nRet < sizeof(ECHOREPLAY)) { printf(" Too few bytes from %s \n", inet_ntoa(from.sin_addr));
}
if (echoReply.echoRequest.icmphdr.icmp_type != 0) { printf(" nonecho type %d recvd \n", echoReply.echoRequest.icmphdr.icmp_type); system("pause"); return 0; }
if (echoReply.echoRequest.icmphdr.icmp_id != GetCurrentProcessId()) { printf(" someone else's packet! \n"); system("pause"); return 0; } printf(" %d bytes Reply from %s: \n", nRet, inet_ntoa(from.sin_addr));
printf(" icmp_seq = %d. ", echoReply.echoRequest.icmphdr.icmp_sequence); int nTick = GetTickCount(); success++; printf(" time: %d ms", nTick - echoReply.echoRequest.icmphdr.icmp_timestamp);
a[i] = nTick - echoReply.echoRequest.icmphdr.icmp_timestamp; i++;
printf(" TTL= %d ", echoReply.iphdr.ipTTL); //printf(echoReply.echoRequest.cData); printf(" \n"); Sleep(1000); }
printf("%s 的ping的统计信息:\n", szDestIp); printf("数据包:已发送 = %d,已接收 = %d,丢失 = %d\n", success, success, fail); if (isFail != 1) {
printf("往返程的估计时间:(以毫秒记)\n"); int timeC = a[0]; int timeD = a[0]; int timeA = a[0]; int j;
for (j = 1; j < 4; j++) {
if (timeC < a[j]) {
timeC = a[j]; } if (timeD > a[j]) {
timeD = a[j];
}
timeA = timeA + a[j];
} timeA = timeA / 4; printf("最短 = %d 最长 = %d 平均 = %d\n", timeD, timeC, timeA); } else {
}
WSACleanup(); system("pause"); return 0; }
-------------------------------------------------------------------------------------------------------------------------------------------------
解释:编写ping程序发送数据包,主要用了两个函数-sendto和recvfrom。这两个函数是基于udp协议的。
-------------------------------------------------------------------------------------------------------------------------------------------------
测试: Cmd
自己编写的ping程序
------------------------------------------------------------------------------------------------------------------------------------------------- 问题1:Ping的实现原理与ping.cpp的内容,编写一个程序,使其能够实现简单的ping的功能,即判断目标网站是否可以连接
由实验结果可知,ping 123.125.114.144(百度网址)可以有返回的数据包,说明可以ping通。证明该目标网站可以连接 ------------------------------------------------------------------------------ 使用wireshark工具抓包分析:(具体过程可以参考实验9)
问题2:然后通过Wireshark进行抓包分析其ICMP协议,指出哪个数据包是ping的请求(request),哪个数据包是对这个请求的回应(reply)(如果reply数据包存在的话)。
通过实验结果可知,ping 123.125.114.144(百度网址)可以有返回的数据包,说明可以其中可以看见是使用ICMP协议,通过上图红框圈出的标记,可以看见request和reply。 request是请求的意思,所有可以知道这个数据包时ping的请求,reply是回应的意思,可以知道这个数据包是对请求的回应。
通过这个实验,我们认识了ping代码的编写。深刻地认识了udp的原理。UDP: UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。UDP在IP报文的协议号是17。 UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在第四层——传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但是即使是在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。 与所熟知的TCP(传输控制协议)协议一样,UDP协议直接位于IP(网际协议)协议的顶层。根据OSI(开放系统互联)参考模型,UDP和TCP都属于传输层协议。UDP协议的主要作用是将网络数据流量压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前8个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。 --来自百度百科-- https://baike.baidu.com/item/UDP/571511?fr=aladdin
由这个实验可以看出udp和tcp的不同,udp建立连接需要三次握手,结束连接四次挥手。而udp不需要这个步骤。
编写ping程序有什么用呢?其实我们可以编写ping程序来进行dos攻击或ddos攻击。针对以上的程序,我们把ping ip地址,该为指定ip(想要攻击的目标),使用while循环来进行不断的发送数据包,就可以进行dos攻击。如果我们抓了不少肉鸡的话。可以几台肉鸡乃至10几台肉鸡来对某个服务器ip一起发送ping请求。可以把这个服务器打掉。
https://blog.csdn.net/pygain/article/details/52134480 - 浅谈DOS与DDOS攻击的原理DOS粗略讲解:
1.TCP饿死:
UDP这种传输方式不会控制自己在通信通道里的流量,可理解为不讲道理的人。他们来到了一个热闹地区的KFC中,但是他们不买东西只排队将所有食物的价格都问一遍,占满所有的座位和过道。而常规的TCP服务通过自己的弹窗机制来控制流量,好比讲道理的人,座位被占满了,TCP自然会离开KFC导致正常的服务不能进行。最终的结果就是UDP将整个通道打满堵死。 2.攻击原理 DOS攻击:一台或多台计算机对受攻击服务器的某一个端口发送大量无关的UDP报文,导致整个通道内的正常服务无法进行。 DDOS攻击:大量的肉鸡对服务器的不同端口发送巨型流量的UDP报文,无法通关关闭端口的方式来进行隔离,破坏力极强,严重会造成服务器当机。 |