用golang实现traceroute

news2024/11/18 20:45:40

Traceroute

概念

traceroute是一种网络诊断工具,通过traceroute可以诊断出本机到目的地IP之间的路由情况,例如路由跳数、延迟、是否可达等信息。该工具在linux环境下的命令是traceroute或者tracepath,在windows下命令是tracert

工作原理

traceroute在linux系列的操作系统,默认通过发送UDP请求到目的地IP,UDP的端口使用的是33434到33545之间。除了UDP的协议,可选用ICMP或者TCP(TCP SYN包)。使用33434到33534之间到端口是因为大部分linux系统的该范围内的端口是不可用的。正常情况下如果我们对一个目的地主机发起UDP请求,并且该端口不存在就会直接返回端口或者主机不可大的信息,这样是无法获取到中途的路由节点。此时需要引入一个TTL的概念。

TTL即Time-To-Live,更多的被理解为路由跳数,该值存于IP头,经过路由转发时会将该值减1,当ttl值为0时,路由就会回复一个ICMP消息"Time Exceeded",表示跳数已经达到最大值,无法进行转发。
IPV4报文
IPV4报文对TTL解释
TTL在ipv4和ipv6头有不同的定义,在ipv4头用8位来存该数值,且命名为“Time to Live”,而在ipv6的头则叫做“Hop Limit”。
IPV6报文
IPV6对于HopLimit的解释

不管是Time to Live还是Hop Limit,其实都是相同的逻辑,路由转发一次就减1,并且该值为0时则无法转发。

我们来看一下traceroute的发包过程:
traceroute发包过程

第一步:主机A往目的主机B发送UDP包,包头需要设置TTL=1,并且设置目的端口为33434。
第二步:主机A的最近的路由A收到UDP包以后,将TTL减1,此时TTL=0,路由A就将该包丢弃,并且回复主机A一条ICMP信息:“Time Exceeded”。
第三步:主机A收到ICMP的消息以后即可记录ICMP发送主机的地址,该地址就是路由IP,并且主机A设置TTL=2,再次发送UDP包到目的主机B的33434端口。
第四步:以此类推,直到TTL超过设置的最大值或者收到目的主机返回的消息时停止发包,这样就得到了一个路由地址列表,同时也能拿到发送到路由之间的消息延迟,如果路由超过设定的时间内没有相应,则置该跳数的路由地址为“*”。

traceroute-go代码实现

由于go语言是高级语言,将udp以及tcp的包头都封装完整,无法定制设置ttl。好在golang提供了syscall库,该库提供依稀了linux下的函数调用,因此可以利用该包的方法达到设置ttl的目的。在1.4之前可以使用标准库syscall,但因为该库已经被弃用,可以使用golang.org/x/sys库,该库是syscall的扩展,提供更加丰富的系统调用方法。
有库的支持,我们则需要了解一下C语言的知识,即用C语言发送udp包和接受icmp的信息,因此这里需要涉及到几个函数:

  1. socket函数,创建一个socke的文件描述,用于发送udp以及接收icmp的消息,golang对应的函数为func Socket(domain, typ, proto int) (fd int, err error)
  2. setsockopt函数,该函数可以用于设定IP的头信息,我们要设定TTL就是利用该函数,同时该函数可以设定socket的请求或者接收消息的超时时间,golang对应的函数为func SetsockoptInt(fd, level, opt int, value int) (err error)
  3. sendto函数,用于发送udp消息,golang对应的函数为func Sendto(fd int, p []byte, flags int, to Sockaddr) (err error)
  4. recvfrom函数,用于接收icmp消息,golang对应的函数为func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error)

函数准备好以后就可以开工编写golang版本的traceroute库了。
在这里插入图片描述

首先,创建sendSocket,用于发送UDP包,注意内部的参数 unix.IPPROTO_UDP表示使用ipv4的udp协议,这个与ipv6协议是有区别的,可以通过命令man socket查看函数说明,然后创建一个recvSocket的socket文件描述符,用于接收ICMP的消息,这里调用了函数SetsockoptTimeval,用于设定接收消息的超时时间。

然后在for循环内循环发送udp消息并且接收icmp消息:
在这里插入图片描述

代码中SetsockoptInt函数设定ipv4的头TTL,初始化ttl=1,通过Sendto函数将消息发送到目的地址和目的端口,这里目的端口从33434开始,会在33434到33534区间内循环。
在这里插入图片描述

发送消息以后,通过Recvfrom接收消息,此时会判断接收消息是否报错,如果报错则直接退出循环并结束traceroute操作;如果没有报错,则需要解析返回的ICMP消息,由于ipv4的Header包头长度最小是20字节,最大是60字节,会出现浮动,因此需要拿到实际的ipv4头长度,这里使用ipv4库的ParseHeader函数解析拿到ipv4的包头结构,然后将收到的消息截取ipHeader.Len长度就得到我们的ICMP消息结构体,拿到ICMP消息结构以后既可以根据Type判定消息类型,由于我们只关注ICMPTypeTimeExceededICMPTypeDestinationUnreachable类型的消息,因此其他消息我们都会丢弃,并且如果收到的是ICMPTypeTimeExceeded,则需要将发送方的地址(路由地址)存下来,并且将ttl+1,然后再次循环发送udp消息到目的地。
如果收到的ICMP消息类型是ICMPTypeDestinationUnreachable或者ttl超过了最大的ttl设定或者接受的的ICMP消息来自于目的地址,则结束发包,并输出结果。
在这里插入图片描述

当然,如果接收到报错的消息,该消息可能是路由不通或者发包超时,因此我们需要将该跳的路由地址设置为“*”,同时判定重试次数,以及是否超过了最大TTL。
最后每次循环都将目的端口值+1,并且超过了最大的端口33534是又从最小端口开始,保障端口范围一直在33434到33534之间。

结果输出:
以下是我们自己的程序结果输出:
在这里插入图片描述
以下是系统自带的traceroute输出:
在这里插入图片描述

总结

traceroute工具原理不难,但要实现这个过程需要涉及到一些基本知识,如ip的报文组成、udp、icmp协议的一些基本知识,另外就是需要知道路由跳数的基本原理,通过实现这个过程也可以加深这些基础知识,同时是对这些知识的运用。
完整代码已经上传到github,地址为:https://github.com/Kseleven/traceroute-go,欢迎大家star,当然如有纰漏或者讲解不正确的地方,欢迎指正。

参考文献

  1. ipv4 rfc 791
  2. ipv6 rfc 2460
  3. icmp rfc 792
  4. traceroute rfc 1393
  5. linux man page-traceroute
  6. traceroute wiki
  7. icmp wiki
  8. golang sys库

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

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

相关文章

动态路由四大天王:OSPF、RIP、IS-IS、BGP,收藏这篇文章足以!

在计算机网络中,OSPF、RIP、IS-IS、BGP 都是常见的路由协议。它们分别具有不同的特点和适用场景。本文将对这四种路由协议进行对比,以帮助读者更好地了解它们的优缺点和适用范围。 OSPF OSPF(Open Shortest Path First)是一种链路…

中文编程最高境界,不用编程,会用excel就会用,香不香?

一直以来,关于中文编程的争议从未消停过。现如今,中文编程发展又是如何? ★为了实现中文编程,从未停下脚步 我们知道,中国人一直以来为了实现中文编程付出了不懈的努力,前前后后研发了几十种中文编程语言。…

JavaSE补充 | 了解数据结构与常用集合的源码分析

目录 一:数据结构 1. 数据结构剖析 1.1 研究对象一:数据间逻辑关系 1.2 研究对象二:数据的存储结构(或物理结构) 1.3 研究对象三:运算结构 2. 常见存储结构之:数组 3. 常见存储结构之&am…

奇葩的new Date()

大家平时在开发的时候有没被new Date()折磨过?就是它的诸多怪异的设定让你每每用的时候,都可能不小心踩坑。造成程序意外出错,却一下子找不到问题出处,那叫一个烦透了…… 下面,我就列举它的“四宗罪”及应用思考 可恶…

微前端运行时

目录 微前端运行时基于 SPA 的微前端架构应用生命周期 微前端运行时 谈到微前端绕不开的话题就是为什么不适用 iframe 作为承载微前端子应用的容器,其实从浏览器原生的方案来说,iframe 不从体验角度上来看几乎是最可靠的微前端方案了,主应用…

关于FPV图传系统时延讨论

关于FPV图传系统时延讨论 1. 源由2. 时延测试方法3. 时延测试资料4. 关于模拟图传5. 关于FPV时延感受5.1 静态时延5.2 动态时延 6. 参考资料7. 附录 DJI 图传系统 1. 源由 视频图传系统最重要的几个指标: 分辨率视角帧率时延传输距离 目前高清图传主要规则&#…

【Cartopy基础入门】如何丝滑的加载Geojson数据

原文作者:我辈李想 版权声明:文章原创,转载时请务必加上原文超链接、作者信息和本声明。 Cartopy基础入门 【Cartopy基础入门】Cartopy的安装 【Cartopy基础入门】如何丝滑的加载Geojson数据 文章目录 Cartopy基础入门一、Geojson数据来源二…

C语言 非本地跳转 实现native层TryCatch

前言 最近研究native hook的技术,了解到了这个非本地跳转,本文就是介绍他,对于解决native crash非常有用。 非本地跳转介绍 C语言的本地跳转是指goto、break、continue等语句,但是这个语句最大局限就是只能实现函数内部的跳转。…

Day3 自学Pytorch 数据集 torchvision.transforms类&torchvision.datasets.ImageFolder类

1.torchvision.transforms类 可调用的函数列表https://pytorch.org/vision/stable/transforms.html 介绍几个常用的函数: ① transforms.Resize() 将图像转换成目标大小 参数列表: size (sequence or int): (h,w&a…

Scala 中的 List 列表详解

目录 一、不可变长的List列表 1.List列表的声明与遍历 2.List列表的map、flatMap函数 3.List列表的filter过滤函数 4.List列表的count计数函数 二、可变长的List列表 1.可变长List声明 2.可变长List的添加方法 三、List列表其余的方法与函数 一、不可变长的List列表 …

任务调度原理 通俗讲解详细(FreeRTOS)

寄存器说明 以cortex-M3,首先先要了解比较特别的几个寄存器: r15 PC程序计数器(Program Counter),存储下一条要执行的指令的地址。 r14 LR连接寄存器(Link Register ),保存函数返回地址&#x…

记忆化搜索-滑雪

题意 给定一个 R 行 C 列的矩阵,表示一个矩形网格滑雪场。 矩阵中第 i 行第 j 列的点表示滑雪场的第 i 行第 j 列区域的高度。 一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。 当然,一个人能够滑动到某…

论文笔记:基于U-Net深度学习网络的地震数据断层检测

0 论文简介 论文:基于U-Net深度学习网络的地震数据断层检测 发表:2021年发表在石油地球物理勘探 1 问题分析和主要解决思路 问题:断层智能识别,就是如何利用人工智能技术识别出断层。 解决思路:结合U-N…

nginx快速入门

本文应侧重操作应用,复杂原理详见相关理论类笔记 Nginx 快速入门笔记 Nginx 的简介 1. 什么是 nginx ​ Nginx 可以作为静态页面的 web 服务器,同时还支持 CGI 协议的动态语言,比如 perl、php等。但是不支持 java。Java 程序只能通过与 t…

tauri+rust 构建项目

文章目录 安装前依赖创建项目第一步第二步第三步最后一步 调试 昨天菜鸟尝试使用 taurirust 构建项目,按照网上的感觉都不是很全,所以这里菜鸟自己总结一下,主要是给自己今后学习 taurirust 使用的,当然也不知道会不会去学&#x…

全球医疗器械研发投入前十,这家中国公司领跑榜单

2023年,《医疗设计》杂志公布了最新一期百强榜,评选出了2022全球医疗器械行业最高研发支出和项目的十家公司。这些公司的每年研发支出超过收入的15%。尽管经济面临逆风,医疗器械行业的销售额却创下了新的历史高点,研发支出也加速增…

多通道振弦传感器无线采集仪 多类型数字传感器独立发送协议

多通道振弦传感器无线采集仪 多类型数字传感器独立发送协议 独立发送传感器数据时,每个传感器是一个独立的数据包,发送至预设的 TCP 服务器。 数据包字符串,结构说明如下: UDID>MDS传感器类型码第 x 个传感器>第 x 包/总 x …

K8s中内置的Prometheus 异常,不断重启的解决方案

要说明的一点是:此处理方式会进行数据的删除,并且多实例情况下最好都做下操作。多实例都操作一遍的意思就是比如我普罗米修斯有如下四个: 如果Prometheus-k8s-0一直重启,则不光需要操作Prometheus-k8s-0,也需要对它的…

VC++如何获取所有运行中的Word实例的COM对象

目录 一 问题的提出二 工程创建2.1 创建一个基于对话框的MFC工程2.2 导入word相关的自动化包装类 三 代码实例3.1 初始化COM库3.2 对话框类头文件修改3.3 对话框类实现文件1.根据进程名称获取进程ID2. 获取一个进程下所有的窗口3. 判断某个窗口是否为主窗口4. 判断word进程下面…

数字ic验证工程师经典笔试面试题(含答案)

数字ic验证工程师在找工作时,刷笔试面试题必不可少,在面试前做好充足的准备才能抓住更多的机会,今天小编为大家准备了数字ic验证工程师大厂面试常用笔试面试题。 下列关于代码覆盖率描述错误的是:CD A.代码覆盖率包括语句覆盖率…