在Go中实现ICMP ping

9

在Go语言中实现ICMP ping是否可行?另一种选择是fork一个“ping”进程,但我更愿意使用Go编写它。

3个回答

13
以下代码展示了如何使用原始套接字执行IPv4 ping(需要root权限):
以下是翻译的内容:

以下代码展示了如何使用原始套接字执行IPv4 ping(需要root权限):

package main

import (
    "log"
    "net"
    "os"

    "golang.org/x/net/icmp"
    "golang.org/x/net/ipv4"
)

const targetIP = "8.8.8.8"

func main() {
    c, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
    if err != nil {
        log.Fatalf("listen err, %s", err)
    }
    defer c.Close()

    wm := icmp.Message{
        Type: ipv4.ICMPTypeEcho, Code: 0,
        Body: &icmp.Echo{
            ID: os.Getpid() & 0xffff, Seq: 1,
            Data: []byte("HELLO-R-U-THERE"),
        },
    }
    wb, err := wm.Marshal(nil)
    if err != nil {
        log.Fatal(err)
    }
    if _, err := c.WriteTo(wb, &net.IPAddr{IP: net.ParseIP(targetIP)}); err != nil {
        log.Fatalf("WriteTo err, %s", err)
    }

    rb := make([]byte, 1500)
    n, peer, err := c.ReadFrom(rb)
    if err != nil {
        log.Fatal(err)
    }
    rm, err := icmp.ParseMessage(ipv4.ICMPTypeEchoReply.Protocol(), rb[:n])
    if err != nil {
        log.Fatal(err)
    }
    switch rm.Type {
    case ipv4.ICMPTypeEchoReply:
        log.Printf("got reflection from %v", peer)
    default:
        log.Printf("got %+v; want echo reply", rm)
    }
}

代码基于此处找到的示例: https://godoc.org/golang.org/x/net/icmp#PacketConn

若要在Linux中作为非特权用户进行ping,请参见此帖子


1
现在,ICMP(1)的IANA编号无法通过iana.ProtocolICMP访问,但可以通过ipv4.ICMPTypeEcho.Protocol()访问。包golang.org/x/net/internal/iana是内部的,当使用go 1.8编译器时,会显示“不允许使用内部包”的消息。参考:https://godoc.org/golang.org/x/net/ipv4#ICMPType.Protocol - TPPZ

5

目前,Go语言的net包不支持ICMP Echo(Ping)功能。

没有支持发送ICMP回显请求的功能。您需要向net包添加支持。 ping


2
现在(自2010年5月底以来),Go支持原始套接字(请参见net.IPConn类型)-这意味着您可以自己实现ping-并且在https://code.google.com/p/go/source/browse/src/pkg/net/ipraw_test.go上有一个ping示例。 - nos
1
链接 @nos 不再有效。新的 URL 应该是:https://golang.org/src/net/ipraw_test.go - TheHippo

2
为了在无需root权限的情况下执行此操作,您可以使用:
package main

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

    "golang.org/x/net/icmp"
    "golang.org/x/net/ipv4"
)

const target = "google.com"

func main() {
    for {
        time.Sleep(time.Second * 1)
        Ping(target)
    }
}

func Ping(target string) {
    ip, err := net.ResolveIPAddr("ip4", target)
    if err != nil {
        panic(err)
    }
    conn, err := icmp.ListenPacket("udp4", "0.0.0.0")
    if err != nil {
        fmt.Printf("Error on ListenPacket")
        panic(err)
    }
    defer conn.Close()

    msg := icmp.Message{
        Type: ipv4.ICMPTypeEcho, Code: 0,
        Body: &icmp.Echo{
            ID: os.Getpid() & 0xffff, Seq: 1,
            Data: []byte(""),
        },
    }
    msg_bytes, err := msg.Marshal(nil)
    if err != nil {
        fmt.Printf("Error on Marshal %v", msg_bytes)
        panic(err)
    }

    // Write the message to the listening connection
    if _, err := conn.WriteTo(msg_bytes, &net.UDPAddr{IP: net.ParseIP(ip.String())}); err != nil {
        fmt.Printf("Error on WriteTo %v", err)
        panic(err)
    }

    err = conn.SetReadDeadline(time.Now().Add(time.Second * 1))
    if err != nil {
        fmt.Printf("Error on SetReadDeadline %v", err)
        panic(err)
    }
    reply := make([]byte, 1500)
    n, _, err := conn.ReadFrom(reply)

    if err != nil {
        fmt.Printf("Error on ReadFrom %v", err)
        panic(err)
    }
    parsed_reply, err := icmp.ParseMessage(1, reply[:n])

    if err != nil {
        fmt.Printf("Error on ParseMessage %v", err)
        panic(err)
    }

    switch parsed_reply.Code {
    case 0:
        // Got a reply so we can save this
        fmt.Printf("Got Reply from %s\n", target)
    case 3:
        fmt.Printf("Host %s is unreachable\n", target)
        // Given that we don't expect google to be unreachable, we can assume that our network is down
    case 11:
        // Time Exceeded so we can assume our network is slow
        fmt.Printf("Host %s is slow\n", target)
    default:
        // We don't know what this is so we can assume it's unreachable
        fmt.Printf("Host %s is unreachable\n", target)
    }
}

这需要设置net.ping_group_range以允许通过命令sysctl -w net.ping_group_range="0 2147483647"进行ICMP。 - Eric C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接