如何使用Golang通过使用gopacket组成原始TCP数据包并通过原始套接字发送它

10

我想使用 gopacket 来构造自定义的 TCP 数据包,然后使用 raw sockets 发送它们。

这里有一个简短易读的示例 Go 程序,演示了我想要做的事情:

package main

import (
    "code.google.com/p/gopacket"
    "code.google.com/p/gopacket/examples/util"
    "code.google.com/p/gopacket/layers"
    "log"
    "net"
)

func main() {
    defer util.Run()()

    // XXX create tcp/ip packet
    srcIP := net.ParseIP("127.0.0.1")
    dstIP := net.ParseIP("192.168.0.1")
    //srcIPaddr := net.IPAddr{
    //  IP: srcIP,
    //}
    dstIPaddr := net.IPAddr{
        IP: dstIP,
    }
    ipLayer := layers.IPv4{
        SrcIP:    srcIP,
        DstIP:    dstIP,
        Protocol: layers.IPProtocolTCP,
    }
    tcpLayer := layers.TCP{
        SrcPort: layers.TCPPort(666),
        DstPort: layers.TCPPort(22),
        SYN:     true,
    }
    tcpLayer.SetNetworkLayerForChecksum(&ipLayer)
    buf := gopacket.NewSerializeBuffer()
    opts := gopacket.SerializeOptions{
        FixLengths:       true,
        ComputeChecksums: true,
    }
    err := gopacket.SerializeLayers(buf, opts, &ipLayer, &tcpLayer)
    if err != nil {
        panic(err)
    }
    // XXX end of packet creation

    // XXX send packet
    ipConn, err := net.ListenPacket("ip4:tcp", "0.0.0.0")
    if err != nil {
        panic(err)
    }
    _, err = ipConn.WriteTo(buf.Bytes(), &dstIPaddr)
    if err != nil {
        panic(err)
    }
    log.Print("packet sent!\n")
}

然而运行此程序无法正常工作...SerializeLayer 失败。

panic: 无效的源 IP 127.0.0.1

goroutine 16 [正在运行]: runtime.panic(0x5bb020, 0xc2090723e0) /home/human/golang-empire/go/src/pkg/runtime/panic.c:279 +0xf5 main.main() /home/human/golang-empire/gopkg/src/github.com/david415/HoneyBadger/packetSendTest.go:41 +0x464

goroutine 19 [终止器等待]: runtime.park(0x413cc0, 0x7bc6c0, 0x7bb189) /home/human/golang-empire/go/src/pkg/runtime/proc.c:1369 +0x89 runtime.parkunlock(0x7bc6c0, 0x7bb189) /home/human/golang-empire/go/src/pkg/runtime/proc.c:1385 +0x3b runfinq() /home/human/golang-empire/go/src/pkg/runtime/mgc0.c:2644 +0xcf runtime.goexit() /home/human/golang-empire/go/src/pkg/runtime/proc.c:1445


好的...我修复了“无效的源IP”错误...这是由于ParseIP返回了错误的类型...它需要使用它的.To4()方法进行转换;-) - David Stainton
目前我的简单且简洁的示例正在发送经过精心制作的TCP/IP数据包...但是端口号和其他部分都不正确。也许TCP端口的"字节序"设置错误了?简单而简洁的示例在这里:https://github.com/david415/HoneyBadger/blob/bd81a0132a2ccd880eee210acf2c965a37b457e3/packetSendTest.go - David Stainton
这个新提交演示了通过比较分组解析后的srcPort和dstPort与预期值,证明生成的TCP/IP数据包存在格式错误:https://github.com/david415/HoneyBadger/blob/c136e5cd58dddfdd5b97b7c4b65e338f41d0b09d/packetSendTest.go - David Stainton
1个回答

13

你的问题说“制作自定义TCP数据包”,但你的代码清楚地表明你也想制作自定义的第三层IP头,这两者之间是有区别的。另外,你没有提到IPv4和IPv6的区别,但是你的代码似乎是针对IPv4的。

根据你的示例代码,我假设你想设置完整的IPv4头部。

截至Go 1.3.3和即将发布的Go 1.4版本,你不能使用核心Go包来实现你想要的功能。为了达成你的目标,你需要做两件事:

  1. 你需要在Go中创建一个原始套接字。与本网站上其他不正确的答案相反,你可以使用net.ListenPacketnet.DialIPnet.ListenIP之一在Go中创建一个原始套接字。

例如:

conn, err := net.ListenIP("ip4:tcp", netaddr)
if err != nil {
    log.Fatalf("ListenIP: %s\n", err)
}

创建一个原始套接字。

  1. 如果您想设置自己的IPv4第三层头部,则需要设置一个套接字选项以启用该功能。

你的问题没有说明你正在使用什么操作系统和架构。在我的Mac OS X笔记本电脑上:

% man ip
. . .
Outgoing packets automatically have an IP header prepended to them
(based on the destination address and the protocol number the socket is created with),
unless the IP_HDRINCL option has been set.

IP_HDRINCL 在Linux中也可用。不幸的是,Core Go没有设置 IP_HDRINCL socket选项的方法,也没有设置其他IP socket选项(如IP_TTL)的方法。我有一套私人补丁,可以在 Core Go 中启用此功能,但这对您没有帮助。

我相信以下软件包具有您所需的所有功能ipv4。请注意,它是一个大型软件包,我自己没有使用过。我进行了grep并支持多个平台上的IP_HDRINCL。您想调用NewRawConn以创建原始连接,此函数将创建原始套接字并设置IP_HDRINCL socket选项。

另请参见这里:raw-sockets-in-go和他在这里编写的代码latency,以了解更简单的方法,如果您只想设置TCP标头,可能会适合您的需求。但是,请注意,此代码不允许您设置IPv4 IP头中的IP地址,我怀疑您想要这样做。


谢谢!是的,那个完美地解决了问题。在这个提交中得到了工作:https://github.com/david415/HoneyBadger/blob/021246788e58cedf88dee75ac5dbf7ae60e12514/packetSendTest.go - David Stainton
1
这是你链接的博客文章的后续,它深入到IP头级别:http://www.darkcoding.net/software/raw-sockets-in-go-link-layer/ - Graham King

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