github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconcilerv2/neighbor.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package reconcilerv2 5 6 import ( 7 "context" 8 "fmt" 9 "net/netip" 10 11 "github.com/cilium/hive/cell" 12 "github.com/sirupsen/logrus" 13 14 "github.com/cilium/cilium/pkg/bgpv1/manager/instance" 15 "github.com/cilium/cilium/pkg/bgpv1/manager/store" 16 "github.com/cilium/cilium/pkg/bgpv1/types" 17 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 18 "github.com/cilium/cilium/pkg/k8s/resource" 19 slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" 20 "github.com/cilium/cilium/pkg/option" 21 ) 22 23 // NeighborReconciler is a ConfigReconciler which reconciles the peers of the 24 // provided BGP server with the provided CiliumBGPVirtualRouter. 25 type NeighborReconciler struct { 26 Logger logrus.FieldLogger 27 SecretStore store.BGPCPResourceStore[*slim_corev1.Secret] 28 PeerConfig store.BGPCPResourceStore[*v2alpha1.CiliumBGPPeerConfig] 29 DaemonConfig *option.DaemonConfig 30 } 31 32 type NeighborReconcilerOut struct { 33 cell.Out 34 35 Reconciler ConfigReconciler `group:"bgp-config-reconciler-v2"` 36 } 37 38 type NeighborReconcilerIn struct { 39 cell.In 40 Logger logrus.FieldLogger 41 SecretStore store.BGPCPResourceStore[*slim_corev1.Secret] 42 PeerConfig store.BGPCPResourceStore[*v2alpha1.CiliumBGPPeerConfig] 43 DaemonConfig *option.DaemonConfig 44 } 45 46 func NewNeighborReconciler(params NeighborReconcilerIn) NeighborReconcilerOut { 47 logger := params.Logger.WithField(types.ReconcilerLogField, "Neighbor") 48 49 return NeighborReconcilerOut{ 50 Reconciler: &NeighborReconciler{ 51 Logger: logger, 52 SecretStore: params.SecretStore, 53 PeerConfig: params.PeerConfig, 54 DaemonConfig: params.DaemonConfig, 55 }, 56 } 57 } 58 59 // PeerData keeps a peer and its configuration. It also keeps the TCP password from secret store. 60 // +deepequal-gen=true 61 // Note: If you change PeerDate, do not forget to 'make generate-k8s-api', which will update DeepEqual method. 62 type PeerData struct { 63 Peer *v2alpha1.CiliumBGPNodePeer 64 Config *v2alpha1.CiliumBGPPeerConfigSpec 65 Password string 66 } 67 68 // NeighborReconcilerMetadata keeps a map of running peers to peer configuration. 69 // key is the peer name. 70 type NeighborReconcilerMetadata map[string][]*PeerData 71 72 func (r *NeighborReconciler) getMetadata(i *instance.BGPInstance) NeighborReconcilerMetadata { 73 if _, found := i.Metadata[r.Name()]; !found { 74 i.Metadata[r.Name()] = make(NeighborReconcilerMetadata) 75 } 76 return i.Metadata[r.Name()].(NeighborReconcilerMetadata) 77 } 78 79 func (r *NeighborReconciler) upsertMetadata(i *instance.BGPInstance, instanceName string, d *PeerData) { 80 if i == nil || d == nil { 81 return 82 } 83 84 neighMetadata := r.getMetadata(i) 85 86 peers, exists := neighMetadata[instanceName] 87 if !exists { 88 neighMetadata[instanceName] = []*PeerData{d} 89 return 90 } 91 92 found := false 93 for i, p := range peers { 94 if p.Peer.Name == d.Peer.Name { 95 peers[i] = d 96 found = true 97 break 98 } 99 } 100 101 if !found { 102 peers = append(peers, d) 103 } 104 105 neighMetadata[instanceName] = peers 106 } 107 108 func (r *NeighborReconciler) deleteMetadata(i *instance.BGPInstance, instanceName string, d *PeerData) { 109 if i == nil || d == nil { 110 return 111 } 112 113 neighMetadata := r.getMetadata(i) 114 peers, exists := neighMetadata[instanceName] 115 if !exists { 116 return 117 } 118 119 for i, p := range peers { 120 if p.Peer.Name == d.Peer.Name { 121 peers[i] = peers[len(peers)-1] 122 peers = peers[:len(peers)-1] 123 break 124 } 125 } 126 127 neighMetadata[instanceName] = peers 128 } 129 130 func (r *NeighborReconciler) Name() string { 131 return "Neighbor" 132 } 133 134 // Priority of neighbor reconciler is higher than pod/service announcements. 135 // This is important for graceful restart case, where all expected routes are pushed 136 // into gobgp RIB before neighbors are added. So, gobgp can send out all prefixes 137 // within initial update message exchange with neighbors before sending EOR marker. 138 func (r *NeighborReconciler) Priority() int { 139 return 60 140 } 141 142 func (r *NeighborReconciler) Init(_ *instance.BGPInstance) error { 143 return nil 144 } 145 146 func (r *NeighborReconciler) Cleanup(_ *instance.BGPInstance) {} 147 148 func (r *NeighborReconciler) Reconcile(ctx context.Context, p ReconcileParams) error { 149 if p.DesiredConfig == nil { 150 return fmt.Errorf("attempted neighbor reconciliation with nil CiliumBGPNodeInstance") 151 } 152 if p.BGPInstance == nil { 153 return fmt.Errorf("attempted neighbor reconciliation with nil BGPInstance") 154 } 155 156 var ( 157 l = r.Logger.WithFields(logrus.Fields{ 158 types.InstanceLogField: p.DesiredConfig.Name, 159 }) 160 161 toCreate []*PeerData 162 toRemove []*PeerData 163 toUpdate []*PeerData 164 curNeigh []*PeerData = nil 165 ) 166 newNeigh := p.DesiredConfig.Peers 167 168 l.Debug("Begin reconciling peers") 169 170 // get current configured peers 171 curInstance := r.getMetadata(p.BGPInstance) 172 if curInstance != nil { 173 curNeigh = curInstance[p.DesiredConfig.Name] 174 } 175 176 type member struct { 177 new *PeerData 178 cur *PeerData 179 } 180 181 nset := map[string]*member{} 182 183 for i, n := range newNeigh { 184 // validate that peer has ASN and address. In current implementation these fields are 185 // mandatory for a peer. Eventually we will relax this restriction with implementation 186 // of BGP unnumbered. 187 if n.PeerASN == nil { 188 return fmt.Errorf("peer %s does not have a PeerASN", n.Name) 189 } 190 191 if n.PeerAddress == nil { 192 return fmt.Errorf("peer %s does not have a PeerAddress", n.Name) 193 } 194 195 var ( 196 key = r.neighborID(&n) 197 h *member 198 ok bool 199 ) 200 201 config, exists, err := r.getPeerConfig(n.PeerConfigRef) 202 if err != nil { 203 return err 204 } 205 if !exists { 206 continue // configured peer config does not exist, skip 207 } 208 209 passwd, err := r.getPeerPassword(p.DesiredConfig.Name, n.Name, config) 210 if err != nil { 211 return err 212 } 213 214 if h, ok = nset[key]; !ok { 215 nset[key] = &member{ 216 new: &PeerData{ 217 Peer: &newNeigh[i], 218 Config: config, 219 Password: passwd, 220 }, 221 } 222 continue 223 } 224 h.new = &PeerData{ 225 Peer: &newNeigh[i], 226 Config: config, 227 Password: passwd, 228 } 229 } 230 231 for i, n := range curNeigh { 232 var ( 233 key = r.neighborID(n.Peer) 234 h *member 235 ok bool 236 ) 237 238 if h, ok = nset[key]; !ok { 239 nset[key] = &member{ 240 cur: curNeigh[i], 241 } 242 continue 243 } 244 h.cur = curNeigh[i] 245 } 246 247 for _, m := range nset { 248 // present in new neighbors (set new) but not in current neighbors (set cur) 249 if m.new != nil && m.cur == nil { 250 toCreate = append(toCreate, m.new) 251 } 252 // present in current neighbors (set cur) but not in new neighbors (set new) 253 if m.cur != nil && m.new == nil { 254 toRemove = append(toRemove, m.cur) 255 } 256 // present in both new neighbors (set new) and current neighbors (set cur), update if they are not equal 257 if m.cur != nil && m.new != nil { 258 if !m.cur.DeepEqual(m.new) { 259 toUpdate = append(toUpdate, m.new) 260 } 261 } 262 } 263 264 if len(toCreate) > 0 || len(toRemove) > 0 || len(toUpdate) > 0 { 265 l.Info("Reconciling peers for instance") 266 } else { 267 l.Debug("No peer changes necessary") 268 } 269 270 // remove neighbors 271 for _, n := range toRemove { 272 l.WithField(types.PeerLogField, n.Peer.Name).Info("Removing peer") 273 274 if err := p.BGPInstance.Router.RemoveNeighbor(ctx, types.NeighborRequest{ 275 Peer: n.Peer, 276 }); err != nil { 277 return fmt.Errorf("failed to remove neigbhor %s from instance %s: %w", n.Peer.Name, p.DesiredConfig.Name, err) 278 } 279 // update metadata 280 r.deleteMetadata(p.BGPInstance, p.DesiredConfig.Name, n) 281 } 282 283 // update neighbors 284 for _, n := range toUpdate { 285 l.WithField(types.PeerLogField, n.Peer.Name).Info("Updating peer") 286 287 if err := p.BGPInstance.Router.UpdateNeighbor(ctx, types.NeighborRequest{ 288 Peer: n.Peer, 289 PeerConfig: n.Config, 290 Password: n.Password, 291 }); err != nil { 292 return fmt.Errorf("failed to update neigbhor %s in instance %s: %w", n.Peer.Name, p.DesiredConfig.Name, err) 293 } 294 // update metadata 295 r.upsertMetadata(p.BGPInstance, p.DesiredConfig.Name, n) 296 } 297 298 // create new neighbors 299 for _, n := range toCreate { 300 l.WithField(types.PeerLogField, n.Peer.Name).Info("Adding peer") 301 302 if err := p.BGPInstance.Router.AddNeighbor(ctx, types.NeighborRequest{ 303 Peer: n.Peer, 304 PeerConfig: n.Config, 305 Password: n.Password, 306 }); err != nil { 307 return fmt.Errorf("failed to add neigbhor %s in instance %s: %w", n.Peer.Name, p.DesiredConfig.Name, err) 308 } 309 // update metadata 310 r.upsertMetadata(p.BGPInstance, p.DesiredConfig.Name, n) 311 } 312 313 l.Debug("Done reconciling peers") 314 return nil 315 } 316 317 // getPeerConfig returns the CiliumBGPPeerConfigSpec for the given peerConfig. 318 // If peerConfig is not specified, returns the default config. 319 // If the referenced peerConfig does not exist, exists returns false. 320 func (r *NeighborReconciler) getPeerConfig(peerConfig *v2alpha1.PeerConfigReference) (conf *v2alpha1.CiliumBGPPeerConfigSpec, exists bool, err error) { 321 if peerConfig == nil || peerConfig.Name == "" { 322 // if peer config is not specified, return default config 323 conf = &v2alpha1.CiliumBGPPeerConfigSpec{} 324 conf.SetDefaults() 325 return conf, true, nil 326 } 327 328 config, exists, err := r.PeerConfig.GetByKey(resource.Key{Name: peerConfig.Name}) 329 if err != nil || !exists { 330 return nil, exists, err 331 } 332 333 conf = &config.Spec 334 conf.SetDefaults() 335 return conf, true, nil 336 } 337 338 func (r *NeighborReconciler) getPeerPassword(instanceName, peerName string, config *v2alpha1.CiliumBGPPeerConfigSpec) (string, error) { 339 if config == nil { 340 return "", nil 341 } 342 343 l := r.Logger.WithFields(logrus.Fields{ 344 types.InstanceLogField: instanceName, 345 types.PeerLogField: peerName, 346 }) 347 348 if config.AuthSecretRef != nil { 349 secretRef := *config.AuthSecretRef 350 351 secret, ok, err := r.fetchSecret(secretRef) 352 if err != nil { 353 return "", fmt.Errorf("failed to fetch secret %q: %w", secretRef, err) 354 } 355 if !ok { 356 return "", nil 357 } 358 tcpPassword := string(secret["password"]) 359 if tcpPassword == "" { 360 return "", fmt.Errorf("failed to fetch secret %q: missing password key", secretRef) 361 } 362 l.Debugf("Using TCP password from secret %q", secretRef) 363 return tcpPassword, nil 364 } 365 return "", nil 366 } 367 368 func (r *NeighborReconciler) fetchSecret(name string) (map[string][]byte, bool, error) { 369 if r.SecretStore == nil { 370 return nil, false, fmt.Errorf("SecretsNamespace not configured") 371 } 372 item, ok, err := r.SecretStore.GetByKey(resource.Key{Namespace: r.DaemonConfig.BGPSecretsNamespace, Name: name}) 373 if err != nil || !ok { 374 return nil, ok, err 375 } 376 result := map[string][]byte{} 377 for k, v := range item.Data { 378 result[k] = []byte(v) 379 } 380 return result, true, nil 381 } 382 383 func GetPeerAddressFromConfig(conf *v2alpha1.CiliumBGPNodeInstance, peerName string) (netip.Addr, error) { 384 if conf == nil { 385 return netip.Addr{}, fmt.Errorf("passed instance is nil") 386 } 387 388 for _, peer := range conf.Peers { 389 if peer.Name == peerName { 390 if peer.PeerAddress != nil { 391 return netip.ParseAddr(*peer.PeerAddress) 392 } else { 393 return netip.Addr{}, fmt.Errorf("peer %s does not have a PeerAddress", peerName) 394 } 395 } 396 } 397 return netip.Addr{}, fmt.Errorf("peer %s not found in instance %s", peerName, conf.Name) 398 } 399 400 func (r *NeighborReconciler) neighborID(n *v2alpha1.CiliumBGPNodePeer) string { 401 return fmt.Sprintf("%s%s%d", n.Name, *n.PeerAddress, *n.PeerASN) 402 }