在MacOSX上使用Go 1.5创建RAW数据包

I am trying to do some basic packet crafting for a testing tool I am working on, but I can not seem to get the packet crafting to work (I am using Go 1.5 on OSX and am running as root.)

I am using the following code (taken from here) to try and create an ICMP packet, but when I try to specify say specific options in the IP header it does not seem to work. Further when I look at this packet in wireshark it shows up as protocol 255 (unknown).

I have read that on Linux system you can use AF_PACKET but on OSX systems you need to use BPF, however the sample code I found is using "syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)" and I am not sure how to get started with BPF. I have also seen some people try to use gopacket instead of the x/net/ipv4 package.

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)
}

If I print out the p variable that contains the packet data in Main() after the data comes back from pkt() it looks right:

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 

And you can see that the protocol is set for "1" in the 10th byte. But when we look at this packet in wireshark it looks like:

wireshark screen shot

doudansui6650
doudansui6650 如果您有原始套接字而OSX没有,则Linux可能会自动设置IP_HDRINCL套接字选项。我尝试添加syscall.SetsockoptInt(s,syscall.IPPROTO_IP,syscall.IP_HDRINCL,1)进行修复,但是我现在发送的数据包出现问题。也许是字节顺序问题之类的。
大约 5 年之前 回复
dsa88886666
dsa88886666 我在玩ICMP只是一个例子。最终,我需要能够为任何协议设计帧/数据包。好像是存在错误,或者我做错了什么。我希望我做错了什么。
大约 5 年之前 回复
du6jws6975
du6jws6975 您应该将协议设置为syscall.IPPROTO_ICMP而不是raw,不是吗?
大约 5 年之前 回复
douju4594
douju4594 在OSX上的“mansocket”手册中,似乎有一个“PF_NDRV”入口点可能会满足我的需要。看来“AF_INET”确实适用于第3层以上。但“PF_NDRV”似乎并不属于Gosyscall.Socket库的一部分。还有其他想法吗?
大约 5 年之前 回复
douzhizao0270
douzhizao0270 添加了其他信息以显示十六进制转储
大约 5 年之前 回复
dtoq41429
dtoq41429 请出示十六进制转储
大约 5 年之前 回复
douwei1930
douwei1930 感谢您弥补这一遗漏,我已将其添加到本文中。是的,我以root用户身份运行,您必须具有root用户访问权限才能打开原始套接字。
大约 5 年之前 回复
dongzao4503
dongzao4503 在Linux上,您需要具有root才能做到这一点,也许在osx上也一样?
大约 5 年之前 回复

2个回答

Here is an answer I got back from Mikio on the Go project. I am adding it here for other people that might be looking for a solution to this problem.

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)
    }
}
doumei9832
doumei9832 这有点酷; 我应该知道会有一个更简单的解决方案。 我对net / ipv4软件包的了解不多。 通常,在Go中,您不必像我们尝试的那样进行syscall。 对于这种类型的联网,将这些部分用C编写并仅从Go代码中调用C可能也很有用。
大约 5 年之前 回复

Ok I was able to get this to work on OS X now. You need to make sure you are setting IP_HDRINCL socket option syscall.SetsockoptInt(s, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1) and then you need to be careful building the packet. One trick that caught me for a LONG TIME was that, for some reason, for Sendto OS X/BSD want the IP length in host byte order, which in my case was LittleEndian, not BigEndian which is the typical network order. If you look at this code (I just kinda built the IP header myself, you can build it another way) it runs as expected.

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("
")
    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])

This code gives me this output in # 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       ......?!......
doujishao8793
doujishao8793 这有效,并且非常有帮助。 我还从Go项目的Mikio那里得到了答复,并提出了另一个解决方案,我将在下面单独发布。
大约 5 年之前 回复
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐