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 }