在Go-Blog中实现Traceroute

2020-08-22 06:56:57

如果您曾经摆弄过网络,那么您一定熟悉著名的traceroute工具。它是一个脚本,可以跟踪到主机的路径,并打印它遇到的每一跳的信息。举个例子,如果您运行traceroute kalbhor.xyz,您应该看到如下所示:

❯traceroute kalbhor.xyztraceroute to kalbhor.xyz(18.140.218.13),最多64跳,52字节数据包1 dlinkrouter.dlink(192.168.0.1)2.035 ms 1.276 ms 1.097 ms 2 10.194.0.1(10.194.0.1)5.985 ms 4.006 ms 3.817 ms 3宽带.actcorp.in(49.207.47.201)4.320 ms 4.715 ms 4.243 ms 4宽带.actcorp.in(49.207.47.225)5.115 ms 5.390 ms 4.893ms 5 14.142.187.85。Static-delhi.vsnl.net.in(14.142.187.85)3.789 ms 3.746 ms 4.004 ms 6 172.31.180.57(172.31.180.57)40.903 ms 41.661 ms 41.531 ms 7**ix-ae-4-2.tcore1.cxr-chennai.as6453.net(180.87.36.9)177.280 ms 8 if-ae-13-2.tcore1.svw-singapore.as6453.net(180.87.36.83)。164.288毫秒176.561毫秒82.274毫秒9 180.87.106.5(180.87.106.5)81.871毫秒84.931毫秒83.477毫秒10 52.93.11.197(52.93.11.197)82.368毫秒84.777毫秒52.93.11.211(52.93.11.211)82.945毫秒11 52.93.11.79(52.93.11.79)83.587毫秒52.93.11.67(52.93.11.67)。)78.292毫秒52.93.11.87(52.93.11.87)79.452毫秒12 52.93.11.80(52.93.11.80)82.862毫秒52.93.11.82(52.93.11.82)86.355毫秒52.93.11.72(52.93.11.72)88.732毫秒52.93.9.161(52.93.9.161)83.706毫秒52.93.9.95(52.93.9.95)。)82.498毫秒52.93.9.139(52.93.9.139)84.551毫秒14 203.83.223.77(203.83.223.77)84.500毫秒52.93.10.95(52.93.10.95)79.663毫秒79.812毫秒。

对于您来说,这些可能有所不同,但对我来说,这是我的计算机连接到kalbhor.xyz所采用的路径。这里有一些有趣的细节,包括dlinkrouter.dlink(192.168.0.1)。是的,看起来很像!这是我的路由器本地IP,这意味着我家里的路由器是第一台处理我的请求的机器。这是很明显的。

接下来,我们看到Broadband.actcorp.in(49.207.47.201),这是我的ISP。我们还可以看到,我的请求被转发到德里的ISP路由器(很可能是地区级ISP),然后进一步经过金奈和新加坡(kalbhor.xyz托管在AWS新加坡服务器上)。

此工具对于检查网络路径和解决问题非常有用。但除此之外,这个工具非常有趣,它的实际实现相当简单。

现在我们了解了traceroute的作用,让我们来看看它的内部。传输的每个TCP/UDP数据包都有一串报头,其中包含有关该数据包的信息。一个这样的报头是TTL报头,它是分组在被丢弃之前经过的跳数。因此,如果我们将此TTL报头设置为1,我们的数据包将到达第一跳并被丢弃,如果我们将其设置为2,则我们的数据包将到达第二跳并被丢弃,依此类推。

既然我们知道数据包如何到达我们和目的地之间的任何一跳,那么我们如何收集有关该跳的信息呢?当服务器/路由器丢弃数据包时,它会返回一条ICMP超时消息。解析此消息将允许我们检索特定跃点的信息。一旦到达目的地(最后一跳),我们就会收到一条ICMP目的地无法到达的消息。

现在我们了解了幕后发生的事情,我们可以粗略地设计一种实现traceroute的方法。实现它的步骤应该如下所示:

现在我们知道我们想要什么了,我们需要做的就是用任何语言实现它。我正在围棋中实现这一点。NET和Syscall软件包将帮助我们一路前进。

注意:我将使用最少的代码来显示主要实现(因此您可能不会在这里看到我处理错误等)。要获得此代码更精炼、开发良好的版本,请查看存储库。

让我们从创建将用于发送和接收数据的套接字开始。

让我们创建一个将迭代的ttl变量和一个定义超时的TimeValue变量。

接下来,让我们为我们将在套接字中发送的包设置ttl和超时值。

此时,我们的套接字已准备好发送和接收数据。我们需要做的是查找sendSocket的目的地地址和机器上recvSocket的网络接口。

Func socketAddr()([4]byte,error){socketAddr:=[4]byte{0,0,0,0}addrs,err:=net.InterfaceAddrs()if err!=nil{return socketAddr,err}for_,a:=range addrs{if ipnet,ok:=a.(*net.IPNet);OK&;&;!ipnet.IP.IsLoopback(){if len(ipnet.IP.To4())==net.IPv4len{copy(socketAddr[:],ipnet.IP.To4())return socketAddr,nil}err=errors.New(";未连接到Internet";)return socketAddr,err}func destAddr(Deststring)([4]byte,error){destAddr:=[4]byte{0,0,0}addrs,err:=net.LookupHost(dest)if err!=nil{return destAddr,err}addr:=addrs[0]ipAddr,err:=net.ResolveIPAddr(";ip";,addr)。

在我们的主函数中,我们使用这些函数来获取套接字将使用的地址

让我们绑定recvSocket,这样它就可以接收消息,并通过sendSocket向目的地发送一个空字节。我们连接到端口33434。

P:=make([]byte,options.Int(56))//这里的整数是数据包sizen,from,err:=syscall.Recvfrom(recvSocket,p,0)ip:=from(*syscall.SockaddrInet4).AddripString:=fmt.Sprintf(";%v%v";,IP[0],IP[1],IP[2],IP[3]。

Recvfrom方法将Sockaddr类型返回给FROM变量。因此,如果我们解析From变量,我们可以获得跃点上的IP信息。我们可以将其与net.LookupAddr一起使用来运行反向搜索,并通过IP获取主机名(域名)。

我们快做完了!我们所需要做的就是将此功能包装在for循环中,并不断更新ttl变量。

Func main(){sendSocket,err:=syscall.Socket(syscall.AF_INET,syscall.SOCK_DGRAM,syscall.IPPROTO_UDP)recvSocket,err:=syscall.Socket(syscall.AF_INET,syscall.SOCK_RAW,syscall.IPPROTO_ICMP)延迟syscall.Close(recvSocket。Syscall.SO_RCVTIMEO,&;电视)destAddr,Err:=destAddr(";google.com";)socketAddr,Err:=socketAddr()destAddrString:=fmt.Sprintf(";%v%v";,destAddr[0],destAddr[1],destAddr[2],destAddr[3])syscall.Bind(recvSocket,&;syscall.Sind。Syscall.SockaddrInet4{端口:33434,地址:目标地址})p:=Make([]字节,options.Int(56))//此处的整数是数据包大小n,from,err:=syscall.Recvfrom(recvSocket,p,0)ip:=from。(*syscall.SockaddrInet4).Addr ipString:=fmt.Sprintf(";%v%v。,ip[0],ip[1],ip[2],ip[3])host,err:=net.LookupAddr(IpString)fmt.Println(Host)fmt.Println(IpString)//如果ipString==destAddrString||ttl>;=56{Break}ttl+=1},我们将停止循环。

请注意,我们添加了IF语句块,以便在到达目的地址或超过跳数的最大值后结束for循环。

这肯定不是最优雅的解决方案,但它解释了traceroute的实现实际上是多么简单。如果您想要签出此代码的更精炼版本,该版本编译良好,并且有许多选项,如设置ttl、最大跳数、超时等,请签出-my Github Repo