一个有趣的网络程序TraceRoute:记录数据包传送路径上的路由器IP

本文主要是介绍一个有趣的网络程序TraceRoute:记录数据包传送路径上的路由器IP,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在大多数操作系统上都附带一个网络程序叫TraceRoute,它的作用是追踪数据包发送到指定对象前,在传送路径上经过了几个路由器转发,下图是用TraceRoute程序追踪从我这台主机发送数据包到百度服务器时所经过的各个路由器的ip:

屏幕快照 2019-02-21 下午5.38.48.png

其中14.215.177.38是域名www.baidu.com对应的服务器ip,从显示上看,数据包从我当前电脑发出,经过7个路由器后才能到达百度服务器,本节我们就看看traceroute应用程序的实现原理。

整个互联网其实是由一个个子网组成的,每个子网相当于一个孤岛,每个孤岛对应一个路由器,两个孤岛间的路由器如果相互连通,那么就相当于在孤岛上架起一座桥梁,于是两座孤岛就可以相互连通,整个互联网就是无数个孤岛通过路由器连接起来的一个巨大整体:

屏幕快照 2019-02-21 下午5.42.43.png

如上图当我们想把数据发送到远端服务器时,数据包从我们所在的“孤岛”通过路由器跳转到下一个孤岛,如果接收目标没有在进入的新孤岛,那么第二个孤岛的路由器会将数据包通过它的路由器提交到第三个孤岛,如此一直传递直到数据包抵达接收目标所在的孤岛,然后对应孤岛的路由器将数据包分发给接收目标。

在IP数据包头中有一个字段,用来记录数据包可以跳转的孤岛数量:
屏幕快照 2018-12-21 下午5.37.41.png

上面显示的是IP数据包头的格式,其中有一个字段是Time To Live,简称TTL,它规定了该数据包可以跳转的孤岛数量,数据包每跳转一个孤岛,该字段的值就减1,如果当该字段的值减到0数据包还没有抵达目标所在的孤岛,那么该孤岛对应的路由器就会向数据包的发送者发出一个由ICMP协议封装的数据包叫ICMP Time Exceeded Message,该数据包的格式如下:

屏幕快照 2019-02-21 下午5.50.39.png

其中type取值11,code取值为0.

traceroute就是利用这个特性来检测数据包发送路径上所经过的路由器。首先它构造一个UDP数据包发送给接收目标,并在数据包的IP报头里将TTL字段设置成1,于是数据包发送给第一个孤岛时,对方回发一个Time Exceeded消息,通过该消息的IP报头它就可以知道第一个孤岛路由器的ip,接着它再次构造同样的UDP数据包只是把其中IP报头的TTL字段改成2,于是同理获得第二个孤岛对应路由器的IP,如此反复进行,直到再也没有Time Exceeded消息返回为止,于是它便可以得知数据包发送给指定目标时,路径上经过了多少路由器转发。

我们看看如何动手实现traceroute程序功能,在这里我们需要使用一个新协议包头,也就是UDP包头,它的具体原理我们在以后的课程中会详细研究,现在我们只需要知道它的包头格式即可:

屏幕快照 2019-02-21 下午5.57.22.png

首先我们新建一个类成为TraceRoute,其代码如下:

package Application;import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.HashMap;import protocol.IProtocol;
import protocol.ProtocolManager;
import protocol.UDPProtocolLayer;public class TraceRoute  extends Application{private char dest_port = 33434;private byte[] dest_ip = null;private byte time_to_live = 1;private static byte ICMP_TIME_EXCEEDED_TYPE = 1;private static byte ICMP_TIME_EXCEEDED_CODE = 0;public TraceRoute( byte[] destIP) {this.dest_ip = destIP;	}public void startTraceRoute() {try {byte[] packet = createPackage(null);ProtocolManager.getInstance().sendData(packet, dest_ip);ProtocolManager.getInstance().registToReceiveICMPPacket(this);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}private byte[] createPackage(byte[] data) throws Exception {byte[] udpHeader = this.createUDPHeader();if (udpHeader == null) {throw new Exception("UDP Header create fail");}		byte[] ipHeader = this.createIP4Header(udpHeader.length);//分别构建ip包头和icmp echo包头后,将两个包头结合在一起byte[] packet  = new byte[udpHeader.length + ipHeader.length];ByteBuffer packetBuffer = ByteBuffer.wrap(packet);packetBuffer.put(ipHeader);packetBuffer.put(udpHeader);return packetBuffer.array();}private byte[] createUDPHeader() {IProtocol udpProto = ProtocolManager.getInstance().getProtocol("udp");if (udpProto == null) {return null;}HashMap<String, Object> headerInfo = new HashMap<String, Object>();char udpPort = (char)this.port;headerInfo.put("source_port", udpPort);headerInfo.put("dest_port", dest_port);byte[] data = new byte[24];headerInfo.put("data", data);return udpProto.createHeader(headerInfo);}protected byte[] createIP4Header(int dataLength) {IProtocol ip4Proto = ProtocolManager.getInstance().getProtocol("ip");if (ip4Proto == null || dataLength <= 0) {return null;}//创建IP包头默认情况下只需要发送数据长度,下层协议号,接收方ip地址HashMap<String, Object> headerInfo = new HashMap<String, Object>();headerInfo.put("data_length", dataLength);ByteBuffer destIP = ByteBuffer.wrap(this.dest_ip);headerInfo.put("destination_ip", destIP.getInt());byte protocol = UDPProtocolLayer.PROTOCOL_UDP;headerInfo.put("protocol", protocol);headerInfo.put("identification", (short)this.port);//该值必须依次递增headerInfo.put("time_to_live", time_to_live);byte[] ipHeader = ip4Proto.createHeader(headerInfo);return ipHeader;}public void handleData(HashMap<String, Object> data) {if (data.get("type") == null || data.get("code") == null) {return;}if ((byte)data.get("type") != ICMP_TIME_EXCEEDED_TYPE ||(byte)data.get("code") != ICMP_TIME_EXCEEDED_CODE) {//收到的不是icmp_time_exceeded类型的消息return;}//获得发送该数据包的路由器ipbyte[] source_ip = (byte[])data.get("source_ip");try {String routerIP = InetAddress.getByAddress(source_ip).toString();System.out.println("ip of the " + time_to_live + "th router in sending route is: " + routerIP );dest_port++;time_to_live++;startTraceRoute();} catch (UnknownHostException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}

它的实现与我们前面开发的HPing类似,都是构造好相应协议包头后,让网卡将数据包发送出去,在这里有一点不同之处在于,它需要构造UDP包头,同时它在构造IP包头的时候,特意把time to live字段的值设置为1,这样能让数据包进入下一个孤岛时收到对应路由器发回来的time exceeded limit 消息。

一旦对应的icmp消息发回来并被本机接收后,handleData接口会被调用,它把发送消息的路由器ip打印出来,然后让time_to_live的值加1,并再次发送数据包,于是数据包能连续进入新孤岛,那么第二个孤岛的路由器回发time exceeded limit消息时,我们就能获得它的ip,这个过程一直进行,直到再也没有time exceeded limit消息回来为止。

接着我们增加一个名为UDPProtocoalLayer的对象,它负责构造UDP包头,其实现代码如下:

package protocol;import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;import jpcap.packet.Packet;public class UDPProtocolLayer implements IProtocol{private static short  UDP_LEHGTH_WITHOUT_DATA = 8;public static byte PROTOCOL_UDP = 17;@Overridepublic byte[] createHeader(HashMap<String, Object> headerInfo) {short total_length = UDP_LEHGTH_WITHOUT_DATA;byte[] data = null;if (headerInfo.get("data") != null) {data = (byte[])headerInfo.get("data");total_length += data.length;}byte[] buf = new byte[total_length];ByteBuffer byteBuffer = ByteBuffer.wrap(buf);if (headerInfo.get("source_port") == null) {return null;}char srcPort = (char)headerInfo.get("source_port");byteBuffer.order(ByteOrder.BIG_ENDIAN);byteBuffer.putChar(srcPort);if (headerInfo.get("dest_port") == null) {return  null;}char  destPort = (char)headerInfo.get("dest_port");byteBuffer.order(ByteOrder.BIG_ENDIAN);byteBuffer.putChar(destPort);byteBuffer.order(ByteOrder.BIG_ENDIAN);byteBuffer.putShort(total_length);//UDP包头的checksum可以直接设置成0xFFFFchar checksum = 65535;byteBuffer.putChar(checksum);if (data != null) {byteBuffer.put(data);}return byteBuffer.array();}@Overridepublic HashMap<String, Object> handlePacket(Packet packet) {// TODO Auto-generated method stubreturn null;}}

它的实现功能简单,仅仅是按照UDP数据包头的格式组装包头而已。然后我们增加一个解读icmp time exceeded limit错误消息的对象,其代码如下:

package protocol;import java.nio.ByteBuffer;
import java.util.HashMap;import jpcap.packet.Packet;public class ICMPTimeExceededHeader implements IProtocol{private static byte ICMP_TIME_EXCEEDED_TYPE = 1;private static byte ICMP_TIME_EXCEEDED_CODE = 0;private static int ICMP_TIME_EXCEEDED_DATA_OFFSET = 8;@Overridepublic byte[] createHeader(HashMap<String, Object> headerInfo) {// TODO Auto-generated method stubreturn null;}@Overridepublic HashMap<String, Object> handlePacket(Packet packet) {ByteBuffer buffer = ByteBuffer.wrap(packet.header);if (buffer.get(0) != ICMP_TIME_EXCEEDED_TYPE &&buffer.get(1) != 	ICMP_TIME_EXCEEDED_CODE) {return  null;}HashMap<String, Object> headerInfo = new HashMap<String, Object>();headerInfo.put("type", ICMP_TIME_EXCEEDED_TYPE);headerInfo.put("code", ICMP_TIME_EXCEEDED_CODE);byte[] data = new byte[packet.header.length - ICMP_TIME_EXCEEDED_DATA_OFFSET];buffer.position(ICMP_TIME_EXCEEDED_DATA_OFFSET);buffer.get(data, 0, data.length);headerInfo.put("data", data);return headerInfo;}}

它的实现原理也很简单,仅仅是按照给定格式抽取出相应字段而已。由于篇幅所限,还有很多代码没有展现出来,更详细的讲解和代码调试演示过程,请点击链接

当上面代码完成后,运行程序,并用wireshark抓包,得到情况如下:
屏幕快照 2019-02-21 下午6.08.34.png

它表明我们的代码正确的构造了数据包,并准确的触发icmp time exceeded limit数据包的回发,然后我们观察到程序运行时会将路径上锁经过的路由器IP打印出来:

屏幕快照 2019-02-21 下午6.10.27.png

由此可见,我们通过程序实现准确的发包和收包,进而完美的再现traceroute的内部运行原理。

更详细的讲解和代码调试演示过程,请点击链接

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
这里写图片描述

这篇关于一个有趣的网络程序TraceRoute:记录数据包传送路径上的路由器IP的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/950048

相关文章

python获取指定名字的程序的文件路径的两种方法

《python获取指定名字的程序的文件路径的两种方法》本文主要介绍了python获取指定名字的程序的文件路径的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 最近在做项目,需要用到给定一个程序名字就可以自动获取到这个程序在Windows系统下的绝对路径,以下

Debian 13升级后网络转发等功能异常怎么办? 并非错误而是管理机制变更

《Debian13升级后网络转发等功能异常怎么办?并非错误而是管理机制变更》很多朋友反馈,更新到Debian13后网络转发等功能异常,这并非BUG而是Debian13Trixie调整... 日前 Debian 13 Trixie 发布后已经有众多网友升级到新版本,只不过升级后发现某些功能存在异常,例如网络转

SpringBoot路径映射配置的实现步骤

《SpringBoot路径映射配置的实现步骤》本文介绍了如何在SpringBoot项目中配置路径映射,使得除static目录外的资源可被访问,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一... 目录SpringBoot路径映射补:springboot 配置虚拟路径映射 @RequestMapp

基于Python编写自动化邮件发送程序(进阶版)

《基于Python编写自动化邮件发送程序(进阶版)》在数字化时代,自动化邮件发送功能已成为企业和个人提升工作效率的重要工具,本文将使用Python编写一个简单的自动化邮件发送程序,希望对大家有所帮助... 目录理解SMTP协议基础配置开发环境构建邮件发送函数核心逻辑实现完整发送流程添加附件支持功能实现htm

Linux查询服务器 IP 地址的命令详解

《Linux查询服务器IP地址的命令详解》在服务器管理和网络运维中,快速准确地获取服务器的IP地址是一项基本但至关重要的技能,下面我们来看看Linux中查询服务器IP的相关命令使用吧... 目录一、hostname 命令:简单高效的 IP 查询工具命令详解实际应用技巧注意事项二、ip 命令:新一代网络配置全

C#控制台程序同步调用WebApi实现方式

《C#控制台程序同步调用WebApi实现方式》控制台程序作为Job时,需同步调用WebApi以确保获取返回结果后执行后续操作,否则会引发TaskCanceledException异常,同步处理可避免异... 目录同步调用WebApi方法Cls001类里面的写法总结控制台程序一般当作Job使用,有时候需要控制

一文带你迅速搞懂路由器/交换机/光猫三者概念区别

《一文带你迅速搞懂路由器/交换机/光猫三者概念区别》讨论网络设备时,常提及路由器、交换机及光猫等词汇,日常生活、工作中,这些设备至关重要,居家上网、企业内部沟通乃至互联网冲浪皆无法脱离其影响力,本文将... 当谈论网络设备时,我们常常会听到路由器、交换机和光猫这几个名词。它们是构建现代网络基础设施的关键组成

99%的人都选错了! 路由器WiFi双频合一还是分开好的专业解析与适用场景探讨

《99%的人都选错了!路由器WiFi双频合一还是分开好的专业解析与适用场景探讨》关于双频路由器的“双频合一”与“分开使用”两种模式,用户往往存在诸多疑问,本文将从多个维度深入探讨这两种模式的优缺点,... 在如今“没有WiFi就等于与世隔绝”的时代,越来越多家庭、办公室都开始配置双频无线路由器。但你有没有注

Python开发简易网络服务器的示例详解(新手入门)

《Python开发简易网络服务器的示例详解(新手入门)》网络服务器是互联网基础设施的核心组件,它本质上是一个持续运行的程序,负责监听特定端口,本文将使用Python开发一个简单的网络服务器,感兴趣的小... 目录网络服务器基础概念python内置服务器模块1. HTTP服务器模块2. Socket服务器模块

Go语言网络故障诊断与调试技巧

《Go语言网络故障诊断与调试技巧》在分布式系统和微服务架构的浪潮中,网络编程成为系统性能和可靠性的核心支柱,从高并发的API服务到实时通信应用,网络的稳定性直接影响用户体验,本文面向熟悉Go基本语法和... 目录1. 引言2. Go 语言网络编程的优势与特色2.1 简洁高效的标准库2.2 强大的并发模型2.