constructing ip/udp packets in go
package main | |
import ( | |
"bufio" | |
"bytes" | |
"encoding/binary" | |
"flag" | |
"fmt" | |
"net" | |
"os" | |
"runtime" | |
"golang.org/x/sys/unix" | |
) | |
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) | |
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) | |
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, | |
id: 0x1234, // the kernel overwrites id if it is zero | |
off: 0, | |
ttl: 64, | |
proto: unix.IPPROTO_UDP, | |
} | |
copy(ip.src[:], ipsrc.To4()) | |
copy(ip.dst[:], ipdst.To4()) | |
// iplen and csum set later | |
udp := udphdr{ | |
src: uint16(udpsrc), | |
dst: uint16(udpdst), | |
} | |
// ulen and csum set later | |
// just use an empty IPv4 sockaddr for Sendto | |
// the kernel 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 > 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) | |
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) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment