【问题标题】:Creating RAW Packets with Go 1.5 on MacOSX在 MacOSX 上使用 Go 1.5 创建 RAW 数据包
【发布时间】:2015-12-11 02:09:14
【问题描述】:

我正在尝试为我正在开发的测试工具做一些基本的数据包制作,但我似乎无法让数据包制作工作(我在 OSX 上使用 Go 1.5 并以 root 身份运行。)

我正在使用以下代码 (taken from here) 尝试创建 ICMP 数据包,但是当我尝试在 IP 标头中指定特定选项时,它似乎不起作用。此外,当我在 Wireshark 中查看此数据包时,它显示为协议 255(未知)。

我已经读过,在 Linux 系统上你可以使用 AF_PACKET,但在 OSX 系统上你需要使用 BPF,但是我发现的示例代码是使用“syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)”而且我不确定如何开始使用 BPF。我还看到有些人尝试使用 gopacket 而不是 x/net/ipv4 包。

package main

import (
    "golang.org/x/net/ipv4"
    "net"
    "syscall"
)

func main() {
    var err error
    fd, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)

    addr := syscall.SockaddrInet4{
        Port: 0,
        Addr: [4]byte{127, 0, 0, 1},
    }
    p := pkt()

    _ = syscall.Sendto(fd, p, 0, &addr)
}

func pkt() []byte {
    h := ipv4.Header{
        Version:  4,
        Len:      20,
        TOS:      0,
        TotalLen: 85, // I can not seem to change this
        ID:       2,  // I can not seem to change this
        TTL:      64, // I can not seem to change this
        Protocol: 1,  // ICMP, This does not seem to work
        Dst:      net.IPv4(127, 0, 0, 1),
    }

    icmp := []byte{
        8, // type: echo request
        0, // code: not used by echo request
        0, // checksum (16 bit), we fill in below
        0,
        0, // identifier (16 bit). zero allowed.
        0,
        0, // sequence number (16 bit). zero allowed.
        0,
        0xC0, // Optional data. ping puts time packet sent here
        0xDE,
    }
    cs := csum(icmp)
    icmp[2] = byte(cs)
    icmp[3] = byte(cs >> 8)

    out, _ := h.Marshal()

    return append(out, icmp...)
}

func csum(b []byte) uint16 {
    var s uint32
    for i := 0; i < len(b); i += 2 {
        s += uint32(b[i+1])<<8 | uint32(b[i])
    }
    // add back the carry
    s = s>>16 + s&0xffff
    s = s + s>>16
    return uint16(^s)
}

如果我在数据从 pkt() 返回后在 Main() 中打印出包含数据包数据的 p 变量,它看起来是正确的:

DEBUG: (decimal) [69 0 60 0 0 0 0 0 64 1 0 0 0 0 0 0 127 0 0 1 8 0 55 33 0 0 0 0 192 222]
DEBUG: (hex)      45 0 3c 0 0 0 0 0 40 1 0 0 0 0 0 0 7f 0 0 1 8 0 37 21 0 0 0 0 c0 de 

您可以看到协议在第 10 个字节中设置为“1”。但是当我们在 Wireshark 中查看这个数据包时,它看起来像:

【问题讨论】:

  • 在Linux上,你需要root才能做到这一点,也许在osx上也是如此?
  • 感谢您发现遗漏,我已将其添加到文章中。是的,我以 root 身份运行,您必须具有 root 访问权限才能打开原始套接字。
  • 请显示十六进制转储
  • 添加了附加信息以显示 hexdumps
  • 在 OSX 的“man socket”手册中,看起来有一个“PF_NDRV”的入口点可能会满足我的需要。看起来“AF_INET”确实适用于第 3 层以上的东西。但“PF_NDRV”似乎不是 Go syscall.Socket 库的一部分。还有其他想法吗?

标签: macos sockets go packet


【解决方案1】:

这是我在 Go 项目中从 Mikio 那里得到的答案。我在这里为其他可能正在寻找此问题的解决方案的人添加它。

package main

import (
    "fmt"
    "golang.org/x/net/ipv4"
    "log"
    "net"
)

func main() {
    ip := net.ParseIP("127.0.0.1")
    proto := 1

    c, err := net.ListenPacket(fmt.Sprintf("ip4:%d", proto), "0.0.0.0")
    if err != nil {
        log.Fatal(err)
    }
    defer c.Close()

    p, err := ipv4.NewRawConn(c)
    if err != nil {
        log.Fatal(err)
    }

    b := []byte("HELLO-R-U-THERE")
    h := &ipv4.Header{
        Version:  ipv4.Version,
        Len:      ipv4.HeaderLen,
        TotalLen: ipv4.HeaderLen + len(b),
        ID:       12345,
        Protocol: proto,
        Dst:      ip.To4(),
    }
    if err := p.WriteTo(h, b, nil); err != nil {
        log.Println(err)
    }
}

【讨论】:

  • 这有点酷;我应该知道会有一个更简单的解决方案。我没怎么看net/ipv4 包。通常在 Go 中,您不必像我们尝试的那样进行系统调用。对于这种类型的网络,用 C 编写这些部分并从您的 Go 代码中调用 C 也可能很有用。
【解决方案2】:

好的,我现在可以让它在 OS X 上运行。您需要确保您设置了IP_HDRINCL 套接字选项syscall.SetsockoptInt(s, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1),然后您需要小心构建数据包。一个让我很长时间的技巧是,出于某种原因,SendtoOS X/BSD want the IP length in host byte order,在我的例子中是 LittleEndian,而不是 BigEndian,这是典型的网络顺序。如果您查看这段代码(我只是自己构建了 IP 标头,您可以用另一种方式构建它)它会按预期运行。

package main

import (
    "encoding/binary"
    "fmt"
    "syscall"
)

func main() {
    s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
    if err != nil {
        panic(err)
    }

    err = syscall.SetsockoptInt(s, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
    if err != nil {
        panic(err)
    }
    addr := syscall.SockaddrInet4{Addr: [4]byte{127, 0, 0, 1}}

    data := makepacket()

    for _, v := range data {
        if v == 0 {
            fmt.Printf("00 ")
            continue
        } else if v < 0xf {
            fmt.Printf("0%x ", v)
            continue
        }
        fmt.Printf("%x ", v)
    }
    fmt.Printf("\n")
    err = syscall.Sendto(s, data, 0, &addr)
    if err != nil {
        panic(err)
    }   
}       

func makepacket() []byte {
    icmp := []byte{
        8, // type: echo request
        0, // code: not used by echo request
        0, // checksum (16 bit), we fill in below
        0,
        0, // identifier (16 bit). zero allowed.
        0,
        0, // sequence number (16 bit). zero allowed.
        0,
        0xC0, // Optional data. ping puts time packet sent here
        0xDE, 
    }
    cs := csum(icmp)
    icmp[2] = byte(cs)
    icmp[3] = byte(cs >> 8)

    buf := []byte{0x45, 0x00, 0x00, 0x00, 0x95, 0x13, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x7f, 0x00, 0x0
0, 0x01, 0x7f, 0x00, 0x00, 0x01}
    binary.LittleEndian.PutUint16(buf[2:4], uint16(len(icmp) + len(buf)))
    return append(buf, icmp...)
}

func csum(b []byte) uint16 {
    var s uint32
    for i := 0; i < len(b); i += 2 {
        s += uint32(b[i+1])<<8 | uint32(b[i])

这段代码在# tcpdump -X -i lo0中给了我这个输出

20:05:24.016465 IP localhost > localhost: ICMP echo request, id 0, seq 0, length 10
    0x0000:  4500 001e 9513 0000 4001 0000 7f00 0001  E.......@.......
    0x0010:  7f00 0001 0800 3721 0000 0000 c0de       ......7!......
20:05:24.016495 IP localhost > localhost: ICMP echo reply, id 0, seq 0, length 10
    0x0000:  4500 001e 3e4f 0000 4001 0000 7f00 0001  E...>O..@.......
    0x0010:  7f00 0001 0000 3f21 0000 0000 c0de       ......?!......

【讨论】:

  • 这很有效,而且很有帮助。我还收到了来自 Go 项目的 Mikio 的回复,其中包含另一个解决方案,我将在下面作为单独的答案发布。
猜你喜欢
  • 2010-09-09
  • 2021-05-18
  • 1970-01-01
  • 2012-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-11
  • 2013-10-24
相关资源
最近更新 更多