【问题标题】:raw socket didn't receive icmp response原始套接字没有收到 icmp 响应
【发布时间】:2016-07-10 06:07:27
【问题描述】:

我正在尝试发送一条 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/ipv4header.gohelper.go

【问题讨论】:

    标签: sockets go ip icmp raw-sockets


    【解决方案1】:

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

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

    我知道如果我在创建套接字时将IPPROTO_ICMP 设置为协议,我可以收到ICMP 回复,但我需要将TTL 设置为1,这必须在IP 层完成。因此,我使用IPPROTO_RAW 套接字发送ICMP 请求,然后使用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)
                    }
                }
            }
        }
    }
    

    其实我在go里面写了一个traceroute,如果有人对此感兴趣,整个代码在github

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 感谢您的提示,我无法在创建套接字时将IPPROTO_ICMP 设置为协议,因为我需要将TTL 设置为1,这必须在IP 层中完成。
      • 没有设置TTL的socket选项吗?
      猜你喜欢
      • 2012-01-19
      • 2012-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-22
      • 1970-01-01
      • 2010-11-10
      • 2018-12-27
      相关资源
      最近更新 更多