github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconcilerv2/policies.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 "maps" 10 "net/netip" 11 "sort" 12 "strconv" 13 "strings" 14 15 "github.com/osrg/gobgp/v3/pkg/packet/bgp" 16 "github.com/sirupsen/logrus" 17 "k8s.io/apimachinery/pkg/util/sets" 18 19 "github.com/cilium/cilium/pkg/bgpv1/types" 20 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 21 "github.com/cilium/cilium/pkg/k8s/resource" 22 ) 23 24 const ( 25 MaxPrefixLenIPv4 = 32 26 MaxPrefixLenIPv6 = 128 27 ) 28 29 // ResourceRoutePolicyMap holds the route policies per resource. 30 type ResourceRoutePolicyMap map[resource.Key]RoutePolicyMap 31 32 // RoutePolicyMap holds routing policies configured by the policy reconciler keyed by policy name. 33 type RoutePolicyMap map[string]*types.RoutePolicy 34 35 type ReconcileRoutePoliciesParams struct { 36 Logger logrus.FieldLogger 37 Ctx context.Context 38 Router types.Router 39 DesiredPolicies RoutePolicyMap 40 CurrentPolicies RoutePolicyMap 41 } 42 43 // ReconcileRoutePolicies reconciles routing policies between the desired and the current state. 44 // It returns the updated routing policies and an error if the reconciliation fails. 45 func ReconcileRoutePolicies(rp *ReconcileRoutePoliciesParams) (RoutePolicyMap, error) { 46 runningPolicies := make(RoutePolicyMap) 47 maps.Copy(runningPolicies, rp.CurrentPolicies) 48 49 var toAdd, toRemove, toUpdate []*types.RoutePolicy 50 51 for _, p := range rp.DesiredPolicies { 52 if existing, found := rp.CurrentPolicies[p.Name]; found { 53 if !existing.DeepEqual(p) { 54 toUpdate = append(toUpdate, p) 55 } 56 } else { 57 toAdd = append(toAdd, p) 58 } 59 } 60 for _, p := range rp.CurrentPolicies { 61 if _, found := rp.DesiredPolicies[p.Name]; !found { 62 toRemove = append(toRemove, p) 63 } 64 } 65 66 // tracks which peers have to be reset because of policy change 67 resetPeers := sets.New[string]() 68 69 // add missing policies 70 for _, p := range toAdd { 71 rp.Logger.WithFields(logrus.Fields{ 72 types.PolicyLogField: p.Name, 73 }).Debug("Adding route policy") 74 75 err := rp.Router.AddRoutePolicy(rp.Ctx, types.RoutePolicyRequest{ 76 DefaultExportAction: types.RoutePolicyActionReject, // do not advertise routes by default 77 Policy: p, 78 }) 79 if err != nil { 80 return runningPolicies, err 81 } 82 83 runningPolicies[p.Name] = p 84 resetPeers.Insert(peerAddressFromPolicy(p)) 85 } 86 87 // update modified policies 88 for _, p := range toUpdate { 89 // As proper implementation of an update operation for complex policies would be quite involved, 90 // we resort to recreating the policies that need an update here. 91 rp.Logger.WithFields(logrus.Fields{ 92 types.PolicyLogField: p.Name, 93 }).Debug("Updating (re-creating) route policy") 94 95 existing := rp.CurrentPolicies[p.Name] 96 err := rp.Router.RemoveRoutePolicy(rp.Ctx, types.RoutePolicyRequest{Policy: existing}) 97 if err != nil { 98 return runningPolicies, err 99 } 100 delete(runningPolicies, existing.Name) 101 102 err = rp.Router.AddRoutePolicy(rp.Ctx, types.RoutePolicyRequest{ 103 DefaultExportAction: types.RoutePolicyActionReject, // do not advertise routes by default 104 Policy: p, 105 }) 106 if err != nil { 107 return runningPolicies, err 108 } 109 110 runningPolicies[p.Name] = p 111 resetPeers.Insert(peerAddressFromPolicy(p)) 112 } 113 114 // remove old policies 115 for _, p := range toRemove { 116 rp.Logger.WithFields(logrus.Fields{ 117 types.PolicyLogField: p.Name, 118 }).Debug("Removing route policy") 119 120 err := rp.Router.RemoveRoutePolicy(rp.Ctx, types.RoutePolicyRequest{Policy: p}) 121 if err != nil { 122 return runningPolicies, err 123 } 124 delete(runningPolicies, p.Name) 125 resetPeers.Insert(peerAddressFromPolicy(p)) 126 } 127 128 // soft-reset affected BGP peers to apply the changes on already advertised routes 129 for peer := range resetPeers { 130 _, err := netip.ParsePrefix(peer) 131 if err != nil { 132 continue 133 } 134 135 rp.Logger.WithFields(logrus.Fields{ 136 types.PeerLogField: peer, 137 }).Debug("Resetting peer due to a routing policy change") 138 139 req := types.ResetNeighborRequest{ 140 PeerAddress: peer, 141 Soft: true, 142 SoftResetDirection: types.SoftResetDirectionOut, // we are using only export policies 143 } 144 145 err = rp.Router.ResetNeighbor(rp.Ctx, req) 146 if err != nil { 147 // non-fatal error (may happen if the neighbor is not up), just log it 148 rp.Logger.WithFields(logrus.Fields{ 149 types.PeerLogField: peer, 150 }).WithError(err).Debug("resetting peer failed after a routing policy change") 151 } 152 } 153 154 return runningPolicies, nil 155 } 156 157 // PolicyName returns a unique route policy name for the provided peer, family and advertisement type. 158 // If there a is a need for multiple route policies per advertisement type, unique resourceID can be provided. 159 func PolicyName(peer, family string, advertType v2alpha1.BGPAdvertisementType, resourceID string) string { 160 if resourceID == "" { 161 return fmt.Sprintf("%s-%s-%s", peer, family, advertType) 162 } 163 return fmt.Sprintf("%s-%s-%s-%s", peer, family, advertType, resourceID) 164 } 165 166 func CreatePolicy(name string, peerAddr netip.Addr, v4Prefixes, v6Prefixes types.PolicyPrefixMatchList, advert v2alpha1.BGPAdvertisement) (*types.RoutePolicy, error) { 167 policy := &types.RoutePolicy{ 168 Name: name, 169 Type: types.RoutePolicyTypeExport, 170 } 171 172 // sort prefixes to have consistent order for DeepEqual 173 sort.Slice(v4Prefixes, v4Prefixes.Less) 174 sort.Slice(v6Prefixes, v6Prefixes.Less) 175 176 // get communities 177 communities, largeCommunities, err := getCommunities(advert) 178 if err != nil { 179 return nil, err 180 } 181 182 // get local preference 183 var localPref *int64 184 if advert.Attributes != nil { 185 localPref = advert.Attributes.LocalPreference 186 } 187 188 // Due to a GoBGP limitation, we need to generate a separate statement for v4 and v6 prefixes, as families 189 // can not be mixed in a single statement. Nevertheless, they can be both part of the same Policy. 190 if len(v4Prefixes) > 0 { 191 policy.Statements = append(policy.Statements, policyStatement(peerAddr, v4Prefixes, localPref, communities, largeCommunities)) 192 } 193 if len(v6Prefixes) > 0 { 194 policy.Statements = append(policy.Statements, policyStatement(peerAddr, v6Prefixes, localPref, communities, largeCommunities)) 195 } 196 197 return policy, nil 198 } 199 200 func getCommunities(advert v2alpha1.BGPAdvertisement) (standard, large []string, err error) { 201 standard, err = mergeAndDedupCommunities(advert) 202 if err != nil { 203 return nil, nil, err 204 } 205 large = dedupLargeCommunities(advert) 206 207 return standard, large, nil 208 } 209 210 // mergeAndDedupCommunities merges numeric standard community and well-known community strings, 211 // deduplicated by their actual community values. 212 func mergeAndDedupCommunities(advert v2alpha1.BGPAdvertisement) ([]string, error) { 213 var res []string 214 215 if advert.Attributes == nil || advert.Attributes.Communities == nil { 216 return res, nil 217 } 218 219 standard := advert.Attributes.Communities.Standard 220 wellKnown := advert.Attributes.Communities.WellKnown 221 222 existing := sets.New[uint32]() 223 for _, c := range standard { 224 val, err := parseCommunity(string(c)) 225 if err != nil { 226 return nil, fmt.Errorf("failed to parse standard BGP community: %w", err) 227 } 228 if existing.Has(val) { 229 continue 230 } 231 existing.Insert(val) 232 res = append(res, string(c)) 233 } 234 235 for _, c := range wellKnown { 236 val, ok := bgp.WellKnownCommunityValueMap[string(c)] 237 if !ok { 238 return nil, fmt.Errorf("invalid well-known community value '%s'", c) 239 } 240 if existing.Has(uint32(val)) { 241 continue 242 } 243 existing.Insert(uint32(val)) 244 res = append(res, string(c)) 245 } 246 return res, nil 247 } 248 249 func parseCommunity(communityStr string) (uint32, error) { 250 // parse as <0-65535>:<0-65535> 251 if elems := strings.Split(communityStr, ":"); len(elems) == 2 { 252 fst, err := strconv.ParseUint(elems[0], 10, 16) 253 if err != nil { 254 return 0, err 255 } 256 snd, err := strconv.ParseUint(elems[1], 10, 16) 257 if err != nil { 258 return 0, err 259 } 260 return uint32(fst<<16 | snd), nil 261 } 262 // parse as a single decimal number 263 c, err := strconv.ParseUint(communityStr, 10, 32) 264 return uint32(c), err 265 } 266 267 // dedupLargeCommunities returns deduplicated large communities as a string slice. 268 func dedupLargeCommunities(advert v2alpha1.BGPAdvertisement) []string { 269 var res []string 270 271 if advert.Attributes == nil || advert.Attributes.Communities == nil { 272 return res 273 } 274 275 communities := advert.Attributes.Communities.Large 276 277 existing := sets.New[string]() 278 for _, c := range communities { 279 if existing.Has(string(c)) { 280 continue 281 } 282 existing.Insert(string(c)) 283 res = append(res, string(c)) 284 } 285 return res 286 } 287 288 func policyStatement(neighborAddr netip.Addr, prefixes []*types.RoutePolicyPrefixMatch, localPref *int64, communities, largeCommunities []string) *types.RoutePolicyStatement { 289 // create /32 or /128 neighbor prefix match 290 neighborPrefix := netip.PrefixFrom(neighborAddr, neighborAddr.BitLen()) 291 292 return &types.RoutePolicyStatement{ 293 Conditions: types.RoutePolicyConditions{ 294 MatchNeighbors: []string{neighborPrefix.String()}, 295 MatchPrefixes: prefixes, 296 }, 297 Actions: types.RoutePolicyActions{ 298 RouteAction: types.RoutePolicyActionAccept, 299 SetLocalPreference: localPref, 300 AddCommunities: communities, 301 AddLargeCommunities: largeCommunities, 302 }, 303 } 304 } 305 306 // peerAddressFromPolicy returns the first neighbor address found in a routing policy. 307 func peerAddressFromPolicy(p *types.RoutePolicy) string { 308 if p == nil { 309 return "" 310 } 311 for _, s := range p.Statements { 312 for _, m := range s.Conditions.MatchNeighbors { 313 return m 314 } 315 } 316 return "" 317 }