diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go index 8be484780ee571432d159e63e7b2d4fcf86d2328..748e36535f049ddb31955e8e86078aef209641cd 100644 --- a/protocols/bgp/packet/path_attributes.go +++ b/protocols/bgp/packet/path_attributes.go @@ -342,6 +342,18 @@ func (pa *PathAttribute) setLength(buf *bytes.Buffer) (int, error) { return bytesRead, nil } +func (pa *PathAttribute) Copy() *PathAttribute { + return &PathAttribute{ + ExtendedLength: pa.ExtendedLength, + Length: pa.Length, + Optional: pa.Optional, + Partial: pa.Partial, + Transitive: pa.Transitive, + TypeCode: pa.TypeCode, + Value: pa.Value, + } +} + // dumpNBytes is used to dump n bytes of buf. This is useful in case an path attributes // length doesn't match a fixed length's attributes length (e.g. ORIGIN is always an octet) func dumpNBytes(buf *bytes.Buffer, n uint16) error { @@ -378,6 +390,8 @@ func (pa *PathAttribute) serialize(buf *bytes.Buffer, opt *Options) uint8 { pathAttrLen = pa.serializeCommunities(buf) case LargeCommunitiesAttr: pathAttrLen = pa.serializeLargeCommunities(buf) + default: + pathAttrLen = pa.serializeUnknownAttribute(buf) } return pathAttrLen @@ -534,6 +548,23 @@ func (pa *PathAttribute) serializeLargeCommunities(buf *bytes.Buffer) uint8 { return length } +func (pa *PathAttribute) serializeUnknownAttribute(buf *bytes.Buffer) uint8 { + attrFlags := uint8(0) + if pa.Optional { + attrFlags = setOptional(attrFlags) + } + attrFlags = setTransitive(attrFlags) + + buf.WriteByte(attrFlags) + buf.WriteByte(pa.TypeCode) + + b := pa.Value.([]byte) + buf.WriteByte(uint8(len(b))) + buf.Write(b) + + return uint8(len(b) + 2) +} + func fourBytesToUint32(address [4]byte) uint32 { return uint32(address[0])<<24 + uint32(address[1])<<16 + uint32(address[2])<<8 + uint32(address[3]) } diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go index 96200f4dc8f355da9b640388b097a3a2a9cc5062..fbf1d4806e127b1b2e4b15255a11e018bd00bbaf 100644 --- a/protocols/bgp/packet/path_attributes_test.go +++ b/protocols/bgp/packet/path_attributes_test.go @@ -1345,6 +1345,41 @@ func TestSerializeCommunities(t *testing.T) { } } +func TestSerializeUnknownAttribute(t *testing.T) { + tests := []struct { + name string + input *PathAttribute + expected []byte + expectedLen uint8 + }{ + { + name: "Arbritary attribute", + input: &PathAttribute{ + TypeCode: 200, + Value: []byte{1, 2, 3, 4}, + Transitive: true, + }, + expected: []byte{ + 64, // Attribute flags + 200, // Type + 4, // Length + 1, 2, 3, 4, // Payload + }, + expectedLen: 6, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + buf := bytes.NewBuffer(nil) + n := test.input.serializeUnknownAttribute(buf) + + assert.Equal(t, test.expectedLen, n) + assert.Equal(t, test.expected, buf.Bytes()) + }) + } +} + func TestSerialize(t *testing.T) { tests := []struct { name string diff --git a/protocols/bgp/server/fsm_established.go b/protocols/bgp/server/fsm_established.go index 3313923e6c7692aa8035d1ab697d3fa6e2e21c13..fdb6ea30a1463ad06e0cb44507205eed71d7c75b 100644 --- a/protocols/bgp/server/fsm_established.go +++ b/protocols/bgp/server/fsm_established.go @@ -221,29 +221,55 @@ func (s *establishedState) updates(u *packet.BGPUpdate) { }, } - for pa := u.PathAttributes; pa != nil; pa = pa.Next { - switch pa.TypeCode { - case packet.OriginAttr: - path.BGPPath.Origin = pa.Value.(uint8) - case packet.LocalPrefAttr: - path.BGPPath.LocalPref = pa.Value.(uint32) - case packet.MEDAttr: - path.BGPPath.MED = pa.Value.(uint32) - case packet.NextHopAttr: - path.BGPPath.NextHop = pa.Value.(uint32) - case packet.ASPathAttr: - path.BGPPath.ASPath = pa.Value.(packet.ASPath) - path.BGPPath.ASPathLen = path.BGPPath.ASPath.Length() - case packet.CommunitiesAttr: - path.BGPPath.Communities = pa.Value.([]uint32) - case packet.LargeCommunitiesAttr: - path.BGPPath.LargeCommunities = pa.Value.([]packet.LargeCommunity) + s.processAttributes(u.PathAttributes, path) + + s.fsm.adjRIBIn.AddPath(pfx, path) + } +} + +func (s *establishedState) processAttributes(attrs *packet.PathAttribute, path *route.Path) { + var currentUnknown *packet.PathAttribute + + for pa := attrs; pa != nil; pa = pa.Next { + switch pa.TypeCode { + case packet.OriginAttr: + path.BGPPath.Origin = pa.Value.(uint8) + case packet.LocalPrefAttr: + path.BGPPath.LocalPref = pa.Value.(uint32) + case packet.MEDAttr: + path.BGPPath.MED = pa.Value.(uint32) + case packet.NextHopAttr: + path.BGPPath.NextHop = pa.Value.(uint32) + case packet.ASPathAttr: + path.BGPPath.ASPath = pa.Value.(packet.ASPath) + path.BGPPath.ASPathLen = path.BGPPath.ASPath.Length() + case packet.CommunitiesAttr: + path.BGPPath.Communities = pa.Value.([]uint32) + case packet.LargeCommunitiesAttr: + path.BGPPath.LargeCommunities = pa.Value.([]packet.LargeCommunity) + default: + currentUnknown = s.processUnknownAttribute(pa, currentUnknown) + if path.BGPPath.UnknownAttributes == nil { + path.BGPPath.UnknownAttributes = currentUnknown } } - s.fsm.adjRIBIn.AddPath(pfx, path) } } +func (s *establishedState) processUnknownAttribute(attr, current *packet.PathAttribute) *packet.PathAttribute { + if !attr.Transitive { + return current + } + + p := attr.Copy() + if current == nil { + return p + } + + current.Next = p + return p +} + func (s *establishedState) keepaliveReceived() (state, string) { if s.fsm.holdTime != 0 { s.fsm.holdTimer.Reset(s.fsm.holdTime) diff --git a/protocols/bgp/server/fsm_established_test.go b/protocols/bgp/server/fsm_established_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4e92e4eede30ed4b0dfedeb1217e5d11962c6252 --- /dev/null +++ b/protocols/bgp/server/fsm_established_test.go @@ -0,0 +1,65 @@ +package server + +import ( + "testing" + + "github.com/bio-routing/bio-rd/protocols/bgp/packet" + "github.com/bio-routing/bio-rd/route" + "github.com/stretchr/testify/assert" +) + +func TestProcessAttribues(t *testing.T) { + unknown3 := &packet.PathAttribute{ + Transitive: true, + TypeCode: 100, + Value: []byte{1, 2, 3, 4}, + Next: nil, + } + + unknown2 := &packet.PathAttribute{ + Transitive: false, + TypeCode: 150, + Value: []byte{20}, + Next: unknown3, + } + + unknown1 := &packet.PathAttribute{ + Transitive: true, + TypeCode: 200, + Value: []byte{5, 6}, + Next: unknown2, + } + + asPath := &packet.PathAttribute{ + Transitive: true, + TypeCode: packet.ASPathAttr, + Value: packet.ASPath{ + packet.ASPathSegment{ + Count: 0, + Type: packet.ASSequence, + ASNs: []uint32{}, + }, + }, + Next: unknown1, + } + + e := &establishedState{} + + p := &route.Path{ + BGPPath: &route.BGPPath{}, + } + e.processAttributes(asPath, p) + + expectedCodes := []uint8{200, 100} + expectedValues := [][]byte{[]byte{5, 6}, []byte{1, 2, 3, 4}} + + i := 0 + for attr := p.BGPPath.UnknownAttributes; attr != nil; attr = attr.Next { + assert.Equal(t, true, attr.Transitive, "Transitive") + assert.Equal(t, expectedCodes[i], attr.TypeCode, "Code") + assert.Equal(t, expectedValues[i], attr.Value, "Value") + i++ + } + + assert.Equal(t, i, 2, "Count") +} diff --git a/protocols/bgp/server/update_helper.go b/protocols/bgp/server/update_helper.go index 50850cb6e3747f95bee8dd802797269bb3d63724..1fe4fdbaf05d6e483047712861874365a3af74a3 100644 --- a/protocols/bgp/server/update_helper.go +++ b/protocols/bgp/server/update_helper.go @@ -34,17 +34,14 @@ func pathAttribues(p *route.Path) (*packet.PathAttribute, error) { nextHop.Next = localPref if p.BGPPath != nil { - err := addOptionalPathAttribues(p, localPref) - - if err != nil { - return nil, err - } + optionals := addOptionalPathAttribues(p, localPref) + optionals.Next = p.BGPPath.UnknownAttributes } return asPath, nil } -func addOptionalPathAttribues(p *route.Path, parent *packet.PathAttribute) error { +func addOptionalPathAttribues(p *route.Path, parent *packet.PathAttribute) *packet.PathAttribute { current := parent if len(p.BGPPath.Communities) > 0 { @@ -65,7 +62,7 @@ func addOptionalPathAttribues(p *route.Path, parent *packet.PathAttribute) error current = largeCommunities } - return nil + return current } type serializeAbleUpdate interface { diff --git a/route/bgp_path.go b/route/bgp_path.go index 509d39844ac25236eef26071052ee5410ec3921f..baac670a2c526fd7777a7dd1d0114b4b0a6399f1 100644 --- a/route/bgp_path.go +++ b/route/bgp_path.go @@ -11,18 +11,19 @@ import ( // BGPPath represents a set of BGP path attributes type BGPPath struct { - PathIdentifier uint32 - NextHop uint32 - LocalPref uint32 - ASPath packet.ASPath - ASPathLen uint16 - Origin uint8 - MED uint32 - EBGP bool - BGPIdentifier uint32 - Source uint32 - Communities []uint32 - LargeCommunities []packet.LargeCommunity + PathIdentifier uint32 + NextHop uint32 + LocalPref uint32 + ASPath packet.ASPath + ASPathLen uint16 + Origin uint8 + MED uint32 + EBGP bool + BGPIdentifier uint32 + Source uint32 + Communities []uint32 + LargeCommunities []packet.LargeCommunity + UnknownAttributes *packet.PathAttribute } // ECMP determines if routes b and c are euqal in terms of ECMP