github.com/cilium/cilium@v1.16.2/pkg/bgpv1/gobgp/peer.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package gobgp 5 6 import ( 7 "context" 8 "fmt" 9 "net/netip" 10 11 gobgp "github.com/osrg/gobgp/v3/api" 12 13 "github.com/cilium/cilium/pkg/bgpv1/types" 14 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 15 ) 16 17 // AddNeighbor will add the CiliumBGPNeighbor to the gobgp.BgpServer, creating 18 // a BGP peering connection. 19 func (g *GoBGPServer) AddNeighbor(ctx context.Context, n types.NeighborRequest) error { 20 peer, _, err := g.getPeerConfig(ctx, n, false) 21 if err != nil { 22 return err 23 } 24 peerReq := &gobgp.AddPeerRequest{ 25 Peer: peer, 26 } 27 if err = g.server.AddPeer(ctx, peerReq); err != nil { 28 return fmt.Errorf("failed while adding peer %s with ASN %d: %w", peer.Conf.NeighborAddress, peer.Conf.PeerAsn, err) 29 } 30 return nil 31 } 32 33 // UpdateNeighbor will update the existing CiliumBGPNeighbor in the gobgp.BgpServer. 34 func (g *GoBGPServer) UpdateNeighbor(ctx context.Context, n types.NeighborRequest) error { 35 peer, needsHardReset, err := g.getPeerConfig(ctx, n, true) 36 if err != nil { 37 return err 38 } 39 40 // update peer config 41 peerReq := &gobgp.UpdatePeerRequest{ 42 Peer: peer, 43 } 44 updateRes, err := g.server.UpdatePeer(ctx, peerReq) 45 if err != nil { 46 return fmt.Errorf("failed while updating peer %v:%v with ASN %v: %w", peer.Conf.NeighborAddress, peer.Transport.RemotePort, peer.Conf.PeerAsn, err) 47 } 48 49 // perform full / soft peer reset if necessary 50 if needsHardReset || updateRes.NeedsSoftResetIn { 51 g.logger.Infof("Resetting peer %s:%v (ASN %d) due to a config change", peer.Conf.NeighborAddress, peer.Transport.RemotePort, peer.Conf.PeerAsn) 52 resetReq := &gobgp.ResetPeerRequest{ 53 Address: peer.Conf.NeighborAddress, 54 Communication: "Peer configuration changed", 55 } 56 if !needsHardReset { 57 resetReq.Soft = true 58 resetReq.Direction = gobgp.ResetPeerRequest_IN 59 } 60 if err = g.server.ResetPeer(ctx, resetReq); err != nil { 61 return fmt.Errorf("failed while resetting peer %v:%v in ASN %v: %w", peer.Conf.NeighborAddress, peer.Transport.RemotePort, peer.Conf.PeerAsn, err) 62 } 63 } 64 65 return nil 66 } 67 68 // convertBGPNeighborSAFI will convert a slice of CiliumBGPFamily to a slice of 69 // gobgp.AfiSafi. 70 // 71 // Our internal S/Afi types use the same integer values as the gobgp library, 72 // so we can simply cast our types into the corresponding gobgp types. 73 func convertBGPNeighborSAFI(fams []v2alpha1.CiliumBGPFamily) ([]*gobgp.AfiSafi, error) { 74 if len(fams) == 0 { 75 return defaultSafiAfi, nil 76 } 77 78 out := make([]*gobgp.AfiSafi, 0, len(fams)) 79 for _, fam := range fams { 80 var safi types.Safi 81 var afi types.Afi 82 if err := safi.FromString(fam.Safi); err != nil { 83 return out, fmt.Errorf("failed to parse Safi: %w", err) 84 } 85 if err := afi.FromString(fam.Afi); err != nil { 86 return out, fmt.Errorf("failed to parse Afi: %w", err) 87 } 88 out = append(out, &gobgp.AfiSafi{ 89 Config: &gobgp.AfiSafiConfig{ 90 Family: &gobgp.Family{ 91 Afi: gobgp.Family_Afi(afi), 92 Safi: gobgp.Family_Safi(safi), 93 }, 94 }, 95 }) 96 } 97 return out, nil 98 } 99 100 func (g *GoBGPServer) getPeerConfig(ctx context.Context, n types.NeighborRequest, isUpdate bool) (peer *gobgp.Peer, needsReset bool, err error) { 101 if n.Peer != nil { 102 // Peer config is set when BGPv2 is enabled. 103 return g.getPeerConfigV2(ctx, n, isUpdate) 104 } 105 106 return g.getPeerConfigV1(ctx, n, isUpdate) 107 } 108 109 // getPeerConfigV1 returns GoBGP Peer configuration for the provided CiliumBGPNeighbor. 110 func (g *GoBGPServer) getPeerConfigV1(ctx context.Context, n types.NeighborRequest, isUpdate bool) (peer *gobgp.Peer, needsReset bool, err error) { 111 if n.Neighbor == nil { 112 return peer, needsReset, fmt.Errorf("nil neighbor in NeighborRequest: %w", err) 113 } 114 115 // cilium neighbor uses prefix string, gobgp neighbor uses IP string, convert. 116 prefix, err := netip.ParsePrefix(n.Neighbor.PeerAddress) 117 if err != nil { 118 // unlikely, we validate this on CR write to k8s api. 119 return peer, needsReset, fmt.Errorf("failed to parse PeerAddress: %w", err) 120 } 121 peerAddr := prefix.Addr() 122 peerPort := uint32(*n.Neighbor.PeerPort) 123 124 var existingPeer *gobgp.Peer 125 peer, existingPeer, err = g.getPeer(ctx, peerAddr, uint32(n.Neighbor.PeerASN), isUpdate) 126 if err != nil { 127 return peer, needsReset, err 128 } 129 130 peer.AfiSafis, err = convertBGPNeighborSAFI(n.Neighbor.Families) 131 if err != nil { 132 return peer, needsReset, fmt.Errorf("failed to convert CiliumBGPNeighbor Families to gobgp AfiSafi: %w", err) 133 } 134 135 // set peer password 136 peer.Conf.AuthPassword = n.Password 137 138 // set peer transport, for local port we do not set it ( default 0 - unset ) 139 g.setPeerTransport(peer, existingPeer, peerAddr, peerPort, 0) 140 141 // set peer ebgp multihop 142 if n.Neighbor.EBGPMultihopTTL != nil { 143 g.setPeerEBGPMultihopTTL(peer, uint32(n.Neighbor.PeerASN), uint32(*n.Neighbor.EBGPMultihopTTL)) 144 } 145 146 // set peer timers 147 g.setPeerTimers(peer, uint64(*n.Neighbor.ConnectRetryTimeSeconds), uint64(*n.Neighbor.HoldTimeSeconds), uint64(*n.Neighbor.KeepAliveTimeSeconds)) 148 149 // set peer graceful restart 150 if n.Neighbor.GracefulRestart != nil && n.Neighbor.GracefulRestart.RestartTimeSeconds != nil { 151 g.setPeerGracefulRestart(peer, uint32(*n.Neighbor.GracefulRestart.RestartTimeSeconds), n.Neighbor.GracefulRestart.Enabled) 152 } 153 154 if isUpdate { 155 // In some cases, we want to perform full session reset on update even if GoBGP would not perform it. 156 // An example of that is updating timer parameters that are negotiated during the session setup. 157 // As we provide declarative API (CRD), we want this config to be applied on existing sessions 158 // immediately, therefore we need full session reset. 159 needsReset = existingPeer != nil && 160 (peer.Timers.Config.HoldTime != existingPeer.Timers.Config.HoldTime || 161 peer.Timers.Config.KeepaliveInterval != existingPeer.Timers.Config.KeepaliveInterval) 162 } 163 164 return peer, needsReset, err 165 } 166 167 // getPeerConfigV2 returns GoBGP Peer configuration for the provided CiliumBGPNodePeer and CiliumBGPPeerConfigSpec. 168 func (g *GoBGPServer) getPeerConfigV2(ctx context.Context, n types.NeighborRequest, isUpdate bool) (peer *gobgp.Peer, needsReset bool, err error) { 169 if n.Peer == nil || n.PeerConfig == nil { 170 return peer, needsReset, fmt.Errorf("nil peer config in NeighborRequest: %w", err) 171 } 172 173 if n.Peer.PeerASN == nil { 174 // currently peer ASN is required. 175 return peer, needsReset, fmt.Errorf("nil peer ASN") 176 } 177 178 if n.Peer.PeerAddress == nil { 179 // currently peer address is required. 180 return peer, needsReset, fmt.Errorf("nil peer address") 181 } 182 183 peerAddr, err := netip.ParseAddr(*n.Peer.PeerAddress) 184 if err != nil { 185 // unlikely, we validate this on CR write to k8s api. 186 return peer, needsReset, fmt.Errorf("failed to parse PeerAddress: %w", err) 187 } 188 189 localPort := uint32(*n.PeerConfig.Transport.LocalPort) 190 peerPort := uint32(*n.PeerConfig.Transport.PeerPort) 191 192 var existingPeer *gobgp.Peer 193 peer, existingPeer, err = g.getPeer(ctx, peerAddr, uint32(*n.Peer.PeerASN), isUpdate) 194 if err != nil { 195 return peer, needsReset, err 196 } 197 198 // set address families 199 var families []v2alpha1.CiliumBGPFamily 200 for _, fam := range n.PeerConfig.Families { 201 families = append(families, fam.CiliumBGPFamily) 202 } 203 204 peer.AfiSafis, err = convertBGPNeighborSAFI(families) 205 if err != nil { 206 return peer, needsReset, fmt.Errorf("failed to convert CiliumBGPNeighbor Families to gobgp AfiSafi: %w", err) 207 } 208 209 // set peer password 210 peer.Conf.AuthPassword = n.Password 211 212 // set peer transport 213 g.setPeerTransport(peer, existingPeer, peerAddr, peerPort, localPort) 214 215 // set peer ebgp multihop 216 if n.PeerConfig.EBGPMultihop != nil { 217 g.setPeerEBGPMultihopTTL(peer, uint32(*n.Peer.PeerASN), uint32(*n.PeerConfig.EBGPMultihop)) 218 } 219 220 // set peer timers 221 if n.PeerConfig.Timers != nil { 222 g.setPeerTimers(peer, uint64(*n.PeerConfig.Timers.ConnectRetryTimeSeconds), uint64(*n.PeerConfig.Timers.HoldTimeSeconds), uint64(*n.PeerConfig.Timers.KeepAliveTimeSeconds)) 223 } 224 225 // set peer graceful restart 226 if n.PeerConfig.GracefulRestart != nil && n.PeerConfig.GracefulRestart.RestartTimeSeconds != nil { 227 g.setPeerGracefulRestart(peer, uint32(*n.PeerConfig.GracefulRestart.RestartTimeSeconds), n.PeerConfig.GracefulRestart.Enabled) 228 } 229 230 if isUpdate { 231 // In some cases, we want to perform full session reset on update even if GoBGP would not perform it. 232 // An example of that is updating timer parameters that are negotiated during the session setup. 233 // As we provide declarative API (CRD), we want this config to be applied on existing sessions 234 // immediately, therefore we need full session reset. 235 needsReset = existingPeer != nil && 236 (peer.Timers.Config.HoldTime != existingPeer.Timers.Config.HoldTime || 237 peer.Timers.Config.KeepaliveInterval != existingPeer.Timers.Config.KeepaliveInterval) 238 } 239 240 return peer, needsReset, nil 241 } 242 243 func (g *GoBGPServer) setPeerTransport(peer, existingPeer *gobgp.Peer, peerAddr netip.Addr, peerPort, localPort uint32) { 244 if existingPeer != nil { 245 peer.Transport = existingPeer.Transport 246 } else { 247 peer.Transport = &gobgp.Transport{} 248 } 249 250 // update local port, 0 is fine as well. In which case, linux will assign a random port. 251 peer.Transport.LocalPort = localPort 252 253 if peerPort > 0 { 254 peer.Transport.RemotePort = peerPort 255 } 256 257 if peerAddr.Is4() { 258 peer.Transport.LocalAddress = wildcardIPv4Addr 259 } else { 260 peer.Transport.LocalAddress = wildcardIPv6Addr 261 } 262 } 263 264 func (g *GoBGPServer) setPeerEBGPMultihopTTL(peer *gobgp.Peer, peerASN, ebgpMultihopTTL uint32) { 265 if g.asn != peerASN && ebgpMultihopTTL > 1 { 266 peer.EbgpMultihop = &gobgp.EbgpMultihop{ 267 Enabled: true, 268 MultihopTtl: ebgpMultihopTTL, 269 } 270 } 271 } 272 273 func (g *GoBGPServer) setPeerTimers(peer *gobgp.Peer, connect, hold, keepalive uint64) { 274 if peer.Timers == nil { 275 peer.Timers = &gobgp.Timers{} 276 } 277 peer.Timers.Config = &gobgp.TimersConfig{ 278 ConnectRetry: connect, 279 HoldTime: hold, 280 KeepaliveInterval: keepalive, 281 IdleHoldTimeAfterReset: idleHoldTimeAfterResetSeconds, 282 } 283 } 284 285 func (g *GoBGPServer) setPeerGracefulRestart(peer *gobgp.Peer, restartTime uint32, enabled bool) { 286 if peer.GracefulRestart == nil { 287 peer.GracefulRestart = &gobgp.GracefulRestart{} 288 } 289 290 if enabled { 291 peer.GracefulRestart.Enabled = true 292 peer.GracefulRestart.RestartTime = restartTime 293 peer.GracefulRestart.NotificationEnabled = true 294 peer.GracefulRestart.LocalRestarting = true 295 } 296 297 for _, afiConf := range peer.AfiSafis { 298 if afiConf.MpGracefulRestart == nil { 299 afiConf.MpGracefulRestart = &gobgp.MpGracefulRestart{} 300 } 301 afiConf.MpGracefulRestart.Config = &gobgp.MpGracefulRestartConfig{ 302 Enabled: peer.GracefulRestart.Enabled, 303 } 304 } 305 } 306 307 func (g *GoBGPServer) getPeer(ctx context.Context, peerAddr netip.Addr, peerASN uint32, isUpdate bool) (peer, existingPeer *gobgp.Peer, err error) { 308 if isUpdate { 309 // If this is an update, try retrieving the existing Peer. 310 // This is necessary as many Peer fields are defaulted internally in GoBGP, 311 // and if they were not set, the update would always cause BGP peer reset. 312 // This will not fail if the peer is not found for whatever reason. 313 existingPeer, err = g.getExistingPeer(ctx, peerAddr, peerASN) 314 if err != nil { 315 return peer, existingPeer, fmt.Errorf("failed retrieving peer: %w", err) 316 } 317 // use only necessary parts of the existing peer struct 318 peer = &gobgp.Peer{ 319 Conf: existingPeer.Conf, 320 Transport: existingPeer.Transport, 321 } 322 } else { 323 // Create a new peer 324 peer = &gobgp.Peer{ 325 Conf: &gobgp.PeerConf{ 326 NeighborAddress: peerAddr.String(), 327 PeerAsn: peerASN, 328 }, 329 } 330 } 331 return peer, existingPeer, nil 332 } 333 334 // getExistingPeer returns the existing GoBGP Peer matching provided peer address and ASN. 335 // If no such peer can be found, error is returned. 336 func (g *GoBGPServer) getExistingPeer(ctx context.Context, peerAddr netip.Addr, peerASN uint32) (*gobgp.Peer, error) { 337 var res *gobgp.Peer 338 fn := func(peer *gobgp.Peer) { 339 pIP, err := netip.ParseAddr(peer.Conf.NeighborAddress) 340 if err == nil && pIP == peerAddr && peer.Conf.PeerAsn == peerASN { 341 res = peer 342 } 343 } 344 345 err := g.server.ListPeer(ctx, &gobgp.ListPeerRequest{Address: peerAddr.String()}, fn) 346 if err != nil { 347 return nil, fmt.Errorf("listing peers failed: %w", err) 348 } 349 if res == nil { 350 return nil, fmt.Errorf("could not find existing peer with ASN: %d and IP: %s", peerASN, peerAddr) 351 } 352 return res, nil 353 } 354 355 // RemoveNeighbor will remove the peer from the gobgp.BgpServer, 356 // disconnecting the BGP peering connection. 357 func (g *GoBGPServer) RemoveNeighbor(ctx context.Context, n types.NeighborRequest) error { 358 var address string 359 if n.Peer != nil { 360 // for BGPv2 n.Peer will set, n.Neighbor will not. 361 addr, err := netip.ParseAddr(*n.Peer.PeerAddress) 362 if err != nil { 363 return fmt.Errorf("failed to parse PeerAddress: %w", err) 364 } 365 address = addr.String() 366 367 } else { 368 // cilium neighbor uses prefix string, gobgp neighbor uses IP string, convert. 369 prefix, err := netip.ParsePrefix(n.Neighbor.PeerAddress) 370 if err != nil { 371 // unlikely, we validate this on CR write to k8s api. 372 return fmt.Errorf("failed to parse PeerAddress: %w", err) 373 } 374 375 address = prefix.Addr().String() 376 } 377 378 peerReq := &gobgp.DeletePeerRequest{ 379 Address: address, 380 } 381 if err := g.server.DeletePeer(ctx, peerReq); err != nil { 382 return fmt.Errorf("failed while reconciling neighbor %v %v: %w", n.Neighbor.PeerAddress, n.Neighbor.PeerASN, err) 383 } 384 return nil 385 } 386 387 // ResetNeighbor resets BGP peering with the provided neighbor address. 388 func (g *GoBGPServer) ResetNeighbor(ctx context.Context, r types.ResetNeighborRequest) error { 389 // for this request we need a peer address without prefix 390 peerAddr := r.PeerAddress 391 if p, err := netip.ParsePrefix(r.PeerAddress); err == nil { 392 peerAddr = p.Addr().String() 393 } 394 395 resetReq := &gobgp.ResetPeerRequest{ 396 Address: peerAddr, 397 Communication: r.AdminCommunication, 398 } 399 if r.Soft { 400 resetReq.Soft = true 401 resetReq.Direction = toGoBGPSoftResetDirection(r.SoftResetDirection) 402 } 403 if err := g.server.ResetPeer(ctx, resetReq); err != nil { 404 return fmt.Errorf("failed while resetting peer %s: %w", r.PeerAddress, err) 405 } 406 return nil 407 }