用golang实现traceroute

news/2024/7/8 1:31:41

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://lihuaxi.xjx100.cn/news/1048004.html

相关文章

小程序封装公共搜索框

小程序封装公共搜索框 在小程序开发中,全局搜索是一个常见的需求,例如在一个商城小程序中,用户可以通过搜索框快速搜索商品。为了提高代码复用性和可维护性,我们可以将搜索框封装成一个组件,同时通过从父组件传递参数…

强训之【求最大连续的bit数和最近公共祖先】

目录 1.求最大连续的bit数1.1题目1.2思路1.2.1暴力求解1.2.2 字符串分割求解1.2.3 位运算“与”求解 1.3代码1.3.1暴力求解代码1.3.2字符串分割求解代码1.3.3位运算“与”求解 2.最近公共祖先2.1题目2.2思路2.3代码 3.选择题 1.求最大连续的bit数 1.1题目 链接: link 描述 求…

电脑端(PC)按键精灵——5.找色/找图命令

电脑端(PC)按键精灵——5.找色/找图命令 注:说了键盘、鼠标、其他、控制命令还有安装内容,现在说下颜色/图形命令,这一节相当重要 按键精灵小白入门详细教程: 电脑端(PC)按键精灵—小白入门 详细教程 命令介绍 1.GetPixelCol…

C. Vus the Cossack and Strings(异或判断二进制位匹配数奇偶)

Problem - C - Codeforces 题目描述 Vus the Cossack has two binary strings, that is, strings that consist only of "0" and "1". We call these strings aa and bb . It is known that |b| \leq |a|∣b∣≤∣a∣ , that is, the length of bb is at m…

增广拍卖——二跳页下的拍卖机制探索

1. 引言 本文提出的方案已被WSDM 2023接收,论文:Boosting Advertising Space: Designing Ad Auctions for Augment Advertising, 下载:https://dl.acm.org/doi/abs/10.1145/3539597.3570381 信息流产品为了保障用户体验通常会严格…

基于stm32f103c8t6的智能小车蓝牙模块

一、蓝牙模块简介 1.1、HC05 1.1.1、HC05AT指令 AT 识别是否进入AT模式 AT+NAME / AT+NAME=< param > 询问名字 / 设置名字 AT+RNAME?< param1> 获取远程蓝牙设备名称: AT+PSWD / AT+PSWD=< param > 询问密码 / 设置密码 AT+UART /A…

“升级大师:不同级别程序员的成长之路和突破痛点难点实战案例“

在编程世界中&#xff0c;程序员的技能和经验各不相同。根据他们的经验水平&#xff0c;我们可以将他们分为&#xff1a;小白、初级、中级、高级和资深程序员。在本文中&#xff0c;我们将讨论这些不同级别的程序员如何处理问题&#xff0c;以及他们在解决问题过程中可能遇到的…

浏览器强缓存与协商缓存

一、强缓存 强制缓存的思想是&#xff0c;在浏览器内置数据库中缓存每次请求中 “可以被缓存” &#xff08;受到一些关键字的管控&#xff09;的静态资源如 image, css, js 文件&#xff0c; 当第二次请求被缓存过的资源时候&#xff0c;会通过校验两个字段 Expires 和 Cache-…