// Copyright 2017 Google, Inc. 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 ( "encoding/binary" "errors" "github.com/google/gopacket" ) // BFD Control Packet Format // ------------------------- // The current version of BFD's RFC (RFC 5880) contains the following // diagram for the BFD Control packet format: // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // |Vers | Diag |Sta|P|F|C|A|D|M| Detect Mult | Length | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | My Discriminator | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Your Discriminator | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Desired Min TX Interval | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Required Min RX Interval | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Required Min Echo RX Interval | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // An optional Authentication Section MAY be present: // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Auth Type | Auth Len | Authentication Data... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // // Simple Password Authentication Section Format // --------------------------------------------- // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Auth Type | Auth Len | Auth Key ID | Password... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // // Keyed MD5 and Meticulous Keyed MD5 Authentication Section Format // ---------------------------------------------------------------- // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Auth Type | Auth Len | Auth Key ID | Reserved | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Sequence Number | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Auth Key/Digest... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // // Keyed SHA1 and Meticulous Keyed SHA1 Authentication Section Format // ------------------------------------------------------------------ // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Auth Type | Auth Len | Auth Key ID | Reserved | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Sequence Number | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Auth Key/Hash... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // From https://tools.ietf.org/rfc/rfc5880.txt const bfdMinimumRecordSizeInBytes int = 24 // BFDVersion represents the version as decoded from the BFD control message type BFDVersion uint8 // BFDDiagnostic represents diagnostic infomation about a BFD session type BFDDiagnostic uint8 // constants that define BFDDiagnostic flags const ( BFDDiagnosticNone BFDDiagnostic = 0 // No Diagnostic BFDDiagnosticTimeExpired BFDDiagnostic = 1 // Control Detection Time Expired BFDDiagnosticEchoFailed BFDDiagnostic = 2 // Echo Function Failed BFDDiagnosticNeighborSignalDown BFDDiagnostic = 3 // Neighbor Signaled Session Down BFDDiagnosticForwardPlaneReset BFDDiagnostic = 4 // Forwarding Plane Reset BFDDiagnosticPathDown BFDDiagnostic = 5 // Path Down BFDDiagnosticConcatPathDown BFDDiagnostic = 6 // Concatenated Path Down BFDDiagnosticAdminDown BFDDiagnostic = 7 // Administratively Down BFDDiagnosticRevConcatPathDown BFDDiagnostic = 8 // Reverse Concatenated Path Dow ) // String returns a string version of BFDDiagnostic func (bd BFDDiagnostic) String() string { switch bd { default: return "Unknown" case BFDDiagnosticNone: return "None" case BFDDiagnosticTimeExpired: return "Control Detection Time Expired" case BFDDiagnosticEchoFailed: return "Echo Function Failed" case BFDDiagnosticNeighborSignalDown: return "Neighbor Signaled Session Down" case BFDDiagnosticForwardPlaneReset: return "Forwarding Plane Reset" case BFDDiagnosticPathDown: return "Path Down" case BFDDiagnosticConcatPathDown: return "Concatenated Path Down" case BFDDiagnosticAdminDown: return "Administratively Down" case BFDDiagnosticRevConcatPathDown: return "Reverse Concatenated Path Down" } } // BFDState represents the state of a BFD session type BFDState uint8 // constants that define BFDState const ( BFDStateAdminDown BFDState = 0 BFDStateDown BFDState = 1 BFDStateInit BFDState = 2 BFDStateUp BFDState = 3 ) // String returns a string version of BFDState func (s BFDState) String() string { switch s { default: return "Unknown" case BFDStateAdminDown: return "Admin Down" case BFDStateDown: return "Down" case BFDStateInit: return "Init" case BFDStateUp: return "Up" } } // BFDDetectMultiplier represents the negotiated transmit interval, // multiplied by this value, provides the Detection Time for the // receiving system in Asynchronous mode. type BFDDetectMultiplier uint8 // BFDDiscriminator is a unique, nonzero discriminator value used // to demultiplex multiple BFD sessions between the same pair of systems. type BFDDiscriminator uint32 // BFDTimeInterval represents a time interval in microseconds type BFDTimeInterval uint32 // BFDAuthType represents the authentication used in the BFD session type BFDAuthType uint8 // constants that define the BFDAuthType const ( BFDAuthTypeNone BFDAuthType = 0 // No Auth BFDAuthTypePassword BFDAuthType = 1 // Simple Password BFDAuthTypeKeyedMD5 BFDAuthType = 2 // Keyed MD5 BFDAuthTypeMeticulousKeyedMD5 BFDAuthType = 3 // Meticulous Keyed MD5 BFDAuthTypeKeyedSHA1 BFDAuthType = 4 // Keyed SHA1 BFDAuthTypeMeticulousKeyedSHA1 BFDAuthType = 5 // Meticulous Keyed SHA1 ) // String returns a string version of BFDAuthType func (at BFDAuthType) String() string { switch at { default: return "Unknown" case BFDAuthTypeNone: return "No Authentication" case BFDAuthTypePassword: return "Simple Password" case BFDAuthTypeKeyedMD5: return "Keyed MD5" case BFDAuthTypeMeticulousKeyedMD5: return "Meticulous Keyed MD5" case BFDAuthTypeKeyedSHA1: return "Keyed SHA1" case BFDAuthTypeMeticulousKeyedSHA1: return "Meticulous Keyed SHA1" } } // BFDAuthKeyID represents the authentication key ID in use for // this packet. This allows multiple keys to be active simultaneously. type BFDAuthKeyID uint8 // BFDAuthSequenceNumber represents the sequence number for this packet. // For Keyed Authentication, this value is incremented occasionally. For // Meticulous Keyed Authentication, this value is incremented for each // successive packet transmitted for a session. This provides protection // against replay attacks. type BFDAuthSequenceNumber uint32 // BFDAuthData represents the authentication key or digest type BFDAuthData []byte // BFDAuthHeader represents authentication data used in the BFD session type BFDAuthHeader struct { AuthType BFDAuthType KeyID BFDAuthKeyID SequenceNumber BFDAuthSequenceNumber Data BFDAuthData } // Length returns the data length of the BFDAuthHeader based on the // authentication type func (h *BFDAuthHeader) Length() int { switch h.AuthType { case BFDAuthTypePassword: return 3 + len(h.Data) case BFDAuthTypeKeyedMD5, BFDAuthTypeMeticulousKeyedMD5: return 8 + len(h.Data) case BFDAuthTypeKeyedSHA1, BFDAuthTypeMeticulousKeyedSHA1: return 8 + len(h.Data) default: return 0 } } // BFD represents a BFD control message packet whose payload contains // the control information required to for a BFD session. // // References // ---------- // // Wikipedia's BFD entry: // https://en.wikipedia.org/wiki/Bidirectional_Forwarding_Detection // This is the best place to get an overview of BFD. // // RFC 5880 "Bidirectional Forwarding Detection (BFD)" (2010) // https://tools.ietf.org/html/rfc5880 // This is the original BFD specification. // // RFC 5881 "Bidirectional Forwarding Detection (BFD) for IPv4 and IPv6 (Single Hop)" (2010) // https://tools.ietf.org/html/rfc5881 // Describes the use of the Bidirectional Forwarding Detection (BFD) // protocol over IPv4 and IPv6 for single IP hops. type BFD struct { BaseLayer // Stores the packet bytes and payload bytes. Version BFDVersion // Version of the BFD protocol. Diagnostic BFDDiagnostic // Diagnostic code for last state change State BFDState // Current state Poll bool // Requesting verification Final bool // Responding to a received BFD Control packet that had the Poll (P) bit set. ControlPlaneIndependent bool // BFD implementation does not share fate with its control plane AuthPresent bool // Authentication Section is present and the session is to be authenticated Demand bool // Demand mode is active Multipoint bool // For future point-to-multipoint extensions. Must always be zero DetectMultiplier BFDDetectMultiplier // Detection time multiplier MyDiscriminator BFDDiscriminator // A unique, nonzero discriminator value YourDiscriminator BFDDiscriminator // discriminator received from the remote system. DesiredMinTxInterval BFDTimeInterval // Minimum interval, in microseconds, the local system would like to use when transmitting BFD Control packets RequiredMinRxInterval BFDTimeInterval // Minimum interval, in microseconds, between received BFD Control packets that this system is capable of supporting RequiredMinEchoRxInterval BFDTimeInterval // Minimum interval, in microseconds, between received BFD Echo packets that this system is capable of supporting AuthHeader *BFDAuthHeader // Authentication data, variable length. } // Length returns the data length of a BFD Control message which // changes based on the presence and type of authentication // contained in the message func (d *BFD) Length() int { if d.AuthPresent && (d.AuthHeader != nil) { return bfdMinimumRecordSizeInBytes + d.AuthHeader.Length() } return bfdMinimumRecordSizeInBytes } // LayerType returns the layer type of the BFD object, which is LayerTypeBFD. func (d *BFD) LayerType() gopacket.LayerType { return LayerTypeBFD } // decodeBFD analyses a byte slice and attempts to decode it as a BFD // control packet // // If it succeeds, it loads p with information about the packet and returns nil. // If it fails, it returns an error (non nil). // // This function is employed in layertypes.go to register the BFD layer. func decodeBFD(data []byte, p gopacket.PacketBuilder) error { // Attempt to decode the byte slice. d := &BFD{} err := d.DecodeFromBytes(data, p) if err != nil { return err } // If the decoding worked, add the layer to the packet and set it // as the application layer too, if there isn't already one. p.AddLayer(d) p.SetApplicationLayer(d) return nil } // DecodeFromBytes analyses a byte slice and attempts to decode it as a BFD // control packet. // // Upon succeeds, it loads the BFD object with information about the packet // and returns nil. // Upon failure, it returns an error (non nil). func (d *BFD) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { // If the data block is too short to be a BFD record, then return an error. if len(data) < bfdMinimumRecordSizeInBytes { df.SetTruncated() return errors.New("BFD packet too short") } pLen := uint8(data[3]) if len(data) != int(pLen) { return errors.New("BFD packet length does not match") } // BFD type embeds type BaseLayer which contains two fields: // Contents is supposed to contain the bytes of the data at this level. // Payload is supposed to contain the payload of this level. // Here we set the baselayer to be the bytes of the BFD record. d.BaseLayer = BaseLayer{Contents: data[:len(data)]} // Extract the fields from the block of bytes. // To make sense of this, refer to the packet diagram // above and the section on endian conventions. // The first few fields are all packed into the first 32 bits. Unpack them. d.Version = BFDVersion(((data[0] & 0xE0) >> 5)) d.Diagnostic = BFDDiagnostic(data[0] & 0x1F) data = data[1:] d.State = BFDState((data[0] & 0xC0) >> 6) d.Poll = data[0]&0x20 != 0 d.Final = data[0]&0x10 != 0 d.ControlPlaneIndependent = data[0]&0x08 != 0 d.AuthPresent = data[0]&0x04 != 0 d.Demand = data[0]&0x02 != 0 d.Multipoint = data[0]&0x01 != 0 data = data[1:] data, d.DetectMultiplier = data[1:], BFDDetectMultiplier(data[0]) data, _ = data[1:], uint8(data[0]) // Consume length // The remaining fields can just be copied in big endian order. data, d.MyDiscriminator = data[4:], BFDDiscriminator(binary.BigEndian.Uint32(data[:4])) data, d.YourDiscriminator = data[4:], BFDDiscriminator(binary.BigEndian.Uint32(data[:4])) data, d.DesiredMinTxInterval = data[4:], BFDTimeInterval(binary.BigEndian.Uint32(data[:4])) data, d.RequiredMinRxInterval = data[4:], BFDTimeInterval(binary.BigEndian.Uint32(data[:4])) data, d.RequiredMinEchoRxInterval = data[4:], BFDTimeInterval(binary.BigEndian.Uint32(data[:4])) if d.AuthPresent && (len(data) > 2) { d.AuthHeader = &BFDAuthHeader{} data, d.AuthHeader.AuthType = data[1:], BFDAuthType(data[0]) data, _ = data[1:], uint8(data[0]) // Consume length data, d.AuthHeader.KeyID = data[1:], BFDAuthKeyID(data[0]) switch d.AuthHeader.AuthType { case BFDAuthTypePassword: d.AuthHeader.Data = BFDAuthData(data) case BFDAuthTypeKeyedMD5, BFDAuthTypeMeticulousKeyedMD5: // Skipped reserved byte data, d.AuthHeader.SequenceNumber = data[5:], BFDAuthSequenceNumber(binary.BigEndian.Uint32(data[1:5])) d.AuthHeader.Data = BFDAuthData(data) case BFDAuthTypeKeyedSHA1, BFDAuthTypeMeticulousKeyedSHA1: // Skipped reserved byte data, d.AuthHeader.SequenceNumber = data[5:], BFDAuthSequenceNumber(binary.BigEndian.Uint32(data[1:5])) d.AuthHeader.Data = BFDAuthData(data) } } return nil } // 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 *BFD) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { data, err := b.PrependBytes(bfdMinimumRecordSizeInBytes) if err != nil { return err } // Pack the first few fields into the first 32 bits. data[0] = byte(byte(d.Version<<5) | byte(d.Diagnostic)) h := uint8(0) h |= (uint8(d.State) << 6) h |= (uint8(bool2uint8(d.Poll)) << 5) h |= (uint8(bool2uint8(d.Final)) << 4) h |= (uint8(bool2uint8(d.ControlPlaneIndependent)) << 3) h |= (uint8(bool2uint8(d.AuthPresent)) << 2) h |= (uint8(bool2uint8(d.Demand)) << 1) h |= uint8(bool2uint8(d.Multipoint)) data[1] = byte(h) data[2] = byte(d.DetectMultiplier) data[3] = byte(d.Length()) // The remaining fields can just be copied in big endian order. binary.BigEndian.PutUint32(data[4:], uint32(d.MyDiscriminator)) binary.BigEndian.PutUint32(data[8:], uint32(d.YourDiscriminator)) binary.BigEndian.PutUint32(data[12:], uint32(d.DesiredMinTxInterval)) binary.BigEndian.PutUint32(data[16:], uint32(d.RequiredMinRxInterval)) binary.BigEndian.PutUint32(data[20:], uint32(d.RequiredMinEchoRxInterval)) if d.AuthPresent && (d.AuthHeader != nil) { auth, err := b.AppendBytes(int(d.AuthHeader.Length())) if err != nil { return err } auth[0] = byte(d.AuthHeader.AuthType) auth[1] = byte(d.AuthHeader.Length()) auth[2] = byte(d.AuthHeader.KeyID) switch d.AuthHeader.AuthType { case BFDAuthTypePassword: copy(auth[3:], d.AuthHeader.Data) case BFDAuthTypeKeyedMD5, BFDAuthTypeMeticulousKeyedMD5: auth[3] = byte(0) binary.BigEndian.PutUint32(auth[4:], uint32(d.AuthHeader.SequenceNumber)) copy(auth[8:], d.AuthHeader.Data) case BFDAuthTypeKeyedSHA1, BFDAuthTypeMeticulousKeyedSHA1: auth[3] = byte(0) binary.BigEndian.PutUint32(auth[4:], uint32(d.AuthHeader.SequenceNumber)) copy(auth[8:], d.AuthHeader.Data) } } return nil } // CanDecode returns a set of layers that BFD objects can decode. // As BFD objects can only decide the BFD layer, we can return just that layer. // Apparently a single layer type implements LayerClass. func (d *BFD) CanDecode() gopacket.LayerClass { return LayerTypeBFD } // NextLayerType specifies the next layer that GoPacket should attempt to // analyse after this (BFD) layer. As BFD packets do not contain any payload // bytes, there are no further layers to analyse. func (d *BFD) NextLayerType() gopacket.LayerType { return gopacket.LayerTypeZero } // Payload returns an empty byte slice as BFD packets do not carry a payload func (d *BFD) Payload() []byte { return nil } // bool2uint8 converts a bool to uint8 func bool2uint8(b bool) uint8 { if b { return 1 } return 0 }