tdo

Dicing the onion
git clone git@abtrout.com:tdo.git
Log | Files | Refs | README

decode.go (5234B)


      1 package main
      2 
      3 import (
      4 	"encoding/binary"
      5 	"errors"
      6 	"log"
      7 	"net"
      8 	"os"
      9 
     10 	"github.com/abtrout/tdo"
     11 )
     12 
     13 func main() {
     14 	bs, err := tdo.DecodePipedInput()
     15 	if err != nil {
     16 		log.Fatalf("Failed to read layer input: %v", err)
     17 	}
     18 
     19 	var out []byte
     20 	packets, err := ParsePackets(bs)
     21 	if err != nil {
     22 		log.Fatalf("Failed to ParsePackets: %v", err)
     23 	}
     24 	for _, p := range FilterPackets(packets) {
     25 		out = append(out, p.udpDG.bs...)
     26 	}
     27 
     28 	if _, err := os.Stdout.Write(out); err != nil {
     29 		log.Fatalf("Failed to write decoded output: %v", err)
     30 	}
     31 }
     32 
     33 func ParsePackets(bs []byte) ([]*Packet, error) {
     34 	var packets []*Packet
     35 	var offset int
     36 	for {
     37 		if offset >= len(bs) {
     38 			break
     39 		}
     40 		// Parse and validate IPv4 header.
     41 		ipH, err := ParseIPv4Header(bs[offset:])
     42 		if err != nil {
     43 			return nil, err
     44 		}
     45 		// Parse and validate UDP header inside IPv4 data.
     46 		ipData := bs[offset+ipH.Len : offset+ipH.TotalLen]
     47 		udpH, err := ParseUDPHeader(ipData)
     48 		if err != nil {
     49 			return nil, err
     50 		}
     51 		udpData := ipData[8:] // skip header (4 uint16 = 8 bytes)
     52 		udpDG := &UDPDatagram{udpH, udpData}
     53 		packets = append(packets, &Packet{ipH, ipData, udpDG})
     54 
     55 		offset += ipH.TotalLen
     56 	}
     57 	return packets, nil
     58 }
     59 
     60 func FilterPackets(packets []*Packet) []*Packet {
     61 	// Filter packets as follows:
     62 	// - The packet was sent FROM any port of 10.1.1.10
     63 	// - The packet was sent TO port 42069 of 10.1.1.200
     64 	// - The IPv4 header checksum is correct
     65 	// - The UDP header checksum is correct
     66 	var filtered []*Packet
     67 	wantSrc := net.IPv4(10, 1, 1, 10)
     68 	wantDst := net.IPv4(10, 1, 1, 200)
     69 	wantDstPort := uint16(42069)
     70 	for _, p := range packets {
     71 		if !p.ipH.Valid || !p.UDPChecksum() {
     72 			continue
     73 		} else if !p.ipH.Src.Equal(wantSrc) {
     74 			continue
     75 		} else if !p.ipH.Dst.Equal(wantDst) {
     76 			continue
     77 		} else if p.udpDG.h.dstPort != wantDstPort {
     78 			continue
     79 		}
     80 		filtered = append(filtered, p)
     81 	}
     82 	return filtered
     83 }
     84 
     85 type Packet struct {
     86 	ipH    *IPv4Header
     87 	ipData []byte
     88 	udpDG  *UDPDatagram
     89 }
     90 
     91 type IPv4Header struct {
     92 	Version  int    // protocol version
     93 	Len      int    // header length
     94 	TotalLen int    // packet total length
     95 	Flags    int    // flags
     96 	FragOff  int    // fragment offset
     97 	TTL      int    // time-to-live
     98 	Protocol int    // next protocol
     99 	Checksum int    // checksum
    100 	Src      net.IP // source address
    101 	Dst      net.IP // destination address
    102 
    103 	Valid bool // the checksum matches wire format data.
    104 }
    105 
    106 // Parse an IPv4 header.
    107 // https://datatracker.ietf.org/doc/html/rfc791#page-11
    108 //
    109 // This is mostly copied from x/net/ipv4, and modified to
    110 // work with wire format.
    111 func ParseIPv4Header(bs []byte) (*IPv4Header, error) {
    112 	if len(bs) < 20 {
    113 		return nil, errors.New("header too short")
    114 	}
    115 	h := IPv4Header{}
    116 	h.Version = int(bs[0] >> 4)
    117 	h.Len = int(bs[0]&0x0f) << 2
    118 	h.TotalLen = int(binary.BigEndian.Uint16(bs[2:4]))
    119 	h.TTL = int(bs[8])
    120 	h.Protocol = int(bs[9])
    121 	h.Checksum = int(binary.BigEndian.Uint16(bs[10:12]))
    122 	h.Src = net.IPv4(bs[12], bs[13], bs[14], bs[15])
    123 	h.Dst = net.IPv4(bs[16], bs[17], bs[18], bs[19])
    124 
    125 	tmp := int(binary.BigEndian.Uint16(bs[6:8]))
    126 	h.Flags = int(tmp&0xe000) >> 13
    127 	h.FragOff = tmp & 0x1fff
    128 
    129 	// Validate checksum.
    130 	var sum uint16
    131 	for i := 0; i < 20; i += 2 {
    132 		sum = Sum(sum, binary.BigEndian.Uint16(bs[i:i+2]))
    133 	}
    134 	h.Valid = (sum == 0xFFFF)
    135 
    136 	return &h, nil
    137 }
    138 
    139 type UDPHeader struct {
    140 	srcPort  uint16 // all fields are 2 bytes.
    141 	dstPort  uint16
    142 	length   uint16
    143 	checksum uint16
    144 }
    145 
    146 func ParseUDPHeader(bs []byte) (*UDPHeader, error) {
    147 	if len(bs) < 8 {
    148 		return nil, errors.New("can't parse UDPHeader; not enough bytes")
    149 	}
    150 	return &UDPHeader{
    151 		srcPort:  binary.BigEndian.Uint16(bs[0:2]),
    152 		dstPort:  binary.BigEndian.Uint16(bs[2:4]),
    153 		length:   binary.BigEndian.Uint16(bs[4:6]),
    154 		checksum: binary.BigEndian.Uint16(bs[6:8]),
    155 	}, nil
    156 }
    157 
    158 type UDPDatagram struct {
    159 	h  *UDPHeader
    160 	bs []byte
    161 }
    162 
    163 // Checks if checksum computed from header matches checksum field.
    164 //
    165 // Checksum is the 16-bit one's complement of the one's complement
    166 // sum of a pseudo header of information from the IP header, the UDP header,
    167 // and the data, padded with zero octets at the end (if necessary) to make
    168 // a multiple of two octets.
    169 func (p *Packet) UDPChecksum() bool {
    170 	var sum uint16
    171 
    172 	// Source IPv4 address
    173 	ip := p.ipH.Src.To4()
    174 	sum = Sum(sum, binary.BigEndian.Uint16(ip[:2]))
    175 	sum = Sum(sum, binary.BigEndian.Uint16(ip[2:]))
    176 	// Destination IPv4 address
    177 	ip = p.ipH.Dst.To4()
    178 	sum = Sum(sum, binary.BigEndian.Uint16(ip[:2]))
    179 	sum = Sum(sum, binary.BigEndian.Uint16(ip[2:]))
    180 	// Protocol; fixed (17) since using UDP
    181 	sum = Sum(sum, uint16(17))
    182 	// UDP length = IPv4 data payload size
    183 	sum = Sum(sum, uint16(len(p.ipData)))
    184 	// Source port
    185 	sum = Sum(sum, p.udpDG.h.srcPort)
    186 	// Destination port
    187 	sum = Sum(sum, p.udpDG.h.dstPort)
    188 	// Length
    189 	sum = Sum(sum, p.udpDG.h.length)
    190 
    191 	// Data
    192 	bs := p.udpDG.bs
    193 	for i := 0; i < len(bs)-1; i += 2 {
    194 		sum = Sum(sum, binary.BigEndian.Uint16(bs[i:i+2]))
    195 	}
    196 	if len(bs)%2 != 0 {
    197 		sum = Sum(sum, binary.BigEndian.Uint16([]byte{bs[len(bs)-1], 0}))
    198 	}
    199 
    200 	// Finally, take ones complement of this sum.
    201 	sum = ^sum
    202 	return sum == p.udpDG.h.checksum
    203 }
    204 
    205 // One's complement sum.
    206 func Sum(a, b uint16) uint16 {
    207 	sum := uint32(a) + uint32(b)
    208 	return uint16(sum&0xFFFF) + uint16(sum>>16)
    209 }