Reputation: 938
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:
Upvotes: 4
Views: 2818
Reputation: 938
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)
}
}
Upvotes: 1
Reputation: 1284
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("\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])
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 ......?!......
Upvotes: 3