raw socket没有收到icmp响应

时间:2016-03-23 04:31:45

标签: sockets go ip icmp raw-sockets

我试图发送一条TTL为1的icmp消息,并期望收到超时消息。该消息确实来了(我从wireshark看到它),但我的程序在syscall.Recvfrom上阻止。谁知道为什么?
icmp.go

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "net"
    "os"
    "syscall"
)

type ICMP struct {
    Type       uint8
    Code       uint8
    Checksum   uint16
    Identifier uint16
    SeqNo      uint16
}

func Checksum(data []byte) uint16 {
    var (
        sum    uint32
        length int = len(data)
        index  int
    )

    for length > 1 {
        sum += uint32(data[index])<<8 + uint32(data[index+1])
        index += 2
        length -= 2
    }

    if length > 0 {
        sum += uint32(data[index])
    }

    sum += (sum >> 16)

    return uint16(^sum)
}

func main() {
    h := Header{
        Version:  4,
        Len:      20,
        TotalLen: 20 + 8,
        TTL:      1,
        Protocol: 1,
        //  Dst:
    }

    argc := len(os.Args)
    if argc < 2 {
        fmt.Println("usage: program + host")
        return
    }

    ipAddr, _ := net.ResolveIPAddr("ip", os.Args[1])
    h.Dst = ipAddr.IP

    icmpReq := ICMP{
        Type:       8,
        Code:       0,
        Identifier: 0,
        SeqNo:      0,
    }

    out, err := h.Marshal()
    if err != nil {
        fmt.Println("ip header error", err)
        return
    }

    var icmpBuf bytes.Buffer
    binary.Write(&icmpBuf, binary.BigEndian, icmpReq)
    icmpReq.Checksum = Checksum(icmpBuf.Bytes())

    icmpBuf.Reset()
    binary.Write(&icmpBuf, binary.BigEndian, icmpReq)

    fd, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
    addr := syscall.SockaddrInet4{
        Port: 0,
    }

    copy(addr.Addr[:], ipAddr.IP[12:16])
    pkg := append(out, icmpBuf.Bytes()...)

    fmt.Println("ip length", len(pkg))

    if err := syscall.Sendto(fd, pkg, 0, &addr); err != nil {
        fmt.Println("Sendto err:", err)
    }

    var recvBuf []byte
    if nBytes, rAddr, err := syscall.Recvfrom(fd, recvBuf, 0); err == nil {
        fmt.Printf("recv %d bytes from %v\n", nBytes, rAddr)
    }
}

另外,我使用https://github.com/golang/net/tree/master/ipv4

中的header.gohelper.go

2 个答案:

答案 0 :(得分:1)

我认为在创建套接字时需要将IPPROTO_ICMP作为协议。 raw(7) man page表示只发送IPPROTO_RAW套接字。此外,如果您使用IPPROTO_ICMP,则不会提供IP标头。 (注意:我实际上并没有在Go中试过这个。)

答案 1 :(得分:1)

安迪指出,raw(7) man page说:

  

仅发送IPPROTO_RAW套接字。如果你真的想收到          所有IP数据包,使用带有ETH_P_IP协议的数据包(7)套接字。          请注意,与原始数据包不同,数据包套接字不会重新组合IP片段          套接字。

我知道如果我在创建套接字时将TTL设置为协议,我可以收到ICMP回复,但是我需要将IPPROTO_RAW设置为1,这必须在IP层中完成。因此,我使用net.ListenIP套接字发送ICMP请求,之后我使用package main import ( "bytes" "encoding/binary" "log" "net" "os" "syscall" ) const icmpID uint16 = 43565 // use a magic number for now type ICMP struct { Type uint8 Code uint8 Checksum uint16 Identifier uint16 SeqNo uint16 } func Checksum(data []byte) uint16 { var ( sum uint32 length int = len(data) index int ) for length > 1 { sum += uint32(data[index])<<8 + uint32(data[index+1]) index += 2 length -= 2 } if length > 0 { sum += uint32(data[index]) } sum += (sum >> 16) return uint16(^sum) } func main() { h := Header{ Version: 4, Len: 20, TotalLen: 20 + 8, TTL: 1, Protocol: 1, } argc := len(os.Args) if argc < 2 { log.Println("usage: program + host") return } ipAddr, _ := net.ResolveIPAddr("ip", os.Args[1]) h.Dst = ipAddr.IP icmpReq := ICMP{ Type: 8, Code: 0, Identifier: icmpID, SeqNo: 1, } out, err := h.Marshal() if err != nil { log.Println("ip header error", err) return } var icmpBuf bytes.Buffer binary.Write(&icmpBuf, binary.BigEndian, icmpReq) icmpReq.Checksum = Checksum(icmpBuf.Bytes()) icmpBuf.Reset() binary.Write(&icmpBuf, binary.BigEndian, icmpReq) fd, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW) addr := syscall.SockaddrInet4{ Port: 0, } copy(addr.Addr[:], ipAddr.IP[12:16]) pkg := append(out, icmpBuf.Bytes()...) if err := syscall.Sendto(fd, pkg, 0, &addr); err != nil { log.Println("Sendto err:", err) } laddr, err := net.ResolveIPAddr("ip4:icmp", "0.0.0.0") if err != nil { log.Fatal(err) } c, err := net.ListenIP("ip4:icmp", laddr) if err != nil { log.Fatal(err) } for { buf := make([]byte, 2048) n, raddr, err := c.ReadFrom(buf) if err != nil { log.Println(err) continue } icmpType := buf[0] if icmpType == 11 { if n == 36 { // Time exceeded messages // A time exceeded message contain IP header(20 bytes) and first 64 bits of the original payload id := binary.BigEndian.Uint16(buf[32:34]) log.Println("recv id", id) if id == icmpID { log.Println("recv Time Exceeded from", raddr) } } } } } 接收ICMP消息。这是代码:

{{1}}

实际上,我正在go中写一个traceroute,如果有人对此感兴趣,整个代码都在github