Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RMCP layer type #653

Merged
merged 7 commits into from May 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions layers/layertypes.go
Expand Up @@ -143,6 +143,7 @@ var (
LayerTypeMLDv2MulticastListenerQuery = gopacket.RegisterLayerType(139, gopacket.LayerTypeMetadata{Name: "MLDv2MulticastListenerQuery", Decoder: gopacket.DecodeFunc(decodeMLDv2MulticastListenerQuery)})
LayerTypeTLS = gopacket.RegisterLayerType(140, gopacket.LayerTypeMetadata{Name: "TLS", Decoder: gopacket.DecodeFunc(decodeTLS)})
LayerTypeModbusTCP = gopacket.RegisterLayerType(141, gopacket.LayerTypeMetadata{Name: "ModbusTCP", Decoder: gopacket.DecodeFunc(decodeModbusTCP)})
LayerTypeRMCP = gopacket.RegisterLayerType(142, gopacket.LayerTypeMetadata{Name: "RMCP", Decoder: gopacket.DecodeFunc(decodeRMCP)})
)

var (
Expand Down
1 change: 1 addition & 0 deletions layers/ports.go
Expand Up @@ -115,6 +115,7 @@ var udpPortLayerType = [65536]gopacket.LayerType{
6081: LayerTypeGeneve,
3784: LayerTypeBFD,
2152: LayerTypeGTPv1U,
623: LayerTypeRMCP,
}

// RegisterUDPPortLayerType creates a new mapping between a UDPPort
Expand Down
162 changes: 162 additions & 0 deletions layers/rmcp.go
@@ -0,0 +1,162 @@
// Copyright 2019 The GoPacket Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file in the root of the source tree.

package layers

// This file implements the ASF-RMCP header specified in section 3.2.2.2 of
// https://www.dmtf.org/sites/default/files/standards/documents/DSP0136.pdf

import (
"fmt"

"github.com/google/gopacket"
)

// RMCPClass is the class of a RMCP layer's payload, e.g. ASF or IPMI. This is a
// 4-bit unsigned int on the wire; all but 6 (ASF), 7 (IPMI) and 8 (OEM-defined)
// are currently reserved.
type RMCPClass uint8

// LayerType returns the payload layer type corresponding to a RMCP class.
func (c RMCPClass) LayerType() gopacket.LayerType {
if lt := rmcpClassLayerTypes[uint8(c)]; lt != 0 {
return lt
}
return gopacket.LayerTypePayload
}

func (c RMCPClass) String() string {
return fmt.Sprintf("%v(%v)", uint8(c), c.LayerType())
}

const (
// RMCPVersion1 identifies RMCP v1.0 in the Version header field. Lower
// values are considered legacy, while higher values are reserved by the
// specification.
RMCPVersion1 uint8 = 0x06

// RMCPNormal indicates a "normal" message, i.e. not an acknowledgement.
RMCPNormal uint8 = 0

// RMCPAck indicates a message is acknowledging a received normal message.
RMCPAck uint8 = 1 << 7

// RMCPClassASF identifies an RMCP message as containing an ASF-RMCP
// payload.
RMCPClassASF RMCPClass = 0x06

// RMCPClassIPMI identifies an RMCP message as containing an IPMI payload.
RMCPClassIPMI RMCPClass = 0x07

// RMCPClassOEM identifies an RMCP message as containing an OEM-defined
// payload.
RMCPClassOEM RMCPClass = 0x08
)

var (
rmcpClassLayerTypes = [16]gopacket.LayerType{
// RMCPClassASF and RMCPClassIPMI are to implement; OEM layer type (8)
// is deliberately not implemented, so we return LayerTypePayload
}
)

// RMCP describes the format of an RMCP header, which forms a UDP payload. See
// section 3.2.2.2.
type RMCP struct {
BaseLayer

// Version identifies the version of the RMCP header. 0x06 indicates RMCP
// v1.0; lower values are legacy, higher values are reserved.
Version uint8

// Sequence is the sequence number assicated with the message. Note that
// this rolls over to 0 after 254, not 255. Seq num 255 indicates the
// receiver must not send an ACK.
Sequence uint8

// Ack indicates whether this packet is an acknowledgement. If it is, the
// payload will be empty.
Ack bool

// Class idicates the structure of the payload. There are only 2^4 valid
// values, however there is no uint4 data type. N.B. the Ack bit has been
// split off into another field. The most significant 4 bits of this field
// will always be 0.
Class RMCPClass
}

// LayerType returns LayerTypeRMCP. It partially satisfies Layer and
// SerializableLayer.
func (*RMCP) LayerType() gopacket.LayerType {
return LayerTypeRMCP
}

// CanDecode returns LayerTypeRMCP. It partially satisfies DecodingLayer.
func (r *RMCP) CanDecode() gopacket.LayerClass {
return r.LayerType()
}

// DecodeFromBytes makes the layer represent the provided bytes. It partially
// satisfies DecodingLayer.
func (r *RMCP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
if len(data) < 4 {
df.SetTruncated()
return fmt.Errorf("invalid RMCP header, length %v less than 4",
len(data))
}

r.BaseLayer.Contents = data[:4]
r.BaseLayer.Payload = data[4:]

r.Version = uint8(data[0])
// 1 byte reserved
r.Sequence = uint8(data[2])
r.Ack = data[3]&RMCPAck != 0
r.Class = RMCPClass(data[3] & 0xF)
return nil
}

// NextLayerType returns the data layer of this RMCP layer. This partially
// satisfies DecodingLayer.
func (r *RMCP) NextLayerType() gopacket.LayerType {
return r.Class.LayerType()
}

// Payload returns the data layer. It partially satisfies ApplicationLayer.
func (r *RMCP) Payload() []byte {
return r.BaseLayer.Payload
}

// SerializeTo writes the serialized fom of this layer into the SerializeBuffer,
// partially satisfying SerializableLayer.
func (r *RMCP) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
// The IPMI v1.5 spec contains a pad byte for frame sizes of certain lengths
// to work around issues in LAN chips. This is no longer necessary as of
// IPMI v2.0 (renamed to "legacy pad") so we do not attempt to add it. The
// same approach is taken by FreeIPMI:
// http://git.savannah.gnu.org/cgit/freeipmi.git/tree/libfreeipmi/interface/ipmi-lan-interface.c?id=b5ffcd38317daf42074458879f4c55ba6804a595#n836
bytes, err := b.PrependBytes(4)
if err != nil {
return err
}
bytes[0] = r.Version
bytes[1] = 0x00
bytes[2] = r.Sequence
bytes[3] = bool2uint8(r.Ack)<<7 | uint8(r.Class) // thanks, BFD layer
return nil
}

// decodeRMCP decodes the byte slice into an RMCP type, and sets the application
// layer to it.
func decodeRMCP(data []byte, p gopacket.PacketBuilder) error {
rmcp := &RMCP{}
err := rmcp.DecodeFromBytes(data, p)
p.AddLayer(rmcp)
p.SetApplicationLayer(rmcp)
if err != nil {
return err
}
return p.NextDecoder(rmcp.NextLayerType())
}
86 changes: 86 additions & 0 deletions layers/rmcp_test.go
@@ -0,0 +1,86 @@
// Copyright 2019 The GoPacket Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file in the root of the source tree.

package layers

import (
"bytes"
"encoding/hex"
"testing"

"github.com/google/gopacket"
)

func RMCPTestDecodeFromBytes(t *testing.T) {
b, err := hex.DecodeString("0600ff06")
if err != nil {
t.Fatalf("Failed to decode RMCP message")
}

rmcp := &RMCP{}
if err := rmcp.DecodeFromBytes(b, gopacket.NilDecodeFeedback); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !bytes.Equal(rmcp.BaseLayer.Payload, []byte{}) {
t.Errorf("payload is %v, want %v", rmcp.BaseLayer.Payload, b)
}
if !bytes.Equal(rmcp.BaseLayer.Contents, b) {
t.Errorf("contents is %v, want %v", rmcp.BaseLayer.Contents, b)
}
if rmcp.Version != RMCPVersion1 {
t.Errorf("version is %v, want %v", rmcp.Version, RMCPVersion1)
}
if rmcp.Sequence != 0xFF {
t.Errorf("sequence is %v, want %v", rmcp.Sequence, 0xFF)
}
if rmcp.Ack {
t.Errorf("ack is true, want false")
}
if rmcp.Class != RMCPClassASF {
t.Errorf("class is %v, want %v", rmcp.Class, RMCPClassASF)
}
}

func serializeRMCP(rmcp *RMCP) ([]byte, error) {
sb := gopacket.NewSerializeBuffer()
err := rmcp.SerializeTo(sb, gopacket.SerializeOptions{})
return sb.Bytes(), err
}

func RMCPTestSerializeTo(t *testing.T) {
table := []struct {
layer *RMCP
want []byte
}{
{
&RMCP{
Version: RMCPVersion1,
Sequence: 1,
Ack: false,
Class: RMCPClassASF,
},
[]byte{0x6, 0x0, 0x1, 0x6},
},
{
&RMCP{
Version: RMCPVersion1,
Sequence: 0xFF,
Ack: true,
Class: RMCPClassIPMI,
},
[]byte{0x6, 0x0, 0xFF, 0x87},
},
}
for _, test := range table {
b, err := serializeRMCP(test.layer)
switch {
case err != nil && test.want != nil:
t.Errorf("serialize %v failed with %v, wanted %v", test.layer,
err, test.want)
case err == nil && !bytes.Equal(b, test.want):
t.Errorf("serialize %v = %v, want %v", test.layer, b, test.want)
}
}
}