diff --git a/base.go b/base.go index 7aa6590..f288d4d 100644 --- a/base.go +++ b/base.go @@ -48,8 +48,12 @@ func (p *Payload) DecodeFromBytes(data []byte, df DecodeFeedback) error { // SerializeTo writes the serialized form of this layer into the // SerializationBuffer, implementing gopacket.SerializableLayer. // See the docs for gopacket.SerializableLayer for more info. -func (p *Payload) SerializeTo(b *SerializeBuffer, opts SerializeOptions) error { - copy(b.PrependBytes(len(*p)), *p) +func (p *Payload) SerializeTo(b SerializeBuffer, opts SerializeOptions) error { + bytes, err := b.PrependBytes(len(*p)) + if err != nil { + return err + } + copy(bytes, *p) return nil } diff --git a/doc.go b/doc.go index d19097a..e82dc8b 100644 --- a/doc.go +++ b/doc.go @@ -311,6 +311,31 @@ create DecodingLayers that are not themselves Layers... see layers.IPv6ExtensionSkipper for an example of this. +Creating Packet Data + +As well as offering the ability to decode packet data, gopacket will allow you +to create packets from scratch, as well. A number of gopacket layers implement +the SerializableLayer interface; these layers can be serialized to a []byte in +the following manner: + + ip := &layers.IPv4{ + SrcIP: net.IP{1, 2, 3, 4}, + DstIP: net.IP{5, 6, 7, 8}, + // etc... + } + buf := gopacket.NewSerializeBuffer() + opts := gopacket.SerializeOptions{} // See SerializeOptions for more details. + err := ip.SerializeTo(&buf, opts) + if err != nil { panic(err) } + fmt.Println(buf.Bytes()) // prints out a byte slice containing the serialized IPv4 layer. + +SerializeTo PREPENDS the given layer onto the SerializeBuffer, and they treat +the current buffer's Bytes() slice as the payload of the serializing layer. +Therefore, you can serialize an entire packet by serializing a set of layers in +reverse order (Payload, then TCP, then IP, then Ethernet, for example). The +SerializeBuffer's SerializeLayers function is a helper that does exactly that. + + A Final Note If you use gopacket, you'll almost definitely want to make sure gopacket/layers diff --git a/layers/decode_test.go b/layers/decode_test.go index 18c55ff..a9a34eb 100644 --- a/layers/decode_test.go +++ b/layers/decode_test.go @@ -121,37 +121,37 @@ func getSerializeLayers() []gopacket.SerializableLayer { func BenchmarkSerializeTcpNoOptions(b *testing.B) { slayers := getSerializeLayers() - var buf gopacket.SerializeBuffer + buf := gopacket.NewSerializeBuffer() opts := gopacket.SerializeOptions{} for i := 0; i < b.N; i++ { - buf.SerializeLayers(opts, slayers...) + gopacket.SerializeLayers(buf, opts, slayers...) } } func BenchmarkSerializeTcpFixLengths(b *testing.B) { slayers := getSerializeLayers() - var buf gopacket.SerializeBuffer + buf := gopacket.NewSerializeBuffer() opts := gopacket.SerializeOptions{FixLengths: true} for i := 0; i < b.N; i++ { - buf.SerializeLayers(opts, slayers...) + gopacket.SerializeLayers(buf, opts, slayers...) } } func BenchmarkSerializeTcpComputeChecksums(b *testing.B) { slayers := getSerializeLayers() - var buf gopacket.SerializeBuffer + buf := gopacket.NewSerializeBuffer() opts := gopacket.SerializeOptions{ComputeChecksums: true} for i := 0; i < b.N; i++ { - buf.SerializeLayers(opts, slayers...) + gopacket.SerializeLayers(buf, opts, slayers...) } } func BenchmarkSerializeTcpFixLengthsComputeChecksums(b *testing.B) { slayers := getSerializeLayers() - var buf gopacket.SerializeBuffer + buf := gopacket.NewSerializeBuffer() opts := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true} for i := 0; i < b.N; i++ { - buf.SerializeLayers(opts, slayers...) + gopacket.SerializeLayers(buf, opts, slayers...) } } @@ -435,8 +435,8 @@ func TestDecodeSimpleTCPPacket(t *testing.T) { gopacket.SerializeOptions{ComputeChecksums: true}, gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}, } { - var buf gopacket.SerializeBuffer - err := buf.SerializeLayers(opts, slayers...) + buf := gopacket.NewSerializeBuffer() + err := gopacket.SerializeLayers(buf, opts, slayers...) if err != nil { t.Errorf("unable to reserialize layers with opts %#v: %v", opts, err) } else if !bytes.Equal(buf.Bytes(), testSimpleTCPPacket) { @@ -474,8 +474,8 @@ func TestDecodeSmallTCPPacketHasEmptyPayload(t *testing.T) { gopacket.SerializeOptions{ComputeChecksums: true}, gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}, } { - var buf gopacket.SerializeBuffer - err := buf.SerializeLayers(opts, slayers...) + buf := gopacket.NewSerializeBuffer() + err := gopacket.SerializeLayers(buf, opts, slayers...) if err != nil { t.Errorf("unable to reserialize layers with opts %#v: %v", opts, err) } else if !bytes.Equal(buf.Bytes(), smallPacket) { diff --git a/layers/dot1q.go b/layers/dot1q.go index 93f3560..f1cccff 100644 --- a/layers/dot1q.go +++ b/layers/dot1q.go @@ -53,8 +53,11 @@ func decodeDot1Q(data []byte, p gopacket.PacketBuilder) error { // SerializeTo writes the serialized form of this layer into the // SerializationBuffer, implementing gopacket.SerializableLayer. // See the docs for gopacket.SerializableLayer for more info. -func (d *Dot1Q) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { - bytes := b.PrependBytes(4) +func (d *Dot1Q) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { + bytes, err := b.PrependBytes(4) + if err != nil { + return err + } if d.VLANIdentifier > 0xFFF { return fmt.Errorf("vlan identifier %v is too high", d.VLANIdentifier) } diff --git a/layers/ethernet.go b/layers/ethernet.go index acaa075..b03ee2d 100644 --- a/layers/ethernet.go +++ b/layers/ethernet.go @@ -60,15 +60,18 @@ func (eth *Ethernet) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) er // SerializeTo writes the serialized form of this layer into the // SerializationBuffer, implementing gopacket.SerializableLayer. // See the docs for gopacket.SerializableLayer for more info. -func (eth *Ethernet) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { - payload := b.Bytes() - bytes := b.PrependBytes(14) +func (eth *Ethernet) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { if len(eth.DstMAC) != 6 { return fmt.Errorf("invalid dst MAC: %v", eth.DstMAC) } if len(eth.SrcMAC) != 6 { return fmt.Errorf("invalid src MAC: %v", eth.SrcMAC) } + payload := b.Bytes() + bytes, err := b.PrependBytes(14) + if err != nil { + return err + } copy(bytes, eth.DstMAC) copy(bytes[6:], eth.SrcMAC) if eth.Length != 0 || eth.EthernetType == EthernetTypeLLC { @@ -87,7 +90,11 @@ func (eth *Ethernet) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.Seri length := len(b.Bytes()) if length < 60 { // Pad out to 60 bytes. - copy(b.AppendBytes(60-length), lotsOfZeros[:]) + padding, err := b.AppendBytes(60 - length) + if err != nil { + return err + } + copy(padding, lotsOfZeros[:]) } return nil } diff --git a/layers/ip4.go b/layers/ip4.go index d71d461..4148c9e 100644 --- a/layers/ip4.go +++ b/layers/ip4.go @@ -74,8 +74,11 @@ func (i IPv4Option) String() string { // SerializeTo writes the serialized form of this layer into the // SerializationBuffer, implementing gopacket.SerializableLayer. -func (ip *IPv4) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { - bytes := b.PrependBytes(20) +func (ip *IPv4) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { + bytes, err := b.PrependBytes(20) + if err != nil { + return err + } bytes[0] = (ip.Version << 4) | ip.IHL bytes[1] = ip.TOS if opts.FixLengths { diff --git a/layers/ip6.go b/layers/ip6.go index 0bfe3b3..3f521fc 100644 --- a/layers/ip6.go +++ b/layers/ip6.go @@ -44,12 +44,15 @@ const ( // SerializeTo writes the serialized form of this layer into the // SerializationBuffer, implementing gopacket.SerializableLayer. // See the docs for gopacket.SerializableLayer for more info. -func (ip6 *IPv6) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { +func (ip6 *IPv6) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { payload := b.Bytes() if ip6.HopByHop != nil { return fmt.Errorf("unable to serialize hopbyhop for now") } - bytes := b.PrependBytes(40) + bytes, err := b.PrependBytes(40) + if err != nil { + return err + } bytes[0] = (ip6.Version << 4) | (ip6.TrafficClass >> 4) bytes[1] = (ip6.TrafficClass << 4) | uint8(ip6.FlowLabel>>16) binary.BigEndian.PutUint16(bytes[2:], uint16(ip6.FlowLabel)) diff --git a/layers/tcp.go b/layers/tcp.go index 02564bf..b3e7944 100644 --- a/layers/tcp.go +++ b/layers/tcp.go @@ -58,7 +58,7 @@ func (t *TCP) LayerType() gopacket.LayerType { return LayerTypeTCP } // SerializeTo writes the serialized form of this layer into the // SerializationBuffer, implementing gopacket.SerializableLayer. // See the docs for gopacket.SerializableLayer for more info. -func (t *TCP) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { +func (t *TCP) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { var optionLength int for _, o := range t.Options { switch o.OptionType { @@ -71,7 +71,10 @@ func (t *TCP) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOp if opts.FixLengths { t.Padding = lotsOfZeros[:optionLength%4] } - bytes := b.PrependBytes(20 + optionLength + len(t.Padding)) + bytes, err := b.PrependBytes(20 + optionLength + len(t.Padding)) + if err != nil { + return err + } binary.BigEndian.PutUint16(bytes, uint16(t.SrcPort)) binary.BigEndian.PutUint16(bytes[2:], uint16(t.DstPort)) binary.BigEndian.PutUint32(bytes[4:], t.Seq) diff --git a/layers/udp.go b/layers/udp.go index 33086af..7738af6 100644 --- a/layers/udp.go +++ b/layers/udp.go @@ -53,9 +53,12 @@ func (udp *UDP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { // SerializeTo writes the serialized form of this layer into the // SerializationBuffer, implementing gopacket.SerializableLayer. // See the docs for gopacket.SerializableLayer for more info. -func (u *UDP) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { +func (u *UDP) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { payload := b.Bytes() - bytes := b.PrependBytes(8) + bytes, err := b.PrependBytes(8) + if err != nil { + return err + } binary.BigEndian.PutUint16(bytes, uint16(u.SrcPort)) binary.BigEndian.PutUint16(bytes[2:], uint16(u.DstPort)) if opts.FixLengths { diff --git a/writer.go b/writer.go index 4ee4ade..58795ad 100644 --- a/writer.go +++ b/writer.go @@ -33,7 +33,7 @@ type SerializableLayer interface { // SerializeTo calls SHOULD entirely ignore LayerContents and // LayerPayload. It just serializes based on struct fields, neither // modifying nor using contents/payload. - SerializeTo(b *SerializeBuffer, opts SerializeOptions) error + SerializeTo(b SerializeBuffer, opts SerializeOptions) error } // SerializeOptions provides options for behaviors that SerializableLayers may want to @@ -61,12 +61,12 @@ type SerializeOptions struct { // typical writes to byte slices using append(), where we only write at the end // of the buffer. // -// As seen with the Clear method in the example above, a write buffer can be -// reused by clearing it. Note, however, that a Clear call will invalidate the +// It can be reused via Clear. Note, however, that a Clear call will invalidate the // byte slices returned by any previous Bytes() call (the same buffer is -// reused). This means that: +// reused). // -// 1) Reusing a write buffer is extremely fast and should be efficient. +// 1) Reusing a write buffer is generally much faster than creating a new one, +// and with the default implementation it avoids additional memory allocations. // 2) If a byte slice from a previous Bytes() call will continue to be used, // it's better to create a new SerializeBuffer. // @@ -75,17 +75,45 @@ type SerializeOptions struct { // Prepend/Append calls, then clear, then make the same calls with the same // sizes, the second round (and all future similar rounds) shouldn't allocate // any new memory. -// -// A SerializeBuffer may be used simply by declaring it (its zero value is fully -// functional). Use of NewSerializeBuffer is just an optimization. -type SerializeBuffer struct { +type SerializeBuffer interface { + // Bytes returns the contiguous set of bytes collected so far by Prepend/Append + // calls. The slice returned by Bytes will be modified by future Clear calls, + // so if you're planning on clearing this SerializeBuffer, you may want to copy + // Bytes somewhere safe first. + Bytes() []byte + // PrependBytes returns a set of bytes which prepends the current bytes in this + // buffer. These bytes start in an indeterminate state, so they should be + // overwritten by the caller. The caller must only call PrependBytes if they + // know they're going to immediately overwrite all bytes returned. + PrependBytes(num int) ([]byte, error) + // AppendBytes returns a set of bytes which prepends the current bytes in this + // buffer. These bytes start in an indeterminate state, so they should be + // overwritten by the caller. The caller must only call AppendBytes if they + // know they're going to immediately overwrite all bytes returned. + AppendBytes(num int) ([]byte, error) + // Clear resets the SerializeBuffer to a new, empty buffer. After a call to clear, + // the byte slice returned by any previous call to Bytes() for this buffer + // should be considered invalidated. + Clear() error +} + +type serializeBuffer struct { data []byte start int prepended, appended int } -func NewSerializeBuffer(expectedPrependLength, expectedAppendLength int) *SerializeBuffer { - return &SerializeBuffer{ +// NewSerializeBuffer creates a new instance of the default implementation of +// the SerializeBuffer interface. +func NewSerializeBuffer() SerializeBuffer { + return &serializeBuffer{} +} + +// NewSerializeBufferExpectedSize creates a new buffer for serialization, optimized for an +// expected number of bytes prepended/appended. This tends to decrease the +// number of memory allocations made by the buffer during writes. +func NewSerializeBufferExpectedSize(expectedPrependLength, expectedAppendLength int) SerializeBuffer { + return &serializeBuffer{ data: make([]byte, expectedPrependLength, expectedPrependLength+expectedAppendLength), start: expectedPrependLength, prepended: expectedPrependLength, @@ -93,17 +121,11 @@ func NewSerializeBuffer(expectedPrependLength, expectedAppendLength int) *Serial } } -// Bytes returns the contiguous set of bytes collected so far by Prepend/Append -// calls. -func (w *SerializeBuffer) Bytes() []byte { +func (w *serializeBuffer) Bytes() []byte { return w.data[w.start:] } -// PrependBytes returns a set of bytes which prepends the current bytes in this -// buffer. These bytes start in an indeterminate state, so they should be -// overwritten by the caller. The caller must only call PrependBytes if they -// know they're going to immediately overwrite all bytes returned. -func (w *SerializeBuffer) PrependBytes(num int) []byte { +func (w *serializeBuffer) PrependBytes(num int) ([]byte, error) { if num < 0 { panic("num < 0") } @@ -121,14 +143,10 @@ func (w *SerializeBuffer) PrependBytes(num int) []byte { w.data = newData[:toPrepend+len(w.data)] } w.start -= num - return w.data[w.start : w.start+num] + return w.data[w.start : w.start+num], nil } -// AppendBytes returns a set of bytes which prepends the current bytes in this -// buffer. These bytes start in an indeterminate state, so they should be -// overwritten by the caller. The caller must only call AppendBytes if they -// know they're going to immediately overwrite all bytes returned. -func (w *SerializeBuffer) AppendBytes(num int) []byte { +func (w *serializeBuffer) AppendBytes(num int) ([]byte, error) { if num < 0 { panic("num < 0") } @@ -145,15 +163,13 @@ func (w *SerializeBuffer) AppendBytes(num int) []byte { } // Grow the buffer. We know it'll be under capacity given above. w.data = w.data[:initialLength+num] - return w.data[initialLength:] + return w.data[initialLength:], nil } -// Clear resets the SerializeBuffer to a new, empty buffer. After a call to clear, -// the byte slice returned by any previous call to Bytes() for this buffer -// should be considered invalidated. -func (w *SerializeBuffer) Clear() { +func (w *serializeBuffer) Clear() error { w.start = w.prepended w.data = w.data[:w.start] + return nil } // SerializeLayers clears the given write buffer, then writes all layers into it so @@ -161,14 +177,13 @@ func (w *SerializeBuffer) Clear() { // invalidates all slices previously returned by w.Bytes() // // Example: -// var buf SerializeBuffer -// var opts SerializeOptions -// buf.SerializeLayers([]SerializableLayer{a, b, c}, opts) +// buf := gopacket.NewSerializeBuffer() +// opts := gopacket.SerializeOptions{} +// gopacket.SerializeLayers(buf, []SerializableLayer{a, b, c}, opts) // firstPayload := buf.Bytes() // contains byte representation of a(b(c)) -// buf.SerializeLayers([]SerializableLayer{d, e, f}, opts) -// secondPayload := buf.Bytes() // contains byte representation of d(e(f)). -// firstPayload is now invalidated, since the SerializeLayers call Clears buf. -func (w *SerializeBuffer) SerializeLayers(opts SerializeOptions, layers ...SerializableLayer) error { +// gopacket.SerializeLayers(buf, []SerializableLayer{d, e, f}, opts) +// secondPayload := buf.Bytes() // contains byte representation of d(e(f)). firstPayload is now invalidated, since the SerializeLayers call Clears buf. +func SerializeLayers(w SerializeBuffer, opts SerializeOptions, layers ...SerializableLayer) error { w.Clear() for i := len(layers) - 1; i >= 0; i-- { layer := layers[i] diff --git a/writer_test.go b/writer_test.go index 067cfb8..778c1fe 100644 --- a/writer_test.go +++ b/writer_test.go @@ -6,7 +6,7 @@ import ( ) func TestExponentialSizeIncreasePrepend(t *testing.T) { - var b SerializeBuffer + var b serializeBuffer for i, test := range []struct { prepend, size int }{ @@ -32,7 +32,7 @@ func TestExponentialSizeIncreasePrepend(t *testing.T) { } func TestExponentialSizeIncreaseAppend(t *testing.T) { - var b SerializeBuffer + var b serializeBuffer for i, test := range []struct { appnd, size int }{ @@ -58,19 +58,24 @@ func TestExponentialSizeIncreaseAppend(t *testing.T) { } func ExampleSerializeBuffer() { - var b SerializeBuffer + b := NewSerializeBuffer() fmt.Println("1:", b.Bytes()) - copy(b.PrependBytes(3), []byte{1, 2, 3}) + bytes, _ := b.PrependBytes(3) + copy(bytes, []byte{1, 2, 3}) fmt.Println("2:", b.Bytes()) - copy(b.AppendBytes(2), []byte{4, 5}) + bytes, _ = b.AppendBytes(2) + copy(bytes, []byte{4, 5}) fmt.Println("3:", b.Bytes()) - copy(b.PrependBytes(1), []byte{0}) + bytes, _ = b.PrependBytes(1) + copy(bytes, []byte{0}) fmt.Println("4:", b.Bytes()) - copy(b.AppendBytes(3), []byte{6, 7, 8}) + bytes, _ = b.AppendBytes(3) + copy(bytes, []byte{6, 7, 8}) fmt.Println("5:", b.Bytes()) b.Clear() fmt.Println("6:", b.Bytes()) - copy(b.PrependBytes(2), []byte{9, 9}) + bytes, _ = b.PrependBytes(2) + copy(bytes, []byte{9, 9}) fmt.Println("7:", b.Bytes()) // Output: // 1: []