jordan2175
jordan2175

Reputation: 938

Creating RAW Packets with Go 1.5 on MacOSX

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

Upvotes: 4

Views: 2818

Answers (2)

jordan2175
jordan2175

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

user3591723
user3591723

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

Related Questions