Last active
August 9, 2024 17:42
-
-
Save chrisnc/0ff3d1c20cb6687454b0 to your computer and use it in GitHub Desktop.
constructing ip/udp packets in go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
Yeah thanks, I am having to build UDP packets by hand (maybe? I believe) to return packets from a UDP transparent proxy server I am doing.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you!! I was looking for a working code that handles IP spoofing with golang!