github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconciler/neighbor.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package reconciler
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	"github.com/cilium/hive/cell"
    11  	"github.com/sirupsen/logrus"
    12  
    13  	"github.com/cilium/cilium/pkg/bgpv1/manager/instance"
    14  	"github.com/cilium/cilium/pkg/bgpv1/manager/store"
    15  	"github.com/cilium/cilium/pkg/bgpv1/types"
    16  	v2alpha1api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    17  	"github.com/cilium/cilium/pkg/k8s/resource"
    18  	slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1"
    19  	"github.com/cilium/cilium/pkg/option"
    20  )
    21  
    22  type NeighborReconcilerOut struct {
    23  	cell.Out
    24  
    25  	Reconciler ConfigReconciler `group:"bgp-config-reconciler"`
    26  }
    27  
    28  // NeighborReconciler is a ConfigReconciler which reconciles the peers of the
    29  // provided BGP server with the provided CiliumBGPVirtualRouter.
    30  type NeighborReconciler struct {
    31  	SecretStore  store.BGPCPResourceStore[*slim_corev1.Secret]
    32  	DaemonConfig *option.DaemonConfig
    33  }
    34  
    35  func NewNeighborReconciler(SecretStore store.BGPCPResourceStore[*slim_corev1.Secret], DaemonConfig *option.DaemonConfig) NeighborReconcilerOut {
    36  	return NeighborReconcilerOut{
    37  		Reconciler: &NeighborReconciler{
    38  			SecretStore:  SecretStore,
    39  			DaemonConfig: DaemonConfig,
    40  		},
    41  	}
    42  }
    43  
    44  func (r *NeighborReconciler) Name() string {
    45  	return "Neighbor"
    46  }
    47  
    48  // Priority of neighbor reconciler is higher than pod/service announcements.
    49  // This is important for graceful restart case, where all expected routes are pushed
    50  // into gobgp RIB before neighbors are added. So, gobgp can send out all prefixes
    51  // within initial update message exchange with neighbors before sending EOR marker.
    52  func (r *NeighborReconciler) Priority() int {
    53  	return 60
    54  }
    55  
    56  func (r *NeighborReconciler) Init(_ *instance.ServerWithConfig) error {
    57  	return nil
    58  }
    59  
    60  func (r *NeighborReconciler) Cleanup(_ *instance.ServerWithConfig) {}
    61  
    62  func (r *NeighborReconciler) Reconcile(ctx context.Context, p ReconcileParams) error {
    63  	if p.DesiredConfig == nil {
    64  		return fmt.Errorf("attempted neighbor reconciliation with nil CiliumBGPPeeringPolicy")
    65  	}
    66  	if p.CurrentServer == nil {
    67  		return fmt.Errorf("attempted neighbor reconciliation with nil ServerWithConfig")
    68  	}
    69  	var (
    70  		l = log.WithFields(
    71  			logrus.Fields{
    72  				"component": "NeighborReconciler",
    73  			},
    74  		)
    75  		toCreate []*v2alpha1api.CiliumBGPNeighbor
    76  		toRemove []*v2alpha1api.CiliumBGPNeighbor
    77  		toUpdate []*v2alpha1api.CiliumBGPNeighbor
    78  		curNeigh []*v2alpha1api.CiliumBGPNeighbor = nil
    79  	)
    80  	newNeigh := p.DesiredConfig.Neighbors
    81  
    82  	metaMap := r.getMetadata(p.CurrentServer)
    83  	if len(metaMap) > 0 {
    84  		curNeigh = []*v2alpha1api.CiliumBGPNeighbor{}
    85  		for _, meta := range metaMap {
    86  			curNeigh = append(curNeigh, meta.currentConfig)
    87  		}
    88  	}
    89  
    90  	// an nset member which book keeps which universe it exists in.
    91  	type member struct {
    92  		new *v2alpha1api.CiliumBGPNeighbor
    93  		cur *v2alpha1api.CiliumBGPNeighbor
    94  	}
    95  
    96  	nset := map[string]*member{}
    97  
    98  	// populate set from universe of new neighbors
    99  	for i, n := range newNeigh {
   100  		var (
   101  			key = r.neighborID(&n)
   102  			h   *member
   103  			ok  bool
   104  		)
   105  		if h, ok = nset[key]; !ok {
   106  			nset[key] = &member{
   107  				new: &newNeigh[i],
   108  			}
   109  			continue
   110  		}
   111  		h.new = &newNeigh[i]
   112  	}
   113  
   114  	// populate set from universe of current neighbors
   115  	for _, n := range curNeigh {
   116  		var (
   117  			key = r.neighborID(n)
   118  			h   *member
   119  			ok  bool
   120  		)
   121  		if h, ok = nset[key]; !ok {
   122  			nset[key] = &member{
   123  				cur: n,
   124  			}
   125  			continue
   126  		}
   127  		h.cur = n
   128  	}
   129  
   130  	for _, m := range nset {
   131  		// present in new neighbors (set new) but not in current neighbors (set cur)
   132  		if m.new != nil && m.cur == nil {
   133  			toCreate = append(toCreate, m.new)
   134  		}
   135  		// present in current neighbors (set cur) but not in new neighbors (set new)
   136  		if m.cur != nil && m.new == nil {
   137  			toRemove = append(toRemove, m.cur)
   138  		}
   139  		// present in both new neighbors (set new) and current neighbors (set cur), update if they are not equal
   140  		if m.cur != nil && m.new != nil {
   141  			if !m.cur.DeepEqual(m.new) {
   142  				toUpdate = append(toUpdate, m.new)
   143  			} else {
   144  				// Fetch the secret to check if the TCP password changed.
   145  				tcpPassword, err := r.fetchPeerPassword(p.CurrentServer, m.new)
   146  				if err != nil {
   147  					return err
   148  				}
   149  				if r.changedPeerPassword(p.CurrentServer, m.new, tcpPassword) {
   150  					toUpdate = append(toUpdate, m.new)
   151  				}
   152  			}
   153  		}
   154  	}
   155  
   156  	// remove neighbors
   157  	for _, n := range toRemove {
   158  		l.Infof("Removing peer %v %v from local ASN %v", n.PeerAddress, n.PeerASN, p.DesiredConfig.LocalASN)
   159  		if err := p.CurrentServer.Server.RemoveNeighbor(ctx, types.NeighborRequest{Neighbor: n}); err != nil {
   160  			return fmt.Errorf("failed while reconciling neighbor %v %v: %w", n.PeerAddress, n.PeerASN, err)
   161  		}
   162  		r.deleteMetadata(p.CurrentServer, n)
   163  	}
   164  
   165  	// update neighbors
   166  	for _, n := range toUpdate {
   167  		l.Infof("Updating peer %v %v in local ASN %v", n.PeerAddress, n.PeerASN, p.DesiredConfig.LocalASN)
   168  		tcpPassword, err := r.fetchPeerPassword(p.CurrentServer, n)
   169  		if err != nil {
   170  			return fmt.Errorf("failed fetching password for neighbor %v %v: %w", n.PeerAddress, n.PeerASN, err)
   171  		}
   172  		if err := p.CurrentServer.Server.UpdateNeighbor(ctx, types.NeighborRequest{Neighbor: n, Password: tcpPassword}); err != nil {
   173  			return fmt.Errorf("failed while reconciling neighbor %v %v: %w", n.PeerAddress, n.PeerASN, err)
   174  		}
   175  		r.updateMetadata(p.CurrentServer, n, tcpPassword)
   176  	}
   177  
   178  	// create new neighbors
   179  	for _, n := range toCreate {
   180  		l.Infof("Adding peer %v %v to local ASN %v", n.PeerAddress, n.PeerASN, p.DesiredConfig.LocalASN)
   181  		tcpPassword, err := r.fetchPeerPassword(p.CurrentServer, n)
   182  		if err != nil {
   183  			return fmt.Errorf("failed fetching password for neighbor %v %v: %w", n.PeerAddress, n.PeerASN, err)
   184  		}
   185  		if err := p.CurrentServer.Server.AddNeighbor(ctx, types.NeighborRequest{Neighbor: n, Password: tcpPassword}); err != nil {
   186  			return fmt.Errorf("failed while reconciling neighbor %v %v: %w", n.PeerAddress, n.PeerASN, err)
   187  		}
   188  		r.updateMetadata(p.CurrentServer, n, tcpPassword)
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  // NeighborReconcilerMetadata keeps a map of peers to passwords, fetched from
   195  // secrets. Key is PeerAddress+PeerASN.
   196  type NeighborReconcilerMetadata map[string]neighborReconcilerMetadata
   197  
   198  type neighborReconcilerMetadata struct {
   199  	currentPassword string
   200  	currentConfig   *v2alpha1api.CiliumBGPNeighbor
   201  }
   202  
   203  func (r *NeighborReconciler) getMetadata(sc *instance.ServerWithConfig) NeighborReconcilerMetadata {
   204  	if _, found := sc.ReconcilerMetadata[r.Name()]; !found {
   205  		sc.ReconcilerMetadata[r.Name()] = make(NeighborReconcilerMetadata)
   206  	}
   207  	return sc.ReconcilerMetadata[r.Name()].(NeighborReconcilerMetadata)
   208  }
   209  
   210  func (r *NeighborReconciler) fetchPeerPassword(sc *instance.ServerWithConfig, n *v2alpha1api.CiliumBGPNeighbor) (string, error) {
   211  	l := log.WithFields(
   212  		logrus.Fields{
   213  			"component": "NeighborReconciler.fetchPeerPassword",
   214  		},
   215  	)
   216  	if n.AuthSecretRef != nil {
   217  		secretRef := *n.AuthSecretRef
   218  		old := r.getMetadata(sc)[r.neighborID(n)].currentPassword
   219  
   220  		secret, ok, err := r.fetchSecret(secretRef)
   221  		if err != nil {
   222  			return "", fmt.Errorf("failed to fetch secret %q: %w", secretRef, err)
   223  		}
   224  		if !ok {
   225  			if old != "" {
   226  				l.Errorf("Failed to fetch secret %q: not found (will continue with old secret)", secretRef)
   227  				return old, nil
   228  			}
   229  			l.Errorf("Failed to fetch secret %q: not found (will continue with empty password)", secretRef)
   230  			return "", nil
   231  		}
   232  		tcpPassword := string(secret["password"])
   233  		if tcpPassword == "" {
   234  			return "", fmt.Errorf("failed to fetch secret %q: missing password key", secretRef)
   235  		}
   236  		l.Debugf("Using TCP password from secret %q", secretRef)
   237  		return tcpPassword, nil
   238  	}
   239  	return "", nil
   240  }
   241  
   242  func (r *NeighborReconciler) fetchSecret(name string) (map[string][]byte, bool, error) {
   243  	if r.SecretStore == nil {
   244  		return nil, false, fmt.Errorf("SecretsNamespace not configured")
   245  	}
   246  	item, ok, err := r.SecretStore.GetByKey(resource.Key{Namespace: r.DaemonConfig.BGPSecretsNamespace, Name: name})
   247  	if err != nil || !ok {
   248  		return nil, ok, err
   249  	}
   250  	result := map[string][]byte{}
   251  	for k, v := range item.Data {
   252  		result[k] = []byte(v)
   253  	}
   254  	return result, true, nil
   255  }
   256  
   257  func (r *NeighborReconciler) changedPeerPassword(sc *instance.ServerWithConfig, n *v2alpha1api.CiliumBGPNeighbor, tcpPassword string) bool {
   258  	return r.getMetadata(sc)[r.neighborID(n)].currentPassword != tcpPassword
   259  }
   260  
   261  func (r *NeighborReconciler) updateMetadata(sc *instance.ServerWithConfig, n *v2alpha1api.CiliumBGPNeighbor, tcpPassword string) {
   262  	r.getMetadata(sc)[r.neighborID(n)] = neighborReconcilerMetadata{
   263  		currentPassword: tcpPassword,
   264  		currentConfig:   n.DeepCopy(),
   265  	}
   266  }
   267  
   268  func (r *NeighborReconciler) deleteMetadata(sc *instance.ServerWithConfig, n *v2alpha1api.CiliumBGPNeighbor) {
   269  	delete(r.getMetadata(sc), r.neighborID(n))
   270  }
   271  
   272  func (r *NeighborReconciler) neighborID(n *v2alpha1api.CiliumBGPNeighbor) string {
   273  	return fmt.Sprintf("%s%d", n.PeerAddress, n.PeerASN)
   274  }