From 54c415719b5d26961dead88c5ddd3094dde25ba0 Mon Sep 17 00:00:00 2001
From: Daniel Czerwonk <daniel@dan-nrw.de>
Date: Sun, 1 Jul 2018 22:53:19 +0200
Subject: [PATCH] bio receives ipv6 routes, tag for ipv6 testbed

---
 config/peer.go                               |  1 +
 main.go                                      | 55 +--------------
 main_ipv4.go                                 | 70 ++++++++++++++++++++
 main_ipv6.go                                 | 48 ++++++++++++++
 protocols/bgp/packet/helper.go               | 10 ++-
 protocols/bgp/packet/mp_reach_nlri.go        | 29 +++++---
 protocols/bgp/packet/mp_reach_nlri_test.go   |  4 +-
 protocols/bgp/packet/mp_unreach_nlri.go      | 25 +++++--
 protocols/bgp/packet/mp_unreach_nlri_test.go |  8 ++-
 protocols/bgp/packet/path_attributes_test.go | 12 ++--
 protocols/bgp/server/fsm_established.go      | 41 +++++++++++-
 protocols/bgp/server/fsm_open_sent.go        |  6 ++
 protocols/bgp/server/peer.go                 | 14 ++++
 protocols/bgp/types/options.go               |  5 +-
 14 files changed, 245 insertions(+), 83 deletions(-)
 create mode 100644 main_ipv4.go
 create mode 100644 main_ipv6.go

diff --git a/config/peer.go b/config/peer.go
index 5f151fe5..a192e317 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 6b924601..11c2a689 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 00000000..65293f07
--- /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 00000000..9afc7e90
--- /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/protocols/bgp/packet/helper.go b/protocols/bgp/packet/helper.go
index 18176cf1..a4a0226b 100644
--- a/protocols/bgp/packet/helper.go
+++ b/protocols/bgp/packet/helper.go
@@ -12,7 +12,7 @@ func serializePrefix(pfx bnet.Prefix) []byte {
 		return []byte{}
 	}
 
-	numBytes := uint8(math.Ceil(float64(pfx.Pfxlen()) / 8))
+	numBytes := numberOfBytesForPrefixLength(pfx.Pfxlen())
 
 	b := make([]byte, numBytes+1)
 	b[0] = pfx.Pfxlen()
@@ -22,9 +22,9 @@ func serializePrefix(pfx bnet.Prefix) []byte {
 }
 
 func deserializePrefix(b []byte, pfxLen uint8, afi uint16) (bnet.Prefix, error) {
-	numBytes := int(math.Ceil(float64(pfxLen) / 8))
+	numBytes := numberOfBytesForPrefixLength(pfxLen)
 
-	if numBytes != len(b) {
+	if numBytes != uint8(len(b)) {
 		return bnet.Prefix{}, fmt.Errorf("could not parse prefix of legth %d. Expected %d bytes, got %d", pfxLen, numBytes, len(b))
 	}
 
@@ -38,3 +38,7 @@ func deserializePrefix(b []byte, pfxLen uint8, afi uint16) (bnet.Prefix, error)
 
 	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
index e886f27c..e6ec075e 100644
--- a/protocols/bgp/packet/mp_reach_nlri.go
+++ b/protocols/bgp/packet/mp_reach_nlri.go
@@ -11,10 +11,10 @@ import (
 
 // 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
-	Prefix  bnet.Prefix
+	AFI      uint16
+	SAFI     uint8
+	NextHop  bnet.IP
+	Prefixes []bnet.Prefix
 }
 
 func (n *MultiProtocolReachNLRI) serialize(buf *bytes.Buffer) uint8 {
@@ -26,7 +26,9 @@ func (n *MultiProtocolReachNLRI) serialize(buf *bytes.Buffer) uint8 {
 	tempBuf.WriteByte(uint8(len(nextHop))) // NextHop length
 	tempBuf.Write(nextHop)
 	tempBuf.WriteByte(0) // RESERVED
-	tempBuf.Write(serializePrefix(n.Prefix))
+	for _, pfx := range n.Prefixes {
+		tempBuf.Write(serializePrefix(pfx))
+	}
 
 	buf.Write(tempBuf.Bytes())
 
@@ -54,9 +56,20 @@ func deserializeMultiProtocolReachNLRI(b []byte) (MultiProtocolReachNLRI, error)
 		return MultiProtocolReachNLRI{}, fmt.Errorf("Failed to decode next hop IP: %v", err)
 	}
 
-	n.Prefix, err = deserializePrefix(variable[nextHopLength+2:], variable[nextHopLength+1], n.AFI)
-	if err != nil {
-		return MultiProtocolReachNLRI{}, err
+	variable = variable[1+nextHopLength:]
+
+	idx := uint8(0)
+	n.Prefixes = make([]bnet.Prefix, 0)
+	for idx < uint8(len(variable)) {
+		l := numberOfBytesForPrefixLength(variable[idx])
+
+		pfx, err := deserializePrefix(variable[idx+1:idx+1+l], variable[idx], n.AFI)
+		if err != nil {
+			return MultiProtocolReachNLRI{}, err
+		}
+		n.Prefixes = append(n.Prefixes, pfx)
+
+		idx = idx + l + 1
 	}
 
 	return n, nil
diff --git a/protocols/bgp/packet/mp_reach_nlri_test.go b/protocols/bgp/packet/mp_reach_nlri_test.go
index 2bbb9d2e..f16ac3c4 100644
--- a/protocols/bgp/packet/mp_reach_nlri_test.go
+++ b/protocols/bgp/packet/mp_reach_nlri_test.go
@@ -20,7 +20,9 @@ func TestSerializeMultiProtocolReachNLRI(t *testing.T) {
 				AFI:     IPv6AFI,
 				SAFI:    UnicastSAFI,
 				NextHop: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0x2),
-				Prefix:  bnet.NewPfx(bnet.IPv6FromBlocks(0x2600, 0x6, 0xff05, 0, 0, 0, 0, 0), 48),
+				Prefixes: []bnet.Prefix{
+					bnet.NewPfx(bnet.IPv6FromBlocks(0x2600, 0x6, 0xff05, 0, 0, 0, 0, 0), 48),
+				},
 			},
 			expected: []byte{
 				0x00, 0x02, // AFI
diff --git a/protocols/bgp/packet/mp_unreach_nlri.go b/protocols/bgp/packet/mp_unreach_nlri.go
index e3e43822..35f3cadf 100644
--- a/protocols/bgp/packet/mp_unreach_nlri.go
+++ b/protocols/bgp/packet/mp_unreach_nlri.go
@@ -9,16 +9,18 @@ import (
 
 // MultiProtocolUnreachNLRI represents network layer withdraw information for one prefix of an IP address family (rfc4760)
 type MultiProtocolUnreachNLRI struct {
-	AFI    uint16
-	SAFI   uint8
-	Prefix bnet.Prefix
+	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)
-	tempBuf.Write(serializePrefix(n.Prefix))
+	for _, pfx := range n.Prefixes {
+		tempBuf.Write(serializePrefix(pfx))
+	}
 
 	buf.Write(tempBuf.Bytes())
 
@@ -39,9 +41,18 @@ func deserializeMultiProtocolUnreachNLRI(b []byte) (MultiProtocolUnreachNLRI, er
 		return MultiProtocolUnreachNLRI{}, err
 	}
 
-	n.Prefix, err = deserializePrefix(prefix[1:], prefix[0], n.AFI)
-	if err != nil {
-		return MultiProtocolUnreachNLRI{}, err
+	idx := uint8(0)
+	n.Prefixes = make([]bnet.Prefix, 0)
+	for idx < uint8(len(prefix)) {
+		l := numberOfBytesForPrefixLength(prefix[idx])
+
+		pfx, err := deserializePrefix(prefix[idx+1:idx+1+l], prefix[idx], n.AFI)
+		if err != nil {
+			return MultiProtocolUnreachNLRI{}, err
+		}
+		n.Prefixes = append(n.Prefixes, pfx)
+
+		idx = idx + l + 1
 	}
 
 	return n, nil
diff --git a/protocols/bgp/packet/mp_unreach_nlri_test.go b/protocols/bgp/packet/mp_unreach_nlri_test.go
index 7e132acc..02e1b00f 100644
--- a/protocols/bgp/packet/mp_unreach_nlri_test.go
+++ b/protocols/bgp/packet/mp_unreach_nlri_test.go
@@ -17,9 +17,11 @@ func TestSerializeMultiProtocolUnreachNLRI(t *testing.T) {
 		{
 			name: "Simple IPv6 prefix",
 			nlri: MultiProtocolUnreachNLRI{
-				AFI:    IPv6AFI,
-				SAFI:   UnicastSAFI,
-				Prefix: bnet.NewPfx(bnet.IPv6FromBlocks(0x2620, 0x110, 0x9000, 0, 0, 0, 0, 0), 44),
+				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
diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go
index 0048f900..3bd22598 100644
--- a/protocols/bgp/packet/path_attributes_test.go
+++ b/protocols/bgp/packet/path_attributes_test.go
@@ -775,7 +775,9 @@ func TestDecodeMultiProtocolReachNLRI(t *testing.T) {
 					AFI:     IPv6AFI,
 					SAFI:    UnicastSAFI,
 					NextHop: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0x2),
-					Prefix:  bnet.NewPfx(bnet.IPv6FromBlocks(0x2600, 0x6, 0xff05, 0, 0, 0, 0, 0), 48),
+					Prefixes: []bnet.Prefix{
+						bnet.NewPfx(bnet.IPv6FromBlocks(0x2600, 0x6, 0xff05, 0, 0, 0, 0, 0), 48),
+					},
 				},
 			},
 		},
@@ -835,9 +837,11 @@ func TestDecodeMultiProtocolUnreachNLRI(t *testing.T) {
 			expected: &PathAttribute{
 				Length: 10,
 				Value: MultiProtocolUnreachNLRI{
-					AFI:    IPv6AFI,
-					SAFI:   UnicastSAFI,
-					Prefix: bnet.NewPfx(bnet.IPv6FromBlocks(0x2620, 0x110, 0x9000, 0, 0, 0, 0, 0), 44),
+					AFI:  IPv6AFI,
+					SAFI: UnicastSAFI,
+					Prefixes: []bnet.Prefix{
+						bnet.NewPfx(bnet.IPv6FromBlocks(0x2620, 0x110, 0x9000, 0, 0, 0, 0, 0), 44),
+					},
 				},
 			},
 		},
diff --git a/protocols/bgp/server/fsm_established.go b/protocols/bgp/server/fsm_established.go
index b5740c57..9c14c322 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,
 	}
 
@@ -163,6 +167,7 @@ func (s *establishedState) keepaliveTimerExpired() (state, string) {
 func (s *establishedState) msgReceived(data []byte) (state, string) {
 	msg, err := packet.Decode(bytes.NewBuffer(data), s.fsm.options)
 	if err != nil {
+		fmt.Println(err)
 		switch bgperr := err.(type) {
 		case packet.BGPError:
 			s.fsm.sendNotification(bgperr.ErrorCode, bgperr.ErrorSubCode)
@@ -201,6 +206,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
 }
@@ -230,6 +236,39 @@ func (s *establishedState) updates(u *packet.BGPUpdate) {
 	}
 }
 
+func (s *establishedState) multiProtocolUpdates(u *packet.BGPUpdate) {
+	path := &route.Path{
+		Type: route.BGPPathType,
+		BGPPath: &route.BGPPath{
+			Source: s.fsm.peer.addr,
+			EBGP:   s.fsm.peer.localASN != s.fsm.peer.peerASN,
+		},
+	}
+
+	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))
+		}
+	}
+}
+
+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 928ed450..3ece0447 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 7a7587f8..638d8b03 100644
--- a/protocols/bgp/server/peer.go
+++ b/protocols/bgp/server/peer.go
@@ -133,6 +133,10 @@ func newPeer(c config.Peer, rib *locRIB.LocRIB, server *bgpServer) (*peer, error
 
 	caps = append(caps, asn4Capability(c))
 
+	if c.IPv6 {
+		caps = append(caps, multiProtocolCapability(packet.IPv6AFI))
+	}
+
 	p.optOpenParams = append(p.optOpenParams, packet.OptParam{
 		Type:  packet.CapabilitiesParamType,
 		Value: caps,
@@ -150,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 19b580d2..8b96d669 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
 }
-- 
GitLab