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  }