From a14508657ae6923d22a127aff5045805058b4e2a Mon Sep 17 00:00:00 2001
From: Daniel Czerwonk <daniel@dan-nrw.de>
Date: Fri, 6 Jul 2018 22:38:58 +0200
Subject: [PATCH] allow multiple families per peer

---
 config/peer.go                             | 13 ++++++--
 main_ipv4.go                               | 18 +++++++----
 main_ipv6.go                               | 20 +++++++-----
 protocols/bgp/server/family_routing.go     | 16 ++++++----
 protocols/bgp/server/fsm.go                | 10 ++++--
 protocols/bgp/server/fsm_established.go    | 32 +++++++++++++------
 protocols/bgp/server/fsm_test.go           | 12 ++++---
 protocols/bgp/server/peer.go               | 37 ++++++++++++++++------
 protocols/bgp/server/server.go             |  7 ++--
 protocols/bgp/server/server_test.go        |  9 ++++--
 protocols/bgp/server/update_sender_test.go | 25 ++++++++++-----
 11 files changed, 133 insertions(+), 66 deletions(-)

diff --git a/config/peer.go b/config/peer.go
index 12124c7c..02d31988 100644
--- a/config/peer.go
+++ b/config/peer.go
@@ -3,6 +3,8 @@ package config
 import (
 	"time"
 
+	"github.com/bio-routing/bio-rd/routingtable/locRIB"
+
 	bnet "github.com/bio-routing/bio-rd/net"
 	"github.com/bio-routing/bio-rd/routingtable"
 	"github.com/bio-routing/bio-rd/routingtable/filter"
@@ -22,10 +24,15 @@ type Peer struct {
 	RouterID                uint32
 	AddPathSend             routingtable.ClientOptions
 	AddPathRecv             bool
-	ImportFilter            *filter.Filter
-	ExportFilter            *filter.Filter
 	RouteServerClient       bool
 	RouteReflectorClient    bool
 	RouteReflectorClusterID uint32
-	IPv6                    bool
+	IPv4                    *AddressFamilyConfig
+	IPv6                    *AddressFamilyConfig
+}
+
+type AddressFamilyConfig struct {
+	RIB          *locRIB.LocRIB
+	ImportFilter *filter.Filter
+	ExportFilter *filter.Filter
 }
diff --git a/main_ipv4.go b/main_ipv4.go
index 65293f07..0d3c5a7c 100644
--- a/main_ipv4.go
+++ b/main_ipv4.go
@@ -43,10 +43,13 @@ func startServer(b server.BGPServer, rib *locRIB.LocRIB) {
 		AddPathSend: routingtable.ClientOptions{
 			MaxPaths: 10,
 		},
-		ImportFilter:      filter.NewAcceptAllFilter(),
-		ExportFilter:      filter.NewAcceptAllFilter(),
+		IPv4: &config.AddressFamilyConfig{
+			RIB:          rib,
+			ImportFilter: filter.NewAcceptAllFilter(),
+			ExportFilter: filter.NewAcceptAllFilter(),
+		},
 		RouteServerClient: true,
-	}, rib)
+	})
 
 	b.AddPeer(config.Peer{
 		AdminEnabled:      true,
@@ -63,8 +66,11 @@ func startServer(b server.BGPServer, rib *locRIB.LocRIB) {
 			MaxPaths: 10,
 		},
 		AddPathRecv:       true,
-		ImportFilter:      filter.NewAcceptAllFilter(),
-		ExportFilter:      filter.NewAcceptAllFilter(),
 		RouteServerClient: true,
-	}, rib)
+		IPv4: &config.AddressFamilyConfig{
+			RIB:          rib,
+			ImportFilter: filter.NewAcceptAllFilter(),
+			ExportFilter: filter.NewAcceptAllFilter(),
+		},
+	})
 }
diff --git a/main_ipv6.go b/main_ipv6.go
index 160007cc..2d4a2410 100644
--- a/main_ipv6.go
+++ b/main_ipv6.go
@@ -41,10 +41,12 @@ func startServer(b server.BGPServer, rib *locRIB.LocRIB) {
 		AddPathSend: routingtable.ClientOptions{
 			BestOnly: true,
 		},
-		ImportFilter: filter.NewAcceptAllFilter(),
-		ExportFilter: filter.NewDrainFilter(),
-		IPv6:         true,
-	}, rib)
+		IPv6: &config.AddressFamilyConfig{
+			RIB:          rib,
+			ImportFilter: filter.NewAcceptAllFilter(),
+			ExportFilter: filter.NewDrainFilter(),
+		},
+	})
 
 	b.AddPeer(config.Peer{
 		AdminEnabled:      true,
@@ -60,8 +62,10 @@ func startServer(b server.BGPServer, rib *locRIB.LocRIB) {
 		AddPathSend: routingtable.ClientOptions{
 			BestOnly: true,
 		},
-		ImportFilter: filter.NewDrainFilter(),
-		ExportFilter: filter.NewAcceptAllFilter(),
-		IPv6:         true,
-	}, rib)
+		IPv6: &config.AddressFamilyConfig{
+			RIB:          rib,
+			ImportFilter: filter.NewDrainFilter(),
+			ExportFilter: filter.NewAcceptAllFilter(),
+		},
+	})
 }
diff --git a/protocols/bgp/server/family_routing.go b/protocols/bgp/server/family_routing.go
index a5115480..64332ac6 100644
--- a/protocols/bgp/server/family_routing.go
+++ b/protocols/bgp/server/family_routing.go
@@ -32,23 +32,25 @@ type familyRouting struct {
 	initialized bool
 }
 
-func newFamilyRouting(afi uint16, safi uint8, rib *locRIB.LocRIB, fsm *FSM) *familyRouting {
+func newFamilyRouting(afi uint16, safi uint8, params *familyParameters, fsm *FSM) *familyRouting {
 	return &familyRouting{
-		afi:  afi,
-		safi: safi,
-		rib:  rib,
-		fsm:  fsm,
+		afi:          afi,
+		safi:         safi,
+		fsm:          fsm,
+		rib:          params.rib,
+		importFilter: params.importFilter,
+		exportFilter: params.exportFilter,
 	}
 }
 
 func (f *familyRouting) init(n *routingtable.Neighbor) {
 	contributingASNs := f.rib.GetContributingASNs()
 
-	f.adjRIBIn = adjRIBIn.New(f.fsm.peer.importFilter, contributingASNs, f.fsm.peer.routerID, f.fsm.peer.clusterID)
+	f.adjRIBIn = adjRIBIn.New(f.importFilter, contributingASNs, f.fsm.peer.routerID, f.fsm.peer.clusterID)
 	contributingASNs.Add(f.fsm.peer.localASN)
 	f.adjRIBIn.Register(f.rib)
 
-	f.adjRIBOut = adjRIBOut.New(n, f.fsm.peer.exportFilter)
+	f.adjRIBOut = adjRIBOut.New(n, f.exportFilter)
 	clientOptions := routingtable.ClientOptions{
 		BestOnly: true,
 	}
diff --git a/protocols/bgp/server/fsm.go b/protocols/bgp/server/fsm.go
index 05a83eec..521e5b98 100644
--- a/protocols/bgp/server/fsm.go
+++ b/protocols/bgp/server/fsm.go
@@ -97,8 +97,14 @@ func newFSM2(peer *peer) *FSM {
 		stopMsgRecvCh:    make(chan struct{}),
 		options:          &types.Options{},
 	}
-	f.ipv4Unicast = newFamilyRouting(packet.IPv4AFI, packet.UnicastSAFI, peer.rib, f)
-	f.ipv6Unicast = newFamilyRouting(packet.IPv6AFI, packet.UnicastSAFI, peer.rib, f)
+
+	if peer.ipv4 != nil {
+		f.ipv4Unicast = newFamilyRouting(packet.IPv4AFI, packet.UnicastSAFI, peer.ipv4, f)
+	}
+
+	if peer.ipv6 != nil {
+		f.ipv6Unicast = newFamilyRouting(packet.IPv6AFI, packet.UnicastSAFI, peer.ipv6, f)
+	}
 
 	return f
 }
diff --git a/protocols/bgp/server/fsm_established.go b/protocols/bgp/server/fsm_established.go
index 78f7f194..f615b19a 100644
--- a/protocols/bgp/server/fsm_established.go
+++ b/protocols/bgp/server/fsm_established.go
@@ -78,15 +78,26 @@ func (s *establishedState) init() error {
 		ClusterID:            s.fsm.peer.clusterID,
 	}
 
-	s.fsm.ipv4Unicast.init(n)
-	s.fsm.ipv6Unicast.init(n)
+	if s.fsm.ipv4Unicast != nil {
+		s.fsm.ipv4Unicast.init(n)
+	}
+
+	if s.fsm.ipv6Unicast != nil {
+		s.fsm.ipv6Unicast.init(n)
+	}
+
 	s.fsm.ribsInitialized = true
 	return nil
 }
 
 func (s *establishedState) uninit() {
-	s.fsm.ipv4Unicast.dispose()
-	s.fsm.ipv6Unicast.dispose()
+	if s.fsm.ipv4Unicast != nil {
+		s.fsm.ipv4Unicast.dispose()
+	}
+
+	if s.fsm.ipv6Unicast != nil {
+		s.fsm.ipv6Unicast.dispose()
+	}
 }
 
 func (s *establishedState) manualStop() (state, string) {
@@ -184,9 +195,13 @@ func (s *establishedState) update(msg *packet.BGPMessage) (state, string) {
 
 	switch afi {
 	case packet.IPv4AFI:
-		s.fsm.ipv4Unicast.processUpdate(u)
+		if s.fsm.ipv4Unicast != nil {
+			s.fsm.ipv4Unicast.processUpdate(u)
+		}
 	case packet.IPv6AFI:
-		s.fsm.ipv6Unicast.processUpdate(u)
+		if s.fsm.ipv6Unicast != nil {
+			s.fsm.ipv6Unicast.processUpdate(u)
+		}
 	}
 
 	return newEstablishedState(s.fsm), s.fsm.reason
@@ -197,10 +212,7 @@ func (s *establishedState) addressFamilyForUpdate(u *packet.BGPUpdate) (afi uint
 		return packet.IPv4AFI, packet.UnicastSAFI
 	}
 
-	cur := u.PathAttributes
-	for cur != nil {
-		cur = cur.Next
-
+	for cur := u.PathAttributes; cur != nil; cur = cur.Next {
 		if cur.TypeCode == packet.MultiProtocolReachNLRICode {
 			a := cur.Value.(packet.MultiProtocolReachNLRI)
 			return a.AFI, a.SAFI
diff --git a/protocols/bgp/server/fsm_test.go b/protocols/bgp/server/fsm_test.go
index e39d3d6a..15249537 100644
--- a/protocols/bgp/server/fsm_test.go
+++ b/protocols/bgp/server/fsm_test.go
@@ -16,11 +16,13 @@ import (
 // TestFSM100Updates emulates receiving 100 BGP updates and withdraws. Checks route counts.
 func TestFSM100Updates(t *testing.T) {
 	fsmA := newFSM2(&peer{
-		addr:         bnet.IPv4FromOctets(169, 254, 100, 100),
-		rib:          locRIB.New(),
-		importFilter: filter.NewAcceptAllFilter(),
-		exportFilter: filter.NewAcceptAllFilter(),
-		routerID:     bnet.IPv4FromOctets(1, 1, 1, 1).ToUint32(),
+		addr:     bnet.IPv4FromOctets(169, 254, 100, 100),
+		routerID: bnet.IPv4FromOctets(1, 1, 1, 1).ToUint32(),
+		ipv4: &familyParameters{
+			rib:          locRIB.New(),
+			importFilter: filter.NewAcceptAllFilter(),
+			exportFilter: filter.NewAcceptAllFilter(),
+		},
 	})
 
 	fsmA.holdTimer = time.NewTimer(time.Second * 90)
diff --git a/protocols/bgp/server/peer.go b/protocols/bgp/server/peer.go
index 02cf053b..4f101d81 100644
--- a/protocols/bgp/server/peer.go
+++ b/protocols/bgp/server/peer.go
@@ -28,7 +28,6 @@ type peer struct {
 	fsms   []*FSM
 	fsmsMu sync.Mutex
 
-	rib                  *locRIB.LocRIB
 	routerID             uint32
 	addPathSend          routingtable.ClientOptions
 	addPathRecv          bool
@@ -36,11 +35,18 @@ type peer struct {
 	keepaliveTime        time.Duration
 	holdTime             time.Duration
 	optOpenParams        []packet.OptParam
-	importFilter         *filter.Filter
-	exportFilter         *filter.Filter
 	routeServerClient    bool
 	routeReflectorClient bool
 	clusterID            uint32
+
+	ipv4 *familyParameters
+	ipv6 *familyParameters
+}
+
+type familyParameters struct {
+	rib          *locRIB.LocRIB
+	importFilter *filter.Filter
+	exportFilter *filter.Filter
 }
 
 func (p *peer) snapshot() PeerInfo {
@@ -103,37 +109,41 @@ 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 *locRIB.LocRIB, server *bgpServer) (*peer, error) {
+func newPeer(c config.Peer, server *bgpServer) (*peer, error) {
 	if c.LocalAS == 0 {
 		c.LocalAS = server.localASN
 	}
+
 	p := &peer{
 		server:               server,
 		addr:                 c.PeerAddress,
 		peerASN:              c.PeerAS,
 		localASN:             c.LocalAS,
 		fsms:                 make([]*FSM, 0),
-		rib:                  rib,
 		addPathSend:          c.AddPathSend,
 		addPathRecv:          c.AddPathRecv,
 		reconnectInterval:    c.ReconnectInterval,
 		keepaliveTime:        c.KeepAlive,
 		holdTime:             c.HoldTime,
 		optOpenParams:        make([]packet.OptParam, 0),
-		importFilter:         filterOrDefault(c.ImportFilter),
-		exportFilter:         filterOrDefault(c.ExportFilter),
 		routeServerClient:    c.RouteServerClient,
 		routeReflectorClient: c.RouteReflectorClient,
 		clusterID:            c.RouteReflectorClusterID,
 	}
 
+	if c.IPv4 != nil {
+		p.ipv4 = &familyParameters{
+			rib:          c.IPv4.RIB,
+			importFilter: filterOrDefault(c.IPv4.ImportFilter),
+			exportFilter: filterOrDefault(c.IPv4.ExportFilter),
+		}
+	}
+
 	// If we are a route reflector and no ClusterID was set, use our RouterID
 	if p.routeReflectorClient && p.clusterID == 0 {
 		p.clusterID = c.RouterID
 	}
 
-	p.fsms = append(p.fsms, NewActiveFSM2(p))
-
 	caps := make(packet.Capabilities, 0)
 
 	addPathEnabled, addPathCap := handleAddPathCapability(c)
@@ -143,7 +153,12 @@ func newPeer(c config.Peer, rib *locRIB.LocRIB, server *bgpServer) (*peer, error
 
 	caps = append(caps, asn4Capability(c))
 
-	if c.IPv6 {
+	if c.IPv6 != nil {
+		p.ipv6 = &familyParameters{
+			rib:          c.IPv6.RIB,
+			importFilter: filterOrDefault(c.IPv6.ImportFilter),
+			exportFilter: filterOrDefault(c.IPv6.ExportFilter),
+		}
 		caps = append(caps, multiProtocolCapability(packet.IPv6AFI))
 	}
 
@@ -152,6 +167,8 @@ func newPeer(c config.Peer, rib *locRIB.LocRIB, server *bgpServer) (*peer, error
 		Value: caps,
 	})
 
+	p.fsms = append(p.fsms, NewActiveFSM2(p))
+
 	return p, nil
 }
 
diff --git a/protocols/bgp/server/server.go b/protocols/bgp/server/server.go
index db6ae24f..1bccb7b6 100644
--- a/protocols/bgp/server/server.go
+++ b/protocols/bgp/server/server.go
@@ -9,7 +9,6 @@ import (
 
 	"github.com/bio-routing/bio-rd/config"
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
-	"github.com/bio-routing/bio-rd/routingtable/locRIB"
 	log "github.com/sirupsen/logrus"
 )
 
@@ -28,7 +27,7 @@ type bgpServer struct {
 type BGPServer interface {
 	RouterID() uint32
 	Start(*config.Global) error
-	AddPeer(config.Peer, *locRIB.LocRIB) error
+	AddPeer(config.Peer) error
 	GetPeerInfoAll() map[string]PeerInfo
 }
 
@@ -112,8 +111,8 @@ func (b *bgpServer) incomingConnectionWorker() {
 	}
 }
 
-func (b *bgpServer) AddPeer(c config.Peer, rib *locRIB.LocRIB) error {
-	peer, err := newPeer(c, rib, b)
+func (b *bgpServer) AddPeer(c config.Peer) error {
+	peer, err := newPeer(c, b)
 	if err != nil {
 		return err
 	}
diff --git a/protocols/bgp/server/server_test.go b/protocols/bgp/server/server_test.go
index fffe879c..896c3201 100644
--- a/protocols/bgp/server/server_test.go
+++ b/protocols/bgp/server/server_test.go
@@ -41,10 +41,13 @@ func TestBgpServerPeerSnapshot(t *testing.T) {
 		AddPathSend: routingtable.ClientOptions{
 			MaxPaths: 10,
 		},
-		ImportFilter: filter.NewDrainFilter(),
-		ExportFilter: filter.NewAcceptAllFilter(),
+		IPv4: &config.AddressFamilyConfig{
+			RIB:          rib,
+			ImportFilter: filter.NewDrainFilter(),
+			ExportFilter: filter.NewAcceptAllFilter(),
+		},
 	}
-	s.AddPeer(pc, rib)
+	s.AddPeer(pc)
 
 	info = s.GetPeerInfoAll()
 	if want, got := 1, len(info); want != got {
diff --git a/protocols/bgp/server/update_sender_test.go b/protocols/bgp/server/update_sender_test.go
index 646bf5d0..19e9a99c 100644
--- a/protocols/bgp/server/update_sender_test.go
+++ b/protocols/bgp/server/update_sender_test.go
@@ -868,12 +868,25 @@ func TestSender(t *testing.T) {
 
 	for _, test := range tests {
 		fsmA := newFSM2(&peer{
-			addr:         bnet.IPv4FromOctets(169, 254, 100, 100),
-			rib:          locRIB.New(),
-			importFilter: filter.NewAcceptAllFilter(),
-			exportFilter: filter.NewAcceptAllFilter(),
+			addr: bnet.IPv4FromOctets(169, 254, 100, 100),
 		})
 
+		rib := locRIB.New()
+		if test.afi == packet.IPv6AFI {
+			fsmA.options.SupportsMultiProtocol = true
+			fsmA.ipv6Unicast = newFamilyRouting(packet.IPv6AFI, packet.UnicastSAFI, &familyParameters{
+				rib:          rib,
+				importFilter: filter.NewAcceptAllFilter(),
+				exportFilter: filter.NewAcceptAllFilter(),
+			}, fsmA)
+		} else {
+			fsmA.ipv4Unicast = newFamilyRouting(packet.IPv4AFI, packet.UnicastSAFI, &familyParameters{
+				rib:          rib,
+				importFilter: filter.NewAcceptAllFilter(),
+				exportFilter: filter.NewAcceptAllFilter(),
+			}, fsmA)
+		}
+
 		fsmA.holdTimer = time.NewTimer(time.Second * 90)
 		fsmA.keepaliveTimer = time.NewTimer(time.Second * 30)
 		fsmA.connectRetryTimer = time.NewTimer(time.Second * 120)
@@ -886,10 +899,6 @@ func TestSender(t *testing.T) {
 			}
 		}
 
-		if test.afi == packet.IPv6AFI {
-			fsmA.options.SupportsMultiProtocol = true
-		}
-
 		updateSender := newUpdateSender(fsmA, test.afi, packet.UnicastSAFI)
 
 		for _, pathPfx := range test.paths {
-- 
GitLab