15.2 主机探测与路由追踪

Ping 使用 Internet 控制消息协议(ICMP)来测试主机之间的连接。当用户发送一个 ping 请求时,则对应的发送一个 ICMP Echo 请求消息到目标主机,并等待目标主机回复一个 ICMP Echo 回应消息。如果目标主机接收到请求并且网络连接正常,则会返回一个回应消息,表示主机之间的网络连接是正常的。如果目标主机没有收到请求消息或网络连接不正常,则不会有回应消息返回。

Ping 工作的步骤如下:

  • Ping发送一个ICMP Echo请求消息到目标主机。
  • 目标主机接收到请求消息后,检查消息中的目标IP地址是否正确,并回复一个ICMP Echo回应消息表示收到请求。
  • Ping接收到回应消息后,并计算从发送到接收的时延(即往返时间 RTT)和丢包率等统计信息,然后输出到命令行上。
  • Ping不断进行第1到第3步的操作,直到达到指定的停止条件(如发送一定数量的请求或持续一定的时间等)为止。

Ping的实现依赖于ICMP协议,Internet控制消息协议(Internet Control Message Protocol,简称 ICMP)是一种在IP网络上发送控制消息的协议。主要是用于在 IP 网络上进行错误处理和诊断。ICMP协议是运行在网络层的协议,它的主要作用是向源主机和目标主机发送控制消息,帮助网络诊断和监控。这些控制消息通常是由网络设备(如路由器、交换机、防火墙等)生成或捕获,并在整个网络传输。

ICMP协议的消息格式通常由两个部分组成:消息头和数据。其中,消息头包含以下字段:

  • 消息类型(Type):指示消息的类型(如 Echo 请求、Echo 回应、目标不可达、重定向等)
  • 代码(Code):指示消息的子类型或错误代码
  • 校验和(Checksum):用于检查消息是否被篡改
  • 消息体(Payload):包含特定类型消息所需的数据,如 IP 数据报片段、Echo 请求消息等

ICMP 协议中常见的消息类型包括:

  • Echo 请求(Ping)和 Echo 回应:用于测试主机之间的连通性和计算往返时间(RTT)
  • 目标不可达:通知源主机无法到达某个目标主机或网络
  • 重定向:用于通知主机更改路由器或网关
  • 时间超时:通知主机数据包已超过了最大存活期
  • 地址掩码请求和地址掩码回应:用于向主机查询和设置子网掩码

Windows平台下要实现Ping命令有多种方法,首先我们先来讲解第一种实现方式,通过自己构造ICMP数据包并发包实现,首先该功能的实现需要定义一个icmp_header头部,并定义好所需要的发送与回应定义,如下所示;

// ICMP头部定义部分
struct icmp_header
{unsigned char icmp_type;           // 消息类型unsigned char icmp_code;           // 代码unsigned short icmp_checksum;      // 校验和unsigned short icmp_id;            // ICMP唯一IDunsigned short icmp_sequence;      // 序列号unsigned long icmp_timestamp;      // 时间戳
};// 计算出ICMP头部长度
#define ICMP_HEADER_SIZE sizeof(icmp_header)// ICMP回送请求消息代码
#define ICMP_ECHO_REQUEST 0x08// ICMP回送响应消息代码
#define ICMP_ECHO_REPLY 0x00

当有了结构体定义那么接着就需要实现一个ICMP校验和的计算方法,ICMP报文检验和是一种用于检测 ICMP 报文数据正确性的校验和。它是 ICMP 协议中一种重要的错误检测机制,用于验证发送和接收的 ICMP 报文的数据是否完整、正确。

校验和计算方法如下:

  • 将要计算校验和的数据(即 ICMP 报文)按照16位为一组进行分组
  • 把所有的 16 位数字相加并加上进位,得到一个数
  • 若上一步和的高位不为零,则把进位加到低位上,重复步骤 2
  • 对累加后的结果进行二进制反转
  • 得到校验和值,将其放置于 ICMP 报文的校验和字段中

ICMP 接收到 ICMP 报文时,将立即计算校验和,比对接收到的校验和值与计算所得的校验和值是否相同,从而决定 ICMP 报文是否正确接收及响应。这样做的好处是可以有效地检测数据在传输过程中的误码、中间路由设备的错误操作等问题,保障 ICMP 报文的正确性。

根据上述描述,计算校验和CheckSum函数,首先对报文的数据进行分组,并依次计算每个16位数字的和。当相加的结果有进位时,将进位加到低位上,并将进位部分加到下一组中。处理完所有数字之后,还需要对结果进行二进制反转,得到最终的校验和值。

// 计算校验和
unsigned short CheckSum(struct icmp_header *picmp, int len)
{long sum = 0;unsigned short *pusicmp = (unsigned short *)picmp;// 将数据按16位分组,相邻的两个16位取出并相加,直到处理完所有数据while (len > 1){sum += *(pusicmp++);// 如果相加的结果有进位,则将进位加到低16位上if (sum & 0x80000000){sum = (sum & 0xffff) + (sum >> 16);}// 减去已经处理完的字节数len -= 2;}// 如果数据的字节数为奇数,则将最后一个字节视为16位,高8位设为0,低8位取余部分。if (len){sum += (unsigned short)*(unsigned char *)pusicmp;}// 如果计算完校验和后还有进位,则将进位加到低16位上while (sum >> 16){sum = (sum & 0xffff) + (sum >> 16);}// 取反得到最终的校验和return (unsigned short)~sum;
}

接着就是实现ICMP测试函数,如下函数首先进行初始化,并创建原始套接字,然后构造 ICMP 报文,计算报文的校验和。接着发送 ICMP 报文,并接收 ICMP 回复报文,解析其中的信息,判断延迟超时,最后返回 ping 测试结果。

发送 ICMP 报文使用 sendto 函数,第一个参数是原始套接字,第二个参数是 ICMP 报文数据缓存区,第三个参数是缓存区的长度,第四个参数是标志,第五个参数是目的地址信息。接收 ICMP 回复报文使用 recvfrom 函数,第一个参数和第五个参数与 sendto 函数相同。函数返回时,判断接收到的 IP 地址是否与发送 ICMP 报文的 IP 地址相同,如果相同,解析 ICMP 回复报文中的信息并返回 true,否则返回 false

ICMP 报文构造中,使用了 Winsock 函数库中的 inet_addrIP 地址转换为网络字节序。在计算 ICMP 报文的校验和时,调用了 CheckSum 函数。

BOOL MyPing(char *szDestIp)
{BOOL bRet = TRUE;WSADATA wsaData;int nTimeOut = 1000;char szBuff[ICMP_HEADER_SIZE + 32] = { 0 };icmp_header *pIcmp = (icmp_header *)szBuff;char icmp_data[32] = { 0 };// 初始化Winsock动态链接库WSAStartup(MAKEWORD(2, 2), &wsaData);// 创建原始套接字SOCKET s = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);// 设置接收超时setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char const*)&nTimeOut, sizeof(nTimeOut));// 设置目的地址sockaddr_in dest_addr;dest_addr.sin_family = AF_INET;dest_addr.sin_addr.S_un.S_addr = inet_addr(szDestIp);dest_addr.sin_port = htons(0);// 构造ICMP封包pIcmp->icmp_type = ICMP_ECHO_REQUEST;pIcmp->icmp_code = 0;pIcmp->icmp_id = (USHORT)::GetCurrentProcessId();pIcmp->icmp_sequence = 0;pIcmp->icmp_timestamp = 0;pIcmp->icmp_checksum = 0;// 拷贝ICMP协议中附带的数据memcpy((szBuff + ICMP_HEADER_SIZE), "abcdefghijklmnopqrstuvwabcdefghi", 32);// 计算校验和pIcmp->icmp_checksum = CheckSum((struct icmp_header *)szBuff, sizeof(szBuff));// 接收ping返回的ICMP数据包sockaddr_in from_addr;char szRecvBuff[1024];int nLen = sizeof(from_addr);// 发送UDP数据包sendto(s, szBuff, sizeof(szBuff), 0, (SOCKADDR *)&dest_addr, sizeof(SOCKADDR));// 等待响应recvfrom(s, szRecvBuff, MAXBYTE, 0, (SOCKADDR *)&from_addr, &nLen);// 判断接收到的是否是自己请求的地址if (lstrcmp(inet_ntoa(from_addr.sin_addr), szDestIp)){bRet = FALSE;}else{// 如果是自己请求的地址,则解析 ICMP 回复报文中的信息struct icmp_header *pIcmp1 = (icmp_header *)(szRecvBuff + 20);printf("%s \r\n", inet_ntoa(from_addr.sin_addr));}return bRet;
}

当读者有了上述函数封装那么实现Ping测试将变得很容易,首先如下调用实例中,通过GetHostByName函数获取到对应域名的IP地址信息返回字符串,并将该字符串传入MyPing函数内,该函数会测试当前主机是否可通信,如果可以返回状态值1,否则返回0。

int main(int argc, char **argv)
{// 获得指定网址的IP地址char * ptr = GetHostByName("www.lyshark.com");// 开始测试for (size_t i = 0; i < 5; i++){int ret = MyPing(ptr);printf("测试结果 = %d \n", ret);}system("pause");return 0;
}

运行代码后读者可看到如下图所示的提示信息;

除了通过自己封装接口外,Windows系统中还为我们提供了一个专用函数IcmpSendEcho,该函数用于通过 ICMP 协议向远程主机发送 Echo 请求并接收 Echo 回复。如果发送 Echo 请求并成功接收 Echo 回复,则函数返回值为非零,否则为零。

该函数的声明如下:

BOOL IcmpSendEcho
(HANDLE IcmpHandle, IPAddr DestinationAddress, LPVOID RequestData, WORD RequestSize, PIP_OPTION_INFORMATION RequestOptions, LPVOID ReplyBuffer, DWORD ReplySize, DWORD Timeout);

函数参数:

  • IcmpHandle:一个有效的 ICMP 句柄
  • DestinationAddress:目标地址,可以是 IP 地址(IPAddr)或主机名(LPCSTR)
  • RequestData:指向要发送的数据的指针
  • RequestSize:要发送的数据的大小(以字节为单位)
  • RequestOptions:指向 IP 选项的信息(IP_OPTION_INFORMATION)
  • ReplyBuffer:指向缓冲区,该缓冲区将用于存储接收到的回复
  • ReplySize:存储在回复缓冲区中的数据的大小(以字节为单位)
  • Timeout:请求超时之前等待回复的时间(以毫秒为单位)

如下函数则是通过IcmpCreateFileIcmpSendEcho函数实现的Ping测试,函数首先将 IP 地址转换为网络字节序,创建 ICMP 句柄并初始化 IP 选项信息。然后,设置要发送的 ICMP 数据报文,和接收 ICMP 数据报文的大小和缓冲区。接着发送 ICMP 数据报文,等待接收回复,并将回复解析为 ICMP_ECHO_REPLY 结构体。最后,判断回复的状态,如果不为 0 则返回失败,否则输出回复信息并返回成功。

// 调用API实现ping
bool IcmpPing(char *Address)
{// 设置超时为1000msDWORD timeOut = 1000;// IP地址转为网络字节序ULONG hAddr = inet_addr(Address);HANDLE handle = IcmpCreateFile();IP_OPTION_INFORMATION ipoi;memset(&ipoi, 0, sizeof(IP_OPTION_INFORMATION));// Time-To-Liveipoi.Ttl = 64;// 设置发送数据包unsigned char SendData[32] = { "send icmp pack" };int repSize = sizeof(ICMP_ECHO_REPLY)+32;// 设置接收数据包unsigned char pReply[128];ICMP_ECHO_REPLY* pEchoReply = (ICMP_ECHO_REPLY*)pReply;// 发送ICMP数据报文DWORD nPackets = IcmpSendEcho(handle, hAddr, SendData, sizeof(SendData), &ipoi, pReply, repSize, timeOut);if (pEchoReply->Status != 0){IcmpCloseHandle(handle);return false;}in_addr inAddr;inAddr.s_addr = pEchoReply->Address;printf("回复地址: %13s 状态: %1d 初始TTL: %3d 回复: TTL: %3d \n",inet_ntoa(inAddr), pEchoReply->Status, ipoi.Ttl, pEchoReply->Options.Ttl);return true;
}

该段代码的调用与上述一致,读者只需要传入主机IP地址的字符串即可,具体调用实现如下所示;

int main(int argc, char *argv[])
{// 解析域名char * HostAddress = GetHostByName("www.lyshark.com");printf("网站IP地址 = %s \n", HostAddress);// 调用Pingfor (int x = 0; x < 3; x++){IcmpPing(HostAddress);Sleep(1000);}system("pause");return 0;
}

运行代码后读者可看到如下图所示的提示信息;

通过使用Ping命令我们还可以实现针对主机路由的追踪功能,路由追踪功能的原理是,它实际上是发送一系列ICMP数据包,数据包每经过一个路由节点则TTL值会减去1,假设TTL值等于0时数据包还没有到达目标主机,那么该路由则会回复给目标主机一个数据包不可达,由此我们就可以获取到目标主机的IP地址。

其跟踪原理如下:

  • 1.一开始发送一个TTL为1的包,这样到达第一个路由器的时候就已经超时了,第一个路由器就会返回一个ICMP通知,该通知包含了对端的IP地址,这样就能够记录下所经过的第一个路由器的IP。
  • 2.然后将TTL加1,让其能够安全的通过第一个路由器,而第二个路由器的的处理过程会自动丢包,发通知说包超时了,这样记录下第二个路由器IP,由此能够一直进行下去,直到这个数据包到达目标主机,由此打印出全部经过的路由器。

由上述流程并配合使用IcmpSendEcho函数设置默认最大跳数为64,通过不间断的循环即可输出本机数据包到达目标之间的所有路由信息,代码片段如下所示;

// 实现路由跟中
void Tracert(char *Address)
{ULONG hAddr = inet_addr(Address);HANDLE handle = IcmpCreateFile();IP_OPTION_INFORMATION ipoi;memset(&ipoi, 0, sizeof(IP_OPTION_INFORMATION));unsigned char SendData[32] = { "send ttl pack" };int repSize = sizeof(ICMP_ECHO_REPLY)+32;unsigned char pReply[128];ICMP_ECHO_REPLY* pEchoReply = (ICMP_ECHO_REPLY*)pReply;for (int ttl = 1; ttl < 64; ttl++){ipoi.Ttl = ttl;DWORD nPackets = IcmpSendEcho(handle, hAddr, SendData, sizeof(SendData), &ipoi, pReply, repSize, 1000);if (pEchoReply->Status != 0){in_addr inAddr;inAddr.s_addr = pEchoReply->Address;printf("-> 第 %2d 跳 --> 地址: %15s -> TTL: %2d \n", ttl, inet_ntoa(inAddr), pEchoReply->Options.Ttl);}}IcmpCloseHandle(handle);
}

上述代码读者可自行运行并传入Tracert(HostAddress)被测试主机IP地址,即可输出当前经过路由的完整信息,如果路由TTL为0则可能是对端路由过滤掉了ICMP请求,如下图所示;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/6ffe4618.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/110416.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【网络协议】聊聊从物理层到MAC层 ARP 交换机

物理层 物理层其实就是电脑、交换器、路由器、光纤等。组成一个局域网的方式可以使用集线器。可以将多台电脑连接起来&#xff0c;然后进行将数据转发给别的端口。 数据链路层 Hub其实就是广播模式&#xff0c;如果A电脑发出一个包&#xff0c;B、C电脑也可以收到。那么数据…

ZKP4.1 SNARKs via Interactive Proofs (Justin Thaler)

ZKP学习笔记 ZK-Learning MOOC课程笔记 Lecture 4: SNARKs via Interactive Proofs (Justin Thaler) 4.1 Interactive Proofs: Motivation and Model Interactive Proofs P solves problem, tells V the answer. Then they have a conversation.P’s goal: convince V the …

java基础练习,九九乘法表(java版),计算器

简介 对于有了解&#xff0c;但是了解不深的同学&#xff0c;学习Java总是感觉一看就会&#xff0c;一些就废。往往需要一些实操练习&#xff0c;来夯实我们的学习结果。九九乘法表和计算器都是在编程学习领域比较经典的案例。本文为大家讲解一下两个基础练习涉及到一些基础知…

【黑夜送书第一期】好书来袭,AI时代程序员/项目经理开发之道送3本~

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师…

【广州华锐互动】VR高层小区安全疏散演练系统

在今天的高科技时代&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已经被广泛应用到各个领域&#xff0c;包括教育和培训。由广州华锐互动定制开发的VR高层小区安全疏散演练系统&#xff0c;开始在房地产行业中崭露头角。这种系统通过模拟真实的紧急情况&#xff0c;帮助…

怎么使用动态代理IP提升网络安全,动态代理IP有哪些好处呢?

目录 一、什么是动态代理IP 二、动态代理IP的优势 三、动态代理IP的代码实现 1. 安装依赖库 2. 获取代理IP 3. 使用代理IP请求目标网站 4. 动态更换代理IP 五、总结 一、什么是动态代理IP 动态代理IP是指在网络通信过程中&#xff0c;代理服务器不是固定IP地址&#x…

Vue3 Element-UI中使用ECharts(前端数据展示开发)

前端数据展示&#xff08;数据可视化、数据大屏等&#xff09;可使用的工具比较多&#xff0c;很多第三方都提供了在线平台&#xff0c;比如阿里云&#xff0c;只需数据接口&#xff0c;在线配置一下界面即可使用。 阿里云的使用&#xff1a;利用阿里云物联网平台&#xff08;I…

【解决】运行vue项目,启动报错 in ./node_modules/@intlify/core-base/dist/core-base.cjs

我的处理方式: 一开始查了好多方法&#xff0c;删除node_modules&#xff0c;重新安装&#xff0c;切换node版本等&#xff0c;但是发现并没有用 之后来发现是安装依赖包的时候有些包安装失败导致的&#xff0c;只要有针对性的重新安装依赖就可以了 例如&#xff1a; in ./n…

遇到工厂索赔你是一竿子处理方式吗?

最近听到一个博主说关于客户索赔的事情&#xff0c;说是自己以前的处理方式要么就是将事情推给工厂&#xff0c;以工厂的态度为原则&#xff0c;工厂赔付给我们&#xff0c;然后我们就同意赔付给客户。如果工厂敷衍我们&#xff0c;那么我们就敷衍客户&#xff0c;这样最起码能…

WPS、Excel表格增加一列,序列1到任意大小 / 填充某个范围的数字到列

Excel添加一列递增的数字方法有如下&#xff1a; 一、最常用的&#xff0c;使用鼠标放到右下角下拉增加 1、选中起始框的右下角&#xff0c;直到显示黑色实心十字 2、一直向下拖动 3、成功 这种填充方式是最常用的&#xff0c;100以内都可以轻松瞬间完成 1~100填充 但是如果…

oracle 表空间详解以及配置操作

Oracle 数据库是由若干个表空间构成的。任何数据库对象在存储时都必须存储在某个 表空间中。表空间对应于若干个数据文件&#xff0c;即表空间是由一个或多个数据文件构成的。 1、常用表空间&#xff1a; 系统表空间 (system tablespace) 是每个 Oracle 数据库都必须具备的。…

算法专题:双指针

目录 题目1&#xff1a;移动零 题目2&#xff1a;复写零 题目3&#xff1a;快乐数 题目4&#xff1a;最多水的容器 题目5&#xff1a;有效三角形的个数 题目6&#xff1a;两数之和为s 题目1&#xff1a;移动零 给定一个数组nums&#xff0c;编写一个函数将所有的0移动到数…

再扩国产化信创版图!朗思科技与中科方德完成产品兼容性互认证

近日&#xff0c;北京朗思智能科技有限公司&#xff08;以下简称“朗思科技”&#xff09;自主研发的数字员工产品与中科方德桌面操作系统完成产品认证。测试结果显示&#xff0c;双方产品完全兼容&#xff0c;整体运行稳定&#xff0c;在功能、性能及兼容性方面表现良好&#…

网络库OKHttp(1)流程+拦截器

序、慢慢来才是最快的方法。 背景 OkHttp 是一套处理 HTTP 网络请求的依赖库&#xff0c;由 Square 公司设计研发并开源&#xff0c;目前可以在 Java 和 Kotlin 中使用。对于 Android App 来说&#xff0c;OkHttp 现在几乎已经占据了所有的网络请求操作。 OKHttp源码官网 版…

Linux系统的特点以及年轻人如何获取第一个Linux系统

由新闻想到的 新闻一&#xff1a;政府机构 5000 万台电脑将替换为国产 Linux &#xff01; 由这个新闻想到的&#xff0c;如果中国的所有个人、企业、政府把电脑系统都换成linux或者是国产操作系统&#xff0c;那将是怎样的一种景象&#xff01;&#xff1f; 新闻二&#xf…

【数之道 05】走进神经网络模型、机器学习的世界

神经网络 神经网络&#xff08;ANN&#xff09;神经网络基础激活函数 神经网络如何通过训练提高预测准确度逆向参数调整法 &#xff08;BackPropagation&#xff09;梯度下降法链式法则增加一层 b站视频连接 神经网络&#xff08;ANN&#xff09; 最简单的例子&#xff0c;视…

【Linux】从零开始学习Linux基本指令(二)

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;Linux入门 &#x1f525;该文章主要了解Linux操作系统下的基本指令。 ⚡️上一篇可以看这里 &#x1f449;【Linux】从零开始学习Linux基本指…

【数据结构】排序算法的稳定性分析

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

网络库OKHTTP(2)面试题

序、慢慢来才是最快的方法。 背景 OkHttp 是一套处理 HTTP 网络请求的依赖库&#xff0c;由 Square 公司设计研发并开源&#xff0c;目前可以在 Java 和 Kotlin 中使用。对于 Android App 来说&#xff0c;OkHttp 现在几乎已经占据了所有的网络请求操作。 OKHttp源码官网 问1…

Python Connect SQLServer 2008

Macos&#xff08;经过了两天&#xff0c;无数次的方法验证&#xff0c;寻找各种资料&#xff0c;总结如下&#xff09; brew install freetds0.91 如果出现错误就进行手工安装&#xff0c;也可以直接使用 brew install freetds安装最新版本&#xff08;测试通过&#xff09; …