diff --git a/.travis.yml b/.travis.yml index c67caebe8740917db2ce244bc3eb06726091f5da..3bb9725c0aaec6fb74c58c06a540187b15b21800 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ script: - mkdir -p $HOME/gopath/src/github.com/bio-routing/ - ln -s $TRAVIS_BUILD_DIR $HOME/gopath/src/github.com/bio-routing/bio-rd || true - cp .bazelrc.travis .bazelrc +- bazel test //... - bazel coverage //... - bazel build //vendor/github.com/q3k/goveralls - bazel-bin/vendor/github.com/q3k/goveralls/linux_amd64_stripped/goveralls -coverprofile=$(find bazel-testlogs/ -iname coverage.dat -or -iname baseline_coverage.dat | paste -sd ',') -merge=false diff --git a/README.md b/README.md index 418f544cb659e5e4d174be5beed66f6fb0675c05..1c7e6bd80a9df3c714279ec50a9b5fdbde0fb8da 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # bio-rd +A re-implementation of BGP, IS-IS and OSPF in go. We value respect and robustness! + [](https://travis-ci.org/bio-routing/bio-rd) [](https://coveralls.io/github/bio-routing/bio-rd?branch=master) [](http://goreportcard.com/report/bio-routing/bio-rd) diff --git a/main.go b/main.go index 7220ba4a6c669dd44368ccbc81c16e72c534d0d7..7c5bd7f1ba15450a5679688079307d3e0428e626 100644 --- a/main.go +++ b/main.go @@ -40,9 +40,9 @@ func main() { b.AddPeer(config.Peer{ AdminEnabled: true, - LocalAS: 6695, + LocalAS: 65200, PeerAS: 65300, - PeerAddress: net.IP([]byte{169, 254, 200, 1}), + PeerAddress: net.IP([]byte{172, 17, 0, 3}), LocalAddress: net.IP([]byte{169, 254, 200, 0}), ReconnectInterval: time.Second * 15, HoldTime: time.Second * 90, @@ -59,9 +59,9 @@ func main() { b.AddPeer(config.Peer{ AdminEnabled: true, - LocalAS: 6695, + LocalAS: 65200, PeerAS: 65100, - PeerAddress: net.IP([]byte{169, 254, 100, 0}), + PeerAddress: net.IP([]byte{172, 17, 0, 2}), LocalAddress: net.IP([]byte{169, 254, 100, 1}), ReconnectInterval: time.Second * 15, HoldTime: time.Second * 90, diff --git a/protocols/bgp/packet/BUILD.bazel b/protocols/bgp/packet/BUILD.bazel index 10c8af2338d9c6ce76bb2f37d23355b04d21af69..49f16b7773345b729ff25a1a486a86b1669992ed 100644 --- a/protocols/bgp/packet/BUILD.bazel +++ b/protocols/bgp/packet/BUILD.bazel @@ -3,12 +3,14 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ + "as_path.go", "bgp.go", "community.go", "decoder.go", "encoder.go", "large_community.go", "nlri.go", + "options.go", "parameters.go", "path_attribute_flags.go", "path_attributes.go", diff --git a/protocols/bgp/packet/as_path.go b/protocols/bgp/packet/as_path.go new file mode 100644 index 0000000000000000000000000000000000000000..0cdfc96a1b6f418013ae6a6656f6cc8ecf554686 --- /dev/null +++ b/protocols/bgp/packet/as_path.go @@ -0,0 +1,44 @@ +package packet + +import "fmt" + +type ASPath []ASPathSegment + +type ASPathSegment struct { + Type uint8 + Count uint8 + ASNs []uint32 +} + +func (pa ASPath) String() (ret string) { + for _, p := range pa { + if p.Type == ASSet { + ret += " (" + } + n := len(p.ASNs) + for i, asn := range p.ASNs { + if i < n-1 { + ret += fmt.Sprintf("%d ", asn) + continue + } + ret += fmt.Sprintf("%d", asn) + } + if p.Type == ASSet { + ret += ")" + } + } + + return +} + +func (pa ASPath) Length() (ret uint16) { + for _, p := range pa { + if p.Type == ASSet { + ret++ + continue + } + ret += uint16(len(p.ASNs)) + } + + return +} diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go index 622b46f37b115dd27779a285284d177d75695230..8aabec7f7c729cd23d47ed6abe517742f2583708 100644 --- a/protocols/bgp/packet/bgp.go +++ b/protocols/bgp/packet/bgp.go @@ -92,9 +92,11 @@ const ( UnicastSAFI = 1 CapabilitiesParamType = 2 AddPathCapabilityCode = 69 + ASN4CapabilityCode = 65 AddPathReceive = 1 AddPathSend = 2 AddPathSendReceive = 3 + ASTransASN = 23456 ) type BGPError struct { @@ -119,7 +121,7 @@ type BGPHeader struct { type BGPOpen struct { Version uint8 - AS uint16 + ASN uint16 HoldTime uint16 BGPIdentifier uint32 OptParmLen uint8 @@ -171,13 +173,6 @@ type NLRIAddPath struct { Next *NLRIAddPath } -type ASPath []ASPathSegment -type ASPathSegment struct { - Type uint8 - Count uint8 - ASNs []uint32 -} - type Aggretator struct { Addr uint32 ASN uint16 diff --git a/protocols/bgp/packet/decoder.go b/protocols/bgp/packet/decoder.go index 31583bf1b68257625dfaa3d8b8e40893982d6713..c4d583f2138edfbbfb5b9be33b2a1c11764ee0bd 100644 --- a/protocols/bgp/packet/decoder.go +++ b/protocols/bgp/packet/decoder.go @@ -10,13 +10,13 @@ import ( ) // Decode decodes a BGP message -func Decode(buf *bytes.Buffer) (*BGPMessage, error) { +func Decode(buf *bytes.Buffer, opt *Options) (*BGPMessage, error) { hdr, err := decodeHeader(buf) if err != nil { return nil, fmt.Errorf("Failed to decode header: %v", err) } - body, err := decodeMsgBody(buf, hdr.Type, hdr.Length-MinLen) + body, err := decodeMsgBody(buf, hdr.Type, hdr.Length-MinLen, opt) if err != nil { return nil, fmt.Errorf("Failed to decode message: %v", err) } @@ -27,12 +27,12 @@ func Decode(buf *bytes.Buffer) (*BGPMessage, error) { }, nil } -func decodeMsgBody(buf *bytes.Buffer, msgType uint8, l uint16) (interface{}, error) { +func decodeMsgBody(buf *bytes.Buffer, msgType uint8, l uint16, opt *Options) (interface{}, error) { switch msgType { case OpenMsg: return decodeOpenMsg(buf) case UpdateMsg: - return decodeUpdateMsg(buf, l) + return decodeUpdateMsg(buf, l, opt) case KeepaliveMsg: return nil, nil // Nothing to decode in Keepalive message case NotificationMsg: @@ -41,7 +41,7 @@ func decodeMsgBody(buf *bytes.Buffer, msgType uint8, l uint16) (interface{}, err return nil, fmt.Errorf("Unknown message type: %d", msgType) } -func decodeUpdateMsg(buf *bytes.Buffer, l uint16) (*BGPUpdate, error) { +func decodeUpdateMsg(buf *bytes.Buffer, l uint16, opt *Options) (*BGPUpdate, error) { msg := &BGPUpdate{} err := decode(buf, []interface{}{&msg.WithdrawnRoutesLen}) @@ -59,7 +59,7 @@ func decodeUpdateMsg(buf *bytes.Buffer, l uint16) (*BGPUpdate, error) { return msg, err } - msg.PathAttributes, err = decodePathAttrs(buf, msg.TotalPathAttrLen) + msg.PathAttributes, err = decodePathAttrs(buf, msg.TotalPathAttrLen, opt) if err != nil { return msg, err } @@ -141,7 +141,7 @@ func _decodeOpenMsg(buf *bytes.Buffer) (interface{}, error) { fields := []interface{}{ &msg.Version, - &msg.AS, + &msg.ASN, &msg.HoldTime, &msg.BGPIdentifier, &msg.OptParmLen, @@ -238,6 +238,12 @@ func decodeCapability(buf *bytes.Buffer) (Capability, error) { return cap, fmt.Errorf("Unable to decode add path capability") } cap.Value = addPathCap + case ASN4CapabilityCode: + asn4Cap, err := decodeASN4Capability(buf) + if err != nil { + return cap, fmt.Errorf("Unable to decode 4 octet ASN capability") + } + cap.Value = asn4Cap default: for i := uint8(0); i < cap.Length; i++ { _, err := buf.ReadByte() @@ -266,6 +272,20 @@ func decodeAddPathCapability(buf *bytes.Buffer) (AddPathCapability, error) { return addPathCap, nil } +func decodeASN4Capability(buf *bytes.Buffer) (ASN4Capability, error) { + asn4Cap := ASN4Capability{} + fields := []interface{}{ + &asn4Cap.ASN4, + } + + err := decode(buf, fields) + if err != nil { + return asn4Cap, err + } + + return asn4Cap, nil +} + func validateOpen(msg *BGPOpen) error { if msg.Version != BGP4Version { return BGPError{ diff --git a/protocols/bgp/packet/decoder_test.go b/protocols/bgp/packet/decoder_test.go index a9ae237b9cb94a3e0ec6af0b988bc09f41d3f806..56416186ba1d714a13fb4f248281d3d5f48451dd 100644 --- a/protocols/bgp/packet/decoder_test.go +++ b/protocols/bgp/packet/decoder_test.go @@ -3,6 +3,7 @@ package packet import ( "bytes" "fmt" + "strconv" "testing" "github.com/bio-routing/bio-rd/net" @@ -70,7 +71,7 @@ func BenchmarkDecodeUpdateMsg(b *testing.B) { for i := 0; i < b.N; i++ { buf := bytes.NewBuffer(input) - _, err := decodeUpdateMsg(buf, uint16(len(input))) + _, err := decodeUpdateMsg(buf, uint16(len(input)), &Options{}) if err != nil { fmt.Printf("decodeUpdateMsg failed: %v\n", err) } @@ -156,7 +157,7 @@ func TestDecode(t *testing.T) { }, Body: &BGPOpen{ Version: 4, - AS: 200, + ASN: 200, HoldTime: 15, BGPIdentifier: uint32(169090600), OptParmLen: 0, @@ -184,7 +185,7 @@ func TestDecode(t *testing.T) { }, Body: &BGPOpen{ Version: 4, - AS: 200, + ASN: 200, HoldTime: 15, BGPIdentifier: uint32(100), }, @@ -251,7 +252,7 @@ func TestDecode(t *testing.T) { for _, test := range tests { buf := bytes.NewBuffer(test.input) - msg, err := Decode(buf) + msg, err := Decode(buf, &Options{}) if err != nil && !test.wantFail { t.Errorf("Unexpected error in test %d: %v", test.testNum, err) @@ -1369,9 +1370,9 @@ func TestDecodeUpdateMsg(t *testing.T) { }, { // 2 withdraws with four path attributes (Communities + AS4Path +AS4Aggregator + Origin), valid update - testNum: 19, + testNum: 20, input: []byte{0, 5, 8, 10, 16, 192, 168, - 0, 30, // Total Path Attribute Length + 0, 32, // Total Path Attribute Length 0, // Attribute flags 8, // Attribute Type code (Community) @@ -1379,10 +1380,12 @@ func TestDecodeUpdateMsg(t *testing.T) { 0, 0, 1, 0, // Arbitrary Community 0, 0, 1, 1, // Arbitrary Community - 128, // Attribute flags - 17, // Attribute Type code (AS4Path) - 4, // Length - 0, 0, 2, 3, // Arbitrary Bytes + 128, // Attribute flags + 17, // Attribute Type code (AS4Path) + 6, // Length + 2, // AS_SEQUENCE + 1, // Number of ASNs + 0x00, 0x03, 0x17, 0xf3, // 202739 128, // Attribute flags 18, // Attribute Type code (AS4Aggregator) @@ -1406,7 +1409,7 @@ func TestDecodeUpdateMsg(t *testing.T) { Pfxlen: 16, }, }, - TotalPathAttrLen: 30, + TotalPathAttrLen: 32, PathAttributes: &PathAttribute{ Optional: false, Transitive: false, @@ -1420,9 +1423,15 @@ func TestDecodeUpdateMsg(t *testing.T) { Transitive: false, Partial: false, ExtendedLength: false, - Length: 4, + Length: 6, TypeCode: 17, - Value: uint32(515), + Value: ASPath{ + ASPathSegment{ + Type: 2, + Count: 1, + ASNs: []uint32{202739}, + }, + }, Next: &PathAttribute{ Optional: true, Transitive: false, @@ -1447,29 +1456,31 @@ func TestDecodeUpdateMsg(t *testing.T) { }, } + t.Parallel() + for _, test := range tests { - buf := bytes.NewBuffer(test.input) - l := test.explicitLength - if l == 0 { - l = uint16(len(test.input)) - } - msg, err := decodeUpdateMsg(buf, l) + t.Run(strconv.Itoa(test.testNum), func(t *testing.T) { + buf := bytes.NewBuffer(test.input) + l := test.explicitLength + if l == 0 { + l = uint16(len(test.input)) + } + msg, err := decodeUpdateMsg(buf, l, &Options{}) - if err != nil && !test.wantFail { - t.Errorf("Unexpected error in test %d: %v", test.testNum, err) - continue - } + if err != nil && !test.wantFail { + t.Fatalf("Unexpected error in test %d: %v", test.testNum, err) + } - if err == nil && test.wantFail { - t.Errorf("Expected error did not happen in test %d", test.testNum) - continue - } + if err == nil && test.wantFail { + t.Fatalf("Expected error did not happen in test %d", test.testNum) + } - if err != nil && test.wantFail { - continue - } + if err != nil && test.wantFail { + return + } - assert.Equalf(t, test.expected, msg, "%d", test.testNum) + assert.Equalf(t, test.expected, msg, "%d", test.testNum) + }) } } @@ -1490,7 +1501,7 @@ func TestDecodeMsgBody(t *testing.T) { } for _, test := range tests { - res, err := decodeMsgBody(test.buffer, test.msgType, test.length) + res, err := decodeMsgBody(test.buffer, test.msgType, test.length, &Options{}) if test.wantFail && err == nil { t.Errorf("Expected error dit not happen in test %q", test.name) } @@ -1512,7 +1523,7 @@ func TestDecodeOpenMsg(t *testing.T) { wantFail: false, expected: &BGPOpen{ Version: 4, - AS: 257, + ASN: 257, HoldTime: 15, BGPIdentifier: 169090600, OptParmLen: 0, diff --git a/protocols/bgp/packet/encoder.go b/protocols/bgp/packet/encoder.go index ea87d1ff64cfc946688e738ae9f7ffa372cf10c7..ac9bc4483199b81f4e8ecc25c6b91e3ec8361b9c 100644 --- a/protocols/bgp/packet/encoder.go +++ b/protocols/bgp/packet/encoder.go @@ -35,7 +35,7 @@ func SerializeOpenMsg(msg *BGPOpen) []byte { serializeHeader(buf, openLen, OpenMsg) buf.WriteByte(msg.Version) - buf.Write(convert.Uint16Byte(msg.AS)) + buf.Write(convert.Uint16Byte(msg.ASN)) buf.Write(convert.Uint16Byte(msg.HoldTime)) buf.Write(convert.Uint32Byte(msg.BGPIdentifier)) @@ -63,7 +63,7 @@ func serializeHeader(buf *bytes.Buffer, length uint16, typ uint8) { buf.WriteByte(typ) } -func (b *BGPUpdateAddPath) SerializeUpdate() ([]byte, error) { +func (b *BGPUpdateAddPath) SerializeUpdate(opt *Options) ([]byte, error) { budget := MaxLen - MinLen buf := bytes.NewBuffer(nil) @@ -78,7 +78,7 @@ func (b *BGPUpdateAddPath) SerializeUpdate() ([]byte, error) { pathAttributesBuf := bytes.NewBuffer(nil) for pa := b.PathAttributes; pa != nil; pa = pa.Next { - paLen := int(pa.serialize(pathAttributesBuf)) + paLen := int(pa.serialize(pathAttributesBuf, opt)) budget -= paLen if budget < 0 { return nil, fmt.Errorf("update too long") @@ -122,7 +122,7 @@ func (b *BGPUpdateAddPath) SerializeUpdate() ([]byte, error) { return buf.Bytes(), nil } -func (b *BGPUpdate) SerializeUpdate() ([]byte, error) { +func (b *BGPUpdate) SerializeUpdate(opt *Options) ([]byte, error) { budget := MaxLen - MinLen buf := bytes.NewBuffer(nil) @@ -137,7 +137,7 @@ func (b *BGPUpdate) SerializeUpdate() ([]byte, error) { pathAttributesBuf := bytes.NewBuffer(nil) for pa := b.PathAttributes; pa != nil; pa = pa.Next { - paLen := int(pa.serialize(pathAttributesBuf)) + paLen := int(pa.serialize(pathAttributesBuf, opt)) budget -= paLen if budget < 0 { return nil, fmt.Errorf("update too long") diff --git a/protocols/bgp/packet/encoder_test.go b/protocols/bgp/packet/encoder_test.go index 0d4f70a3483dabd8f7332dd148c72b09df626dee..fd7405d735a5d11a4117d144e7086828c86f5628 100644 --- a/protocols/bgp/packet/encoder_test.go +++ b/protocols/bgp/packet/encoder_test.go @@ -70,7 +70,7 @@ func TestSerializeOpenMsg(t *testing.T) { name: "Valid #1", input: &BGPOpen{ Version: 4, - AS: 15169, + ASN: 15169, HoldTime: 120, BGPIdentifier: convert.Uint32([]byte{100, 111, 120, 130}), OptParmLen: 0, diff --git a/protocols/bgp/packet/large_community.go b/protocols/bgp/packet/large_community.go index 5b3f7788b16c921f114880ac36d04e8868938627..4d93eb600bff85f3899e3a3df8246002f903dfb4 100644 --- a/protocols/bgp/packet/large_community.go +++ b/protocols/bgp/packet/large_community.go @@ -12,7 +12,7 @@ type LargeCommunity struct { DataPart2 uint32 } -func (c LargeCommunity) String() string { +func (c *LargeCommunity) String() string { return fmt.Sprintf("(%d,%d,%d)", c.GlobalAdministrator, c.DataPart1, c.DataPart2) } diff --git a/protocols/bgp/packet/options.go b/protocols/bgp/packet/options.go new file mode 100644 index 0000000000000000000000000000000000000000..b2d35bc60cbe17490f94314c28da26de9a19e40a --- /dev/null +++ b/protocols/bgp/packet/options.go @@ -0,0 +1,5 @@ +package packet + +type Options struct { + Supports4OctetASN bool +} diff --git a/protocols/bgp/packet/parameters.go b/protocols/bgp/packet/parameters.go index 8edf42fc58e05bb3b73cacd4272012844bedcd45..0ed9af66fe01f96600c02eeb2c00b69b11edc76c 100644 --- a/protocols/bgp/packet/parameters.go +++ b/protocols/bgp/packet/parameters.go @@ -52,3 +52,11 @@ func (a AddPathCapability) serialize(buf *bytes.Buffer) { buf.WriteByte(a.SAFI) buf.WriteByte(a.SendReceive) } + +type ASN4Capability struct { + ASN4 uint32 +} + +func (a ASN4Capability) serialize(buf *bytes.Buffer) { + buf.Write(convert.Uint32Byte(a.ASN4)) +} diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go index 3b3192034590bd357b29a13833a2bcaaa632f0cf..8be484780ee571432d159e63e7b2d4fcf86d2328 100644 --- a/protocols/bgp/packet/path_attributes.go +++ b/protocols/bgp/packet/path_attributes.go @@ -3,13 +3,11 @@ package packet import ( "bytes" "fmt" - "strconv" - "strings" "github.com/taktv6/tflow2/convert" ) -func decodePathAttrs(buf *bytes.Buffer, tpal uint16) (*PathAttribute, error) { +func decodePathAttrs(buf *bytes.Buffer, tpal uint16, opt *Options) (*PathAttribute, error) { var ret *PathAttribute var eol *PathAttribute var pa *PathAttribute @@ -18,7 +16,7 @@ func decodePathAttrs(buf *bytes.Buffer, tpal uint16) (*PathAttribute, error) { p := uint16(0) for p < tpal { - pa, consumed, err = decodePathAttr(buf) + pa, consumed, err = decodePathAttr(buf, opt) if err != nil { return nil, fmt.Errorf("Unable to decode path attr: %v", err) } @@ -36,7 +34,7 @@ func decodePathAttrs(buf *bytes.Buffer, tpal uint16) (*PathAttribute, error) { return ret, nil } -func decodePathAttr(buf *bytes.Buffer) (pa *PathAttribute, consumed uint16, err error) { +func decodePathAttr(buf *bytes.Buffer, opt *Options) (pa *PathAttribute, consumed uint16, err error) { pa = &PathAttribute{} err = decodePathAttrFlags(buf, pa) @@ -63,9 +61,18 @@ func decodePathAttr(buf *bytes.Buffer) (pa *PathAttribute, consumed uint16, err return nil, consumed, fmt.Errorf("Failed to decode Origin: %v", err) } case ASPathAttr: - if err := pa.decodeASPath(buf); err != nil { + asnLength := uint8(2) + if opt.Supports4OctetASN { + asnLength = 4 + } + + if err := pa.decodeASPath(buf, asnLength); err != nil { return nil, consumed, fmt.Errorf("Failed to decode AS Path: %v", err) } + case AS4PathAttr: + if err := pa.decodeASPath(buf, 4); err != nil { + return nil, consumed, fmt.Errorf("Failed to decode AS4 Path: %v", err) + } case NextHopAttr: if err := pa.decodeNextHop(buf); err != nil { return nil, consumed, fmt.Errorf("Failed to decode Next-Hop: %v", err) @@ -88,10 +95,6 @@ func decodePathAttr(buf *bytes.Buffer) (pa *PathAttribute, consumed uint16, err if err := pa.decodeCommunities(buf); err != nil { return nil, consumed, fmt.Errorf("Failed to decode Community: %v", err) } - case AS4PathAttr: - if err := pa.decodeAS4Path(buf); err != nil { - return nil, consumed, fmt.Errorf("Failed to skip not supported AS4Path: %v", err) - } case AS4AggregatorAttr: if err := pa.decodeAS4Aggregator(buf); err != nil { return nil, consumed, fmt.Errorf("Failed to skip not supported AS4Aggregator: %v", err) @@ -139,14 +142,11 @@ func (pa *PathAttribute) decodeOrigin(buf *bytes.Buffer) error { return dumpNBytes(buf, pa.Length-p) } -func (pa *PathAttribute) decodeASPath(buf *bytes.Buffer) error { +func (pa *PathAttribute) decodeASPath(buf *bytes.Buffer, asnLength uint8) error { pa.Value = make(ASPath, 0) - p := uint16(0) for p < pa.Length { - segment := ASPathSegment{ - ASNs: make([]uint32, 0), - } + segment := ASPathSegment{} err := decode(buf, []interface{}{&segment.Type, &segment.Count}) if err != nil { @@ -162,23 +162,51 @@ func (pa *PathAttribute) decodeASPath(buf *bytes.Buffer) error { return fmt.Errorf("Invalid AS Path segment length: %d", segment.Count) } + segment.ASNs = make([]uint32, segment.Count) for i := uint8(0); i < segment.Count; i++ { - asn := uint16(0) - - err := decode(buf, []interface{}{&asn}) + asn, err := pa.decodeASN(buf, asnLength) if err != nil { return err } - p += 2 + p += uint16(asnLength) - segment.ASNs = append(segment.ASNs, uint32(asn)) + segment.ASNs[i] = asn } + pa.Value = append(pa.Value.(ASPath), segment) } return nil } +func (pa *PathAttribute) decodeASN(buf *bytes.Buffer, asnSize uint8) (asn uint32, err error) { + if asnSize == 4 { + return pa.decode4ByteASN(buf) + } + + return pa.decode2ByteASN(buf) +} + +func (pa *PathAttribute) decode4ByteASN(buf *bytes.Buffer) (asn uint32, err error) { + asn4 := uint32(0) + err = decode(buf, []interface{}{&asn4}) + if err != nil { + return 0, err + } + + return uint32(asn4), nil +} + +func (pa *PathAttribute) decode2ByteASN(buf *bytes.Buffer) (asn uint32, err error) { + asn4 := uint16(0) + err = decode(buf, []interface{}{&asn4}) + if err != nil { + return 0, err + } + + return uint32(asn4), nil +} + func (pa *PathAttribute) decodeNextHop(buf *bytes.Buffer) error { return pa.decodeUint32(buf, "next hop") } @@ -273,10 +301,6 @@ func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error { return nil } -func (pa *PathAttribute) decodeAS4Path(buf *bytes.Buffer) error { - return pa.decodeUint32(buf, "AS4Path") -} - func (pa *PathAttribute) decodeAS4Aggregator(buf *bytes.Buffer) error { return pa.decodeUint32(buf, "AS4Aggregator") } @@ -318,57 +342,6 @@ func (pa *PathAttribute) setLength(buf *bytes.Buffer) (int, error) { return bytesRead, nil } -func (pa *PathAttribute) ASPathString() (ret string) { - for _, p := range pa.Value.(ASPath) { - if p.Type == ASSet { - ret += " (" - } - n := len(p.ASNs) - for i, asn := range p.ASNs { - if i < n-1 { - ret += fmt.Sprintf("%d ", asn) - continue - } - ret += fmt.Sprintf("%d", asn) - } - if p.Type == ASSet { - ret += ")" - } - } - - return -} - -func (pa *PathAttribute) ASPathLen() (ret uint16) { - for _, p := range pa.Value.(ASPath) { - if p.Type == ASSet { - ret++ - continue - } - ret += uint16(len(p.ASNs)) - } - - return -} - -func (a *PathAttribute) CommunityString() string { - s := "" - for _, com := range a.Value.([]uint32) { - s += CommunityStringForUint32(com) + " " - } - - return strings.TrimRight(s, " ") -} - -func (a *PathAttribute) LargeCommunityString() string { - s := "" - for _, com := range a.Value.([]LargeCommunity) { - s += com.String() + " " - } - - return strings.TrimRight(s, " ") -} - // 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 { @@ -383,14 +356,14 @@ func dumpNBytes(buf *bytes.Buffer, n uint16) error { return nil } -func (pa *PathAttribute) serialize(buf *bytes.Buffer) uint8 { +func (pa *PathAttribute) serialize(buf *bytes.Buffer, opt *Options) uint8 { pathAttrLen := uint8(0) switch pa.TypeCode { case OriginAttr: pathAttrLen = pa.serializeOrigin(buf) case ASPathAttr: - pathAttrLen = pa.serializeASPath(buf) + pathAttrLen = pa.serializeASPath(buf, opt) case NextHopAttr: pathAttrLen = pa.serializeNextHop(buf) case MEDAttr: @@ -421,21 +394,32 @@ func (pa *PathAttribute) serializeOrigin(buf *bytes.Buffer) uint8 { return 4 } -func (pa *PathAttribute) serializeASPath(buf *bytes.Buffer) uint8 { +func (pa *PathAttribute) serializeASPath(buf *bytes.Buffer, opt *Options) uint8 { attrFlags := uint8(0) attrFlags = setTransitive(attrFlags) buf.WriteByte(attrFlags) buf.WriteByte(ASPathAttr) + asnLength := uint8(2) + if opt.Supports4OctetASN { + asnLength = 4 + } + length := uint8(0) segmentsBuf := bytes.NewBuffer(nil) for _, segment := range pa.Value.(ASPath) { segmentsBuf.WriteByte(segment.Type) segmentsBuf.WriteByte(uint8(len(segment.ASNs))) + for _, asn := range segment.ASNs { - segmentsBuf.Write(convert.Uint16Byte(uint16(asn))) + if asnLength == 2 { + segmentsBuf.Write(convert.Uint16Byte(uint16(asn))) + } else { + segmentsBuf.Write(convert.Uint32Byte(asn)) + } } - length += 2 + uint8(len(segment.ASNs))*2 + fmt.Println(segment.ASNs) + length += 2 + uint8(len(segment.ASNs))*asnLength } buf.WriteByte(length) @@ -550,135 +534,6 @@ func (pa *PathAttribute) serializeLargeCommunities(buf *bytes.Buffer) uint8 { return length } -/*func (pa *PathAttribute) PrependASPath(prepend []uint32) { - if pa.TypeCode != ASPathAttr { - return - } - - asPath := pa.Value.(ASPath) - asPathSegementCount := len(asPath) - currentSegment := asPathSegementCount - 1 - - newSegmentNeeded := false - if asPath[asPathSegementCount-1].Type == ASSequence { - newSegmentNeeded = true - } else { - if len(asPath[asPathSegementCount-1].ASNs) >= MaxASNsSegment { - newSegmentNeeded = true - } - } - - for _, asn := range prepend { - if newSegmentNeeded { - segment := ASPathSegment{ - Type: ASSequence, - ASNs: make([]uint32, 0), - }, - } - - asPath[currentSegment].ASNs = append(asPath[currentSegment].ASNs, asn) - if len(asPath[asPathSegementCount-1].ASNs) >= MaxASNsSegment { - newSegmentNeeded = true - } - } - -}*/ - -// ParseASPathStr converts an AS path from string representation info an PathAttribute object -func ParseASPathStr(asPathString string) (*PathAttribute, error) { - asPath := ASPath{} - - currentType := ASSequence - newSegmentNeeded := true - currentSegment := -1 - for _, asn := range strings.Split(asPathString, " ") { - if asn == "" { - continue - } - - if isBeginOfASSet(asn) { - currentType = ASSet - newSegmentNeeded = true - asn = strings.Replace(asn, "(", "", 1) - } - - if newSegmentNeeded { - seg := ASPathSegment{ - Type: uint8(currentType), - ASNs: make([]uint32, 0), - } - asPath = append(asPath, seg) - currentSegment++ - newSegmentNeeded = false - } - - if isEndOfASSset(asn) { - currentType = ASSequence - newSegmentNeeded = true - asn = strings.Replace(asn, ")", "", 1) - } - - numericASN, err := strconv.Atoi(asn) - if err != nil { - return nil, fmt.Errorf("Unable to convert ASN: %v", err) - } - asPath[currentSegment].ASNs = append(asPath[currentSegment].ASNs, uint32(numericASN)) - - if len(asPath[currentSegment].ASNs) == MaxASNsSegment { - newSegmentNeeded = true - } - } - - return &PathAttribute{ - TypeCode: ASPathAttr, - Value: asPath, - }, nil -} - -func LargeCommunityAttributeForString(s string) (*PathAttribute, error) { - strs := strings.Split(s, " ") - coms := make([]LargeCommunity, len(strs)) - - var err error - for i, str := range strs { - coms[i], err = ParseLargeCommunityString(str) - if err != nil { - return nil, err - } - } - - return &PathAttribute{ - TypeCode: LargeCommunitiesAttr, - Value: coms, - }, nil -} - -func CommunityAttributeForString(s string) (*PathAttribute, error) { - strs := strings.Split(s, " ") - coms := make([]uint32, len(strs)) - - var err error - for i, str := range strs { - coms[i], err = ParseCommunityString(str) - if err != nil { - return nil, err - } - } - - return &PathAttribute{ - TypeCode: CommunitiesAttr, - Value: coms, - }, nil -} - -func isBeginOfASSet(asPathPart string) bool { - return strings.Contains(asPathPart, "(") -} - -func isEndOfASSset(asPathPart string) bool { - return strings.Contains(asPathPart, ")") -} - 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 248b731714389096a62e2381a548780beb8318e7..96200f4dc8f355da9b640388b097a3a2a9cc5062 100644 --- a/protocols/bgp/packet/path_attributes_test.go +++ b/protocols/bgp/packet/path_attributes_test.go @@ -50,7 +50,7 @@ func TestDecodePathAttrs(t *testing.T) { } for _, test := range tests { - res, err := decodePathAttrs(bytes.NewBuffer(test.input), uint16(len(test.input))) + res, err := decodePathAttrs(bytes.NewBuffer(test.input), uint16(len(test.input)), &Options{}) if test.wantFail && err == nil { t.Errorf("Expected error did not happen for test %q", test.name) @@ -173,7 +173,7 @@ func TestDecodePathAttr(t *testing.T) { } for _, test := range tests { - res, _, err := decodePathAttr(bytes.NewBuffer(test.input)) + res, _, err := decodePathAttr(bytes.NewBuffer(test.input), &Options{}) if test.wantFail && err == nil { t.Errorf("Expected error did not happen for test %q", test.name) @@ -264,6 +264,7 @@ func TestDecodeASPath(t *testing.T) { input []byte wantFail bool explicitLength uint16 + use4OctetASNs bool expected *PathAttribute }{ { @@ -308,6 +309,28 @@ func TestDecodeASPath(t *testing.T) { }, }, }, + { + name: "32 bit ASNs in AS_PATH", + input: []byte{ + 1, // AS_SEQUENCE + 3, // Path Length + 0, 0, 0, 100, 0, 0, 0, 222, 0, 0, 0, 240, + }, + wantFail: false, + use4OctetASNs: true, + expected: &PathAttribute{ + Length: 14, + Value: ASPath{ + ASPathSegment{ + Type: 1, + Count: 3, + ASNs: []uint32{ + 100, 222, 240, + }, + }, + }, + }, + }, { name: "Empty input", input: []byte{}, @@ -326,28 +349,36 @@ func TestDecodeASPath(t *testing.T) { } for _, test := range tests { - l := uint16(len(test.input)) - if test.explicitLength != 0 { - l = test.explicitLength - } - pa := &PathAttribute{ - Length: l, - } - err := pa.decodeASPath(bytes.NewBuffer(test.input)) + t.Run(test.name, func(t *testing.T) { + l := uint16(len(test.input)) + if test.explicitLength != 0 { + l = test.explicitLength + } + pa := &PathAttribute{ + Length: l, + } - if test.wantFail && err == nil { - t.Errorf("Expected error did not happen for test %q", test.name) - } + asnLength := uint8(2) + if test.use4OctetASNs { + asnLength = 4 + } - if !test.wantFail && err != nil { - t.Errorf("Unexpected failure for test %q: %v", test.name, err) - } + err := pa.decodeASPath(bytes.NewBuffer(test.input), asnLength) - if err != nil { - continue - } + if test.wantFail && err == nil { + t.Errorf("Expected error did not happen for test %q", test.name) + } - assert.Equal(t, test.expected, pa) + if !test.wantFail && err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + } + + if err != nil { + return + } + + assert.Equal(t, test.expected, pa) + }) } } @@ -841,107 +872,6 @@ func TestDecodeUint32(t *testing.T) { } } -func TestASPathString(t *testing.T) { - tests := []struct { - name string - pa *PathAttribute - expected string - }{ - { - name: "Test #1", - pa: &PathAttribute{ - Value: ASPath{ - { - Type: ASSequence, - ASNs: []uint32{10, 20, 30}, - }, - }, - }, - expected: "10 20 30", - }, - { - name: "Test #2", - pa: &PathAttribute{ - Value: ASPath{ - { - Type: ASSequence, - ASNs: []uint32{10, 20, 30}, - }, - { - Type: ASSet, - ASNs: []uint32{200, 300}, - }, - }, - }, - expected: "10 20 30 (200 300)", - }, - } - - for _, test := range tests { - res := test.pa.ASPathString() - assert.Equal(t, test.expected, res) - } -} - -func TestLargeCommunityString(t *testing.T) { - tests := []struct { - name string - pa *PathAttribute - expected string - }{ - { - name: "two attributes", - pa: &PathAttribute{ - Value: []LargeCommunity{ - { - GlobalAdministrator: 1, - DataPart1: 2, - DataPart2: 3, - }, - { - GlobalAdministrator: 4, - DataPart1: 5, - DataPart2: 6, - }, - }, - }, - expected: "(1,2,3) (4,5,6)", - }, - } - - for _, test := range tests { - t.Run(test.name, func(te *testing.T) { - res := test.pa.LargeCommunityString() - assert.Equal(te, test.expected, res) - }) - } -} - -func TestCommunityString(t *testing.T) { - tests := []struct { - name string - pa *PathAttribute - expected string - }{ - { - name: "two attributes", - pa: &PathAttribute{ - Value: []uint32{ - 131080, 16778241, - }, - }, - expected: "(2,8) (256,1025)", - }, - } - - for _, test := range tests { - t.Run(test.name, func(te *testing.T) { - res := test.pa.CommunityString() - assert.Equal(te, test.expected, res) - }) - } -} - func TestSetOptional(t *testing.T) { tests := []struct { name string @@ -1239,6 +1169,7 @@ func TestSerializeASPath(t *testing.T) { input *PathAttribute expected []byte expectedLen uint8 + use32BitASN bool }{ { name: "Test #1", @@ -1265,17 +1196,49 @@ func TestSerializeASPath(t *testing.T) { }, expectedLen: 10, }, + { + name: "32bit ASN", + input: &PathAttribute{ + TypeCode: ASPathAttr, + Value: ASPath{ + { + Type: 2, // Sequence + ASNs: []uint32{ + 100, 200, 210, + }, + }, + }, + }, + expected: []byte{ + 64, // Attribute flags + 2, // Type + 14, // Length + 2, // AS_SEQUENCE + 3, // ASN count + 0, 0, 0, 100, // ASN 100 + 0, 0, 0, 200, // ASN 200 + 0, 0, 0, 210, // ASN 210 + }, + expectedLen: 16, + use32BitASN: true, + }, } + t.Parallel() + for _, test := range tests { - buf := bytes.NewBuffer(nil) - n := test.input.serializeASPath(buf) - if n != test.expectedLen { - t.Errorf("Unexpected length for test %q: %d", test.name, n) - continue - } + t.Run(test.name, func(t *testing.T) { + buf := bytes.NewBuffer(nil) + opt := &Options{ + Supports4OctetASN: test.use32BitASN, + } + n := test.input.serializeASPath(buf, opt) + if n != test.expectedLen { + t.Fatalf("Unexpected length for test %q: %d", test.name, n) + } - assert.Equal(t, test.expected, buf.Bytes()) + assert.Equal(t, test.expected, buf.Bytes()) + }) } } @@ -1561,7 +1524,8 @@ func TestSerialize(t *testing.T) { } for _, test := range tests { - res, err := test.msg.SerializeUpdate() + opt := &Options{} + res, err := test.msg.SerializeUpdate(opt) if err != nil { if test.wantFail { continue @@ -1767,7 +1731,8 @@ func TestSerializeAddPath(t *testing.T) { } for _, test := range tests { - res, err := test.msg.SerializeUpdate() + opt := &Options{} + res, err := test.msg.SerializeUpdate(opt) if err != nil { if test.wantFail { continue @@ -1786,108 +1751,6 @@ func TestSerializeAddPath(t *testing.T) { } } -func TestParseASPathStr(t *testing.T) { - tests := []struct { - name string - input string - wantFail bool - expected *PathAttribute - }{ - { - name: "Empty AS Path", - input: "", - wantFail: false, - expected: &PathAttribute{ - TypeCode: ASPathAttr, - Value: ASPath{}, - }, - }, - { - name: "Simple AS_SEQUENCE", - input: "3320 15169", - wantFail: false, - expected: &PathAttribute{ - TypeCode: ASPathAttr, - Value: ASPath{ - ASPathSegment{ - Type: ASSequence, - ASNs: []uint32{3320, 15169}, - }, - }, - }, - }, - { - name: "AS_SEQUENCE with more than 255 elements", - input: "123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123", - wantFail: false, - expected: &PathAttribute{ - TypeCode: ASPathAttr, - Value: ASPath{ - ASPathSegment{ - Type: ASSequence, - ASNs: []uint32{123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123}, - }, - ASPathSegment{ - Type: ASSequence, - ASNs: []uint32{123, 123, 123, 123, 123}, - }, - }, - }, - }, - { - name: "AS_SET only", - input: "(3320 201701 15169)", - wantFail: false, - expected: &PathAttribute{ - TypeCode: ASPathAttr, - Value: ASPath{ - ASPathSegment{ - Type: ASSet, - ASNs: []uint32{3320, 201701, 15169}, - }, - }, - }, - }, - { - name: "Mixed AS Path", - input: "199714 51324 (3320 201701 15169)", - wantFail: false, - expected: &PathAttribute{ - TypeCode: ASPathAttr, - Value: ASPath{ - ASPathSegment{ - Type: ASSequence, - ASNs: []uint32{199714, 51324}, - }, - ASPathSegment{ - Type: ASSet, - ASNs: []uint32{3320, 201701, 15169}, - }, - }, - }, - }, - } - - for _, test := range tests { - res, err := ParseASPathStr(test.input) - if err != nil { - if test.wantFail { - continue - } - - t.Errorf("Unexpected failure for test %q: %v", test.name, err) - continue - } - - if test.wantFail { - t.Errorf("Unexpected success for test %q", test.name) - continue - } - - assert.Equal(t, test.expected, res) - } -} - func TestFourBytesToUint32(t *testing.T) { tests := []struct { name string diff --git a/protocols/bgp/server/BUILD.bazel b/protocols/bgp/server/BUILD.bazel index 02f664a367f52241763b99d0e46bf462ff5b0f67..183cb1c9a288ac66a0713c1438ebe4baf4c7d8ae 100644 --- a/protocols/bgp/server/BUILD.bazel +++ b/protocols/bgp/server/BUILD.bazel @@ -33,6 +33,7 @@ go_library( "//routingtable/adjRIBIn:go_default_library", "//routingtable/adjRIBOut:go_default_library", "//routingtable/filter:go_default_library", + "//routingtable/locRIB:go_default_library", "//vendor/github.com/sirupsen/logrus:go_default_library", ], ) @@ -40,6 +41,8 @@ go_library( go_test( name = "go_default_test", srcs = [ + "fsm_open_sent_test.go", + "fsm_test.go", "server_test.go", "update_helper_test.go", "withdraw_test.go", @@ -53,6 +56,7 @@ go_test( "//routingtable:go_default_library", "//routingtable/filter:go_default_library", "//routingtable/locRIB:go_default_library", + "//testing:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", ], ) diff --git a/protocols/bgp/server/fsm.go b/protocols/bgp/server/fsm.go index 33e000d1c6010a2e3b976b13bec591a7e0e67545..cf970b40483511c2ecafc1d36597f66117de06ef 100644 --- a/protocols/bgp/server/fsm.go +++ b/protocols/bgp/server/fsm.go @@ -8,6 +8,7 @@ import ( "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/routingtable" + "github.com/bio-routing/bio-rd/routingtable/locRIB" log "github.com/sirupsen/logrus" ) @@ -56,12 +57,14 @@ type FSM struct { capAddPathSend bool capAddPathRecv bool + options *packet.Options + local net.IP ribsInitialized bool adjRIBIn routingtable.RouteTableClient adjRIBOut routingtable.RouteTableClient - rib routingtable.RouteTableClient + rib *locRIB.LocRIB updateSender routingtable.RouteTableClient neighborID uint32 @@ -99,6 +102,7 @@ func newFSM2(peer *peer) *FSM { msgRecvFailCh: make(chan error), stopMsgRecvCh: make(chan struct{}), rib: peer.rib, + options: &packet.Options{}, } } @@ -219,13 +223,7 @@ func (fsm *FSM) resetConnectRetryCounter() { } func (fsm *FSM) sendOpen() error { - msg := packet.SerializeOpenMsg(&packet.BGPOpen{ - Version: BGPVersion, - AS: uint16(fsm.peer.localASN), - HoldTime: uint16(fsm.peer.holdTime / time.Second), - BGPIdentifier: fsm.peer.server.routerID, - OptParams: fsm.peer.optOpenParams, - }) + msg := packet.SerializeOpenMsg(fsm.openMessage()) _, err := fsm.con.Write(msg) if err != nil { @@ -235,6 +233,24 @@ func (fsm *FSM) sendOpen() error { return nil } +func (fsm *FSM) openMessage() *packet.BGPOpen { + return &packet.BGPOpen{ + Version: BGPVersion, + ASN: fsm.local16BitASN(), + HoldTime: uint16(fsm.peer.holdTime / time.Second), + BGPIdentifier: fsm.peer.routerID, + OptParams: fsm.peer.optOpenParams, + } +} + +func (fsm *FSM) local16BitASN() uint16 { + if fsm.peer.localASN > uint32(^uint16(0)) { + return packet.ASTransASN + } + + return uint16(fsm.peer.localASN) +} + func (fsm *FSM) sendNotification(errorCode uint8, errorSubCode uint8) error { msg := packet.SerializeNotificationMsg(&packet.BGPNotification{}) diff --git a/protocols/bgp/server/fsm_established.go b/protocols/bgp/server/fsm_established.go index 80ff7fa7990493186924f8c35f1a90deb0c93e5b..e74a660ab859e00b2d70f66175ddc6c750ffd495 100644 --- a/protocols/bgp/server/fsm_established.go +++ b/protocols/bgp/server/fsm_established.go @@ -55,7 +55,10 @@ func (s establishedState) run() (state, string) { } func (s *establishedState) init() error { - s.fsm.adjRIBIn = adjRIBIn.New(s.fsm.peer.importFilter) + contributingASNs := s.fsm.rib.GetContributingASNs() + + s.fsm.adjRIBIn = adjRIBIn.New(s.fsm.peer.importFilter, contributingASNs) + contributingASNs.Add(s.fsm.peer.localASN) s.fsm.adjRIBIn.Register(s.fsm.rib) host, _, err := net.SplitHostPort(s.fsm.con.LocalAddr().String()) @@ -96,6 +99,7 @@ func (s *establishedState) init() error { } func (s *establishedState) uninit() { + s.fsm.rib.GetContributingASNs().Remove(s.fsm.peer.localASN) s.fsm.adjRIBIn.Unregister(s.fsm.rib) s.fsm.rib.Unregister(s.fsm.adjRIBOut) s.fsm.adjRIBOut.Unregister(s.fsm.updateSender) @@ -154,7 +158,7 @@ func (s *establishedState) keepaliveTimerExpired() (state, string) { } func (s *establishedState) msgReceived(data []byte) (state, string) { - msg, err := packet.Decode(bytes.NewBuffer(data)) + msg, err := packet.Decode(bytes.NewBuffer(data), s.fsm.options) if err != nil { switch bgperr := err.(type) { case packet.BGPError: @@ -167,6 +171,7 @@ func (s *establishedState) msgReceived(data []byte) (state, string) { } switch msg.Header.Type { case packet.NotificationMsg: + fmt.Println(data) return s.notification() case packet.UpdateMsg: return s.update(msg) @@ -226,12 +231,12 @@ func (s *establishedState) updates(u *packet.BGPUpdate) { case packet.NextHopAttr: path.BGPPath.NextHop = pa.Value.(uint32) case packet.ASPathAttr: - path.BGPPath.ASPath = pa.ASPathString() - path.BGPPath.ASPathLen = pa.ASPathLen() + path.BGPPath.ASPath = pa.Value.(packet.ASPath) + path.BGPPath.ASPathLen = path.BGPPath.ASPath.Length() case packet.CommunitiesAttr: - path.BGPPath.Communities = pa.CommunityString() + path.BGPPath.Communities = pa.Value.([]uint32) case packet.LargeCommunitiesAttr: - path.BGPPath.LargeCommunities = pa.LargeCommunityString() + path.BGPPath.LargeCommunities = pa.Value.([]packet.LargeCommunity) } } s.fsm.adjRIBIn.AddPath(pfx, path) diff --git a/protocols/bgp/server/fsm_open_confirm.go b/protocols/bgp/server/fsm_open_confirm.go index d2d25e4f533ec30d9a5d15aec25504d055c45f54..07b8a6152dd64c56055c7cbd0745980e462c3886 100644 --- a/protocols/bgp/server/fsm_open_confirm.go +++ b/protocols/bgp/server/fsm_open_confirm.go @@ -82,7 +82,7 @@ func (s *openConfirmState) keepaliveTimerExpired() (state, string) { } func (s *openConfirmState) msgReceived(data []byte) (state, string) { - msg, err := packet.Decode(bytes.NewBuffer(data)) + msg, err := packet.Decode(bytes.NewBuffer(data), s.fsm.options) if err != nil { switch bgperr := err.(type) { case packet.BGPError: diff --git a/protocols/bgp/server/fsm_open_sent.go b/protocols/bgp/server/fsm_open_sent.go index 63680636d8e0c65afe1dad9ec9d1280c34e9c37d..121ea883a1c4d2de0e9384e275c394608b8665b7 100644 --- a/protocols/bgp/server/fsm_open_sent.go +++ b/protocols/bgp/server/fsm_open_sent.go @@ -10,7 +10,8 @@ import ( ) type openSentState struct { - fsm *FSM + fsm *FSM + peerASNRcvd uint32 } func newOpenSentState(fsm *FSM) *openSentState { @@ -73,7 +74,7 @@ func (s *openSentState) holdTimerExpired() (state, string) { } func (s *openSentState) msgReceived(data []byte) (state, string) { - msg, err := packet.Decode(bytes.NewBuffer(data)) + msg, err := packet.Decode(bytes.NewBuffer(data), s.fsm.options) if err != nil { switch bgperr := err.(type) { case packet.BGPError: @@ -104,6 +105,8 @@ func (s *openSentState) unexpectedMessage() (state, string) { func (s *openSentState) openMsgReceived(msg *packet.BGPMessage) (state, string) { openMsg := msg.Body.(*packet.BGPOpen) + s.peerASNRcvd = uint32(openMsg.ASN) + s.fsm.neighborID = openMsg.BGPIdentifier stopTimer(s.fsm.connectRetryTimer) if s.fsm.peer.collisionHandling(s.fsm) { @@ -114,6 +117,10 @@ func (s *openSentState) openMsgReceived(msg *packet.BGPMessage) (state, string) return s.tcpFailure() } + return s.handleOpenMessage(openMsg) +} + +func (s *openSentState) handleOpenMessage(openMsg *packet.BGPOpen) (state, string) { s.fsm.holdTime = time.Duration(math.Min(float64(s.fsm.peer.holdTime), float64(time.Duration(openMsg.HoldTime)*time.Second))) if s.fsm.holdTime != 0 { if !s.fsm.holdTimer.Reset(s.fsm.holdTime) { @@ -123,7 +130,14 @@ func (s *openSentState) openMsgReceived(msg *packet.BGPMessage) (state, string) s.fsm.keepaliveTimer = time.NewTimer(s.fsm.keepaliveTime) } + s.peerASNRcvd = uint32(openMsg.ASN) s.processOpenOptions(openMsg.OptParams) + + if s.peerASNRcvd != s.fsm.peer.peerASN { + s.fsm.sendNotification(packet.OpenMessageError, packet.BadPeerAS) + return newCeaseState(), fmt.Sprintf("Bad Peer AS %d, expected: %d", s.peerASNRcvd, s.fsm.peer.peerASN) + } + return newOpenConfirmState(s.fsm), "Received OPEN message" } @@ -153,7 +167,8 @@ func (s *openSentState) processCapability(cap packet.Capability) { switch cap.Code { case packet.AddPathCapabilityCode: s.processAddPathCapability(cap.Value.(packet.AddPathCapability)) - + case packet.ASN4CapabilityCode: + s.processASN4Capability(cap.Value.(packet.ASN4Capability)) } } @@ -184,6 +199,14 @@ func (s *openSentState) processAddPathCapability(addPathCap packet.AddPathCapabi } } +func (s *openSentState) processASN4Capability(cap packet.ASN4Capability) { + s.fsm.options.Supports4OctetASN = true + + if s.peerASNRcvd == packet.ASTransASN { + s.peerASNRcvd = cap.ASN4 + } +} + func (s *openSentState) notification(msg *packet.BGPMessage) (state, string) { stopTimer(s.fsm.connectRetryTimer) s.fsm.con.Close() diff --git a/protocols/bgp/server/fsm_open_sent_test.go b/protocols/bgp/server/fsm_open_sent_test.go new file mode 100644 index 0000000000000000000000000000000000000000..fe34eede357c762de2674701132ddfa91bf170b9 --- /dev/null +++ b/protocols/bgp/server/fsm_open_sent_test.go @@ -0,0 +1,89 @@ +package server + +import ( + "testing" + + "github.com/bio-routing/bio-rd/protocols/bgp/packet" + btesting "github.com/bio-routing/bio-rd/testing" + "github.com/stretchr/testify/assert" +) + +func TestOpenMsgReceived(t *testing.T) { + tests := []struct { + asn uint32 + name string + msg packet.BGPOpen + wantsCease bool + }{ + { + name: "valid open message (16bit ASN)", + asn: 12345, + msg: packet.BGPOpen{ + HoldTime: 90, + BGPIdentifier: 1, + Version: 4, + ASN: 12345, + }, + }, + { + name: "valid open message (32bit ASN)", + asn: 202739, + msg: packet.BGPOpen{ + HoldTime: 90, + BGPIdentifier: 1, + Version: 4, + ASN: 23456, + OptParmLen: 1, + OptParams: []packet.OptParam{ + { + Type: packet.CapabilitiesParamType, + Length: 6, + Value: packet.Capabilities{ + packet.Capability{ + Code: packet.ASN4CapabilityCode, + Length: 4, + Value: packet.ASN4Capability{ + ASN4: 202739, + }, + }, + }, + }, + }, + }, + }, + { + name: "open message does not match configured remote ASN", + asn: 12345, + msg: packet.BGPOpen{ + HoldTime: 90, + BGPIdentifier: 1, + Version: 4, + ASN: 54321, + }, + wantsCease: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fsm := newFSM2(&peer{ + peerASN: test.asn, + }) + fsm.con = &btesting.MockConn{} + + s := &openSentState{ + fsm: fsm, + } + + state, _ := s.handleOpenMessage(&test.msg) + + if test.wantsCease { + assert.IsType(t, &ceaseState{}, state, "state") + return + } + + assert.IsType(t, &openConfirmState{}, state, "state") + assert.Equal(t, test.asn, s.peerASNRcvd, "asn") + }) + } +} diff --git a/protocols/bgp/server/fsm_test.go b/protocols/bgp/server/fsm_test.go index 1382af03915ffe0b3d6b8c23377dcc7e29be78e9..a6f6bd7a19e2c571c374df2daa59914d02640a66 100644 --- a/protocols/bgp/server/fsm_test.go +++ b/protocols/bgp/server/fsm_test.go @@ -1,15 +1,15 @@ package server import ( + "net" "sync" "testing" "time" + "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/routingtable/filter" - - "net" - "github.com/bio-routing/bio-rd/routingtable/locRIB" + "github.com/stretchr/testify/assert" ) // TestFSM100Updates emulates receiving 100 BGP updates and withdraws. Checks route counts. @@ -121,3 +121,92 @@ func TestFSM100Updates(t *testing.T) { fsmA.eventCh <- ManualStop wg.Wait() } + +func TestOpenMessage(t *testing.T) { + tests := []struct { + name string + localASN uint32 + holdTime time.Duration + routerID uint32 + expected packet.BGPOpen + }{ + { + name: "16bit ASN", + localASN: 12345, + holdTime: time.Duration(30 * time.Second), + routerID: 1, + expected: packet.BGPOpen{ + ASN: 12345, + BGPIdentifier: 1, + HoldTime: 30, + OptParams: []packet.OptParam{ + packet.OptParam{ + Type: packet.CapabilitiesParamType, + Value: packet.Capabilities{ + packet.Capability{ + Code: 65, + Value: packet.ASN4Capability{ + ASN4: 12345, + }, + }, + }, + }, + }, + Version: 4, + }, + }, + { + name: "32bit ASN", + localASN: 202739, + holdTime: time.Duration(30 * time.Second), + routerID: 1, + expected: packet.BGPOpen{ + ASN: 23456, + BGPIdentifier: 1, + HoldTime: 30, + OptParams: []packet.OptParam{ + packet.OptParam{ + Type: packet.CapabilitiesParamType, + Value: packet.Capabilities{ + packet.Capability{ + Code: 65, + Value: packet.ASN4Capability{ + ASN4: 202739, + }, + }, + }, + }, + }, + Version: 4, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + p := peer{ + localASN: test.localASN, + holdTime: test.holdTime, + routerID: test.routerID, + optOpenParams: []packet.OptParam{ + packet.OptParam{ + Type: packet.CapabilitiesParamType, + Value: packet.Capabilities{ + packet.Capability{ + Code: 65, + Value: packet.ASN4Capability{ + ASN4: test.localASN, + }, + }, + }, + }, + }, + } + + fsm := newFSM2(&p) + msg := fsm.openMessage() + + assert.Equal(t, &test.expected, msg) + }) + } +} diff --git a/protocols/bgp/server/peer.go b/protocols/bgp/server/peer.go index bad268285a156e5d32576af75521f42a7436a603..125393fb8fbfbfb5ed4bb626a5bc0aa352094d82 100644 --- a/protocols/bgp/server/peer.go +++ b/protocols/bgp/server/peer.go @@ -9,6 +9,7 @@ import ( "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/routingtable" "github.com/bio-routing/bio-rd/routingtable/filter" + "github.com/bio-routing/bio-rd/routingtable/locRIB" ) type PeerInfo struct { @@ -27,7 +28,7 @@ type peer struct { fsms []*FSM fsmsMu sync.Mutex - rib routingtable.RouteTableClient + rib *locRIB.LocRIB routerID uint32 addPathSend routingtable.ClientOptions addPathRecv bool @@ -100,7 +101,7 @@ func isEstablishedState(s state) bool { // NewPeer creates a new peer with the given config. If an connection is established, the adjRIBIN of the peer is connected // to the given rib. To actually connect the peer, call Start() on the returned peer. -func newPeer(c config.Peer, rib routingtable.RouteTableClient, server *bgpServer) (*peer, error) { +func newPeer(c config.Peer, rib *locRIB.LocRIB, server *bgpServer) (*peer, error) { if c.LocalAS == 0 { c.LocalAS = server.localASN } @@ -125,24 +126,12 @@ func newPeer(c config.Peer, rib routingtable.RouteTableClient, server *bgpServer caps := make([]packet.Capability, 0) - addPath := uint8(0) - if c.AddPathRecv { - addPath += packet.AddPathReceive - } - if !c.AddPathSend.BestOnly { - addPath += packet.AddPathSend + addPathEnabled, addPathCap := handleAddPathCapability(c) + if addPathEnabled { + caps = append(caps, addPathCap) } - if addPath > 0 { - caps = append(caps, packet.Capability{ - Code: packet.AddPathCapabilityCode, - Value: packet.AddPathCapability{ - AFI: packet.IPv4AFI, - SAFI: packet.UnicastSAFI, - SendReceive: addPath, - }, - }) - } + caps = append(caps, asn4Capability(c)) for _, cap := range caps { p.optOpenParams = append(p.optOpenParams, packet.OptParam{ @@ -154,6 +143,38 @@ func newPeer(c config.Peer, rib routingtable.RouteTableClient, server *bgpServer return p, nil } +func asn4Capability(c config.Peer) packet.Capability { + return packet.Capability{ + Code: packet.ASN4CapabilityCode, + Value: packet.ASN4Capability{ + ASN4: c.LocalAS, + }, + } +} + +func handleAddPathCapability(c config.Peer) (bool, packet.Capability) { + addPath := uint8(0) + if c.AddPathRecv { + addPath += packet.AddPathReceive + } + if !c.AddPathSend.BestOnly { + addPath += packet.AddPathSend + } + + if addPath == 0 { + return false, packet.Capability{} + } + + return true, packet.Capability{ + Code: packet.AddPathCapabilityCode, + Value: packet.AddPathCapability{ + AFI: packet.IPv4AFI, + SAFI: packet.UnicastSAFI, + SendReceive: addPath, + }, + } +} + func filterOrDefault(f *filter.Filter) *filter.Filter { if f != nil { return f diff --git a/protocols/bgp/server/server.go b/protocols/bgp/server/server.go index 61c79de10315e5efa65c27e7da820b3a39aee4df..db6ae24f310cf027114e5e95aee7fee3211aac8b 100644 --- a/protocols/bgp/server/server.go +++ b/protocols/bgp/server/server.go @@ -9,12 +9,11 @@ import ( "github.com/bio-routing/bio-rd/config" "github.com/bio-routing/bio-rd/protocols/bgp/packet" - "github.com/bio-routing/bio-rd/routingtable" + "github.com/bio-routing/bio-rd/routingtable/locRIB" log "github.com/sirupsen/logrus" ) const ( - uint16max = 65535 BGPVersion = 4 ) @@ -29,7 +28,7 @@ type bgpServer struct { type BGPServer interface { RouterID() uint32 Start(*config.Global) error - AddPeer(config.Peer, routingtable.RouteTableClient) error + AddPeer(config.Peer, *locRIB.LocRIB) error GetPeerInfoAll() map[string]PeerInfo } @@ -113,11 +112,7 @@ func (b *bgpServer) incomingConnectionWorker() { } } -func (b *bgpServer) AddPeer(c config.Peer, rib routingtable.RouteTableClient) error { - if c.LocalAS > uint16max || c.PeerAS > uint16max { - return fmt.Errorf("32bit ASNs are not supported yet") - } - +func (b *bgpServer) AddPeer(c config.Peer, rib *locRIB.LocRIB) error { peer, err := newPeer(c, rib, b) if err != nil { return err diff --git a/protocols/bgp/server/server_test.go b/protocols/bgp/server/server_test.go index 86095be8e22e28168e2b66c1a70217969263fe18..baf30a19344cb12c284ec22eb364809d7dc4cd72 100644 --- a/protocols/bgp/server/server_test.go +++ b/protocols/bgp/server/server_test.go @@ -11,23 +11,6 @@ import ( "github.com/bio-routing/bio-rd/routingtable/locRIB" ) -func TestBgpServerConfigCheck(t *testing.T) { - s := NewBgpServer() - - err := s.Start(&config.Global{}) - if err == nil { - t.Fatalf("server with empty config should not start") - } - - err = s.Start(&config.Global{ - LocalASN: 204880, - RouterID: 2137, - }) - if err != nil { - t.Fatalf("server should have started, got err: %v", err) - } -} - func TestBgpServerPeerSnapshot(t *testing.T) { s := NewBgpServer() err := s.Start(&config.Global{ diff --git a/protocols/bgp/server/update_helper.go b/protocols/bgp/server/update_helper.go index e0a24a6e3bbf1369b59b25d04f1f6c3c16930844..50850cb6e3747f95bee8dd802797269bb3d63724 100644 --- a/protocols/bgp/server/update_helper.go +++ b/protocols/bgp/server/update_helper.go @@ -3,7 +3,6 @@ package server import ( "fmt" "io" - "strings" "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/route" @@ -11,22 +10,22 @@ import ( ) func pathAttribues(p *route.Path) (*packet.PathAttribute, error) { - asPathPA, err := packet.ParseASPathStr(strings.TrimRight(p.BGPPath.ASPath, " ")) - if err != nil { - return nil, fmt.Errorf("Unable to parse AS path: %v", err) + asPath := &packet.PathAttribute{ + TypeCode: packet.ASPathAttr, + Value: p.BGPPath.ASPath, } origin := &packet.PathAttribute{ TypeCode: packet.OriginAttr, Value: p.BGPPath.Origin, - Next: asPathPA, } + asPath.Next = origin nextHop := &packet.PathAttribute{ TypeCode: packet.NextHopAttr, Value: p.BGPPath.NextHop, } - asPathPA.Next = nextHop + origin.Next = nextHop localPref := &packet.PathAttribute{ TypeCode: packet.LocalPrefAttr, @@ -42,28 +41,26 @@ func pathAttribues(p *route.Path) (*packet.PathAttribute, error) { } } - return origin, nil + return asPath, nil } func addOptionalPathAttribues(p *route.Path, parent *packet.PathAttribute) error { current := parent if len(p.BGPPath.Communities) > 0 { - communities, err := packet.CommunityAttributeForString(p.BGPPath.Communities) - if err != nil { - return fmt.Errorf("Could not create communities attribute: %v", err) + communities := &packet.PathAttribute{ + TypeCode: packet.CommunitiesAttr, + Value: p.BGPPath.Communities, } - current.Next = communities current = communities } if len(p.BGPPath.LargeCommunities) > 0 { - largeCommunities, err := packet.LargeCommunityAttributeForString(p.BGPPath.LargeCommunities) - if err != nil { - return fmt.Errorf("Could not create large communities attribute: %v", err) + largeCommunities := &packet.PathAttribute{ + TypeCode: packet.LargeCommunitiesAttr, + Value: p.BGPPath.LargeCommunities, } - current.Next = largeCommunities current = largeCommunities } @@ -72,11 +69,11 @@ func addOptionalPathAttribues(p *route.Path, parent *packet.PathAttribute) error } type serializeAbleUpdate interface { - SerializeUpdate() ([]byte, error) + SerializeUpdate(opt *packet.Options) ([]byte, error) } -func serializeAndSendUpdate(out io.Writer, update serializeAbleUpdate) error { - updateBytes, err := update.SerializeUpdate() +func serializeAndSendUpdate(out io.Writer, update serializeAbleUpdate, opt *packet.Options) error { + updateBytes, err := update.SerializeUpdate(opt) if err != nil { log.Errorf("Unable to serialize BGP Update: %v", err) return nil diff --git a/protocols/bgp/server/update_helper_test.go b/protocols/bgp/server/update_helper_test.go index 5752edded4aba03b66acd88950796e21c2c82620..d2d0a7fcf424276b6a351176cebc9a3959f9ec69 100644 --- a/protocols/bgp/server/update_helper_test.go +++ b/protocols/bgp/server/update_helper_test.go @@ -16,7 +16,7 @@ import ( type failingUpdate struct{} -func (f *failingUpdate) SerializeUpdate() ([]byte, error) { +func (f *failingUpdate) SerializeUpdate(opt *packet.Options) ([]byte, error) { return nil, errors.New("general error") } @@ -94,12 +94,12 @@ func TestSerializeAndSendUpdate(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := serializeAndSendUpdate(test.buf, test.testUpdate) + opt := &packet.Options{} + err := serializeAndSendUpdate(test.buf, test.testUpdate, opt) assert.Equal(t, test.err, err) assert.Equal(t, test.expected, test.buf.Bytes()) }) - } } diff --git a/protocols/bgp/server/update_sender.go b/protocols/bgp/server/update_sender.go index b389ae117076e0df9e4755ac173a95f02173b28f..23da78c532952a70bce63b8cc9570dba2d9e503e 100644 --- a/protocols/bgp/server/update_sender.go +++ b/protocols/bgp/server/update_sender.go @@ -42,12 +42,12 @@ func (u *UpdateSender) AddPath(pfx net.Prefix, p *route.Path) error { }, } - return serializeAndSendUpdate(u.fsm.con, update) + return serializeAndSendUpdate(u.fsm.con, update, u.fsm.options) } // RemovePath withdraws prefix `pfx` from a peer func (u *UpdateSender) RemovePath(pfx net.Prefix, p *route.Path) bool { - err := withDrawPrefixes(u.fsm.con, pfx) + err := withDrawPrefixes(u.fsm.con, u.fsm.options, pfx) return err == nil } diff --git a/protocols/bgp/server/update_sender_add_path.go b/protocols/bgp/server/update_sender_add_path.go index 2414e14d8fe879c68d6b6e249f6d74b8f1e4e3aa..66d4a594cc6b59852bf206447c59cdb949d25abf 100644 --- a/protocols/bgp/server/update_sender_add_path.go +++ b/protocols/bgp/server/update_sender_add_path.go @@ -39,12 +39,12 @@ func (u *UpdateSenderAddPath) AddPath(pfx net.Prefix, p *route.Path) error { Pfxlen: pfx.Pfxlen(), }, } - return serializeAndSendUpdate(u.fsm.con, update) + return serializeAndSendUpdate(u.fsm.con, update, u.fsm.options) } // RemovePath withdraws prefix `pfx` from a peer func (u *UpdateSenderAddPath) RemovePath(pfx net.Prefix, p *route.Path) bool { - err := withDrawPrefixesAddPath(u.fsm.con, pfx, p) + err := withDrawPrefixesAddPath(u.fsm.con, u.fsm.options, pfx, p) return err == nil } diff --git a/protocols/bgp/server/withdraw.go b/protocols/bgp/server/withdraw.go index 42e97a399cdcf8c208fc29063f7213e4d780ad96..089f9f2c0f7a0e25d1c5d1f2e9f292cdeec74a3c 100644 --- a/protocols/bgp/server/withdraw.go +++ b/protocols/bgp/server/withdraw.go @@ -11,7 +11,7 @@ import ( // withDrawPrefixes generates a BGPUpdate message and write it to the given // io.Writer. -func withDrawPrefixes(out io.Writer, prefixes ...net.Prefix) error { +func withDrawPrefixes(out io.Writer, opt *packet.Options, prefixes ...net.Prefix) error { if len(prefixes) < 1 { return nil } @@ -35,13 +35,13 @@ func withDrawPrefixes(out io.Writer, prefixes ...net.Prefix) error { update := &packet.BGPUpdate{ WithdrawnRoutes: rootNLRI, } - return serializeAndSendUpdate(out, update) + return serializeAndSendUpdate(out, update, opt) } // withDrawPrefixesAddPath generates a BGPUpdateAddPath message and write it to the given // io.Writer. -func withDrawPrefixesAddPath(out io.Writer, pfx net.Prefix, p *route.Path) error { +func withDrawPrefixesAddPath(out io.Writer, opt *packet.Options, pfx net.Prefix, p *route.Path) error { if p.Type != route.BGPPathType { return errors.New("wrong path type, expected BGPPathType") } @@ -55,5 +55,5 @@ func withDrawPrefixesAddPath(out io.Writer, pfx net.Prefix, p *route.Path) error Pfxlen: pfx.Pfxlen(), }, } - return serializeAndSendUpdate(out, update) + return serializeAndSendUpdate(out, update, opt) } diff --git a/protocols/bgp/server/withdraw_test.go b/protocols/bgp/server/withdraw_test.go index 0df94f75259aa446af0d78d1a65aa9ea7a4c67b2..d8831c3eb526d3afdaceb0a3506bf5dca39c2c41 100644 --- a/protocols/bgp/server/withdraw_test.go +++ b/protocols/bgp/server/withdraw_test.go @@ -3,6 +3,8 @@ package server import ( "testing" + "github.com/bio-routing/bio-rd/protocols/bgp/packet" + "errors" "bytes" @@ -52,7 +54,8 @@ func TestWithDrawPrefixes(t *testing.T) { } for _, tc := range testcases { buf := bytes.NewBuffer([]byte{}) - err := withDrawPrefixes(buf, tc.Prefix...) + opt := &packet.Options{} + err := withDrawPrefixes(buf, opt, tc.Prefix...) assert.Equal(t, tc.ExpectedError, err, "error mismatch in testcase %v", tc.Name) assert.Equal(t, tc.Expected, buf.Bytes(), "expected different bytes in testcase %v", tc.Name) } @@ -108,7 +111,8 @@ func TestWithDrawPrefixesAddPath(t *testing.T) { } for _, tc := range testcases { buf := bytes.NewBuffer([]byte{}) - err := withDrawPrefixesAddPath(buf, tc.Prefix, tc.Path) + opt := &packet.Options{} + err := withDrawPrefixesAddPath(buf, opt, tc.Prefix, tc.Path) assert.Equal(t, tc.ExpectedError, err, "error mismatch in testcase %v", tc.Name) assert.Equal(t, tc.Expected, buf.Bytes(), "expected different bytes in testcase %v", tc.Name) } diff --git a/route/BUILD.bazel b/route/BUILD.bazel index 924cd80663f4a23fc0de1b3e5dd33b6f4500eceb..43ece0720b95d36285849f9f80d9d07d2d259b6d 100644 --- a/route/BUILD.bazel +++ b/route/BUILD.bazel @@ -3,7 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ - "bgp.go", + "bgp_path.go", "bgp_path_manager.go", "path.go", "route.go", @@ -13,6 +13,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//net:go_default_library", + "//protocols/bgp/packet:go_default_library", "//vendor/github.com/taktv6/tflow2/convert:go_default_library", ], ) @@ -20,12 +21,15 @@ go_library( go_test( name = "go_default_test", srcs = [ + "bgp_path_test.go", + "bgp_test.go", "path_test.go", "route_test.go", ], embed = [":go_default_library"], deps = [ "//net:go_default_library", + "//protocols/bgp/packet:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", ], ) diff --git a/route/bgp.go b/route/bgp_path.go similarity index 60% rename from route/bgp.go rename to route/bgp_path.go index a63eb2fd396f16b2ae61e6f24d863ea5fbace185..0ab7dc4b2a3f6a23d77be1a0613b68515a5d1604 100644 --- a/route/bgp.go +++ b/route/bgp_path.go @@ -1,10 +1,11 @@ package route import ( + "crypto/sha256" "fmt" - "strconv" "strings" + "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/taktv6/tflow2/convert" ) @@ -13,15 +14,15 @@ type BGPPath struct { PathIdentifier uint32 NextHop uint32 LocalPref uint32 - ASPath string + ASPath packet.ASPath ASPathLen uint16 Origin uint8 MED uint32 EBGP bool BGPIdentifier uint32 Source uint32 - Communities string - LargeCommunities string + Communities []uint32 + LargeCommunities []packet.LargeCommunity } // ECMP determines if routes b and c are euqal in terms of ECMP @@ -154,14 +155,14 @@ func (b *BGPPath) Print() string { } ret := fmt.Sprintf("\t\tLocal Pref: %d\n", b.LocalPref) ret += fmt.Sprintf("\t\tOrigin: %s\n", origin) - ret += fmt.Sprintf("\t\tAS Path: %s\n", b.ASPath) + ret += fmt.Sprintf("\t\tAS Path: %v\n", b.ASPath) 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) - ret += fmt.Sprintf("\t\tCommunities: %s\n", b.Communities) - ret += fmt.Sprintf("\t\tLargeCommunities: %s\n", b.LargeCommunities) + ret += fmt.Sprintf("\t\tCommunities: %v\n", b.Communities) + ret += fmt.Sprintf("\t\tLargeCommunities: %v\n", b.LargeCommunities) return ret } @@ -171,16 +172,40 @@ func (b *BGPPath) Prepend(asn uint32, times uint16) { return } - asnStr := strconv.FormatUint(uint64(asn), 10) + if len(b.ASPath) == 0 { + b.insertNewASSequence() + } - path := make([]string, times+1) - for i := 0; uint16(i) < times; i++ { - path[i] = asnStr + first := b.ASPath[0] + if first.Type == packet.ASSet { + b.insertNewASSequence() } - path[times] = b.ASPath - b.ASPath = strings.TrimSuffix(strings.Join(path, " "), " ") - b.ASPathLen = b.ASPathLen + times + for i := 0; i < int(times); i++ { + if len(b.ASPath) == packet.MaxASNsSegment { + b.insertNewASSequence() + } + + old := b.ASPath[0].ASNs + asns := make([]uint32, len(old)+1) + copy(asns[1:], old) + asns[0] = asn + b.ASPath[0].ASNs = asns + } + + b.ASPathLen = b.ASPath.Length() +} + +func (b *BGPPath) insertNewASSequence() packet.ASPath { + pa := make(packet.ASPath, len(b.ASPath)+1) + copy(pa[1:], b.ASPath) + pa[0] = packet.ASPathSegment{ + ASNs: make([]uint32, 0), + Count: 0, + Type: packet.ASSequence, + } + + return pa } func (p *BGPPath) Copy() *BGPPath { @@ -192,6 +217,44 @@ func (p *BGPPath) Copy() *BGPPath { return &cp } +// ComputeHash computes an hash over all attributes of the path +func (b *BGPPath) ComputeHash() string { + s := fmt.Sprintf("%d\t%d\t%v\t%d\t%d\t%v\t%d\t%d\t%v\t%v\t%d", + b.NextHop, + b.LocalPref, + b.ASPath, + b.Origin, + b.MED, + b.EBGP, + b.BGPIdentifier, + b.Source, + b.Communities, + b.LargeCommunities, + b.PathIdentifier) + + return fmt.Sprintf("%x", sha256.Sum256([]byte(s))) +} + +// CommunitiesString returns the formated communities +func (b *BGPPath) CommunitiesString() string { + str := "" + for _, com := range b.Communities { + str += packet.CommunityStringForUint32(com) + " " + } + + return strings.TrimRight(str, " ") +} + +// LargeCommunitiesString returns the formated communities +func (b *BGPPath) LargeCommunitiesString() string { + str := "" + for _, com := range b.LargeCommunities { + str += com.String() + " " + } + + return strings.TrimRight(str, " ") +} + func uint32To4Byte(addr uint32) [4]byte { slice := convert.Uint32Byte(addr) ret := [4]byte{ diff --git a/route/bgp_path_manager.go b/route/bgp_path_manager.go index f846d3b60e108fc4be97597fabefe4ab46cb9d13..0eec9427911eced59371ea3ebf44a91bd2649342 100644 --- a/route/bgp_path_manager.go +++ b/route/bgp_path_manager.go @@ -7,7 +7,7 @@ import ( // BGPPathManager is a component used to deduplicate BGP Path objects type BGPPathManager struct { - paths map[BGPPath]*BGPPathCounter + paths map[string]*BGPPathCounter mu sync.Mutex } @@ -24,7 +24,7 @@ func NewBGPPathManager() *BGPPathManager { } func (m *BGPPathManager) lookup(p BGPPath) *BGPPath { - pathCounter, ok := m.paths[p] + pathCounter, ok := m.paths[p.ComputeHash()] if !ok { return nil } @@ -37,15 +37,17 @@ func (m *BGPPathManager) AddPath(p BGPPath) *BGPPath { m.mu.Lock() defer m.mu.Unlock() + hash := p.ComputeHash() + q := m.lookup(p) if q == nil { - m.paths[p] = &BGPPathCounter{ + m.paths[hash] = &BGPPathCounter{ path: &p, } } - m.paths[p].usageCount++ - return m.paths[p].path + m.paths[hash].usageCount++ + return m.paths[hash].path } // RemovePath notifies us that there is one user less for path p @@ -58,8 +60,10 @@ func (m *BGPPathManager) RemovePath(p BGPPath) { return } - m.paths[p].usageCount-- - if m.paths[p].usageCount == 0 { - delete(m.paths, p) + hash := p.ComputeHash() + + m.paths[hash].usageCount-- + if m.paths[hash].usageCount == 0 { + delete(m.paths, hash) } } diff --git a/route/bgp_path_test.go b/route/bgp_path_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d0f0a9937a7ded1309664ffd31321d5edf844344 --- /dev/null +++ b/route/bgp_path_test.go @@ -0,0 +1,66 @@ +package route + +import ( + "testing" + + "github.com/bio-routing/bio-rd/protocols/bgp/packet" + "github.com/stretchr/testify/assert" +) + +func TestCommunitiesString(t *testing.T) { + tests := []struct { + name string + comms []uint32 + expected string + }{ + { + name: "two attributes", + comms: []uint32{131080, 16778241}, + expected: "(2,8) (256,1025)", + }, + } + + for _, test := range tests { + t.Run(test.name, func(te *testing.T) { + p := &BGPPath{ + Communities: test.comms, + } + + assert.Equal(te, test.expected, p.CommunitiesString()) + }) + } +} + +func TestLargeCommunitiesString(t *testing.T) { + tests := []struct { + name string + comms []packet.LargeCommunity + expected string + }{ + { + name: "two attributes", + comms: []packet.LargeCommunity{ + { + GlobalAdministrator: 1, + DataPart1: 2, + DataPart2: 3, + }, + { + GlobalAdministrator: 4, + DataPart1: 5, + DataPart2: 6, + }, + }, + expected: "(1,2,3) (4,5,6)", + }, + } + + for _, test := range tests { + t.Run(test.name, func(te *testing.T) { + p := &BGPPath{ + LargeCommunities: test.comms, + } + assert.Equal(te, test.expected, p.LargeCommunitiesString()) + }) + } +} diff --git a/route/bgp_test.go b/route/bgp_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2eb017c2a6b33e5174b94cb9d43e4325750e968d --- /dev/null +++ b/route/bgp_test.go @@ -0,0 +1,44 @@ +package route + +import ( + "testing" + + "github.com/bio-routing/bio-rd/protocols/bgp/packet" + + "github.com/stretchr/testify/assert" +) + +func TestComputeHash(t *testing.T) { + p := &BGPPath{ + ASPath: packet.ASPath{ + packet.ASPathSegment{ + ASNs: []uint32{123, 456}, + Count: 2, + Type: packet.ASSequence, + }, + }, + BGPIdentifier: 1, + Communities: []uint32{ + 123, 456, + }, + EBGP: false, + LargeCommunities: []packet.LargeCommunity{ + packet.LargeCommunity{ + DataPart1: 1, + DataPart2: 2, + GlobalAdministrator: 3, + }, + }, + LocalPref: 100, + MED: 1, + NextHop: 100, + PathIdentifier: 5, + Source: 4, + } + + assert.Equal(t, "45e238420552b88043edb8cb402034466b08d53b49f8e0fedc680747014ddeff", p.ComputeHash()) + + p.LocalPref = 150 + + assert.NotEqual(t, "45e238420552b88043edb8cb402034466b08d53b49f8e0fedc680747014ddeff", p.ComputeHash()) +} diff --git a/routingtable/BUILD.bazel b/routingtable/BUILD.bazel index 1c166185a9a502a88d8442ba33f90aac43a22ee1..1344741ba00193ae2cf24e4e93065d83c62985b0 100644 --- a/routingtable/BUILD.bazel +++ b/routingtable/BUILD.bazel @@ -5,6 +5,8 @@ go_library( srcs = [ "client_interface.go", "client_manager.go", + "contributing_asn_list.go", + "mock_client.go", "neighbor.go", "rib_interface.go", "table.go", @@ -17,7 +19,6 @@ go_library( "//net:go_default_library", "//protocols/bgp/packet:go_default_library", "//route:go_default_library", - "//vendor/github.com/sirupsen/logrus:go_default_library", ], ) @@ -25,6 +26,7 @@ go_test( name = "go_default_test", srcs = [ "client_manager_test.go", + "contributing_asn_list_test.go", "table_test.go", "trie_test.go", "update_helper_test.go", @@ -32,6 +34,7 @@ go_test( embed = [":go_default_library"], deps = [ "//net:go_default_library", + "//protocols/bgp/packet:go_default_library", "//route:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", ], diff --git a/routingtable/adjRIBIn/adj_rib_in.go b/routingtable/adjRIBIn/adj_rib_in.go index ac58e1399b3d5321f151153f5d839ce4e1667174..bf3bab1c8a4aa14d4f969429e4c10eae0aa74731 100644 --- a/routingtable/adjRIBIn/adj_rib_in.go +++ b/routingtable/adjRIBIn/adj_rib_in.go @@ -14,16 +14,18 @@ import ( // AdjRIBIn represents an Adjacency RIB In as described in RFC4271 type AdjRIBIn struct { routingtable.ClientManager - rt *routingtable.RoutingTable - mu sync.RWMutex - exportFilter *filter.Filter + rt *routingtable.RoutingTable + mu sync.RWMutex + exportFilter *filter.Filter + contributingASNs *routingtable.ContributingASNs } // New creates a new Adjacency RIB In -func New(exportFilter *filter.Filter) *AdjRIBIn { +func New(exportFilter *filter.Filter, contributingASNs *routingtable.ContributingASNs) *AdjRIBIn { a := &AdjRIBIn{ - rt: routingtable.NewRoutingTable(), - exportFilter: exportFilter, + rt: routingtable.NewRoutingTable(), + exportFilter: exportFilter, + contributingASNs: contributingASNs, } a.ClientManager = routingtable.NewClientManager(a) return a @@ -70,12 +72,29 @@ func (a *AdjRIBIn) AddPath(pfx net.Prefix, p *route.Path) error { return nil } + // Bail out - for all clients for now - if any of our ASNs is within the path + if a.ourASNsInPath(p) { + return nil + } + for _, client := range a.ClientManager.Clients() { client.AddPath(pfx, p) } return nil } +func (a *AdjRIBIn) ourASNsInPath(p *route.Path) bool { + for _, pathSegment := range p.BGPPath.ASPath { + for _, asn := range pathSegment.ASNs { + if a.contributingASNs.IsContributingASN(asn) { + return true + } + } + } + + return false +} + // RemovePath removes the path for prefix `pfx` func (a *AdjRIBIn) RemovePath(pfx net.Prefix, p *route.Path) bool { a.mu.Lock() diff --git a/routingtable/adjRIBIn/adj_rib_in_test.go b/routingtable/adjRIBIn/adj_rib_in_test.go index 48ac1fdb3efbe6c4aac8c0cbbef8371f1dc691af..9939e805d0dc73bd9e9f6555463c343f6ca16bcc 100644 --- a/routingtable/adjRIBIn/adj_rib_in_test.go +++ b/routingtable/adjRIBIn/adj_rib_in_test.go @@ -1,56 +1,16 @@ package adjRIBIn import ( - "fmt" "testing" - "github.com/bio-routing/bio-rd/routingtable/filter" - "github.com/stretchr/testify/assert" "github.com/bio-routing/bio-rd/net" "github.com/bio-routing/bio-rd/route" "github.com/bio-routing/bio-rd/routingtable" + "github.com/bio-routing/bio-rd/routingtable/filter" ) -type RTMockClient struct { - removePathParams struct { - pfx net.Prefix - path *route.Path - } -} - -func NewRTMockClient() *RTMockClient { - return &RTMockClient{} -} - -func (m *RTMockClient) AddPath(pfx net.Prefix, p *route.Path) error { - return nil -} - -func (m *RTMockClient) UpdateNewClient(client routingtable.RouteTableClient) error { - return fmt.Errorf("Not implemented") -} - -func (m *RTMockClient) Register(routingtable.RouteTableClient) { - return -} - -func (m *RTMockClient) RegisterWithOptions(routingtable.RouteTableClient, routingtable.ClientOptions) { - return -} - -func (m *RTMockClient) Unregister(routingtable.RouteTableClient) { - return -} - -// RemovePath removes the path for prefix `pfx` -func (m *RTMockClient) RemovePath(pfx net.Prefix, p *route.Path) bool { - m.removePathParams.pfx = pfx - m.removePathParams.path = p - return true -} - func TestAddPath(t *testing.T) { tests := []struct { name string @@ -115,19 +75,20 @@ func TestAddPath(t *testing.T) { } for _, test := range tests { - adjRIBIn := New(filter.NewAcceptAllFilter()) - mc := NewRTMockClient() + adjRIBIn := New(filter.NewAcceptAllFilter(), routingtable.NewContributingASNs()) + mc := routingtable.NewRTMockClient() adjRIBIn.ClientManager.Register(mc) for _, route := range test.routes { adjRIBIn.AddPath(route.Prefix(), route.Paths()[0]) } - if mc.removePathParams.pfx != test.removePfx { - t.Errorf("Test %q failed: Call to RemovePath did not propagate prefix properly: Got: %s Want: %s", test.name, mc.removePathParams.pfx.String(), test.removePfx.String()) + removePathParams := mc.GetRemovePathParams() + if removePathParams.Pfx != test.removePfx { + t.Errorf("Test %q failed: Call to RemovePath did not propagate prefix properly: Got: %s Want: %s", test.name, removePathParams.Pfx.String(), test.removePfx.String()) } - assert.Equal(t, test.removePath, mc.removePathParams.path) + assert.Equal(t, test.removePath, removePathParams.Path) assert.Equal(t, test.expected, adjRIBIn.rt.Dump()) } } @@ -206,22 +167,24 @@ func TestRemovePath(t *testing.T) { } for _, test := range tests { - adjRIBIn := New(filter.NewAcceptAllFilter()) + adjRIBIn := New(filter.NewAcceptAllFilter(), routingtable.NewContributingASNs()) for _, route := range test.routes { adjRIBIn.AddPath(route.Prefix(), route.Paths()[0]) } - mc := NewRTMockClient() + mc := routingtable.NewRTMockClient() adjRIBIn.ClientManager.Register(mc) adjRIBIn.RemovePath(test.removePfx, test.removePath) if test.wantPropagation { - if mc.removePathParams.pfx != test.removePfx { - t.Errorf("Test %q failed: Call to RemovePath did not propagate prefix properly: Got: %s Want: %s", test.name, mc.removePathParams.pfx.String(), test.removePfx.String()) + removePathParams := mc.GetRemovePathParams() + if removePathParams.Pfx != test.removePfx { + t.Errorf("Test %q failed: Call to RemovePath did not propagate prefix properly: Got: %s Want: %s", test.name, removePathParams.Pfx.String(), test.removePfx.String()) } - assert.Equal(t, test.removePath, mc.removePathParams.path) + assert.Equal(t, test.removePath, removePathParams.Path) } else { - if mc.removePathParams.pfx != net.NewPfx(0, 0) { + removePathParams := mc.GetRemovePathParams() + if removePathParams.Pfx != net.NewPfx(0, 0) { t.Errorf("Test %q failed: Call to RemovePath propagated unexpectedly", test.name) } } diff --git a/routingtable/adjRIBOut/adj_rib_out.go b/routingtable/adjRIBOut/adj_rib_out.go index 1c1e8cef6d3def34e53b8fd2a5cc86c08c8f72b9..180e2aeca7d94d92e078528279ca89ff5a572936 100644 --- a/routingtable/adjRIBOut/adj_rib_out.go +++ b/routingtable/adjRIBOut/adj_rib_out.go @@ -57,7 +57,7 @@ func (a *AdjRIBOut) AddPath(pfx bnet.Prefix, p *route.Path) error { p = p.Copy() if !a.neighbor.IBGP && !a.neighbor.RouteServerClient { - p.BGPPath.ASPath = fmt.Sprintf("%d %s", a.neighbor.LocalASN, p.BGPPath.ASPath) + p.BGPPath.Prepend(a.neighbor.LocalASN, 1) } if !a.neighbor.IBGP && !a.neighbor.RouteServerClient { diff --git a/routingtable/adjRIBOut/path_id_manager.go b/routingtable/adjRIBOut/path_id_manager.go index 377ce9b3057101b6c1561d0124d9802f83c74faf..78054e3ec0294a7ba472e6de3928e2705c5ed536 100644 --- a/routingtable/adjRIBOut/path_id_manager.go +++ b/routingtable/adjRIBOut/path_id_manager.go @@ -11,7 +11,7 @@ var maxUint32 = ^uint32(0) // pathIDManager manages BGP path identifiers for add-path. This is no thread safe (and doesn't need to be). type pathIDManager struct { ids map[uint32]uint64 - idByPath map[route.BGPPath]uint32 + idByPath map[string]uint32 last uint32 used uint32 } @@ -19,13 +19,15 @@ type pathIDManager struct { func newPathIDManager() *pathIDManager { return &pathIDManager{ ids: make(map[uint32]uint64), - idByPath: make(map[route.BGPPath]uint32), + idByPath: make(map[string]uint32), } } func (fm *pathIDManager) addPath(p *route.Path) (uint32, error) { - if _, exists := fm.idByPath[*p.BGPPath]; exists { - id := fm.idByPath[*p.BGPPath] + hash := p.BGPPath.ComputeHash() + + if _, exists := fm.idByPath[hash]; exists { + id := fm.idByPath[hash] fm.ids[id]++ return id, nil } @@ -43,7 +45,7 @@ func (fm *pathIDManager) addPath(p *route.Path) (uint32, error) { break } - fm.idByPath[*p.BGPPath] = fm.last + fm.idByPath[hash] = fm.last fm.ids[fm.last] = 1 fm.used++ @@ -51,16 +53,19 @@ func (fm *pathIDManager) addPath(p *route.Path) (uint32, error) { } func (fm *pathIDManager) releasePath(p *route.Path) (uint32, error) { - if _, exists := fm.idByPath[*p.BGPPath]; !exists { + hash := p.BGPPath.ComputeHash() + + if _, exists := fm.idByPath[hash]; !exists { return 0, fmt.Errorf("ID not found for path: %s", p.Print()) } - id := fm.idByPath[*p.BGPPath] + id := fm.idByPath[hash] fm.ids[id]-- if fm.ids[id] == 0 { - delete(fm.ids, fm.idByPath[*p.BGPPath]) - delete(fm.idByPath, *p.BGPPath) + delete(fm.ids, fm.idByPath[hash]) + delete(fm.idByPath, hash) } + fm.used-- return id, nil } diff --git a/routingtable/adjRIBOut/path_id_manager_test.go b/routingtable/adjRIBOut/path_id_manager_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e4ac49466587f0f11af3910d5fd2785e030e7cca --- /dev/null +++ b/routingtable/adjRIBOut/path_id_manager_test.go @@ -0,0 +1,173 @@ +package adjRIBOut + +import ( + "testing" + + "github.com/bio-routing/bio-rd/route" + "github.com/stretchr/testify/assert" +) + +func TestAddPath(t *testing.T) { + tests := []struct { + name string + maxIDs uint32 + count int + wantFail bool + }{ + { + name: "Out of path IDs", + maxIDs: 10, + count: 11, + wantFail: true, + }, + { + name: "Success", + maxIDs: 10, + count: 10, + wantFail: false, + }, + } + +X: + for _, test := range tests { + maxUint32 = test.maxIDs + m := newPathIDManager() + for i := 0; i < test.count; i++ { + _, err := m.addPath(&route.Path{BGPPath: &route.BGPPath{LocalPref: uint32(i)}}) + if err != nil { + if test.wantFail { + continue X + } + + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + continue X + } + } + + if test.wantFail { + t.Errorf("Unexpected success for test %q", test.name) + continue + } + } + +} + +func TestReleasePath(t *testing.T) { + tests := []struct { + name string + pm *pathIDManager + release *route.Path + expected *pathIDManager + wantFail bool + }{ + { + name: "Release existent", + pm: &pathIDManager{ + ids: map[uint32]uint64{ + 0: 1, + 1: 1, + 2: 1, + }, + idByPath: map[route.BGPPath]uint32{ + route.BGPPath{ + LocalPref: 0, + }: 0, + route.BGPPath{ + LocalPref: 1, + }: 1, + route.BGPPath{ + LocalPref: 2, + }: 2, + }, + last: 2, + used: 3, + }, + release: &route.Path{BGPPath: &route.BGPPath{ + LocalPref: 2, + }}, + expected: &pathIDManager{ + ids: map[uint32]uint64{ + 0: 1, + 1: 1, + }, + idByPath: map[route.BGPPath]uint32{ + route.BGPPath{ + LocalPref: 0, + }: 0, + route.BGPPath{ + LocalPref: 1, + }: 1, + }, + last: 2, + used: 2, + }, + }, + { + name: "Release non-existent", + pm: &pathIDManager{ + ids: map[uint32]uint64{ + 0: 1, + 1: 1, + 2: 1, + }, + idByPath: map[route.BGPPath]uint32{ + route.BGPPath{ + LocalPref: 0, + }: 0, + route.BGPPath{ + LocalPref: 1, + }: 1, + route.BGPPath{ + LocalPref: 2, + }: 2, + }, + last: 2, + used: 3, + }, + release: &route.Path{BGPPath: &route.BGPPath{ + LocalPref: 4, + }}, + expected: &pathIDManager{ + ids: map[uint32]uint64{ + 0: 1, + 1: 1, + 2: 1, + }, + idByPath: map[route.BGPPath]uint32{ + route.BGPPath{ + LocalPref: 0, + }: 0, + route.BGPPath{ + LocalPref: 1, + }: 1, + route.BGPPath{ + LocalPref: 2, + }: 2, + }, + last: 2, + used: 3, + }, + wantFail: true, + }, + } + + for _, test := range tests { + _, err := test.pm.releasePath(test.release) + if err != nil { + if test.wantFail { + continue + } + + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + continue + } + + if test.wantFail { + t.Errorf("Unexpected success for test %q", test.name) + continue + } + + assert.Equalf(t, test.expected, test.pm, "%s", test.name) + } +} + diff --git a/routingtable/contributing_asn_list.go b/routingtable/contributing_asn_list.go new file mode 100644 index 0000000000000000000000000000000000000000..500b79b61d66825b6f0e9c21d3508adc970c36e3 --- /dev/null +++ b/routingtable/contributing_asn_list.go @@ -0,0 +1,88 @@ +package routingtable + +import ( + "fmt" + "math" + "sync" +) + +type contributingASN struct { + asn uint32 + count uint32 +} + +// ContributingASNs contains a list of contributing ASN to a LocRIB to check ASPaths for possible routing loops. +type ContributingASNs struct { + contributingASNs []*contributingASN + contributingASNsLock sync.RWMutex +} + +// NewContributingASNs creates a list of contributing ASNs to a LocRIB for routing loop prevention. +func NewContributingASNs() *ContributingASNs { + c := &ContributingASNs{ + contributingASNs: []*contributingASN{}, + } + + return c +} + +// Add a new ASN to the list of contributing ASNs or add the ref count of an existing one. +func (c *ContributingASNs) Add(asn uint32) { + c.contributingASNsLock.Lock() + defer c.contributingASNsLock.Unlock() + + for _, cASN := range c.contributingASNs { + if cASN.asn == asn { + cASN.count++ + + if cASN.count == math.MaxUint32 { + panic(fmt.Sprintf("Contributing ASNs counter overflow triggered for AS %d. Dyning of shame.", asn)) + } + + return + } + } + + c.contributingASNs = append(c.contributingASNs, &contributingASN{ + asn: asn, + count: 1, + }) +} + +// Remove a ASN to the list of contributing ASNs or decrement the ref count of an existing one. +func (c *ContributingASNs) Remove(asn uint32) { + c.contributingASNsLock.Lock() + defer c.contributingASNsLock.Unlock() + + asnList := c.contributingASNs + + for i, cASN := range asnList { + if cASN.asn != asn { + continue + } + + cASN.count-- + + if cASN.count == 0 { + copy(asnList[i:], asnList[i+1:]) + asnList = asnList[:len(asnList)] + c.contributingASNs = asnList[:len(asnList)-1] + } + + return + } +} + +// IsContributingASN checks if a given ASN is part of the contributing ASNs +func (c *ContributingASNs) IsContributingASN(asn uint32) bool { + c.contributingASNsLock.RLock() + defer c.contributingASNsLock.RUnlock() + + for _, cASN := range c.contributingASNs { + if asn == cASN.asn { + return true + } + } + + return false +} diff --git a/routingtable/contributing_asn_list_test.go b/routingtable/contributing_asn_list_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6387b18bcca92c290bc724ed8d13032104d247fa --- /dev/null +++ b/routingtable/contributing_asn_list_test.go @@ -0,0 +1,90 @@ +package routingtable + +import ( + "fmt" + "testing" +) + +func TestFancy(t *testing.T) { + c := NewContributingASNs() + + tests := []struct { + runCmd func() + expect func() bool + msg string + }{ + // Empty list + { + runCmd: func() {}, + expect: func() bool { return !c.IsContributingASN(41981) }, + msg: "AS41981 shouldn't be contributing yet.", + }, + + // Add and remove one ASN + { + runCmd: func() { c.Add(41981) }, + expect: func() bool { return c.IsContributingASN(41981) }, + msg: "AS41981 should be contributing.", + }, + { + runCmd: func() { c.Remove(41981) }, + expect: func() bool { return !c.IsContributingASN(41981) }, + msg: "AS41981 shouldn't be contributing no more.", + }, + + // Two ASNs present + { + runCmd: func() { c.Add(41981) }, + expect: func() bool { return c.IsContributingASN(41981) }, + msg: "AS41981 should be contributing.", + }, + { + runCmd: func() { c.Add(201701) }, + expect: func() bool { return c.IsContributingASN(41981) }, + msg: "AS201701 should be contributing.", + }, + + // Add AS41981 2nd time + { + runCmd: func() { c.Add(41981) }, + expect: func() bool { return c.IsContributingASN(41981) }, + msg: "AS41981 should be still contributing.", + }, + { + runCmd: func() {}, + expect: func() bool { return c.contributingASNs[0].asn == 41981 }, + msg: "AS41981 is first ASN in list.", + }, + { + runCmd: func() { fmt.Printf("%+v", c.contributingASNs) }, + expect: func() bool { return c.contributingASNs[0].count == 2 }, + msg: "AS41981 should be present twice.", + }, + + // Remove 2nd AS41981 + { + runCmd: func() { c.Remove(41981) }, + expect: func() bool { return c.IsContributingASN(41981) }, + msg: "AS41981 should still be contributing.", + }, + { + runCmd: func() {}, + expect: func() bool { return c.contributingASNs[0].count == 1 }, + msg: "S41981 should be present once.", + }, + + // Remove AS201701 + { + runCmd: func() { c.Remove(201701) }, + expect: func() bool { return !c.IsContributingASN(201701) }, + msg: "AS201701 shouldn't be contributing no more.", + }, + } + + for i, test := range tests { + test.runCmd() + if !test.expect() { + t.Errorf("Test %d failed: %v", i, test.msg) + } + } +} diff --git a/routingtable/filter/actions/add_community_action.go b/routingtable/filter/actions/add_community_action.go index 01ad05f62936574bb576d55fe1c7bcf23697a38f..c24107e8b94be6657174fbd3dffb06ae978ff767 100644 --- a/routingtable/filter/actions/add_community_action.go +++ b/routingtable/filter/actions/add_community_action.go @@ -1,10 +1,7 @@ package actions import ( - "strings" - "github.com/bio-routing/bio-rd/net" - "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/route" ) @@ -26,9 +23,8 @@ func (a *AddCommunityAction) Do(p net.Prefix, pa *route.Path) (modPath *route.Pa modified := pa.Copy() for _, com := range a.communities { - modified.BGPPath.Communities = modified.BGPPath.Communities + " " + packet.CommunityStringForUint32(com) + modified.BGPPath.Communities = append(modified.BGPPath.Communities, com) } - modified.BGPPath.Communities = strings.TrimLeft(modified.BGPPath.Communities, " ") return modified, false } diff --git a/routingtable/filter/actions/add_community_action_test.go b/routingtable/filter/actions/add_community_action_test.go index 0687e86c4bc80b8648d172cb125449962dea9cf6..9ec3d1401b87da808c324a984e1d5ef7620658cd 100644 --- a/routingtable/filter/actions/add_community_action_test.go +++ b/routingtable/filter/actions/add_community_action_test.go @@ -11,7 +11,7 @@ import ( func TestAddingCommunities(t *testing.T) { tests := []struct { name string - current string + current []uint32 communities []uint32 expected string }{ @@ -23,16 +23,20 @@ func TestAddingCommunities(t *testing.T) { expected: "(1,2)", }, { - name: "add one to existing", - current: "(1,2)", + name: "add one to existing", + current: []uint32{ + 65538, + }, communities: []uint32{ 196612, }, expected: "(1,2) (3,4)", }, { - name: "add two to existing", - current: "(1,2)", + name: "add two to existing", + current: []uint32{ + 65538, + }, communities: []uint32{ 196612, 327686, }, @@ -41,7 +45,7 @@ func TestAddingCommunities(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(te *testing.T) { + t.Run(test.name, func(t *testing.T) { p := &route.Path{ BGPPath: &route.BGPPath{ Communities: test.current, @@ -51,7 +55,7 @@ func TestAddingCommunities(t *testing.T) { a := NewAddCommunityAction(test.communities) modPath, _ := a.Do(net.Prefix{}, p) - assert.Equal(te, test.expected, modPath.BGPPath.Communities) + assert.Equal(t, test.expected, modPath.BGPPath.CommunitiesString()) }) } } diff --git a/routingtable/filter/actions/add_large_community_action.go b/routingtable/filter/actions/add_large_community_action.go index 17169c315817509c8c6c1a2b382ab39c3b5f0428..534ad30b66e4580ca6b8078bf7a077f87b6e758f 100644 --- a/routingtable/filter/actions/add_large_community_action.go +++ b/routingtable/filter/actions/add_large_community_action.go @@ -1,18 +1,16 @@ package actions import ( - "strings" - "github.com/bio-routing/bio-rd/net" "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/route" ) type AddLargeCommunityAction struct { - communities []*packet.LargeCommunity + communities []packet.LargeCommunity } -func NewAddLargeCommunityAction(coms []*packet.LargeCommunity) *AddLargeCommunityAction { +func NewAddLargeCommunityAction(coms []packet.LargeCommunity) *AddLargeCommunityAction { return &AddLargeCommunityAction{ communities: coms, } @@ -24,11 +22,7 @@ func (a *AddLargeCommunityAction) Do(p net.Prefix, pa *route.Path) (modPath *rou } modified := pa.Copy() - - for _, com := range a.communities { - modified.BGPPath.LargeCommunities = modified.BGPPath.LargeCommunities + " " + com.String() - } - modified.BGPPath.LargeCommunities = strings.TrimLeft(modified.BGPPath.LargeCommunities, " ") + modified.BGPPath.LargeCommunities = append(modified.BGPPath.LargeCommunities, a.communities...) return modified, false } diff --git a/routingtable/filter/actions/add_large_community_action_test.go b/routingtable/filter/actions/add_large_community_action_test.go index d2c597fd5cbe8011b3eeb7b74443a09c726d6eb7..f7af91396fdd7d697cf667d7712e18c9784d68e3 100644 --- a/routingtable/filter/actions/add_large_community_action_test.go +++ b/routingtable/filter/actions/add_large_community_action_test.go @@ -12,14 +12,14 @@ import ( func TestAddingLargeCommunities(t *testing.T) { tests := []struct { name string - current string - communities []*packet.LargeCommunity + current []packet.LargeCommunity + communities []packet.LargeCommunity expected string }{ { name: "add one to empty", - communities: []*packet.LargeCommunity{ - &packet.LargeCommunity{ + communities: []packet.LargeCommunity{ + packet.LargeCommunity{ GlobalAdministrator: 1, DataPart1: 2, DataPart2: 3, @@ -28,10 +28,16 @@ func TestAddingLargeCommunities(t *testing.T) { expected: "(1,2,3)", }, { - name: "add one to existing", - current: "(5,6,7)", - communities: []*packet.LargeCommunity{ - &packet.LargeCommunity{ + name: "add one to existing", + current: []packet.LargeCommunity{ + packet.LargeCommunity{ + GlobalAdministrator: 5, + DataPart1: 6, + DataPart2: 7, + }, + }, + communities: []packet.LargeCommunity{ + packet.LargeCommunity{ GlobalAdministrator: 1, DataPart1: 2, DataPart2: 3, @@ -40,15 +46,21 @@ func TestAddingLargeCommunities(t *testing.T) { expected: "(5,6,7) (1,2,3)", }, { - name: "add two to existing", - current: "(5,6,7)", - communities: []*packet.LargeCommunity{ - &packet.LargeCommunity{ + name: "add two to existing", + current: []packet.LargeCommunity{ + packet.LargeCommunity{ + GlobalAdministrator: 5, + DataPart1: 6, + DataPart2: 7, + }, + }, + communities: []packet.LargeCommunity{ + packet.LargeCommunity{ GlobalAdministrator: 1, DataPart1: 2, DataPart2: 3, }, - &packet.LargeCommunity{ + packet.LargeCommunity{ GlobalAdministrator: 7, DataPart1: 8, DataPart2: 9, @@ -69,7 +81,7 @@ func TestAddingLargeCommunities(t *testing.T) { a := NewAddLargeCommunityAction(test.communities) modPath, _ := a.Do(net.Prefix{}, p) - assert.Equal(te, test.expected, modPath.BGPPath.LargeCommunities) + assert.Equal(te, test.expected, modPath.BGPPath.LargeCommunitiesString()) }) } } diff --git a/routingtable/filter/actions/as_path_prepend_action_test.go b/routingtable/filter/actions/as_path_prepend_action_test.go index 348116ccb33b7e067e7e50effa438b9eb0d42c3a..253c119ffc91d702e0f4d4cf31e1f88c35d2008c 100644 --- a/routingtable/filter/actions/as_path_prepend_action_test.go +++ b/routingtable/filter/actions/as_path_prepend_action_test.go @@ -3,6 +3,8 @@ package actions import ( "testing" + "github.com/bio-routing/bio-rd/protocols/bgp/packet" + "github.com/bio-routing/bio-rd/net" "github.com/bio-routing/bio-rd/route" "github.com/stretchr/testify/assert" @@ -23,7 +25,13 @@ func TestAppendPath(t *testing.T) { name: "append 0", times: 0, bgpPath: &route.BGPPath{ - ASPath: "12345 12345", + ASPath: packet.ASPath{ + packet.ASPathSegment{ + Count: 2, + Type: packet.ASSequence, + ASNs: []uint32{12345, 12345}, + }, + }, ASPathLen: 2, }, expectedPath: "12345 12345", @@ -33,7 +41,13 @@ func TestAppendPath(t *testing.T) { name: "append 3", times: 3, bgpPath: &route.BGPPath{ - ASPath: "12345 15169", + ASPath: packet.ASPath{ + packet.ASPathSegment{ + Count: 2, + Type: packet.ASSequence, + ASNs: []uint32{12345, 15169}, + }, + }, ASPathLen: 2, }, expectedPath: "12345 12345 12345 12345 15169", @@ -52,7 +66,7 @@ func TestAppendPath(t *testing.T) { return } - assert.Equal(te, test.expectedPath, p.BGPPath.ASPath, "ASPath") + assert.Equal(te, test.expectedPath, p.BGPPath.ASPath.String(), "ASPath") assert.Equal(te, test.expectedLength, p.BGPPath.ASPathLen, "ASPathLen") }) } diff --git a/routingtable/filter/community_filter.go b/routingtable/filter/community_filter.go index 9d384e54ad0a58ee622e3d2b0bb1b857c867b0c3..b07d4b332349de507ca5591010475ebd920fed94 100644 --- a/routingtable/filter/community_filter.go +++ b/routingtable/filter/community_filter.go @@ -1,15 +1,15 @@ package filter -import ( - "strings" - - "github.com/bio-routing/bio-rd/protocols/bgp/packet" -) - type CommunityFilter struct { community uint32 } -func (f *CommunityFilter) Matches(communityString string) bool { - return strings.Contains(communityString, packet.CommunityStringForUint32(f.community)) +func (f *CommunityFilter) Matches(coms []uint32) bool { + for _, com := range coms { + if com == f.community { + return true + } + } + + return false } diff --git a/routingtable/filter/filter_test.go b/routingtable/filter/filter_test.go index 2f4aace7637f30095d106520495d12a2d3ad9626..273e986d29dc4e25a6788bfb8f750e54bff6beb4 100644 --- a/routingtable/filter/filter_test.go +++ b/routingtable/filter/filter_test.go @@ -1,85 +1,21 @@ package filter -/*func TestAddPath(t *testing.T) { - tests := []struct { - name string - prefix net.Prefix - path *route.Path - term *Term - exptectCalled bool - expectModified bool - }{ - { - name: "accept", - prefix: net.NewPfx(0, 0), - path: &route.Path{}, - term: &Term{ - then: []FilterAction{ - &actions.AcceptAction{}, - }, - }, - exptectCalled: true, - expectModified: false, - }, - { - name: "reject", - prefix: net.NewPfx(0, 0), - path: &route.Path{}, - term: &Term{ - then: []FilterAction{ - &actions.RejectAction{}, - }, - }, - exptectCalled: false, - expectModified: false, - }, - { - name: "modified", - prefix: net.NewPfx(0, 0), - path: &route.Path{}, - term: &Term{ - then: []FilterAction{ - &mockAction{}, - &actions.AcceptAction{}, - }, - }, - exptectCalled: true, - expectModified: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(te *testing.T) { - m := newClientMock() - - f := NewFilter([]*Term{test.term}) - f.Register(m) +import ( + "testing" - f.AddPath(test.prefix, test.path) - assert.Equal(te, test.exptectCalled, m.addPathCalled, "called") + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" + "github.com/bio-routing/bio-rd/routingtable/filter/actions" + "github.com/stretchr/testify/assert" +) - if !test.exptectCalled { - return - } - - if m.path != test.path && !test.expectModified { - te.Fatal("expected path to be not modified but was") - } - - if m.path == test.path && test.expectModified { - te.Fatal("expected path to be modified but was same reference") - } - }) - } -} - -func TestRemovePath(t *testing.T) { +func TestProcessTerms(t *testing.T) { tests := []struct { name string prefix net.Prefix path *route.Path term *Term - exptectCalled bool + exptectAccept bool expectModified bool }{ { @@ -91,7 +27,7 @@ func TestRemovePath(t *testing.T) { &actions.AcceptAction{}, }, }, - exptectCalled: true, + exptectAccept: true, expectModified: false, }, { @@ -103,7 +39,7 @@ func TestRemovePath(t *testing.T) { &actions.RejectAction{}, }, }, - exptectCalled: false, + exptectAccept: false, expectModified: false, }, { @@ -116,32 +52,21 @@ func TestRemovePath(t *testing.T) { &actions.AcceptAction{}, }, }, - exptectCalled: true, + exptectAccept: true, expectModified: true, }, } for _, test := range tests { t.Run(test.name, func(te *testing.T) { - m := newClientMock() - f := NewFilter([]*Term{test.term}) - f.Register(m) + p, reject := f.ProcessTerms(test.prefix, test.path) - f.RemovePath(test.prefix, test.path) - assert.Equal(te, test.exptectCalled, m.removePathCalled, "called") - - if !test.exptectCalled { - return - } + assert.Equal(t, test.exptectAccept, !reject) - if m.path != test.path && !test.expectModified { - te.Fatal("expected path to be not modified but was") - } - - if m.path == test.path && test.expectModified { - te.Fatal("expected path to be modified but was same reference") + if test.expectModified { + assert.NotEqual(t, test.path, p) } }) } -}*/ +} diff --git a/routingtable/filter/helper_test.go b/routingtable/filter/helper_test.go index 5d82727a9f2b5b5e0f692a980d9d7515ccd4c71d..19a444d4a5c8d656534cfb415f2b401a11ae4c57 100644 --- a/routingtable/filter/helper_test.go +++ b/routingtable/filter/helper_test.go @@ -1,27 +1,23 @@ package filter -/*func TestNewAcceptAllFilter(t *testing.T) { - f := NewAcceptAllFilter() +import ( + "testing" - m := &clientMock{} - f.Register(m) + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" + "github.com/stretchr/testify/assert" +) - f.AddPath(net.NewPfx(0, 0), &route.Path{}) +func TestNewAcceptAllFilter(t *testing.T) { + f := NewAcceptAllFilter() - if !m.addPathCalled { - t.Fatalf("expected accepted, but was filtered") - } + _, reject := f.ProcessTerms(net.NewPfx(0, 0), &route.Path{}) + assert.Equal(t, false, reject) } func TestNewDrainFilter(t *testing.T) { f := NewDrainFilter() - m := &clientMock{} - f.Register(m) - - f.AddPath(net.NewPfx(0, 0), &route.Path{}) - - if m.addPathCalled { - t.Fatalf("expected filtered, but was accepted") - } -}*/ + _, reject := f.ProcessTerms(net.NewPfx(0, 0), &route.Path{}) + assert.Equal(t, true, reject) +} diff --git a/routingtable/filter/large_community_filter.go b/routingtable/filter/large_community_filter.go index 0f9d474e69728ba682cde441e819a0d08bd7cc77..cb4722bdfe98a68cd6c6e9938f188bbbf606c968 100644 --- a/routingtable/filter/large_community_filter.go +++ b/routingtable/filter/large_community_filter.go @@ -1,15 +1,19 @@ package filter import ( - "strings" - "github.com/bio-routing/bio-rd/protocols/bgp/packet" ) type LargeCommunityFilter struct { - community *packet.LargeCommunity + community packet.LargeCommunity } -func (f *LargeCommunityFilter) Matches(communityString string) bool { - return strings.Contains(communityString, f.community.String()) +func (f *LargeCommunityFilter) Matches(coms []packet.LargeCommunity) bool { + for _, com := range coms { + if com == f.community { + return true + } + } + + return false } diff --git a/routingtable/filter/term_condition_test.go b/routingtable/filter/term_condition_test.go index cca90950aa5a443c042c8061f77a08d3f4f7a0c4..e1b3d4bd6173b196ddaca029e768274a08364883 100644 --- a/routingtable/filter/term_condition_test.go +++ b/routingtable/filter/term_condition_test.go @@ -107,7 +107,7 @@ func TestMatches(t *testing.T) { name: "community matches", prefix: net.NewPfx(strAddr("10.0.0.0"), 24), bgpPath: &route.BGPPath{ - Communities: "(1,2) (3,4) (5,6)", + Communities: []uint32{65538, 196612, 327686}, // (1,2) (3,4) (5,6) }, communityFilters: []*CommunityFilter{ &CommunityFilter{196612}, // (3,4) @@ -118,7 +118,7 @@ func TestMatches(t *testing.T) { name: "community does not match", prefix: net.NewPfx(strAddr("10.0.0.0"), 24), bgpPath: &route.BGPPath{ - Communities: "(1,2) (3,4) (5,6)", + Communities: []uint32{65538, 196612, 327686}, // (1,2) (3,4) (5,6) }, communityFilters: []*CommunityFilter{ &CommunityFilter{196608}, // (3,0) @@ -137,11 +137,22 @@ func TestMatches(t *testing.T) { name: "large community matches", prefix: net.NewPfx(strAddr("10.0.0.0"), 24), bgpPath: &route.BGPPath{ - LargeCommunities: "(1,2,0) (1,2,3)", + LargeCommunities: []packet.LargeCommunity{ + packet.LargeCommunity{ + GlobalAdministrator: 1, + DataPart1: 2, + DataPart2: 3, + }, + packet.LargeCommunity{ + GlobalAdministrator: 1, + DataPart1: 2, + DataPart2: 0, + }, + }, }, largeCommunityFilters: []*LargeCommunityFilter{ { - &packet.LargeCommunity{ + packet.LargeCommunity{ GlobalAdministrator: 1, DataPart1: 2, DataPart2: 3, @@ -156,7 +167,7 @@ func TestMatches(t *testing.T) { bgpPath: &route.BGPPath{}, largeCommunityFilters: []*LargeCommunityFilter{ { - &packet.LargeCommunity{ + packet.LargeCommunity{ GlobalAdministrator: 1, DataPart1: 2, DataPart2: 3, @@ -170,7 +181,7 @@ func TestMatches(t *testing.T) { prefix: net.NewPfx(strAddr("10.0.0.0"), 24), largeCommunityFilters: []*LargeCommunityFilter{ { - &packet.LargeCommunity{ + packet.LargeCommunity{ GlobalAdministrator: 1, DataPart1: 2, DataPart2: 3, diff --git a/routingtable/locRIB/loc_rib.go b/routingtable/locRIB/loc_rib.go index e8788c5eb3b3c2b1e3e7397d52a4ed6f04feb21b..037565a86e32b77f4344d40724b0781094234f22 100644 --- a/routingtable/locRIB/loc_rib.go +++ b/routingtable/locRIB/loc_rib.go @@ -14,19 +14,26 @@ import ( // LocRIB represents a routing information base type LocRIB struct { routingtable.ClientManager - rt *routingtable.RoutingTable - mu sync.RWMutex + rt *routingtable.RoutingTable + mu sync.RWMutex + contributingASNs *routingtable.ContributingASNs } // New creates a new routing information base func New() *LocRIB { a := &LocRIB{ - rt: routingtable.NewRoutingTable(), + rt: routingtable.NewRoutingTable(), + contributingASNs: routingtable.NewContributingASNs(), } a.ClientManager = routingtable.NewClientManager(a) return a } +// GetContributingASNs returns a pointer to the list of contributing ASNs +func (a *LocRIB) GetContributingASNs() *routingtable.ContributingASNs { + return a.contributingASNs +} + //Count routes from the LocRIP func (a *LocRIB) Count() uint64 { a.mu.RLock() diff --git a/routingtable/mock_client.go b/routingtable/mock_client.go new file mode 100644 index 0000000000000000000000000000000000000000..806dd2b3b29865d6821002cda1c2c7dc4c360279 --- /dev/null +++ b/routingtable/mock_client.go @@ -0,0 +1,52 @@ +package routingtable + +import ( + "fmt" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" +) + +type RemovePathParams struct { + Pfx net.Prefix + Path *route.Path +} + +type RTMockClient struct { + removePathParams RemovePathParams +} + +func NewRTMockClient() *RTMockClient { + return &RTMockClient{} +} + +func (m *RTMockClient) GetRemovePathParams() RemovePathParams { + return m.removePathParams +} + +func (m *RTMockClient) AddPath(pfx net.Prefix, p *route.Path) error { + return nil +} + +func (m *RTMockClient) UpdateNewClient(client RouteTableClient) error { + return fmt.Errorf("Not implemented") +} + +func (m *RTMockClient) Register(RouteTableClient) { + return +} + +func (m *RTMockClient) RegisterWithOptions(RouteTableClient, ClientOptions) { + return +} + +func (m *RTMockClient) Unregister(RouteTableClient) { + return +} + +// RemovePath removes the path for prefix `pfx` +func (m *RTMockClient) RemovePath(pfx net.Prefix, p *route.Path) bool { + m.removePathParams.Pfx = pfx + m.removePathParams.Path = p + return true +} diff --git a/routingtable/update_helper.go b/routingtable/update_helper.go index c18936f3a21055678401a9ec4697ab00363fc4f0..6709bd43ab7e2d39914c6ebd1c59abb6518e8189 100644 --- a/routingtable/update_helper.go +++ b/routingtable/update_helper.go @@ -1,12 +1,9 @@ package routingtable import ( - "strings" - "github.com/bio-routing/bio-rd/net" "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/route" - log "github.com/sirupsen/logrus" ) // ShouldPropagateUpdate performs some default checks and returns if an route update should be propagated to a neighbor @@ -32,17 +29,7 @@ func isDisallowedByCommunity(p *route.Path, n *Neighbor) bool { return false } - strs := strings.Split(p.BGPPath.Communities, " ") - for _, str := range strs { - com, err := packet.ParseCommunityString(str) - if err != nil { - log.WithField("Sender", "routingtable.ShouldAnnounce()"). - WithField("community", str). - WithError(err). - Error("Could not parse community") - continue - } - + for _, com := range p.BGPPath.Communities { if (com == packet.WellKnownCommunityNoExport && !n.IBGP) || com == packet.WellKnownCommunityNoAdvertise { return true } diff --git a/routingtable/update_helper_test.go b/routingtable/update_helper_test.go index 29342f376ead000006039e6aa95caf3399937dc1..452a7e4af04152e21c13631c38190b8d4795649d 100644 --- a/routingtable/update_helper_test.go +++ b/routingtable/update_helper_test.go @@ -2,9 +2,11 @@ package routingtable import ( "net" + "strings" "testing" bnet "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/route" "github.com/stretchr/testify/assert" ) @@ -58,19 +60,31 @@ func TestShouldPropagateUpdate(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(te *testing.T) { - pfx := bnet.NewPfx(0, 32) + t.Run(test.name, func(t *testing.T) { + comms := make([]uint32, 0) + for _, s := range strings.Split(test.communities, " ") { + if s == "" { + continue + } + + com, err := packet.ParseCommunityString(s) + if err != nil { + t.Fatalf("test failed: %s", err) + } + comms = append(comms, com) + } + pfx := bnet.NewPfx(0, 32) pa := &route.Path{ Type: route.BGPPathType, BGPPath: &route.BGPPath{ - Communities: test.communities, + Communities: comms, Source: bnet.IPv4ToUint32(net.ParseIP("192.168.1.1")), }, } res := ShouldPropagateUpdate(pfx, pa, &test.neighbor) - assert.Equal(te, test.expected, res) + assert.Equal(t, test.expected, res) }) } } diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..3071b82f935eeb1d0f3f722214b282c98763245d --- /dev/null +++ b/testing/BUILD.bazel @@ -0,0 +1,8 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["conn_mock.go"], + importpath = "github.com/bio-routing/bio-rd/testing", + visibility = ["//visibility:public"], +) diff --git a/testing/conn_mock.go b/testing/conn_mock.go new file mode 100644 index 0000000000000000000000000000000000000000..86e8e1ae7d1c91eaa6efce317d34e8b08212c825 --- /dev/null +++ b/testing/conn_mock.go @@ -0,0 +1,32 @@ +package testing + +import "net" + +// MockConn mock an connection +type MockConn struct { + net.Conn + + // Bytes are the bytes writen + Bytes []byte +} + +func NewMockConn() *MockConn { + return &MockConn{ + Bytes: make([]byte, 0), + } +} + +func (m *MockConn) Write(b []byte) (int, error) { + m.Bytes = append(m.Bytes, b...) + return len(b), nil +} + +func (m *MockConn) Read(b []byte) (n int, err error) { + count := len(b) + if count > len(m.Bytes) { + count = len(m.Bytes) + } + + copy(m.Bytes[0:count], b) + return count, nil +}