如何使用Golang 实现类似于 ping的命令

62 min read

在 Golang 中实现类似于 ping 的命令可以使用 nettime 库来发送 ICMP Echo 请求并接收响应。以下是一个简单的实现:

package main

import (
    "fmt"
    "net"
    "os"
    "time"
)

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]
    timeout := time.Duration(5 * time.Second)

    conn, err := net.DialTimeout("ip4:icmp", host, timeout)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(1)
    }
    defer conn.Close()

    msg := make([]byte, 1024)
    msg[0] = 8 // ICMP Echo Request
    msg[1] = 0 // ICMP Echo Request
    msg[2] = 0 // ICMP Echo Request
    msg[3] = 0 // ICMP Echo Request
    msg[4] = 0 // ICMP Echo Request
    msg[5] = 13 // ICMP Echo Request
    msg[6] = 0  // ICMP Echo Request
    msg[7] = 37 // ICMP Echo Request

    checksum := checkSum(msg[0:8])
    msg[2] = byte(checksum >> 8)
    msg[3] = byte(checksum & 255)

    start := time.Now()
    conn.Write(msg[0:8])
    conn.SetReadDeadline(time.Now().Add(timeout))

    n, err := conn.Read(msg)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(1)
    }
    elapsed := time.Since(start)
    fmt.Printf("Reply from %s: time=%v\n", host, elapsed)
    fmt.Printf("Received: %d bytes\n", n)
}

func checkSum(msg []byte) uint16 {
    sum := 0
    for n := 1; n < len(msg)-1; n += 2 {
        sum += int(msg[n])*256 + int(msg[n+1])
    }
    sum = (sum >> 16) + (sum & 0xffff)
    sum = sum + (sum >> 16)
    return uint16(^sum)
}

在命令行中执行 go run ping.go google.com,可以看到类似于以下输出:

Reply from google.com: time=22.726866ms
Received: 8 bytes

值得注意的是,这个程序需要在具有 root 权限的情况下运行,因为发送 ICMP 数据包需要使用原始套接字。