使用 Conn.Write 在 Go 中写入原始 IP 数据包

问题描述 投票:0回答:1

我有一个本机 ping 实现,其中生成一个原始 IP 数据包(IP 标头 + ICMP 回显请求数据包作为负载)并使用

Conn.Write
发送它。

这就是最终字节流的样子,前 20 个字节形成 IP 标头,接下来的 8 个字节形成有效负载(icmp 数据包)。

45 00 00 1c 00 00 00 00 40 01 26 a0 c0 a8 00 64 68 f4 2a 41 08 00 f7 ff 00 00 00 00
                                                           |----------icmp---------|
|--------------------ip header-----------------------------|

问题是,当我发送这个字节流时,我没有收到回显回复。 Wireshark 显示格式错误的数据包,其中字节未对齐。

但是,如果我将 ICMP 字节放在 IP 标头字节之前(即

08 00 f7 ff 00 00 00 00 45 00 00 1c 00 00 00 00 40 01 26 e0 c0 a8 00 64 68 f4 2a 01
),我收到回显回复。

这是一个小的可执行文件,它演示了我正在谈论的内容。

package main

import (
    "log"
    "net"
    "bytes"
    "fmt"
)

func main() {
    conn, err := net.Dial("ip4:icmp", "104.244.42.129")
    if err != nil {
        log.Fatal(err)
    }

    // this does not work although byte order is correct
    packet := []byte{0x45, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x26, 0xa0, 0xc0, 0xa8, 0x00, 0x64, 0x68, 0xf4, 0x2a, 0x41, 0x08, 0x00, 0xf7, 0xff, 0x00, 0x00, 0x00, 0x00} 

    // this works although byte order is wrong
    // packet := []byte{0x08, 0x00, 0xf7, 0xff, 0x00, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x26, 0xa0, 0xc0, 0xa8, 0x00, 0x64, 0x68, 0xf4, 0x2a, 0x41} 

    _, err = conn.Write(packet)
    if err != nil {
        log.Fatal(err)
    }

    reply := make([]byte, 1048)

    _, err = conn.Read(reply)
    if err != nil {
        log.Fatal(err)
    }
    reply = bytes.Trim(reply, "\x00")

    fmt.Println(reply)
}

我一直在试图解释为什么会发生这种情况,但无法想出任何答案。一些想法,

  • 我不认为这与字节顺序(字节顺序)有关。我在序列化数据包时使用了网络字节顺序,这得到了这个字节流。这取决于我首先放入缓冲区的内容 - 标头或有效负载。常识说应该首先将标头写入缓冲区。
func (p *Packet) Serialize() ([]byte, error) {
    buf := new(bytes.Buffer)
    headerSerialized, err := p.Header.Serialize()
    if err != nil {
        return nil, errors.Wrapf(err, "error serializing IPv4 packet header")
    }
    // buf.Write(p.Payload) // writing payload before header seems wrong, but it works
    buf.Write(headerSerialized)
    buf.Write(p.Payload) // this seems right, but doesn't work

    return buf.Bytes(), nil
}
  • 我在想,如果内核的网络堆栈自行添加 IP 标头,则可能会导致字节未对齐。但如果是这样的话,我在这两种情况下都不应该收到回显回复。
  • 我想知道
    Conn.Write
    是否是发送原始数据包的正确工具,因此我查看了
    Write
    函数内部以了解如何处理字节。但这似乎很难,所以我决定先在这里问一下,然后再进入另一个兔子洞。

一些帮助和见解会有很大帮助。

go networking network-programming ping
1个回答
0
投票

线路:

conn, err := net.Dial("ip4:icmp", "104.244.42.129")

不会创建raw套接字,因此,正如您所怀疑的那样,每当您调用

conn.Write
时,IP标头都会预先添加到您的数据包中。因此,在这种情况下,您需要“仅”提供有效的 ICMP 数据包,而不是完整的 IP 数据包。 IP 数据包不是有效的 ICMP 数据包,因此将被丢弃。然而,正如您所看到的,重新排序数据并将 ICMP 部分放在第一位将会起作用。 为了可视化,您正在尝试的将生成一个如下所示的包:

|IP|IP|ICMP|

这是无效的,但您的重新排序将生成一个如下所示的包:

|IP|ICMP|IP|

哪个是有效的。

如果您想创建原始套接字,您可能需要使用套接字系统调用:

fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)

	
© www.soinside.com 2019 - 2024. All rights reserved.