traceroute是一种用于查找特定目标地址的网络路径的工具。它使用ICMP协议发送数据包,每个数据包带有不同的TTL(生存时间)。路由上的每个节点都会降低TTL,当TTL为0时,目标主机在回复ICMP超时消息之前就会被丢弃,这样traceroute就可以确定途经路径。以下是使用Go实现traceroute工具的示例代码:
package main
import (
"fmt"
"net"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s host\n", os.Args[0])
os.Exit(1)
}
host := os.Args[1]
addr, err := net.ResolveIPAddr("ip", host)
if err != nil {
fmt.Println("Unable to resolve host IP address:", err)
os.Exit(1)
}
conn, err := net.Dial("ip:icmp", addr.String())
if err != nil {
fmt.Println("Unable to establish ICMP connection:", err)
os.Exit(1)
}
defer conn.Close()
fmt.Println("Tracing route to", addr.String())
const maxHops = 30
for i := 1; i <= maxHops; i++ {
conn.SetTTL(i)
var msg [512]byte
msg[0] = 8 // ICMP Echo Request Type
msg[1] = 0 // ICMP Echo Request Code
msg[2] = 0 // ICMP Echo Request Checksum (16 bits)
msg[3] = 0 // ICMP Echo Request Checksum (16 bits)
msg[4] = 0 // ICMP Echo Request Identifier (16 bits)
msg[5] = 0 // ICMP Echo Request Identifier (16 bits)
msg[6] = byte(i) // ICMP Echo Request Sequence Number (16 bits)
msg[7] = 0 // ICMP Echo Request Sequence Number (16 bits)
length := 8
checksum := checkSum(msg[:length])
msg[2] = byte(checksum >> 8)
msg[3] = byte(checksum & 0xff)
start := now()
_, err := conn.Write(msg[:length])
if err != nil {
fmt.Println("Error sending ICMP ping request:", err)
continue
}
reply := make([]byte, 512)
conn.SetReadDeadline(start.add(5*time.Second))
n, err := conn.Read(reply)
elapsed := time.Since(start)
if err != nil {
if netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {
fmt.Printf("%02d * * *\n", i)
continue
} else {
fmt.Println("Error reading ICMP ping reply:", err)
continue
}
}
if reply[0] == 11 && reply[1] == 0 {
fmt.Printf("%02d %v %v\n", i, addr.String(), elapsed)
break
} else {
fmt.Printf("%02d %v %v\n", i, net.IP(reply[12:16]).String(), elapsed)
}
}
}
func checkSum(data []byte) uint16 {
var sum uint32
for i := 0; i < len(data)-1; i += 2 {
sum += uint32(data[i+1])<<8 | uint32(data[i])
}
if len(data)%2 != 0 {
sum += uint32(data[len(data)-1])
}
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
return uint16(^sum)
}
func now() time.Time {
return time.Unix(0, syscall.Gettimeofday().Nsec)
}
该示例使用net包创建ICMP连接,并使用SetTTL()
设置TTL值。然后,它构造了一个ICMP Echo请求消息,并使用网络连接发送它。接下来,它等待接收ICMP Echo回复消息。如果它没有收到回复或者超时,则移动到下一个TTL值。如果它收到ICMP Echo回复,并且对于该TTL值它已经到达目标主机,则它打印出目标主机的IP地址和往返时间。否则,它将打印出到达该TTL值的第一个路由器的IP地址和往返时间。