如何使用golang实现traceroute

 更新时间:2023年04月21日 11:39:32   作者:水番丘山  
这篇文章主要介绍了如何使用golang实现traceroute,该工具在linux环境下的命令是traceroute或者tracepath,在windows下命令是tracert,本文给大家详细讲解需要的朋友可以参考下

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的信息,因此这里需要涉及到几个函数:

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

到此这篇关于如何使用golang实现traceroute的文章就介绍到这了,更多相关golang实现traceroute内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 通过案例详细聊聊Go语言的变量与常量

    通过案例详细聊聊Go语言的变量与常量

    在任何一门现代的高级语言中,变量和常量都是它非常基础的程序结构的组成部分,下面这篇文章主要给大家介绍了关于如何通过案例详细聊聊Go语言的变量与常量的相关资料,需要的朋友可以参考下
    2023-03-03
  • Go代码检查工具golangci-lint安装使用方法

    Go代码检查工具golangci-lint安装使用方法

    这篇文章主要给大家介绍了关于Go代码检查工具golangci-lint安装使用的相关资料,golangci-lint用于许多开源项目中,比如kubernetes、Prometheus、TiDB等都使用golangci-lint用于代码检查,需要的朋友可以参考下
    2024-01-01
  • Go语言中websocket的使用demo分享

    Go语言中websocket的使用demo分享

    WebSocket是一种在单个TCP连接上进行全双工通信的协议。这篇文章主要和大家分享了一个Go语言中websocket的使用demo,需要的可以参考一下
    2022-12-12
  • Go语言基础函数基本用法及示例详解

    Go语言基础函数基本用法及示例详解

    这篇文章主要为大家介绍了Go语言基础函数基本用法及示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2021-11-11
  • Golang库插件注册加载机制的问题

    Golang库插件注册加载机制的问题

    这篇文章主要介绍了Golang库插件注册加载机制,这里说的插件并不是指的golang原生的可以在buildmode中加载指定so文件的那种加载机制,需要的朋友可以参考下
    2022-03-03
  • golang如何获得一个变量的类型

    golang如何获得一个变量的类型

    这篇文章主要介绍了golang获得一个变量类型的实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • Go语言普通指针unsafe.Pointer uintpt之间的关系及指针运算

    Go语言普通指针unsafe.Pointer uintpt之间的关系及指针运算

    这篇文章主要为大家介绍了Go语言普通指针unsafe.Pointer uintpt之间的关系及指针运算示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • Golang执行go get私有库提示

    Golang执行go get私有库提示"410 Gone" 的问题及解决办法

    这篇文章主要介绍了Golang执行go get私有库提示”410 Gone“ 解决办法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-02-02
  • Go语言的文件操作代码汇总

    Go语言的文件操作代码汇总

    本文给大家汇总介绍了go语言中的文件操作的代码,包括文件的读写,文件的新建打开和删除等,希望对大家学习go语言能够有所帮助
    2018-10-10
  • 深入理解golang中io.Writer接口的使用

    深入理解golang中io.Writer接口的使用

    io 是一个 Golang 标准库包,它为围绕输入和输出的许多操作和用例定义了灵活的接口,这篇文章主要为大家介绍了Go中Writer接口的使用,需要的可以参考下
    2023-10-10

最新评论