diff --git a/BUILD.bazel b/BUILD.bazel index f04b329b3f7bba0a788dc6fec24c1988c50d594e..ae2f1b1751ac95160a2e2097c520c3f99339211f 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -10,7 +10,10 @@ gazelle( go_library( name = "go_default_library", - srcs = ["main.go"], + srcs = [ + "main.go", + "main_ipv4.go", + ], importpath = "github.com/bio-routing/bio-rd", visibility = ["//visibility:private"], deps = [ diff --git a/config/peer.go b/config/peer.go index 5f151fe56c59ffa4ca410eb174745e755b15c09d..a192e317d3225560a7076232fac6d217ba4b790c 100644 --- a/config/peer.go +++ b/config/peer.go @@ -25,4 +25,5 @@ type Peer struct { ImportFilter *filter.Filter ExportFilter *filter.Filter RouteServerClient bool + IPv6 bool } diff --git a/main.go b/main.go index 6b924601418c4335d5c7ccb7878eb4281d29710c..11c2a6895ade4237dbf48249437c2d202bb8aa58 100644 --- a/main.go +++ b/main.go @@ -2,15 +2,11 @@ package main import ( "fmt" - "net" "time" "github.com/sirupsen/logrus" - "github.com/bio-routing/bio-rd/config" "github.com/bio-routing/bio-rd/protocols/bgp/server" - "github.com/bio-routing/bio-rd/routingtable" - "github.com/bio-routing/bio-rd/routingtable/filter" "github.com/bio-routing/bio-rd/routingtable/locRIB" bnet "github.com/bio-routing/bio-rd/net" @@ -26,56 +22,7 @@ func main() { rib := locRIB.New() b := server.NewBgpServer() - - err := b.Start(&config.Global{ - Listen: true, - LocalAddressList: []net.IP{ - net.IPv4(169, 254, 100, 1), - net.IPv4(169, 254, 200, 0), - }, - }) - if err != nil { - logrus.Fatalf("Unable to start BGP server: %v", err) - } - - b.AddPeer(config.Peer{ - AdminEnabled: true, - LocalAS: 65200, - PeerAS: 65300, - PeerAddress: bnet.IPv4FromOctets(172, 17, 0, 3), - LocalAddress: bnet.IPv4FromOctets(169, 254, 200, 0), - ReconnectInterval: time.Second * 15, - HoldTime: time.Second * 90, - KeepAlive: time.Second * 30, - Passive: true, - RouterID: b.RouterID(), - AddPathSend: routingtable.ClientOptions{ - MaxPaths: 10, - }, - ImportFilter: filter.NewAcceptAllFilter(), - ExportFilter: filter.NewAcceptAllFilter(), - RouteServerClient: true, - }, rib) - - b.AddPeer(config.Peer{ - AdminEnabled: true, - LocalAS: 65200, - PeerAS: 65100, - PeerAddress: bnet.IPv4FromOctets(172, 17, 0, 2), - LocalAddress: bnet.IPv4FromOctets(169, 254, 100, 1), - ReconnectInterval: time.Second * 15, - HoldTime: time.Second * 90, - KeepAlive: time.Second * 30, - Passive: true, - RouterID: b.RouterID(), - AddPathSend: routingtable.ClientOptions{ - MaxPaths: 10, - }, - AddPathRecv: true, - ImportFilter: filter.NewAcceptAllFilter(), - ExportFilter: filter.NewAcceptAllFilter(), - RouteServerClient: true, - }, rib) + startServer(b, rib) go func() { for { diff --git a/main_ipv4.go b/main_ipv4.go new file mode 100644 index 0000000000000000000000000000000000000000..65293f07853d5819abdb96a6ab5def218f0c2320 --- /dev/null +++ b/main_ipv4.go @@ -0,0 +1,70 @@ +// +build !ipv6 + +package main + +import ( + "net" + "time" + + "github.com/bio-routing/bio-rd/routingtable/locRIB" + + "github.com/bio-routing/bio-rd/config" + "github.com/bio-routing/bio-rd/protocols/bgp/server" + "github.com/bio-routing/bio-rd/routingtable" + "github.com/bio-routing/bio-rd/routingtable/filter" + "github.com/sirupsen/logrus" + + bnet "github.com/bio-routing/bio-rd/net" +) + +func startServer(b server.BGPServer, rib *locRIB.LocRIB) { + err := b.Start(&config.Global{ + Listen: true, + LocalAddressList: []net.IP{ + net.IPv4(169, 254, 100, 1), + net.IPv4(169, 254, 200, 0), + }, + }) + if err != nil { + logrus.Fatalf("Unable to start BGP server: %v", err) + } + + b.AddPeer(config.Peer{ + AdminEnabled: true, + LocalAS: 65200, + PeerAS: 65300, + PeerAddress: bnet.IPv4FromOctets(172, 17, 0, 3), + LocalAddress: bnet.IPv4FromOctets(169, 254, 200, 0), + ReconnectInterval: time.Second * 15, + HoldTime: time.Second * 90, + KeepAlive: time.Second * 30, + Passive: true, + RouterID: b.RouterID(), + AddPathSend: routingtable.ClientOptions{ + MaxPaths: 10, + }, + ImportFilter: filter.NewAcceptAllFilter(), + ExportFilter: filter.NewAcceptAllFilter(), + RouteServerClient: true, + }, rib) + + b.AddPeer(config.Peer{ + AdminEnabled: true, + LocalAS: 65200, + PeerAS: 65100, + PeerAddress: bnet.IPv4FromOctets(172, 17, 0, 2), + LocalAddress: bnet.IPv4FromOctets(169, 254, 100, 1), + ReconnectInterval: time.Second * 15, + HoldTime: time.Second * 90, + KeepAlive: time.Second * 30, + Passive: true, + RouterID: b.RouterID(), + AddPathSend: routingtable.ClientOptions{ + MaxPaths: 10, + }, + AddPathRecv: true, + ImportFilter: filter.NewAcceptAllFilter(), + ExportFilter: filter.NewAcceptAllFilter(), + RouteServerClient: true, + }, rib) +} diff --git a/main_ipv6.go b/main_ipv6.go new file mode 100644 index 0000000000000000000000000000000000000000..9afc7e903d6e186bb386f64b00d268acde967140 --- /dev/null +++ b/main_ipv6.go @@ -0,0 +1,48 @@ +// +build ipv6 + +package main + +import ( + "net" + "time" + + "github.com/bio-routing/bio-rd/config" + "github.com/bio-routing/bio-rd/protocols/bgp/server" + "github.com/bio-routing/bio-rd/routingtable" + "github.com/bio-routing/bio-rd/routingtable/filter" + "github.com/bio-routing/bio-rd/routingtable/locRIB" + "github.com/sirupsen/logrus" + + bnet "github.com/bio-routing/bio-rd/net" +) + +func startServer(b server.BGPServer, rib *locRIB.LocRIB) { + err := b.Start(&config.Global{ + Listen: true, + LocalAddressList: []net.IP{ + net.IP{0x20, 0x01, 0x6, 0x78, 0x1, 0xe0, 0, 0, 0, 0, 0, 0, 0, 0, 0xca, 0xfe}, + }, + }) + if err != nil { + logrus.Fatalf("Unable to start BGP server: %v", err) + } + + b.AddPeer(config.Peer{ + AdminEnabled: true, + LocalAS: 65200, + PeerAS: 202739, + PeerAddress: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 1), + LocalAddress: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0xcafe), + ReconnectInterval: time.Second * 15, + HoldTime: time.Second * 90, + KeepAlive: time.Second * 30, + Passive: true, + RouterID: b.RouterID(), + AddPathSend: routingtable.ClientOptions{ + BestOnly: true, + }, + ImportFilter: filter.NewAcceptAllFilter(), + ExportFilter: filter.NewDrainFilter(), + IPv6: true, + }, rib) +} diff --git a/net/ip.go b/net/ip.go index 2855235eae0e6c44753f351f2363ed790f2ed9e3..8a532a2c09f36033fbb27d0e8aa69a9e10942492 100644 --- a/net/ip.go +++ b/net/ip.go @@ -41,6 +41,27 @@ func IPv6FromBlocks(b1, b2, b3, b4, b5, b6, b7, b8 uint16) IP { uint64(uint64(b5)<<48+uint64(b6)<<32+uint64(b7)<<16+uint64(b8))) } +// IPFromBytes returns an IP address for a byte slice +func IPFromBytes(b []byte) (IP, error) { + if len(b) == 4 { + return IPv4FromOctets(b[0], b[1], b[2], b[3]), nil + } + + if len(b) == 16 { + return IPv6FromBlocks( + uint16(b[0])<<8+uint16(b[1]), + uint16(b[2])<<8+uint16(b[3]), + uint16(b[4])<<8+uint16(b[5]), + uint16(b[6])<<8+uint16(b[7]), + uint16(b[8])<<8+uint16(b[9]), + uint16(b[10])<<8+uint16(b[11]), + uint16(b[12])<<8+uint16(b[13]), + uint16(b[14])<<8+uint16(b[15])), nil + } + + return IP{}, fmt.Errorf("byte slice has an invalid legth. Expected either 4 (IPv4) or 16 (IPv6) bytes but got: %d", len(b)) +} + // Equal returns true if ip is equal to other func (ip IP) Equal(other IP) bool { return ip == other @@ -161,5 +182,13 @@ func (ip IP) bitAtPositionIPv4(pos uint8) bool { } func (ip IP) bitAtPositionIPv6(pos uint8) bool { - panic("No IPv6 support yet!") + if pos > 128 { + return false + } + + if pos <= 64 { + return (ip.higher & (1 << (64 - pos))) != 0 + } + + return (ip.lower & (1 << (128 - pos))) != 0 } diff --git a/net/ip_test.go b/net/ip_test.go index 613e342d7ffe46460007aeec692da8c39286258f..4720985f825ba242b1c78cf75773499d55bdd9bf 100644 --- a/net/ip_test.go +++ b/net/ip_test.go @@ -228,6 +228,50 @@ func TestIPv6FromBlocks(t *testing.T) { } } +func TestIPFromBytes(t *testing.T) { + tests := []struct { + name string + bytes []byte + expected IP + wantFail bool + }{ + { + name: "IPV4: 172.217.16.195", + bytes: []byte{172, 217, 16, 195}, + expected: IP{ + higher: 0, + lower: 2899906755, + ipVersion: 4, + }, + }, + { + name: "IPV6: IPv6 2001:678:1E0:1234:5678:DEAD:BEEF:CAFE", + bytes: []byte{0x20, 0x01, 0x06, 0x78, 0x01, 0xE0, 0x12, 0x34, 0x56, 0x78, 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE}, + expected: IP{ + higher: 2306131596687708724, + lower: 6230974922281175806, + ipVersion: 6, + }, + }, + { + name: "invalid length", + bytes: []byte{172, 217, 123, 231, 95}, + wantFail: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ip, err := IPFromBytes(test.bytes) + if err == nil && test.wantFail { + t.Fatalf("Expected test to fail, but did not") + } + + assert.Equal(t, test.expected, ip) + }) + } +} + func TestToNetIP(t *testing.T) { tests := []struct { name string @@ -269,17 +313,53 @@ func TestBitAtPosition(t *testing.T) { expected bool }{ { - name: "Bit 8 from 1.0.0.0 -> 0", + name: "IPv4: all ones -> 0", + input: IPv4FromOctets(255, 255, 255, 255), + position: 1, + expected: true, + }, + { + name: "IPv4: Bit 8 from 1.0.0.0 -> 0", input: IPv4FromOctets(10, 0, 0, 0), position: 8, expected: false, }, { - name: "Bit 8 from 11.0.0.0 -> 1", + name: "IPv4: Bit 8 from 11.0.0.0 -> 1", input: IPv4FromOctets(11, 0, 0, 0), position: 8, expected: true, }, + { + name: "IPv6: Bit 16 from 2001:678:1e0:: -> 1", + input: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0), + position: 16, + expected: true, + }, + { + name: "IPv6: Bit 17 from 2001:678:1e0:: -> 0", + input: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0), + position: 17, + expected: false, + }, + { + name: "IPv6: Bit 113 from 2001:678:1e0::cafe -> 1", + input: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0xcafe), + position: 113, + expected: true, + }, + { + name: "IPv6: Bit 115 from 2001:678:1e0::cafe -> 0", + input: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0xcafe), + position: 115, + expected: false, + }, + { + name: "IPv6: all ones -> 1", + input: IPv6FromBlocks(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF), + position: 1, + expected: true, + }, } for _, test := range tests { diff --git a/net/prefix.go b/net/prefix.go index c40420247dfbfb11e8e05c01bae2fa1ed7d5e183..5abd32f141f8ae5cfb9c107d2bc5a7ce98bddf4e 100644 --- a/net/prefix.go +++ b/net/prefix.go @@ -70,7 +70,7 @@ func (pfx Prefix) Contains(x Prefix) bool { return pfx.containsIPv4(x) } - panic("No IPv6 support yet!") + return pfx.containsIPv6(x) } func (pfx Prefix) containsIPv4(x Prefix) bool { @@ -78,6 +78,20 @@ func (pfx Prefix) containsIPv4(x Prefix) bool { return (pfx.addr.ToUint32() & mask) == (x.addr.ToUint32() & mask) } +func (pfx Prefix) containsIPv6(x Prefix) bool { + var maskHigh, maskLow uint64 + if pfx.pfxlen <= 64 { + maskHigh = math.MaxUint32 << (64 - pfx.pfxlen) + maskLow = uint64(0) + } else { + maskHigh = math.MaxUint32 + maskLow = math.MaxUint32 << (128 - pfx.pfxlen) + } + + return pfx.addr.higher&maskHigh&maskHigh == x.addr.higher&maskHigh&maskHigh && + pfx.addr.lower&maskHigh&maskLow == x.addr.lower&maskHigh&maskLow +} + // Equal checks if pfx and x are equal func (pfx Prefix) Equal(x Prefix) bool { return pfx == x @@ -89,7 +103,7 @@ func (pfx Prefix) GetSupernet(x Prefix) Prefix { return pfx.supernetIPv4(x) } - panic("No IPv6 support yet!") + return pfx.supernetIPv6(x) } func (pfx Prefix) supernetIPv4(x Prefix) Prefix { @@ -109,6 +123,37 @@ func (pfx Prefix) supernetIPv4(x Prefix) Prefix { } } +func (pfx Prefix) supernetIPv6(x Prefix) Prefix { + maxPfxLen := min(pfx.pfxlen, x.pfxlen) + + a := pfx.addr.BitAtPosition(1) + b := x.addr.BitAtPosition(1) + pfxLen := uint8(0) + mask := uint64(0) + for a == b && pfxLen < maxPfxLen { + a = pfx.addr.BitAtPosition(pfxLen + 2) + b = x.addr.BitAtPosition(pfxLen + 2) + pfxLen++ + + if pfxLen == 64 { + mask = 0 + } + + m := pfxLen % 64 + mask = mask + uint64(1)<<(64-m) + } + + if pfxLen == 0 { + return NewPfx(IPv6(0, 0), pfxLen) + } + + if pfxLen > 64 { + return NewPfx(IPv6(pfx.addr.higher, pfx.addr.lower&mask), pfxLen) + } + + return NewPfx(IPv6(pfx.addr.higher&mask, 0), pfxLen) +} + func min(a uint8, b uint8) uint8 { if a < b { return a diff --git a/net/prefix_test.go b/net/prefix_test.go index d886a49e4ae1ed49fede133df33ac4d90331dd16..ccb05a7d3d753d61d6389d9ba6363da7562317f2 100644 --- a/net/prefix_test.go +++ b/net/prefix_test.go @@ -63,7 +63,7 @@ func TestGetSupernet(t *testing.T) { expected Prefix }{ { - name: "Test 1", + name: "Supernet of 10.0.0.0 and 11.100.123.0 -> 10.0.0.0/7", a: Prefix{ addr: IPv4FromOctets(10, 0, 0, 0), pfxlen: 8, @@ -78,7 +78,7 @@ func TestGetSupernet(t *testing.T) { }, }, { - name: "Test 2", + name: "Supernet of 10.0.0.0 and 192.168.0.0 -> 0.0.0.0/0", a: Prefix{ addr: IPv4FromOctets(10, 0, 0, 0), pfxlen: 8, @@ -88,15 +88,64 @@ func TestGetSupernet(t *testing.T) { pfxlen: 24, }, expected: Prefix{ - addr: IPv4(0), // 0.0.0.0/0 + addr: IPv4(0), + pfxlen: 0, + }, + }, + { + name: "Supernet of 2001:678:1e0:100:23::/64 and 2001:678:1e0:1ff::/64 -> 2001:678:1e0:100::/56", + a: Prefix{ + addr: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x100, 0x23, 0, 0, 0), + pfxlen: 64, + }, + b: Prefix{ + addr: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x1ff, 0, 0, 0, 0), + pfxlen: 64, + }, + expected: Prefix{ + addr: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x100, 0, 0, 0, 0), + pfxlen: 56, + }, + }, + { + name: "Supernet of 2001:678:1e0::/128 and 2001:678:1e0::1/128 -> 2001:678:1e0:100::/127", + a: Prefix{ + addr: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0), + pfxlen: 128, + }, + b: Prefix{ + addr: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 1), + pfxlen: 128, + }, + expected: Prefix{ + addr: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0), + pfxlen: 127, + }, + }, + { + name: "Supernet of all ones and all zeros -> ::/0", + a: Prefix{ + addr: IPv6FromBlocks(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF), + pfxlen: 128, + }, + b: Prefix{ + addr: IPv6(0, 0), + pfxlen: 128, + }, + expected: Prefix{ + addr: IPv6FromBlocks(0, 0, 0, 0, 0, 0, 0, 0), pfxlen: 0, }, }, } + t.Parallel() + for _, test := range tests { - s := test.a.GetSupernet(test.b) - assert.Equal(t, s, test.expected) + t.Run(test.name, func(t *testing.T) { + s := test.a.GetSupernet(test.b) + assert.Equal(t, test.expected, s) + }) } } @@ -191,6 +240,30 @@ func TestContains(t *testing.T) { }, expected: false, }, + { + name: "IPv6: 2001:678:1e0:100::/56 is subnet of 2001:678:1e0::/48", + a: Prefix{ + addr: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0), + pfxlen: 48, + }, + expected: true, + b: Prefix{ + addr: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x100, 0, 0, 0, 0), + pfxlen: 56, + }, + }, + { + name: "IPv6: 2001:678:1e0:100::/56 is subnet of 2001:678:1e0::/48", + a: Prefix{ + addr: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x200, 0, 0, 0, 0), + pfxlen: 56, + }, + b: Prefix{ + addr: IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x100, 0, 0, 0, 0), + pfxlen: 64, + }, + expected: false, + }, } for _, test := range tests { diff --git a/protocols/bgp/packet/BUILD.bazel b/protocols/bgp/packet/BUILD.bazel index d6f4d73f49b4a5ec4eaf7b1d288a1e32bd2eb88d..f9886e41d0eea9ee21bac03dc902ad7f4218b131 100644 --- a/protocols/bgp/packet/BUILD.bazel +++ b/protocols/bgp/packet/BUILD.bazel @@ -6,6 +6,9 @@ go_library( "bgp.go", "decoder.go", "encoder.go", + "helper.go", + "mp_reach_nlri.go", + "mp_unreach_nlri.go", "nlri.go", "parameters.go", "path_attribute_flags.go", @@ -27,6 +30,8 @@ go_test( srcs = [ "decoder_test.go", "encoder_test.go", + "mp_reach_nlri_test.go", + "mp_unreach_nlri_test.go", "nlri_test.go", "path_attributes_test.go", ], diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go index c6776ace6c41f6ccbfd8e2b126f38fc5c16d4798..e008205a2ad95e97872c0a691a479efe42888c24 100644 --- a/protocols/bgp/packet/bgp.go +++ b/protocols/bgp/packet/bgp.go @@ -85,15 +85,26 @@ const ( ConnectionCollisionResolution = 7 OutOfResoutces = 8 - IPv4AFI = 1 - UnicastSAFI = 1 - CapabilitiesParamType = 2 - AddPathCapabilityCode = 69 - ASN4CapabilityCode = 65 - AddPathReceive = 1 - AddPathSend = 2 - AddPathSendReceive = 3 - ASTransASN = 23456 + IPv4AFI = 1 + IPv6AFI = 2 + UnicastSAFI = 1 + CapabilitiesParamType = 2 + MultiProtocolCapabilityCode = 1 + MultiProtocolReachNLRICode = 14 + MultiProtocolUnreachNLRICode = 15 + AddPathCapabilityCode = 69 + ASN4CapabilityCode = 65 + AddPathReceive = 1 + AddPathSend = 2 + AddPathSendReceive = 3 + ASTransASN = 23456 +) + +var ( + afiAddrLenBytes = map[uint16]uint8{ + 1: 4, + 2: 16, + } ) type BGPError struct { diff --git a/protocols/bgp/packet/decoder.go b/protocols/bgp/packet/decoder.go index ca717c781194adf20d2d5c08f332c03f060be849..cd951463a15215bd6aaa43e8799e59c95cee549b 100644 --- a/protocols/bgp/packet/decoder.go +++ b/protocols/bgp/packet/decoder.go @@ -233,6 +233,12 @@ func decodeCapability(buf *bytes.Buffer) (Capability, error) { } switch cap.Code { + case MultiProtocolCapabilityCode: + mpCap, err := decodeMultiProtocolCapability(buf) + if err != nil { + return cap, fmt.Errorf("Unable to decode multi protocol capability") + } + cap.Value = mpCap case AddPathCapabilityCode: addPathCap, err := decodeAddPathCapability(buf) if err != nil { @@ -257,6 +263,21 @@ func decodeCapability(buf *bytes.Buffer) (Capability, error) { return cap, nil } +func decodeMultiProtocolCapability(buf *bytes.Buffer) (MultiProtocolCapability, error) { + mpCap := MultiProtocolCapability{} + reserved := uint8(0) + fields := []interface{}{ + &mpCap.AFI, &reserved, &mpCap.SAFI, + } + + err := decode(buf, fields) + if err != nil { + return mpCap, err + } + + return mpCap, nil +} + func decodeAddPathCapability(buf *bytes.Buffer) (AddPathCapability, error) { addPathCap := AddPathCapability{} fields := []interface{}{ diff --git a/protocols/bgp/packet/decoder_test.go b/protocols/bgp/packet/decoder_test.go index 5613bbcb6c46a385492f5c38f5a837ae5e287ee7..ac611ceff133e4375e70995a2b564900671ed8aa 100644 --- a/protocols/bgp/packet/decoder_test.go +++ b/protocols/bgp/packet/decoder_test.go @@ -1736,6 +1736,19 @@ func TestDecodeCapability(t *testing.T) { }, wantFail: false, }, + { + name: "MP Capability (IPv6)", + input: []byte{1, 4, 0, 2, 0, 1}, + expected: Capability{ + Code: MultiProtocolCapabilityCode, + Length: 4, + Value: MultiProtocolCapability{ + AFI: IPv6AFI, + SAFI: UnicastSAFI, + }, + }, + wantFail: false, + }, { name: "Fail", input: []byte{69, 4, 0, 1}, diff --git a/protocols/bgp/packet/encoder_test.go b/protocols/bgp/packet/encoder_test.go index fd7405d735a5d11a4117d144e7086828c86f5628..1b0b60d5c919227acc5418dfef3e5802aa92b8d7 100644 --- a/protocols/bgp/packet/encoder_test.go +++ b/protocols/bgp/packet/encoder_test.go @@ -106,30 +106,61 @@ func TestSerializeOptParams(t *testing.T) { expected: []byte{}, }, { - name: "1 Option", + name: "AddPath", optParams: []OptParam{ - { + OptParam{ Type: 2, Length: 6, - Value: Capability{ - Code: 69, - Length: 4, - Value: AddPathCapability{ - AFI: 1, - SAFI: 1, - SendReceive: 3, + Value: Capabilities{ + Capability{ + Code: 69, + Length: 4, + Value: AddPathCapability{ + AFI: 1, + SAFI: 1, + SendReceive: 3, + }, }, }, }, }, expected: []byte{2, 6, 69, 4, 0, 1, 1, 3}, }, + { + name: "Multi Protocol Support (IPv6), 32 bit ASNs", + optParams: []OptParam{ + OptParam{ + Length: 12, + Type: CapabilitiesParamType, + Value: Capabilities{ + Capability{ + Code: MultiProtocolCapabilityCode, + Length: 4, + Value: MultiProtocolCapability{ + AFI: 2, + SAFI: 1, + }, + }, + Capability{ + Code: ASN4CapabilityCode, + Length: 4, + Value: ASN4Capability{ + ASN4: 202739, + }, + }, + }, + }, + }, + expected: []byte{2, 12, 1, 4, 0, 2, 0, 1, 65, 4, 0x00, 0x03, 0x17, 0xf3}, + }, } for _, test := range tests { - buf := bytes.NewBuffer(make([]byte, 0)) - serializeOptParams(buf, test.optParams) - assert.Equal(t, test.expected, buf.Bytes()) + t.Run(test.name, func(t *testing.T) { + buf := bytes.NewBuffer(make([]byte, 0)) + serializeOptParams(buf, test.optParams) + assert.Equal(t, test.expected, buf.Bytes()) + }) } } diff --git a/protocols/bgp/packet/helper.go b/protocols/bgp/packet/helper.go new file mode 100644 index 0000000000000000000000000000000000000000..991af5a5eb9410c6875400c3b88ed3ee2e7c8de4 --- /dev/null +++ b/protocols/bgp/packet/helper.go @@ -0,0 +1,44 @@ +package packet + +import ( + "fmt" + "math" + + bnet "github.com/bio-routing/bio-rd/net" +) + +func serializePrefix(pfx bnet.Prefix) []byte { + if pfx.Pfxlen() == 0 { + return []byte{} + } + + numBytes := numberOfBytesForPrefixLength(pfx.Pfxlen()) + + b := make([]byte, numBytes+1) + b[0] = pfx.Pfxlen() + copy(b[1:numBytes+1], pfx.Addr().Bytes()[0:numBytes]) + + return b +} + +func deserializePrefix(b []byte, pfxLen uint8, afi uint16) (bnet.Prefix, error) { + numBytes := numberOfBytesForPrefixLength(pfxLen) + + if numBytes != uint8(len(b)) { + return bnet.Prefix{}, fmt.Errorf("could not parse prefix of length %d. Expected %d bytes, got %d", pfxLen, numBytes, len(b)) + } + + ipBytes := make([]byte, afiAddrLenBytes[afi]) + copy(ipBytes, b) + + ip, err := bnet.IPFromBytes(ipBytes) + if err != nil { + return bnet.Prefix{}, err + } + + return bnet.NewPfx(ip, pfxLen), nil +} + +func numberOfBytesForPrefixLength(pfxLen uint8) uint8 { + return uint8(math.Ceil(float64(pfxLen) / 8)) +} diff --git a/protocols/bgp/packet/mp_reach_nlri.go b/protocols/bgp/packet/mp_reach_nlri.go new file mode 100644 index 0000000000000000000000000000000000000000..ec18df05c242e714951ff433fad194e8e24db80b --- /dev/null +++ b/protocols/bgp/packet/mp_reach_nlri.go @@ -0,0 +1,90 @@ +package packet + +import ( + "bytes" + "fmt" + + "github.com/taktv6/tflow2/convert" + + bnet "github.com/bio-routing/bio-rd/net" +) + +// MultiProtocolReachNLRI represents network layer reachability information for one prefix of an IP address family (rfc4760) +type MultiProtocolReachNLRI struct { + AFI uint16 + SAFI uint8 + NextHop bnet.IP + Prefixes []bnet.Prefix +} + +func (n *MultiProtocolReachNLRI) serialize(buf *bytes.Buffer) uint8 { + nextHop := n.NextHop.Bytes() + + tempBuf := bytes.NewBuffer(nil) + tempBuf.Write(convert.Uint16Byte(n.AFI)) + tempBuf.WriteByte(n.SAFI) + tempBuf.WriteByte(uint8(len(nextHop))) + tempBuf.Write(nextHop) + tempBuf.WriteByte(0) // RESERVED + for _, pfx := range n.Prefixes { + tempBuf.Write(serializePrefix(pfx)) + } + + buf.Write(tempBuf.Bytes()) + + return uint8(tempBuf.Len()) +} + +func deserializeMultiProtocolReachNLRI(b []byte) (MultiProtocolReachNLRI, error) { + n := MultiProtocolReachNLRI{} + nextHopLength := uint8(0) + variable := make([]byte, len(b)-4) + + fields := []interface{}{ + &n.AFI, + &n.SAFI, + &nextHopLength, + &variable, + } + err := decode(bytes.NewBuffer(b), fields) + if err != nil { + return MultiProtocolReachNLRI{}, err + } + + n.NextHop, err = bnet.IPFromBytes(variable[:nextHopLength]) + if err != nil { + return MultiProtocolReachNLRI{}, fmt.Errorf("Failed to decode next hop IP: %v", err) + } + + variable = variable[1+nextHopLength:] + + n.Prefixes = make([]bnet.Prefix, 0) + + if len(variable) == 0 { + return n, nil + } + + idx := uint16(0) + for idx < uint16(len(variable)) { + pfxLen := variable[idx] + numBytes := uint16(numberOfBytesForPrefixLength(pfxLen)) + idx++ + + r := uint16(len(variable)) - idx + if r < numBytes { + return MultiProtocolReachNLRI{}, fmt.Errorf("expected %d bytes for NLRI, only %d remaining", numBytes, r) + } + + start := idx + end := idx + numBytes + pfx, err := deserializePrefix(variable[start:end], pfxLen, n.AFI) + if err != nil { + return MultiProtocolReachNLRI{}, err + } + n.Prefixes = append(n.Prefixes, pfx) + + idx = idx + numBytes + } + + return n, nil +} diff --git a/protocols/bgp/packet/mp_reach_nlri_test.go b/protocols/bgp/packet/mp_reach_nlri_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f16ac3c4a181eb3bd0534f0e831bbb00eb09c8cf --- /dev/null +++ b/protocols/bgp/packet/mp_reach_nlri_test.go @@ -0,0 +1,44 @@ +package packet + +import ( + "bytes" + "testing" + + bnet "github.com/bio-routing/bio-rd/net" + "github.com/stretchr/testify/assert" +) + +func TestSerializeMultiProtocolReachNLRI(t *testing.T) { + tests := []struct { + name string + nlri MultiProtocolReachNLRI + expected []byte + }{ + { + name: "Simple IPv6 prefix", + nlri: MultiProtocolReachNLRI{ + AFI: IPv6AFI, + SAFI: UnicastSAFI, + NextHop: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0x2), + Prefixes: []bnet.Prefix{ + bnet.NewPfx(bnet.IPv6FromBlocks(0x2600, 0x6, 0xff05, 0, 0, 0, 0, 0), 48), + }, + }, + expected: []byte{ + 0x00, 0x02, // AFI + 0x01, // SAFI + 0x10, 0x20, 0x01, 0x06, 0x78, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // NextHop + 0x00, // RESERVED + 0x30, 0x26, 0x00, 0x00, 0x06, 0xff, 0x05, // Prefix + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + buf := &bytes.Buffer{} + test.nlri.serialize(buf) + assert.Equal(t, test.expected, buf.Bytes()) + }) + } +} diff --git a/protocols/bgp/packet/mp_unreach_nlri.go b/protocols/bgp/packet/mp_unreach_nlri.go new file mode 100644 index 0000000000000000000000000000000000000000..b7835cd11a24da70d811c1653f3c93cb13b824a3 --- /dev/null +++ b/protocols/bgp/packet/mp_unreach_nlri.go @@ -0,0 +1,72 @@ +package packet + +import ( + "bytes" + "fmt" + + bnet "github.com/bio-routing/bio-rd/net" + "github.com/taktv6/tflow2/convert" +) + +// MultiProtocolUnreachNLRI represents network layer withdraw information for one prefix of an IP address family (rfc4760) +type MultiProtocolUnreachNLRI struct { + AFI uint16 + SAFI uint8 + Prefixes []bnet.Prefix +} + +func (n *MultiProtocolUnreachNLRI) serialize(buf *bytes.Buffer) uint8 { + tempBuf := bytes.NewBuffer(nil) + tempBuf.Write(convert.Uint16Byte(n.AFI)) + tempBuf.WriteByte(n.SAFI) + for _, pfx := range n.Prefixes { + tempBuf.Write(serializePrefix(pfx)) + } + + buf.Write(tempBuf.Bytes()) + + return uint8(tempBuf.Len()) +} + +func deserializeMultiProtocolUnreachNLRI(b []byte) (MultiProtocolUnreachNLRI, error) { + n := MultiProtocolUnreachNLRI{} + prefix := make([]byte, len(b)-3) + + fields := []interface{}{ + &n.AFI, + &n.SAFI, + &prefix, + } + err := decode(bytes.NewBuffer(b), fields) + if err != nil { + return MultiProtocolUnreachNLRI{}, err + } + + if len(prefix) == 0 { + return n, nil + } + + idx := uint16(0) + for idx < uint16(len(prefix)) { + pfxLen := prefix[idx] + numBytes := uint16(numberOfBytesForPrefixLength(pfxLen)) + idx++ + + r := uint16(len(prefix)) - idx + if r < numBytes { + return MultiProtocolUnreachNLRI{}, fmt.Errorf("expected %d bytes for NLRI, only %d remaining", numBytes, r) + } + + start := idx + end := idx + numBytes + pfx, err := deserializePrefix(prefix[start:end], pfxLen, n.AFI) + if err != nil { + return MultiProtocolUnreachNLRI{}, err + } + n.Prefixes = append(n.Prefixes, pfx) + + idx = idx + numBytes + } + + return n, nil +} diff --git a/protocols/bgp/packet/mp_unreach_nlri_test.go b/protocols/bgp/packet/mp_unreach_nlri_test.go new file mode 100644 index 0000000000000000000000000000000000000000..02e1b00fff6d161b37509e1748d20bd04dcb3d35 --- /dev/null +++ b/protocols/bgp/packet/mp_unreach_nlri_test.go @@ -0,0 +1,41 @@ +package packet + +import ( + "bytes" + "testing" + + bnet "github.com/bio-routing/bio-rd/net" + "github.com/stretchr/testify/assert" +) + +func TestSerializeMultiProtocolUnreachNLRI(t *testing.T) { + tests := []struct { + name string + nlri MultiProtocolUnreachNLRI + expected []byte + }{ + { + name: "Simple IPv6 prefix", + nlri: MultiProtocolUnreachNLRI{ + AFI: IPv6AFI, + SAFI: UnicastSAFI, + Prefixes: []bnet.Prefix{ + bnet.NewPfx(bnet.IPv6FromBlocks(0x2620, 0x110, 0x9000, 0, 0, 0, 0, 0), 44), + }, + }, + expected: []byte{ + 0x00, 0x02, // AFI + 0x01, // SAFI + 0x2c, 0x26, 0x20, 0x01, 0x10, 0x90, 0x00, // Prefix + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + buf := &bytes.Buffer{} + test.nlri.serialize(buf) + assert.Equal(t, test.expected, buf.Bytes()) + }) + } +} diff --git a/protocols/bgp/packet/parameters.go b/protocols/bgp/packet/parameters.go index 0ed9af66fe01f96600c02eeb2c00b69b11edc76c..2f99f773af6e2659beb1c4585cb7c2b27ed5ff01 100644 --- a/protocols/bgp/packet/parameters.go +++ b/protocols/bgp/packet/parameters.go @@ -29,6 +29,8 @@ func (c Capabilities) serialize(buf *bytes.Buffer) { for _, cap := range c { cap.serialize(tmpBuf) } + + buf.Write(tmpBuf.Bytes()) } func (c Capability) serialize(buf *bytes.Buffer) { @@ -60,3 +62,14 @@ type ASN4Capability struct { func (a ASN4Capability) serialize(buf *bytes.Buffer) { buf.Write(convert.Uint32Byte(a.ASN4)) } + +type MultiProtocolCapability struct { + AFI uint16 + SAFI uint8 +} + +func (a MultiProtocolCapability) serialize(buf *bytes.Buffer) { + buf.Write(convert.Uint16Byte(a.AFI)) + buf.WriteByte(0) // RESERVED + buf.WriteByte(a.SAFI) +} diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go index f1aae242153e09da32de808fabe40efbcc719456..77025f38ef976917f088509a8481aa3e53a363a5 100644 --- a/protocols/bgp/packet/path_attributes.go +++ b/protocols/bgp/packet/path_attributes.go @@ -99,6 +99,14 @@ func decodePathAttr(buf *bytes.Buffer, opt *types.Options) (pa *PathAttribute, c if err := pa.decodeCommunities(buf); err != nil { return nil, consumed, fmt.Errorf("Failed to decode Community: %v", err) } + case MultiProtocolReachNLRICode: + if err := pa.decodeMultiProtocolReachNLRI(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to multi protocol reachable NLRI: %v", err) + } + case MultiProtocolUnreachNLRICode: + if err := pa.decodeMultiProtocolUnreachNLRI(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to multi protocol unreachable NLRI: %v", err) + } case AS4AggregatorAttr: if err := pa.decodeAS4Aggregator(buf); err != nil { return nil, consumed, fmt.Errorf("Failed to skip not supported AS4Aggregator: %v", err) @@ -116,18 +124,53 @@ func decodePathAttr(buf *bytes.Buffer, opt *types.Options) (pa *PathAttribute, c return pa, consumed + pa.Length, nil } +func (pa *PathAttribute) decodeMultiProtocolReachNLRI(buf *bytes.Buffer) error { + b := make([]byte, pa.Length) + n, err := buf.Read(b) + if err != nil { + return fmt.Errorf("Unable to read %d bytes from buffer: %v", pa.Length, err) + } + if n != int(pa.Length) { + return fmt.Errorf("Unable to read %d bytes from buffer, only got %d bytes", pa.Length, n) + } + + nlri, err := deserializeMultiProtocolReachNLRI(b) + if err != nil { + return fmt.Errorf("Unable to decode MP_REACH_NLRI: %v", err) + } + + pa.Value = nlri + return nil +} + +func (pa *PathAttribute) decodeMultiProtocolUnreachNLRI(buf *bytes.Buffer) error { + b := make([]byte, pa.Length) + n, err := buf.Read(b) + if err != nil { + return fmt.Errorf("Unable to read %d bytes from buffer: %v", pa.Length, err) + } + if n != int(pa.Length) { + return fmt.Errorf("Unable to read %d bytes from buffer, only got %d bytes", pa.Length, n) + } + + nlri, err := deserializeMultiProtocolUnreachNLRI(b) + if err != nil { + return fmt.Errorf("Unable to decode MP_UNREACH_NLRI: %v", err) + } + + pa.Value = nlri + return nil +} + func (pa *PathAttribute) decodeUnknown(buf *bytes.Buffer) error { u := make([]byte, pa.Length) - p := uint16(0) err := decode(buf, []interface{}{&u}) if err != nil { return fmt.Errorf("Unable to decode: %v", err) } pa.Value = u - p += pa.Length - return nil } diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go index 4a0966e6b3dee2a6bbbf3bb4712247eefc7696e1..3bd225984559e952a1a151c56834d15876a264ab 100644 --- a/protocols/bgp/packet/path_attributes_test.go +++ b/protocols/bgp/packet/path_attributes_test.go @@ -746,6 +746,137 @@ func TestDecodeCommunity(t *testing.T) { } } +func TestDecodeMultiProtocolReachNLRI(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + explicitLength uint16 + expected *PathAttribute + }{ + { + name: "incomplete", + input: []byte{0, 0, 0, 0}, + wantFail: true, + explicitLength: 32, + }, + { + name: "valid MP_REACH_NLRI", + input: []byte{ + 0x00, 0x02, // AFI + 0x01, // SAFI + 0x10, 0x20, 0x01, 0x06, 0x78, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // NextHop + 0x00, // RESERVED + 0x30, 0x26, 0x00, 0x00, 0x06, 0xff, 0x05, // Prefix + }, + expected: &PathAttribute{ + Length: 28, + Value: MultiProtocolReachNLRI{ + AFI: IPv6AFI, + SAFI: UnicastSAFI, + NextHop: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0x2), + Prefixes: []bnet.Prefix{ + bnet.NewPfx(bnet.IPv6FromBlocks(0x2600, 0x6, 0xff05, 0, 0, 0, 0, 0), 48), + }, + }, + }, + }, + } + + t.Parallel() + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + l := uint16(len(test.input)) + if test.explicitLength != 0 { + l = test.explicitLength + } + + pa := &PathAttribute{ + Length: l, + } + err := pa.decodeMultiProtocolReachNLRI(bytes.NewBuffer(test.input)) + + if test.wantFail { + if err != nil { + return + } + t.Fatalf("Expected error did not happen for test %q", test.name) + } + + if err != nil { + t.Fatalf("Unexpected failure for test %q: %v", test.name, err) + } + + assert.Equal(t, test.expected, pa) + }) + } +} + +func TestDecodeMultiProtocolUnreachNLRI(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + explicitLength uint16 + expected *PathAttribute + }{ + { + name: "incomplete", + input: []byte{0, 0, 0, 0}, + wantFail: true, + explicitLength: 32, + }, + { + name: "valid MP_UNREACH_NLRI", + input: []byte{ + 0x00, 0x02, // AFI + 0x01, // SAFI + 0x2c, 0x26, 0x20, 0x01, 0x10, 0x90, 0x00, // Prefix + }, + expected: &PathAttribute{ + Length: 10, + Value: MultiProtocolUnreachNLRI{ + AFI: IPv6AFI, + SAFI: UnicastSAFI, + Prefixes: []bnet.Prefix{ + bnet.NewPfx(bnet.IPv6FromBlocks(0x2620, 0x110, 0x9000, 0, 0, 0, 0, 0), 44), + }, + }, + }, + }, + } + + t.Parallel() + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + l := uint16(len(test.input)) + if test.explicitLength != 0 { + l = test.explicitLength + } + + pa := &PathAttribute{ + Length: l, + } + err := pa.decodeMultiProtocolUnreachNLRI(bytes.NewBuffer(test.input)) + + if test.wantFail { + if err != nil { + return + } + t.Fatalf("Expected error did not happen for test %q", test.name) + } + + if err != nil { + t.Fatalf("Unexpected failure for test %q: %v", test.name, err) + } + + assert.Equal(t, test.expected, pa) + }) + } +} + func TestSetLength(t *testing.T) { tests := []struct { name string diff --git a/protocols/bgp/server/fsm_established.go b/protocols/bgp/server/fsm_established.go index b5740c57b9d32ef299d2e783c87b16d06a6e0b14..3c5d96055663e167fc3e451ce03bed2340c757b7 100644 --- a/protocols/bgp/server/fsm_established.go +++ b/protocols/bgp/server/fsm_established.go @@ -69,6 +69,10 @@ func (s *establishedState) init() error { } hostIP := net.ParseIP(host) if hostIP == nil { + return fmt.Errorf("Unable to parse address") + } + localAddr, err := bnet.IPFromBytes(hostIP) + if err != nil { return fmt.Errorf("Unable to parse address: %v", err) } @@ -78,7 +82,7 @@ func (s *establishedState) init() error { IBGP: s.fsm.peer.localASN == s.fsm.peer.peerASN, LocalASN: s.fsm.peer.localASN, RouteServerClient: s.fsm.peer.routeServerClient, - LocalAddress: bnet.IPv4(bnet.IPv4ToUint32(hostIP)), + LocalAddress: localAddr, CapAddPathRX: s.fsm.options.AddPathRX, } @@ -201,6 +205,7 @@ func (s *establishedState) update(msg *packet.BGPMessage) (state, string) { u := msg.Body.(*packet.BGPUpdate) s.withdraws(u) s.updates(u) + s.multiProtocolUpdates(u) return newEstablishedState(s.fsm), s.fsm.reason } @@ -216,20 +221,53 @@ func (s *establishedState) updates(u *packet.BGPUpdate) { for r := u.NLRI; r != nil; r = r.Next { pfx := bnet.NewPfx(bnet.IPv4(r.IP), r.Pfxlen) - path := &route.Path{ - Type: route.BGPPathType, - BGPPath: &route.BGPPath{ - Source: s.fsm.peer.addr, - EBGP: s.fsm.peer.localASN != s.fsm.peer.peerASN, - }, + path := s.newRoutePath() + s.processAttributes(u.PathAttributes, path) + + s.fsm.adjRIBIn.AddPath(pfx, path) + } +} + +func (s *establishedState) multiProtocolUpdates(u *packet.BGPUpdate) { + if !s.fsm.options.SupportsMultiProtocol { + return + } + + path := s.newRoutePath() + s.processAttributes(u.PathAttributes, path) + + for pa := u.PathAttributes; pa != nil; pa = pa.Next { + switch pa.TypeCode { + case packet.MultiProtocolReachNLRICode: + s.multiProtocolUpdate(path, pa.Value.(packet.MultiProtocolReachNLRI)) + case packet.MultiProtocolUnreachNLRICode: + s.multiProtocolWithdraw(path, pa.Value.(packet.MultiProtocolUnreachNLRI)) } + } +} - s.processAttributes(u.PathAttributes, path) +func (s *establishedState) newRoutePath() *route.Path { + return &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{ + Source: s.fsm.peer.addr, + EBGP: s.fsm.peer.localASN != s.fsm.peer.peerASN, + }, + } +} +func (s *establishedState) multiProtocolUpdate(path *route.Path, nlri packet.MultiProtocolReachNLRI) { + for _, pfx := range nlri.Prefixes { s.fsm.adjRIBIn.AddPath(pfx, path) } } +func (s *establishedState) multiProtocolWithdraw(path *route.Path, nlri packet.MultiProtocolUnreachNLRI) { + for _, pfx := range nlri.Prefixes { + s.fsm.adjRIBIn.RemovePath(pfx, path) + } +} + func (s *establishedState) processAttributes(attrs *packet.PathAttribute, path *route.Path) { for pa := attrs; pa != nil; pa = pa.Next { switch pa.TypeCode { diff --git a/protocols/bgp/server/fsm_open_sent.go b/protocols/bgp/server/fsm_open_sent.go index 928ed450b7b2a4cb230acaca5db418af80923c11..3ece04475f645340a60e814e3452110ce56ab5ff 100644 --- a/protocols/bgp/server/fsm_open_sent.go +++ b/protocols/bgp/server/fsm_open_sent.go @@ -169,9 +169,15 @@ func (s *openSentState) processCapability(cap packet.Capability) { s.processAddPathCapability(cap.Value.(packet.AddPathCapability)) case packet.ASN4CapabilityCode: s.processASN4Capability(cap.Value.(packet.ASN4Capability)) + case packet.MultiProtocolCapabilityCode: + s.processMultiProtocolCapability(cap.Value.(packet.MultiProtocolCapability)) } } +func (s *openSentState) processMultiProtocolCapability(cap packet.MultiProtocolCapability) { + s.fsm.options.SupportsMultiProtocol = true +} + func (s *openSentState) processAddPathCapability(addPathCap packet.AddPathCapability) { if addPathCap.AFI != 1 { return diff --git a/protocols/bgp/server/peer.go b/protocols/bgp/server/peer.go index 2772903061d6180c6d49ff48c70e3f96af4dfc33..638d8b0314c9b06fa8c65c7bcee24a117ee69388 100644 --- a/protocols/bgp/server/peer.go +++ b/protocols/bgp/server/peer.go @@ -124,7 +124,7 @@ func newPeer(c config.Peer, rib *locRIB.LocRIB, server *bgpServer) (*peer, error } p.fsms = append(p.fsms, NewActiveFSM2(p)) - caps := make([]packet.Capability, 0) + caps := make(packet.Capabilities, 0) addPathEnabled, addPathCap := handleAddPathCapability(c) if addPathEnabled { @@ -133,13 +133,15 @@ func newPeer(c config.Peer, rib *locRIB.LocRIB, server *bgpServer) (*peer, error caps = append(caps, asn4Capability(c)) - for _, cap := range caps { - p.optOpenParams = append(p.optOpenParams, packet.OptParam{ - Type: packet.CapabilitiesParamType, - Value: cap, - }) + if c.IPv6 { + caps = append(caps, multiProtocolCapability(packet.IPv6AFI)) } + p.optOpenParams = append(p.optOpenParams, packet.OptParam{ + Type: packet.CapabilitiesParamType, + Value: caps, + }) + return p, nil } @@ -152,6 +154,16 @@ func asn4Capability(c config.Peer) packet.Capability { } } +func multiProtocolCapability(afi uint16) packet.Capability { + return packet.Capability{ + Code: packet.MultiProtocolCapabilityCode, + Value: packet.MultiProtocolCapability{ + AFI: afi, + SAFI: packet.UnicastSAFI, + }, + } +} + func handleAddPathCapability(c config.Peer) (bool, packet.Capability) { addPath := uint8(0) if c.AddPathRecv { diff --git a/protocols/bgp/types/options.go b/protocols/bgp/types/options.go index 19b580d2829b96d8f974311536b299211a98ca95..8b96d6697b7262b53954a12663dff872fd3492b2 100644 --- a/protocols/bgp/types/options.go +++ b/protocols/bgp/types/options.go @@ -2,6 +2,7 @@ package types // Options represents options to the update sender, decoder and encoder type Options struct { - Supports4OctetASN bool - AddPathRX bool + Supports4OctetASN bool + SupportsMultiProtocol bool + AddPathRX bool }