Skip to content

Commit

Permalink
Merge pull request #653 from gebn/rmcp
Browse files Browse the repository at this point in the history
Implement RMCP layer type
  • Loading branch information
notti committed May 20, 2019
2 parents 6d3e261 + 05e1be1 commit 1eff9ae
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 0 deletions.
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)
}
}
}

0 comments on commit 1eff9ae

Please sign in to comment.