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 e74a660ab859e00b2d70f66175ddc6c750ffd495..fdb6ea30a1463ad06e0cb44507205eed71d7c75b 100644 --- a/protocols/bgp/server/fsm_established.go +++ b/protocols/bgp/server/fsm_established.go @@ -217,32 +217,59 @@ func (s *establishedState) updates(u *packet.BGPUpdate) { Type: route.BGPPathType, BGPPath: &route.BGPPath{ Source: bnet.IPv4ToUint32(s.fsm.peer.addr), + EBGP: s.fsm.peer.localASN != s.fsm.peer.peerASN, }, } - 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 d28f586d1cc7059cad7b4c2ead5a3394ff1f2777..175d7bf4c8d45d1a24edc7d6bb0e98b0aafc16d8 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 } // Legth get's the length of serialized path @@ -51,6 +52,9 @@ func (b *BGPPath) Compare(c *BGPPath) int8 { return -1 } + // 9.1.2.2. Breaking Ties (Phase 2) + + // a) if c.ASPathLen > b.ASPathLen { return 1 } @@ -59,6 +63,7 @@ func (b *BGPPath) Compare(c *BGPPath) int8 { return -1 } + // b) if c.Origin > b.Origin { return 1 } @@ -67,6 +72,7 @@ func (b *BGPPath) Compare(c *BGPPath) int8 { return -1 } + // c) if c.MED > b.MED { return 1 } @@ -75,6 +81,18 @@ func (b *BGPPath) Compare(c *BGPPath) int8 { return -1 } + // d) + if c.EBGP && !b.EBGP { + return -1 + } + + if !c.EBGP && b.EBGP { + return 1 + } + + // e) TODO: interiour cost (hello IS-IS and OSPF) + + // f) if c.BGPIdentifier < b.BGPIdentifier { return 1 } @@ -83,6 +101,7 @@ func (b *BGPPath) Compare(c *BGPPath) int8 { return -1 } + // g) if c.Source < b.Source { return 1 } @@ -154,6 +173,7 @@ func (b *BGPPath) better(c *BGPPath) bool { return false } +// Print all known information about a route in human readable form func (b *BGPPath) Print() string { origin := "" switch b.Origin { @@ -164,20 +184,29 @@ func (b *BGPPath) Print() string { case 2: origin = "IGP" } + + bgpType := "internal" + if b.EBGP { + bgpType = "external" + } + ret := fmt.Sprintf("\t\tLocal Pref: %d\n", b.LocalPref) ret += fmt.Sprintf("\t\tOrigin: %s\n", origin) ret += fmt.Sprintf("\t\tAS Path: %v\n", b.ASPath) + ret += fmt.Sprintf("\t\tBGP type: %s\n", bgpType) nh := uint32To4Byte(b.NextHop) ret += fmt.Sprintf("\t\tNEXT HOP: %d.%d.%d.%d\n", nh[0], nh[1], nh[2], nh[3]) ret += fmt.Sprintf("\t\tMED: %d\n", b.MED) ret += fmt.Sprintf("\t\tPath ID: %d\n", b.PathIdentifier) - ret += fmt.Sprintf("\t\tSource: %d\n", b.Source) + src := uint32To4Byte(b.Source) + ret += fmt.Sprintf("\t\tSource: %d.%d.%d.%d\n", src[0], src[1], src[2], src[3]) ret += fmt.Sprintf("\t\tCommunities: %v\n", b.Communities) ret += fmt.Sprintf("\t\tLargeCommunities: %v\n", b.LargeCommunities) return ret } +// Prepend the given BGPPath with the given ASN given times func (b *BGPPath) Prepend(asn uint32, times uint16) { if times == 0 { return diff --git a/routingtable/adjRIBOut/adj_rib_out.go b/routingtable/adjRIBOut/adj_rib_out.go index 180e2aeca7d94d92e078528279ca89ff5a572936..e211479b0480a338259a0dccce1a66b060ebcfe1 100644 --- a/routingtable/adjRIBOut/adj_rib_out.go +++ b/routingtable/adjRIBOut/adj_rib_out.go @@ -58,9 +58,6 @@ func (a *AdjRIBOut) AddPath(pfx bnet.Prefix, p *route.Path) error { p = p.Copy() if !a.neighbor.IBGP && !a.neighbor.RouteServerClient { p.BGPPath.Prepend(a.neighbor.LocalASN, 1) - } - - if !a.neighbor.IBGP && !a.neighbor.RouteServerClient { p.BGPPath.NextHop = a.neighbor.LocalAddress } @@ -114,14 +111,19 @@ func (a *AdjRIBOut) RemovePath(pfx bnet.Prefix, p *route.Path) bool { } a.rt.RemovePath(pfx, p) - pathID, err := a.pathIDManager.releasePath(p) - if err != nil { - log.Warningf("Unable to release path: %v", err) - return true + + // If the neighbar has AddPath capabilities, try to find the PathID + if a.neighbor.CapAddPathRX { + pathID, err := a.pathIDManager.releasePath(p) + if err != nil { + log.Warningf("Unable to release path for prefix %s: %v", pfx.String(), err) + return true + } + + p = p.Copy() + p.BGPPath.PathIdentifier = pathID } - p = p.Copy() - p.BGPPath.PathIdentifier = pathID a.removePathFromClients(pfx, p) return true }