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 }