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

huangapple go评论100阅读模式
英文:

Creating RAW Packets with Go 1.5 on MacOSX

问题

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

我正在使用以下代码(来自这里)尝试创建一个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, // 我似乎无法更改这个
		ID:       2,  // 我似乎无法更改这个
		TTL:      64, // 我似乎无法更改这个
		Protocol: 1,  // ICMP,这似乎不起作用
		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)
}

如果我在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中查看此数据包时,它看起来像这样:

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

英文:

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 (
&quot;golang.org/x/net/ipv4&quot;
&quot;net&quot;
&quot;syscall&quot;
)
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, &amp;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 &gt;&gt; 8)
out, _ := h.Marshal()
return append(out, icmp...)
}
func csum(b []byte) uint16 {
var s uint32
for i := 0; i &lt; len(b); i += 2 {
s += uint32(b[i+1])&lt;&lt;8 | uint32(b[i])
}
// add back the carry
s = s&gt;&gt;16 + s&amp;0xffff
s = s + s&gt;&gt;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:

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

答案1

得分: 2

好的,我将为你翻译以下内容:

好的,我现在已经在OS X上成功运行了这个代码。你需要确保设置了IP_HDRINCL套接字选项syscall.SetsockoptInt(s, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1),然后在构建数据包时要小心。有一个让我困扰了很长时间的技巧是,由于某种原因,对于Sendto来说,OS X/BSD希望以主机字节顺序表示IP长度,而在我的情况下,它是小端字节序,而不是典型的网络字节顺序大端字节序。如果你看一下这段代码(我只是自己构建了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...&gt;O..@.......
0x0010:  7f00 0001 0000 3f21 0000 0000 c0de       ......?!......
英文:

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 (
&quot;encoding/binary&quot;
&quot;fmt&quot;
&quot;syscall&quot;
)
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(&quot;00 &quot;)
continue
} else if v &lt; 0xf {
fmt.Printf(&quot;0%x &quot;, v)
continue
}
fmt.Printf(&quot;%x &quot;, v)
}
fmt.Printf(&quot;\n&quot;)
err = syscall.Sendto(s, data, 0, &amp;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 &gt;&gt; 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 &lt; len(b); i += 2 {
s += uint32(b[i+1])&lt;&lt;8 | uint32(b[i])

This code gives me this output in # tcpdump -X -i lo0

20:05:24.016465 IP localhost &gt; 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 &gt; localhost: ICMP echo reply, id 0, seq 0, length 10
0x0000:  4500 001e 3e4f 0000 4001 0000 7f00 0001  E...&gt;O..@.......
0x0010:  7f00 0001 0000 3f21 0000 0000 c0de       ......?!......

答案2

得分: 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)
    }
}

希望对你有帮助!

英文:

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 (
&quot;fmt&quot;
&quot;golang.org/x/net/ipv4&quot;
&quot;log&quot;
&quot;net&quot;
)
func main() {
ip := net.ParseIP(&quot;127.0.0.1&quot;)
proto := 1
c, err := net.ListenPacket(fmt.Sprintf(&quot;ip4:%d&quot;, proto), &quot;0.0.0.0&quot;)
if err != nil {
log.Fatal(err)
}
defer c.Close()
p, err := ipv4.NewRawConn(c)
if err != nil {
log.Fatal(err)
}
b := []byte(&quot;HELLO-R-U-THERE&quot;)
h := &amp;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)
}
}

huangapple
  • 本文由 发表于 2015年9月15日 07:43:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/32575558.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定