netshovel

Network Archaeology library for Go
git clone https://git.woozle.org/neale/netshovel.git

Neale Pickett  ·  2020-09-24

packet.go

  1package netshovel
  2
  3import (
  4	"encoding/binary"
  5	"encoding/hex"
  6	"fmt"
  7	"strings"
  8	"time"
  9
 10	"github.com/dirtbags/netshovel/gapstring"
 11)
 12
 13// ShortError is returned by convenience methods that are unable to get enough data
 14type ShortError struct {
 15	Wanted    int // How many bytes you needed
 16	Available int // How many bytes were available
 17}
 18
 19func (e *ShortError) Error() string {
 20	return fmt.Sprintf("Short read: wanted %d of %d available", e.Wanted, e.Available)
 21}
 22
 23// MissingError is returned by convenience methods that are unable to operate on gaps in data
 24type MissingError struct {
 25}
 26
 27func (e *MissingError) Error() string {
 28	return "Operation on missing bytes"
 29}
 30
 31// A Key,Value Pair
 32type namedField struct {
 33	key, value string
 34}
 35
 36// An application protocol header field
 37type headerField struct {
 38	name  string
 39	bits  int
 40	value interface{}
 41	order binary.ByteOrder
 42}
 43
 44// A Packet represents a single application-layer packet
 45//
 46// The Packet struct provides helper methods to assist
 47// with
 48// reverse-engineering new protocols
 49// and
 50// documenting header structure.
 51type Packet struct {
 52	Opcode      int
 53	Description string
 54	When        time.Time
 55	Payload     gapstring.GapString
 56	header      []headerField
 57	fields      []namedField
 58}
 59
 60var never = time.Unix(0, 0)
 61
 62// NewPacket returns a new packet
 63func NewPacket() Packet {
 64	return Packet{
 65		Opcode:      -1,
 66		Description: "Undefined",
 67		When:        never,
 68		Payload:     gapstring.GapString{},
 69		header:      []headerField{},
 70		fields:      []namedField{},
 71	}
 72}
 73
 74// DescribeType returns a string with timestamp, opcode, and description of this packet
 75func (pkt *Packet) DescribeType() string {
 76	return fmt.Sprintf(
 77		"  %s Opcode %d: %s",
 78		pkt.When.UTC().Format(time.RFC3339Nano),
 79		pkt.Opcode,
 80		pkt.Description,
 81	)
 82}
 83
 84// DescribeFields returns a multi-line string describing fields in this packet
 85func (pkt *Packet) DescribeFields() string {
 86	out := new(strings.Builder)
 87	for _, f := range pkt.fields {
 88		fmt.Fprintf(out, "    %s: %s\n", f.key, f.value)
 89	}
 90	return out.String()
 91}
 92
 93func center(s string, w int) string {
 94	if w < 3 {
 95		return "?"
 96	}
 97	if len(s) > w {
 98		s = s[0:w-3] + "…"
 99	}
100	return fmt.Sprintf("%*s", -w, fmt.Sprintf("%*s", (w+len(s))/2, s))
101}
102
103// DescribeHeader returns a multi-line string describing this packet's header structure
104func (pkt *Packet) DescribeHeader() string {
105	out := new(strings.Builder)
106	out.WriteString(" 0                               1\n")
107	out.WriteString(" 0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 a b c d e f\n")
108	out.WriteString("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n")
109
110	bitOffset := 0
111	for _, f := range pkt.header {
112		bits := f.bits
113		for bits > 0 {
114			linebits := bits
115			if linebits+bitOffset > 0x20 {
116				linebits = 0x20 - bitOffset
117			}
118
119			// Generate centered string
120			val := fmt.Sprintf("0x%x", f.value)
121			nameval := f.name
122			if f.bits == bits {
123				out.WriteString("|")
124			} else {
125				out.WriteString(" ")
126				val = ""
127				nameval = "..."
128			}
129			out.WriteString(center(nameval, linebits*2-len(val)-2))
130			out.WriteString(val)
131			out.WriteString(" ")
132
133			bitOffset += linebits
134			bits -= linebits
135			if bitOffset == 0x20 {
136				if bits == 0 {
137					out.WriteString("|")
138				} else {
139					out.WriteString(" ")
140				}
141				out.WriteString("\n")
142				out.WriteString("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n")
143				bitOffset = 0
144			}
145		}
146	}
147	if bitOffset > 0 {
148		out.WriteString("|\n")
149		for o := 0; o < bitOffset; o++ {
150			out.WriteString("+-")
151		}
152		out.WriteString("+\n")
153	}
154	return out.String()
155}
156
157// Describe returns a multi-line string describing this packet
158//
159// This shows the timestamp, opcode, description, and hex dump.
160// If you set any values, those are displayed in the order they were set.
161//
162// This will quickly get unweildy, especially for large conversations.
163// You are encouraged to implement your own Describe() method.
164func (pkt *Packet) Describe() string {
165	out := new(strings.Builder)
166
167	fmt.Fprintln(out, pkt.DescribeType())
168	fmt.Fprint(out, pkt.DescribeFields())
169	fmt.Fprint(out, pkt.DescribeHeader())
170	fmt.Fprint(out, pkt.Payload.Hexdump())
171	return out.String()
172}
173
174// Set a value
175//
176// This is intended to be used to note debugging information
177// that you'd like to see on each packet.
178func (pkt *Packet) Set(key, value string) {
179	pkt.fields = append(pkt.fields, namedField{key, value})
180}
181
182// SetString sets a string value, displaying its Go string representation
183func (pkt *Packet) SetString(key, value string) {
184	pkt.Set(key, fmt.Sprintf("%#v", value))
185}
186
187// SetInt sets an int value, displaying its decimal and hexadecimal representations
188func (pkt *Packet) SetInt(key string, value int) {
189	pkt.Set(key, fmt.Sprintf("%d == 0x%x", value, value))
190}
191
192// SetUint sets an unsigned int value, displaying its decimal and hexadecimal representations
193func (pkt *Packet) SetUint(key string, value uint) {
194	pkt.Set(key, fmt.Sprintf("%d == 0x%x", value, value))
195}
196
197// SetUint32 sets an Unt32 value, displaying its decimal and 0-padded hexadecimal representations
198func (pkt *Packet) SetUint32(key string, value uint32) {
199	pkt.Set(key, fmt.Sprintf("%d == 0x%04x", value, value))
200}
201
202// SetBytes sets a []byte value, displaying the hex encoding of the bytes
203func (pkt *Packet) SetBytes(key string, value []byte) {
204	pkt.Set(key, hex.EncodeToString(value))
205}
206
207// SetGapString sets a GapString value, displaying the hex encoding and runes encoding (like a hex dump)
208func (pkt *Packet) SetGapString(key string, value gapstring.GapString) {
209	pkt.Set(key, fmt.Sprintf("%s  %s", value.HexString(), value.Runes()))
210}
211
212// Peel octets bytes off of the Payload, returning those bytes
213func (pkt *Packet) Peel(octets int) ([]byte, error) {
214	pllen := pkt.Payload.Length()
215	if octets > pllen {
216		return nil, &ShortError{octets, pllen}
217	}
218	buf := pkt.Payload.Slice(0, octets)
219	if buf.Missing() > 0 {
220		return nil, &MissingError{}
221	}
222
223	pkt.Payload = pkt.Payload.Slice(octets, pkt.Payload.Length())
224	b := buf.Bytes()
225	return b, nil
226}
227
228// AddHeaderField adds a field to the header field description
229func (pkt *Packet) AddHeaderField(order binary.ByteOrder, name string, bits int, value interface{}) {
230	h := headerField{
231		name:  name,
232		bits:  bits,
233		value: value,
234		order: order,
235	}
236	pkt.header = append(pkt.header, h)
237}
238
239// Peel from Payload an unsigned integer of size bits, adding it to the header field list
240func (pkt *Packet) readUint(order binary.ByteOrder, bits int, name string) (interface{}, error) {
241	switch bits {
242	case 8:
243	case 16:
244	case 32:
245	case 64:
246	default:
247		return 0, fmt.Errorf("Weird number of bits: %d", bits)
248	}
249
250	octets := bits >> 3
251	b, err := pkt.Peel(octets)
252	if err != nil {
253		return 0, err
254	}
255
256	var value interface{}
257	switch bits {
258	case 8:
259		value = b[0]
260	case 16:
261		value = order.Uint16(b)
262	case 32:
263		value = order.Uint32(b)
264	case 64:
265		value = order.Uint64(b)
266	}
267	pkt.AddHeaderField(order, name, bits, value)
268
269	return value, nil
270}
271
272// Uint64LE peels off a uint64, little-endian
273func (pkt *Packet) Uint64LE(name string) (uint64, error) {
274	value, err := pkt.readUint(binary.LittleEndian, 64, name)
275	if err != nil {
276		return 0, err
277	}
278	return value.(uint64), err
279}
280
281// Uint32LE peels off a uint32, little-endian
282func (pkt *Packet) Uint32LE(name string) (uint32, error) {
283	value, err := pkt.readUint(binary.LittleEndian, 32, name)
284	if err != nil {
285		return 0, err
286	}
287	return value.(uint32), err
288}
289
290// Uint16LE peels off a uint16, little-endian
291func (pkt *Packet) Uint16LE(name string) (uint16, error) {
292	value, err := pkt.readUint(binary.LittleEndian, 16, name)
293	if err != nil {
294		return 0, err
295	}
296	return value.(uint16), err
297}
298
299// Uint64BE peels off a uint64, big-endian
300func (pkt *Packet) Uint64BE(name string) (uint64, error) {
301	value, err := pkt.readUint(binary.BigEndian, 64, name)
302	if err != nil {
303		return 0, err
304	}
305	return value.(uint64), err
306}
307
308// Uint32BE peels off a uint32, big-endian
309func (pkt *Packet) Uint32BE(name string) (uint32, error) {
310	value, err := pkt.readUint(binary.BigEndian, 32, name)
311	if err != nil {
312		return 0, err
313	}
314	return value.(uint32), err
315}
316
317// Uint16BE peels off a uint16, big-endian
318func (pkt *Packet) Uint16BE(name string) (uint16, error) {
319	value, err := pkt.readUint(binary.BigEndian, 16, name)
320	if err != nil {
321		return 0, err
322	}
323	return value.(uint16), err
324}
325
326// Uint8 peels off a uint8 (aka byte)
327func (pkt *Packet) Uint8(name string) (uint8, error) {
328	value, err := pkt.readUint(binary.BigEndian, 8, name)
329	if err != nil {
330		return 0, err
331	}
332	return value.(uint8), err
333}