github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconcilerv2/pod_cidr.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/types"
    16  	"github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    17  	"github.com/cilium/cilium/pkg/option"
    18  )
    19  
    20  type PodCIDRReconcilerOut struct {
    21  	cell.Out
    22  
    23  	Reconciler ConfigReconciler `group:"bgp-config-reconciler-v2"`
    24  }
    25  
    26  type PodCIDRReconcilerIn struct {
    27  	cell.In
    28  
    29  	Logger       logrus.FieldLogger
    30  	PeerAdvert   *CiliumPeerAdvertisement
    31  	DaemonConfig *option.DaemonConfig
    32  }
    33  
    34  type PodCIDRReconciler struct {
    35  	logger     logrus.FieldLogger
    36  	peerAdvert *CiliumPeerAdvertisement
    37  }
    38  
    39  // PodCIDRReconcilerMetadata is a map of advertisements per family, key is family type
    40  type PodCIDRReconcilerMetadata struct {
    41  	AFPaths       AFPathsMap
    42  	RoutePolicies RoutePolicyMap
    43  }
    44  
    45  func NewPodCIDRReconciler(params PodCIDRReconcilerIn) PodCIDRReconcilerOut {
    46  	// Don't provide the reconciler if the IPAM mode is not supported
    47  	if !types.CanAdvertisePodCIDR(params.DaemonConfig.IPAMMode()) {
    48  		params.Logger.Info("Unsupported IPAM mode, disabling PodCIDR advertisements.")
    49  		return PodCIDRReconcilerOut{}
    50  	}
    51  	return PodCIDRReconcilerOut{
    52  		Reconciler: &PodCIDRReconciler{
    53  			logger:     params.Logger.WithField(types.ReconcilerLogField, "PodCIDR"),
    54  			peerAdvert: params.PeerAdvert,
    55  		},
    56  	}
    57  }
    58  
    59  func (r *PodCIDRReconciler) Name() string {
    60  	return "PodCIDR"
    61  }
    62  
    63  func (r *PodCIDRReconciler) Priority() int {
    64  	return 30
    65  }
    66  
    67  func (r *PodCIDRReconciler) Init(_ *instance.BGPInstance) error {
    68  	return nil
    69  }
    70  
    71  func (r *PodCIDRReconciler) Cleanup(_ *instance.BGPInstance) {}
    72  
    73  func (r *PodCIDRReconciler) Reconcile(ctx context.Context, p ReconcileParams) error {
    74  	if p.DesiredConfig == nil {
    75  		return fmt.Errorf("BUG: PodCIDR reconciler called with nil CiliumBGPNodeConfig")
    76  	}
    77  
    78  	if p.CiliumNode == nil {
    79  		return fmt.Errorf("BUG: PodCIDR reconciler called with nil CiliumNode")
    80  	}
    81  
    82  	// get pod CIDR prefixes
    83  	var podCIDRPrefixes []netip.Prefix
    84  	for _, cidr := range p.CiliumNode.Spec.IPAM.PodCIDRs {
    85  		prefix, err := netip.ParsePrefix(cidr)
    86  		if err != nil {
    87  			return fmt.Errorf("failed to parse prefix %s: %w", cidr, err)
    88  		}
    89  		podCIDRPrefixes = append(podCIDRPrefixes, prefix)
    90  	}
    91  
    92  	// get per peer per family pod cidr advertisements
    93  	desiredPeerAdverts, err := r.peerAdvert.GetConfiguredAdvertisements(p.DesiredConfig, v2alpha1.BGPPodCIDRAdvert)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	err = r.reconcileRoutePolicies(ctx, p, desiredPeerAdverts, podCIDRPrefixes)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	return r.reconcilePaths(ctx, p, desiredPeerAdverts, podCIDRPrefixes)
   104  }
   105  
   106  func (r *PodCIDRReconciler) reconcilePaths(ctx context.Context, p ReconcileParams, desiredPeerAdverts PeerAdvertisements, podPrefixes []netip.Prefix) error {
   107  	metadata := r.getMetadata(p.BGPInstance)
   108  
   109  	// get desired paths per address family
   110  	desiredFamilyAdverts, err := r.getDesiredPathsPerFamily(desiredPeerAdverts, podPrefixes)
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	// reconcile family advertisements
   116  	updatedAFPaths, err := ReconcileAFPaths(&ReconcileAFPathsParams{
   117  		Logger:       r.logger.WithField(types.InstanceLogField, p.DesiredConfig.Name),
   118  		Ctx:          ctx,
   119  		Router:       p.BGPInstance.Router,
   120  		DesiredPaths: desiredFamilyAdverts,
   121  		CurrentPaths: metadata.AFPaths,
   122  	})
   123  
   124  	metadata.AFPaths = updatedAFPaths
   125  	r.setMetadata(p.BGPInstance, metadata)
   126  	return err
   127  }
   128  
   129  func (r *PodCIDRReconciler) reconcileRoutePolicies(ctx context.Context, p ReconcileParams, desiredPeerAdverts PeerAdvertisements, podPrefixes []netip.Prefix) error {
   130  	metadata := r.getMetadata(p.BGPInstance)
   131  
   132  	// get desired policies
   133  	desiredRoutePolicies, err := r.getDesiredRoutePolicies(p, desiredPeerAdverts, podPrefixes)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	// reconcile route policies
   139  	updatedPolicies, err := ReconcileRoutePolicies(&ReconcileRoutePoliciesParams{
   140  		Logger:          r.logger.WithField(types.InstanceLogField, p.DesiredConfig.Name),
   141  		Ctx:             ctx,
   142  		Router:          p.BGPInstance.Router,
   143  		DesiredPolicies: desiredRoutePolicies,
   144  		CurrentPolicies: r.getMetadata(p.BGPInstance).RoutePolicies,
   145  	})
   146  
   147  	metadata.RoutePolicies = updatedPolicies
   148  	r.setMetadata(p.BGPInstance, metadata)
   149  	return err
   150  }
   151  
   152  // getDesiredPathsPerFamily returns a map of desired paths per address family.
   153  // Note: This returns prefixes per address family. Global routing table will contain prefix per family not per neighbor.
   154  // Per neighbor advertisement will be controlled by BGP Policy.
   155  func (r *PodCIDRReconciler) getDesiredPathsPerFamily(desiredPeerAdverts PeerAdvertisements, desiredPrefixes []netip.Prefix) (AFPathsMap, error) {
   156  	// Calculate desired paths per address family, collapsing per-peer advertisements into per-family advertisements.
   157  	desiredFamilyAdverts := make(AFPathsMap)
   158  	for _, peerFamilyAdverts := range desiredPeerAdverts {
   159  		for family, familyAdverts := range peerFamilyAdverts {
   160  			agentFamily := types.ToAgentFamily(family)
   161  			pathsPerFamily, exists := desiredFamilyAdverts[agentFamily]
   162  			if !exists {
   163  				pathsPerFamily = make(PathMap)
   164  				desiredFamilyAdverts[agentFamily] = pathsPerFamily
   165  			}
   166  
   167  			// there are some advertisements which have pod CIDR advert enabled.
   168  			// we need to add podCIDR prefixes to the desiredFamilyAdverts.
   169  			if len(familyAdverts) != 0 {
   170  				for _, prefix := range desiredPrefixes {
   171  					path := types.NewPathForPrefix(prefix)
   172  					path.Family = agentFamily
   173  
   174  					// we only add path corresponding to the family of the prefix.
   175  					if agentFamily.Afi == types.AfiIPv4 && prefix.Addr().Is4() {
   176  						pathsPerFamily[path.NLRI.String()] = path
   177  					}
   178  					if agentFamily.Afi == types.AfiIPv6 && prefix.Addr().Is6() {
   179  						pathsPerFamily[path.NLRI.String()] = path
   180  					}
   181  				}
   182  			}
   183  		}
   184  	}
   185  	return desiredFamilyAdverts, nil
   186  }
   187  
   188  func (r *PodCIDRReconciler) getDesiredRoutePolicies(p ReconcileParams, desiredPeerAdverts PeerAdvertisements, desiredPrefixes []netip.Prefix) (RoutePolicyMap, error) {
   189  	desiredPolicies := make(RoutePolicyMap)
   190  
   191  	for peer, afAdverts := range desiredPeerAdverts {
   192  		peerAddr, err := GetPeerAddressFromConfig(p.DesiredConfig, peer)
   193  		if err != nil {
   194  			return nil, err
   195  		}
   196  
   197  		for family, adverts := range afAdverts {
   198  			fam := types.ToAgentFamily(family)
   199  
   200  			for _, advert := range adverts {
   201  				var v4Prefixes, v6Prefixes types.PolicyPrefixMatchList
   202  				for _, prefix := range desiredPrefixes {
   203  					match := &types.RoutePolicyPrefixMatch{CIDR: prefix, PrefixLenMin: prefix.Bits(), PrefixLenMax: prefix.Bits()}
   204  
   205  					if fam.Afi == types.AfiIPv4 && prefix.Addr().Is4() {
   206  						v4Prefixes = append(v4Prefixes, match)
   207  					}
   208  
   209  					if fam.Afi == types.AfiIPv6 && prefix.Addr().Is6() {
   210  						v6Prefixes = append(v6Prefixes, match)
   211  					}
   212  				}
   213  
   214  				if len(v6Prefixes) > 0 || len(v4Prefixes) > 0 {
   215  					name := PolicyName(peer, fam.Afi.String(), advert.AdvertisementType, "")
   216  					policy, err := CreatePolicy(name, peerAddr, v4Prefixes, v6Prefixes, advert)
   217  					if err != nil {
   218  						return nil, err
   219  					}
   220  					desiredPolicies[name] = policy
   221  				}
   222  			}
   223  		}
   224  	}
   225  
   226  	return desiredPolicies, nil
   227  }
   228  
   229  func (r *PodCIDRReconciler) getMetadata(i *instance.BGPInstance) PodCIDRReconcilerMetadata {
   230  	if _, found := i.Metadata[r.Name()]; !found {
   231  		i.Metadata[r.Name()] = PodCIDRReconcilerMetadata{
   232  			AFPaths:       make(AFPathsMap),
   233  			RoutePolicies: make(RoutePolicyMap),
   234  		}
   235  	}
   236  	return i.Metadata[r.Name()].(PodCIDRReconcilerMetadata)
   237  }
   238  
   239  func (r *PodCIDRReconciler) setMetadata(i *instance.BGPInstance, metadata PodCIDRReconcilerMetadata) {
   240  	i.Metadata[r.Name()] = metadata
   241  }