Skip to content

Instantly share code, notes, and snippets.

@chrisnc
Last active April 7, 2025 13:30
Show Gist options
  • Select an option

  • Save chrisnc/0ff3d1c20cb6687454b0 to your computer and use it in GitHub Desktop.

Select an option

Save chrisnc/0ff3d1c20cb6687454b0 to your computer and use it in GitHub Desktop.

Revisions

  1. chrisnc revised this gist Sep 20, 2015. 1 changed file with 18 additions and 13 deletions.
    31 changes: 18 additions & 13 deletions rawudp.go
    Original file line number Diff line number Diff line change
    @@ -13,8 +13,6 @@ import (
    "golang.org/x/sys/unix"
    )

    const maxPacketSize = 2 << 16

    type iphdr struct {
    vhl uint8
    tos uint8
    @@ -98,10 +96,12 @@ func main() {
    ipdststr := "127.0.0.1"
    udpsrc := uint(10000)
    udpdst := uint(15000)
    showcsum := false
    flag.StringVar(&ipsrcstr, "ipsrc", ipsrcstr, "IPv4 source address")
    flag.StringVar(&ipdststr, "ipdst", ipdststr, "IPv4 destination address")
    flag.UintVar(&udpsrc, "udpsrc", udpsrc, "UDP source port")
    flag.UintVar(&udpdst, "udpdst", udpdst, "UDP destination port")
    flag.BoolVar(&showcsum, "showcsum", showcsum, "show checksums")
    flag.Parse()

    ipsrc := net.ParseIP(ipsrcstr)
    @@ -130,27 +130,25 @@ func main() {
    }

    ip := iphdr{
    vhl: 0x45,
    tos: 0,
    // iplen set later
    id: 0,
    vhl: 0x45,
    tos: 0,
    id: 0x1234, // the kernel overwrites id if it is zero
    off: 0,
    ttl: 64,
    proto: unix.IPPROTO_UDP,
    // ipsum set later
    }
    copy(ip.src[:], ipsrc.To4())
    copy(ip.dst[:], ipdst.To4())
    // iplen and csum set later

    udp := udphdr{
    src: uint16(udpsrc),
    dst: uint16(udpdst),
    // ulen set later
    // csum set later
    }
    // ulen and csum set later

    // just use an empty IPv4 sockaddr
    // the OS will route the packet based on the IP header
    // just use an empty IPv4 sockaddr for Sendto
    // the kernel will route the packet based on the IP header
    addr := unix.SockaddrInet4{}

    for {
    @@ -163,16 +161,23 @@ func main() {
    payload := []byte(line)
    udplen := 8 + len(payload)
    totalLen := 20 + udplen
    if totalLen > maxPacketSize {
    fmt.Fprintf(os.Stderr, "message is too large to fit into a packet: %v > %v\n", totalLen, maxPacketSize)
    if totalLen > 0xffff {
    fmt.Fprintf(os.Stderr, "message is too large to fit into a packet: %v > %v\n", totalLen, 0xffff)
    continue
    }

    // the kernel will overwrite the IP checksum, so this is included just for
    // completeness
    ip.iplen = uint16(totalLen)
    ip.checksum()

    // the kernel doesn't touch the UDP checksum, so we can either set it
    // correctly or leave it zero to indicate that we didn't use a checksum
    udp.ulen = uint16(udplen)
    udp.checksum(&ip, payload)
    if showcsum {
    fmt.Printf("ip checksum: 0x%x, udp checksum: 0x%x\n", ip.csum, udp.csum)
    }

    var b bytes.Buffer
    err = binary.Write(&b, binary.BigEndian, &ip)
  2. chrisnc created this gist Sep 15, 2015.
    216 changes: 216 additions & 0 deletions rawudp.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,216 @@
    package main

    import (
    "bufio"
    "bytes"
    "encoding/binary"
    "flag"
    "fmt"
    "net"
    "os"
    "runtime"

    "golang.org/x/sys/unix"
    )

    const maxPacketSize = 2 << 16

    type iphdr struct {
    vhl uint8
    tos uint8
    iplen uint16
    id uint16
    off uint16
    ttl uint8
    proto uint8
    csum uint16
    src [4]byte
    dst [4]byte
    }

    type udphdr struct {
    src uint16
    dst uint16
    ulen uint16
    csum uint16
    }

    // pseudo header used for checksum calculation
    type pseudohdr struct {
    ipsrc [4]byte
    ipdst [4]byte
    zero uint8
    ipproto uint8
    plen uint16
    }

    func checksum(buf []byte) uint16 {
    sum := uint32(0)

    for ; len(buf) >= 2; buf = buf[2:] {
    sum += uint32(buf[0])<<8 | uint32(buf[1])
    }
    if len(buf) > 0 {
    sum += uint32(buf[0]) << 8
    }
    for sum > 0xffff {
    sum = (sum >> 16) + (sum & 0xffff)
    }
    csum := ^uint16(sum)
    /*
    * From RFC 768:
    * If the computed checksum is zero, it is transmitted as all ones (the
    * equivalent in one's complement arithmetic). An all zero transmitted
    * checksum value means that the transmitter generated no checksum (for
    * debugging or for higher level protocols that don't care).
    */
    if csum == 0 {
    csum = 0xffff
    }
    return csum
    }

    func (h *iphdr) checksum() {
    h.csum = 0
    var b bytes.Buffer
    binary.Write(&b, binary.BigEndian, h)
    h.csum = checksum(b.Bytes())
    }

    func (u *udphdr) checksum(ip *iphdr, payload []byte) {
    u.csum = 0
    phdr := pseudohdr{
    ipsrc: ip.src,
    ipdst: ip.dst,
    zero: 0,
    ipproto: ip.proto,
    plen: u.ulen,
    }
    var b bytes.Buffer
    binary.Write(&b, binary.BigEndian, &phdr)
    binary.Write(&b, binary.BigEndian, u)
    binary.Write(&b, binary.BigEndian, &payload)
    u.csum = checksum(b.Bytes())
    }

    func main() {
    ipsrcstr := "127.0.0.1"
    ipdststr := "127.0.0.1"
    udpsrc := uint(10000)
    udpdst := uint(15000)
    flag.StringVar(&ipsrcstr, "ipsrc", ipsrcstr, "IPv4 source address")
    flag.StringVar(&ipdststr, "ipdst", ipdststr, "IPv4 destination address")
    flag.UintVar(&udpsrc, "udpsrc", udpsrc, "UDP source port")
    flag.UintVar(&udpdst, "udpdst", udpdst, "UDP destination port")
    flag.Parse()

    ipsrc := net.ParseIP(ipsrcstr)
    if ipsrc == nil {
    fmt.Fprintf(os.Stderr, "invalid source IP: %v\n", ipsrc)
    os.Exit(1)
    }
    ipdst := net.ParseIP(ipdststr)
    if ipdst == nil {
    fmt.Fprintf(os.Stderr, "invalid destination IP: %v\n", ipdst)
    os.Exit(1)
    }

    fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_RAW)

    if err != nil || fd < 0 {
    fmt.Fprintf(os.Stdout, "error creating a raw socket: %v\n", err)
    os.Exit(1)
    }

    err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_HDRINCL, 1)
    if err != nil {
    fmt.Fprintf(os.Stderr, "error enabling IP_HDRINCL: %v\n", err)
    unix.Close(fd)
    os.Exit(1)
    }

    ip := iphdr{
    vhl: 0x45,
    tos: 0,
    // iplen set later
    id: 0,
    off: 0,
    ttl: 64,
    proto: unix.IPPROTO_UDP,
    // ipsum set later
    }
    copy(ip.src[:], ipsrc.To4())
    copy(ip.dst[:], ipdst.To4())

    udp := udphdr{
    src: uint16(udpsrc),
    dst: uint16(udpdst),
    // ulen set later
    // csum set later
    }

    // just use an empty IPv4 sockaddr
    // the OS will route the packet based on the IP header
    addr := unix.SockaddrInet4{}

    for {
    stdin := bufio.NewReader(os.Stdin)
    line, err := stdin.ReadString('\n')
    if err != nil {
    fmt.Fprintln(os.Stderr, err)
    break
    }
    payload := []byte(line)
    udplen := 8 + len(payload)
    totalLen := 20 + udplen
    if totalLen > maxPacketSize {
    fmt.Fprintf(os.Stderr, "message is too large to fit into a packet: %v > %v\n", totalLen, maxPacketSize)
    continue
    }

    ip.iplen = uint16(totalLen)
    ip.checksum()

    udp.ulen = uint16(udplen)
    udp.checksum(&ip, payload)

    var b bytes.Buffer
    err = binary.Write(&b, binary.BigEndian, &ip)
    if err != nil {
    fmt.Fprintf(os.Stderr, "error encoding the IP header: %v\n", err)
    continue
    }
    err = binary.Write(&b, binary.BigEndian, &udp)
    if err != nil {
    fmt.Fprintf(os.Stderr, "error encoding the UDP header: %v\n", err)
    continue
    }
    err = binary.Write(&b, binary.BigEndian, &payload)
    if err != nil {
    fmt.Fprintf(os.Stderr, "error encoding the payload: %v\n", err)
    continue
    }
    bb := b.Bytes()

    /*
    * For some reason, the IP header's length field needs to be in host byte order
    * in OS X.
    */
    if runtime.GOOS == "darwin" {
    bb[2], bb[3] = bb[3], bb[2]
    }

    err = unix.Sendto(fd, bb, 0, &addr)
    if err != nil {
    fmt.Fprintf(os.Stderr, "error sending the packet: %v\n", err)
    continue
    }
    fmt.Printf("%v bytes were sent\n", len(bb))
    }

    err = unix.Close(fd)
    if err != nil {
    fmt.Fprintf(os.Stderr, "error closing the socket: %v\n", err)
    os.Exit(1)
    }
    }