为了账号安全,请及时绑定邮箱和手机立即绑定

原始套接字未收到 icmp 响应

原始套接字未收到 icmp 响应

Go
繁华开满天机 2021-12-27 15:40:45
我正在尝试发送 TTL 仅为 1 的 icmp 消息,并希望收到超时消息。该消息确实来了(我从wireshark 看到它),但是我的程序在syscall.Recvfrom. 有谁知道为什么?icmp.gopackage mainimport (    "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)    }另外,我使用header.go和helper.go来自https://github.com/golang/net/tree/master/ipv4
查看完整描述

2 回答

?
萧十郎

TA贡献1815条经验 获得超13个赞

正如安迪指出的那样,raw(7) 手册页说:


仅发送 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 中。


查看完整回答
反对 回复 2021-12-27
?
倚天杖

TA贡献1828条经验 获得超3个赞

我认为您需要IPPROTO_ICMP在创建套接字时提供协议。该原料(7)手册页说,一个IPPROTO_RAW插座只发送。此外,如果您使用IPPROTO_ICMP,则不会提供 IP 标头。(注意:我实际上并没有在 Go 中尝试过这个。)


查看完整回答
反对 回复 2021-12-27
  • 2 回答
  • 0 关注
  • 207 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信