github.com/cilium/cilium@v1.16.2/pkg/fqdn/name_manager.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package fqdn 5 6 import ( 7 "context" 8 "hash/fnv" 9 "net/netip" 10 "os" 11 "path/filepath" 12 "regexp" 13 "slices" 14 15 "github.com/go-openapi/strfmt" 16 "github.com/sirupsen/logrus" 17 "golang.org/x/sync/errgroup" 18 "k8s.io/apimachinery/pkg/util/sets" 19 20 "github.com/cilium/cilium/api/v1/models" 21 "github.com/cilium/cilium/pkg/controller" 22 "github.com/cilium/cilium/pkg/ip" 23 "github.com/cilium/cilium/pkg/ipcache" 24 ipcacheTypes "github.com/cilium/cilium/pkg/ipcache/types" 25 "github.com/cilium/cilium/pkg/labels" 26 "github.com/cilium/cilium/pkg/lock" 27 "github.com/cilium/cilium/pkg/logging/logfields" 28 "github.com/cilium/cilium/pkg/metrics" 29 "github.com/cilium/cilium/pkg/option" 30 "github.com/cilium/cilium/pkg/policy/api" 31 "github.com/cilium/cilium/pkg/source" 32 "github.com/cilium/cilium/pkg/time" 33 ) 34 35 // NameManager maintains state DNS names, via FQDNSelector or exact match for 36 // polling, need to be tracked. It is the main structure which relates the FQDN 37 // subsystem to the policy subsystem for plumbing the relation between a DNS 38 // name and the corresponding IPs which have been returned via DNS lookups. 39 // When DNS updates are given to a NameManager it update cached selectors as 40 // required via UpdateSelectors. 41 // DNS information is cached, respecting TTL. 42 type NameManager struct { 43 lock.RWMutex 44 45 // config is a copy from when this instance was initialized. 46 // It is read-only once set 47 config Config 48 49 // allSelectors contains all FQDNSelectors which are present in all policy. We 50 // use these selectors to map selectors --> IPs. 51 allSelectors map[api.FQDNSelector]*regexp.Regexp 52 53 // cache is a private copy of the pointer from config. 54 cache *DNSCache 55 56 bootstrapCompleted bool 57 58 // restoredPrefixes contains all prefixes for which we have restored the 59 // IPCache metadata from previous Cilium v1.15 installation. 60 // Cleared by CompleteBoostrap 61 restoredPrefixes sets.Set[netip.Prefix] 62 63 manager *controller.Manager 64 65 // list of locks used as coordination points for name updates 66 // see LockName() for details. 67 nameLocks []*lock.Mutex 68 } 69 70 // GetModel returns the API model of the NameManager. 71 func (n *NameManager) GetModel() *models.NameManager { 72 n.RWMutex.RLock() 73 defer n.RWMutex.RUnlock() 74 75 allSelectors := make([]*models.SelectorEntry, 0, len(n.allSelectors)) 76 for fqdnSel, regex := range n.allSelectors { 77 pair := &models.SelectorEntry{ 78 SelectorString: fqdnSel.String(), 79 RegexString: regex.String(), 80 } 81 allSelectors = append(allSelectors, pair) 82 } 83 84 return &models.NameManager{ 85 FQDNPolicySelectors: allSelectors, 86 } 87 } 88 89 var ( 90 DNSSourceLookup = "lookup" 91 DNSSourceConnection = "connection" 92 ) 93 94 type NoEndpointIDMatch struct { 95 ID string 96 } 97 98 func (e NoEndpointIDMatch) Error() string { 99 return "unable to find target endpoint ID: " + e.ID 100 } 101 102 // GetDNSHistoryModel returns API models.DNSLookup copies of DNS data in each 103 // endpoint's DNSHistory. These are filtered by the specified matchers if 104 // they are non-empty. 105 // 106 // Note that this does *NOT* dump the NameManager's own global DNSCache. 107 // 108 // endpointID may be "" in order to get DNS history for all endpoints. 109 func (n *NameManager) GetDNSHistoryModel(endpointID string, prefixMatcher PrefixMatcherFunc, nameMatcher NameMatcherFunc, source string) (lookups []*models.DNSLookup, err error) { 110 eps := n.config.GetEndpointsDNSInfo(endpointID) 111 if eps == nil { 112 return nil, &NoEndpointIDMatch{ID: endpointID} 113 } 114 for _, ep := range eps { 115 lookupSourceEntries := []*models.DNSLookup{} 116 connectionSourceEntries := []*models.DNSLookup{} 117 for _, lookup := range ep.DNSHistory.Dump() { 118 if !nameMatcher(lookup.Name) { 119 continue 120 } 121 122 // The API model needs strings 123 IPStrings := make([]string, 0, len(lookup.IPs)) 124 125 // only proceed if any IP matches the prefix selector 126 anIPMatches := false 127 for _, ip := range lookup.IPs { 128 anIPMatches = anIPMatches || prefixMatcher(ip) 129 IPStrings = append(IPStrings, ip.String()) 130 } 131 if !anIPMatches { 132 continue 133 } 134 135 lookupSourceEntries = append(lookupSourceEntries, &models.DNSLookup{ 136 Fqdn: lookup.Name, 137 Ips: IPStrings, 138 LookupTime: strfmt.DateTime(lookup.LookupTime), 139 TTL: int64(lookup.TTL), 140 ExpirationTime: strfmt.DateTime(lookup.ExpirationTime), 141 EndpointID: int64(ep.ID64), 142 Source: DNSSourceLookup, 143 }) 144 } 145 146 for _, delete := range ep.DNSZombies.DumpAlive(prefixMatcher) { 147 for _, name := range delete.Names { 148 if !nameMatcher(name) { 149 continue 150 } 151 152 connectionSourceEntries = append(connectionSourceEntries, &models.DNSLookup{ 153 Fqdn: name, 154 Ips: []string{delete.IP.String()}, 155 LookupTime: strfmt.DateTime(delete.AliveAt), 156 TTL: 0, 157 ExpirationTime: strfmt.DateTime(ep.DNSZombies.nextCTGCUpdate), 158 EndpointID: int64(ep.ID64), 159 Source: DNSSourceConnection, 160 }) 161 } 162 } 163 164 switch source { 165 case DNSSourceLookup: 166 lookups = append(lookups, lookupSourceEntries...) 167 case DNSSourceConnection: 168 lookups = append(lookups, connectionSourceEntries...) 169 default: 170 lookups = append(lookups, lookupSourceEntries...) 171 lookups = append(lookups, connectionSourceEntries...) 172 } 173 } 174 175 return lookups, nil 176 } 177 178 // RegisterFQDNSelector exposes this FQDNSelector so that the identity labels 179 // of IPs contained in a DNS response that matches said selector can be 180 // associated with that selector. 181 // This function also evaluates if any DNS names in the cache are matched by 182 // this new selector and updates the labels for those DNS names accordingly. 183 func (n *NameManager) RegisterFQDNSelector(selector api.FQDNSelector) { 184 n.Lock() 185 defer n.Unlock() 186 187 _, exists := n.allSelectors[selector] 188 if exists { 189 log.WithField("fqdnSelector", selector).Warning("FQDNSelector was already registered for updates.") 190 } else { 191 // This error should never occur since the FQDNSelector has already been 192 // validated, but account for it for good measure. 193 regex, err := selector.ToRegex() 194 if err != nil { 195 log.WithError(err).WithField("fqdnSelector", selector).Error("FQDNSelector did not compile to valid regex") 196 return 197 } 198 199 n.allSelectors[selector] = regex 200 if metrics.FQDNSelectors.IsEnabled() { 201 metrics.FQDNSelectors.Set(float64(len(n.allSelectors))) 202 } 203 } 204 205 // The newly added FQDN selector could match DNS Names in the cache. If 206 // that is the case, we want to update the IPCache metadata for all 207 // associated IPs 208 selectedNamesAndIPs := n.mapSelectorsToNamesLocked(selector) 209 n.updateMetadata(deriveLabelsForNames(selectedNamesAndIPs, n.allSelectors)) 210 } 211 212 // UnregisterFQDNSelector removes this FQDNSelector from the set of 213 // IPs which are being tracked by the identityNotifier. The result 214 // of this is that an IP may be evicted from IPCache if it is no longer 215 // selected by any other FQDN selector. 216 func (n *NameManager) UnregisterFQDNSelector(selector api.FQDNSelector) { 217 n.Lock() 218 defer n.Unlock() 219 220 // Remove selector 221 delete(n.allSelectors, selector) 222 if metrics.FQDNSelectors.IsEnabled() { 223 metrics.FQDNSelectors.Set(float64(len(n.allSelectors))) 224 } 225 226 // Re-compute labels for affected names and IPs 227 selectedNamesAndIPs := n.mapSelectorsToNamesLocked(selector) 228 n.updateMetadata(deriveLabelsForNames(selectedNamesAndIPs, n.allSelectors)) 229 } 230 231 // NewNameManager creates an initialized NameManager. 232 // When config.Cache is nil, the global fqdn.DefaultDNSCache is used. 233 func NewNameManager(config Config) *NameManager { 234 235 if config.Cache == nil { 236 config.Cache = NewDNSCache(0) 237 } 238 239 if config.GetEndpointsDNSInfo == nil { 240 config.GetEndpointsDNSInfo = func(_ string) []EndpointDNSInfo { 241 return nil 242 } 243 } 244 245 n := &NameManager{ 246 config: config, 247 allSelectors: make(map[api.FQDNSelector]*regexp.Regexp), 248 cache: config.Cache, 249 manager: controller.NewManager(), 250 nameLocks: make([]*lock.Mutex, option.Config.DNSProxyLockCount), 251 } 252 253 for i := range n.nameLocks { 254 n.nameLocks[i] = &lock.Mutex{} 255 } 256 257 return n 258 } 259 260 // UpdateGenerateDNS inserts the new DNS information into the cache. If the IPs 261 // have changed for a name they will be reflected in updatedDNSIPs. 262 func (n *NameManager) UpdateGenerateDNS(ctx context.Context, lookupTime time.Time, updatedDNSIPs map[string]*DNSIPRecords) *errgroup.Group { 263 n.RWMutex.Lock() 264 defer n.RWMutex.Unlock() 265 266 // Update IPs in n 267 updatedDNSNames, ipcacheRevision := n.updateDNSIPs(lookupTime, updatedDNSIPs) 268 for dnsName, IPs := range updatedDNSNames { 269 log.WithFields(logrus.Fields{ 270 "matchName": dnsName, 271 "IPs": IPs, 272 }).Debug("Updated FQDN with new IPs") 273 } 274 275 g, ctx := errgroup.WithContext(ctx) 276 g.Go(func() error { 277 return n.config.IPCache.WaitForRevision(ctx, ipcacheRevision) 278 }) 279 return g 280 } 281 282 func (n *NameManager) CompleteBootstrap() { 283 n.Lock() 284 defer n.Unlock() 285 286 n.bootstrapCompleted = true 287 if len(n.restoredPrefixes) > 0 { 288 log.WithField("prefixes", len(n.restoredPrefixes)).Debug("Removing restored IPCache labels") 289 290 // The following logic needs to match the restoration logic in RestoreCaches 291 ipcacheUpdates := make([]ipcache.MU, 0, len(n.restoredPrefixes)) 292 for prefix := range n.restoredPrefixes { 293 ipcacheUpdates = append(ipcacheUpdates, ipcache.MU{ 294 Prefix: prefix, 295 Source: source.Restored, 296 Resource: restorationIPCacheResource, 297 Metadata: []ipcache.IPMetadata{ 298 labels.Labels{}, // remove restored labels 299 }, 300 }) 301 } 302 n.config.IPCache.RemoveMetadataBatch(ipcacheUpdates...) 303 n.restoredPrefixes = nil 304 305 checkpointPath := filepath.Join(option.Config.StateDir, checkpointFile) 306 if err := os.Remove(checkpointPath); err != nil { 307 log.WithError(err).WithField(logfields.Path, checkpointPath). 308 Debug("Failed to remove checkpoint file") 309 } 310 } 311 } 312 313 // updateDNSIPs updates the IPs for each DNS name in updatedDNSIPs. 314 // It returns: 315 // updatedNames: a map of DNS names to all the valid IPs we store for each. 316 // ipcacheRevision: a revision number to pass to WaitForRevision() 317 func (n *NameManager) updateDNSIPs(lookupTime time.Time, updatedDNSIPs map[string]*DNSIPRecords) (updatedNames map[string][]netip.Addr, ipcacheRevision uint64) { 318 updatedNames = make(map[string][]netip.Addr, len(updatedDNSIPs)) 319 updatedMetadata := make(map[string]nameMetadata, len(updatedDNSIPs)) 320 321 for dnsName, lookupIPs := range updatedDNSIPs { 322 addrs := ip.MustAddrsFromIPs(lookupIPs.IPs) 323 updated := n.updateIPsForName(lookupTime, dnsName, addrs, lookupIPs.TTL) 324 325 // The IPs didn't change. No more to be done for this dnsName 326 if !updated && n.bootstrapCompleted { 327 log.WithFields(logrus.Fields{ 328 "dnsName": dnsName, 329 "lookupIPs": lookupIPs, 330 }).Debug("FQDN: IPs didn't change for DNS name") 331 continue 332 } 333 334 // record the IPs that were different 335 updatedNames[dnsName] = addrs 336 337 // accumulate the new labels affected by new IPs 338 if len(n.allSelectors) == 0 { 339 log.WithFields(logrus.Fields{ 340 "dnsName": dnsName, 341 "lookupIPs": lookupIPs, 342 }).Debug("FQDN: No selectors registered for updates") 343 continue 344 } 345 346 // derive labels for this DNS name 347 nameLabels := deriveLabelsForName(dnsName, n.allSelectors) 348 if len(nameLabels) == 0 { 349 // If no selectors care about this name, then skip IPCache updates 350 // for this name. 351 // If any selectors/ are added later, ipcache insertion will happen then. 352 continue 353 } 354 355 updatedMetadata[dnsName] = nameMetadata{ 356 addrs: addrs, 357 labels: nameLabels, 358 } 359 } 360 361 // If new IPs were detected, and these IPs are selected by selectors, 362 // then ensure they have an identity allocated to them via the ipcache. 363 ipcacheRevision = n.updateMetadata(updatedMetadata) 364 return updatedNames, ipcacheRevision 365 } 366 367 // updateIPsName will update the IPs for dnsName. It always retains a copy of 368 // newIPs. 369 // updated is true when the new IPs differ from the old IPs 370 func (n *NameManager) updateIPsForName(lookupTime time.Time, dnsName string, newIPs []netip.Addr, ttl int) (updated bool) { 371 oldCacheIPs := n.cache.Lookup(dnsName) 372 373 if n.config.MinTTL > ttl { 374 ttl = n.config.MinTTL 375 } 376 377 changed := n.cache.Update(lookupTime, dnsName, newIPs, ttl) 378 if !changed { // Changed may have false positives, but not false negatives 379 return false 380 } 381 382 newCacheIPs := n.cache.Lookup(dnsName) // DNSCache returns IPs unsorted 383 384 // The 0 checks below account for an unlike race condition where this 385 // function is called with already expired data and if other cache data 386 // from before also expired. 387 if len(oldCacheIPs) != len(newCacheIPs) || len(oldCacheIPs) == 0 { 388 return true 389 } 390 391 ip.SortAddrList(oldCacheIPs) // sorts in place 392 ip.SortAddrList(newCacheIPs) 393 394 return !slices.Equal(oldCacheIPs, newCacheIPs) 395 } 396 397 func ipcacheResource(dnsName string) ipcacheTypes.ResourceID { 398 return ipcacheTypes.NewResourceID(ipcacheTypes.ResourceKindDaemon, "fqdn-name-manager", dnsName) 399 } 400 401 // updateMetadata updates (i.e. upserts or removes) the metadata in IPCache for 402 // each (name, IP) pair provided in nameToMetadata. 403 func (n *NameManager) updateMetadata(nameToMetadata map[string]nameMetadata) (ipcacheRevision uint64) { 404 var ipcacheUpserts, ipcacheRemovals []ipcache.MU 405 406 for dnsName, metadata := range nameToMetadata { 407 var updates []ipcache.MU 408 resource := ipcacheResource(dnsName) 409 410 if option.Config.Debug { 411 log.WithFields(logrus.Fields{ 412 "name": dnsName, 413 "prefixes": metadata.addrs, 414 "labels": metadata.labels, 415 }).Debug("Updating prefix labels in IPCache") 416 } 417 418 for _, addr := range metadata.addrs { 419 updates = append(updates, ipcache.MU{ 420 Prefix: netip.PrefixFrom(addr, addr.BitLen()), 421 Source: source.Generated, 422 Resource: resource, 423 Metadata: []ipcache.IPMetadata{ 424 metadata.labels, 425 }, 426 }) 427 } 428 429 // If labels are empty (i.e. this domain is no longer selected), 430 // then we want to the labels of our resource owner 431 if len(metadata.labels) > 0 { 432 ipcacheUpserts = append(ipcacheUpserts, updates...) 433 } else { 434 ipcacheRemovals = append(ipcacheRemovals, updates...) 435 } 436 } 437 438 if len(ipcacheUpserts) > 0 { 439 ipcacheRevision = n.config.IPCache.UpsertMetadataBatch(ipcacheUpserts...) 440 } 441 if len(ipcacheRemovals) > 0 { 442 ipcacheRevision = n.config.IPCache.RemoveMetadataBatch(ipcacheRemovals...) 443 } 444 445 return ipcacheRevision 446 } 447 448 // maybeRemoveMetadata removes the ipcache metadata from every (name, IP) pair 449 // in maybeRemoved, as long as that (name, IP) is not still in the dns cache. 450 func (n *NameManager) maybeRemoveMetadata(maybeRemoved map[netip.Addr][]string) { 451 // Need to take an RLock here so that no DNS updates are processed. 452 // Otherwise, we might accidentally remove an IP that is newly inserted. 453 n.RWMutex.RLock() 454 defer n.RWMutex.RUnlock() 455 456 n.cache.RLock() 457 ipCacheUpdates := make([]ipcache.MU, 0, len(maybeRemoved)) 458 for ip, names := range maybeRemoved { 459 for _, name := range names { 460 if !n.cache.entryExistsLocked(name, ip) { 461 ipCacheUpdates = append(ipCacheUpdates, ipcache.MU{ 462 Prefix: netip.PrefixFrom(ip, ip.BitLen()), 463 Source: source.Generated, 464 Resource: ipcacheResource(name), 465 Metadata: []ipcache.IPMetadata{ 466 labels.Labels{}, // remove all labels for this (ip, name) pair 467 }, 468 }) 469 } 470 } 471 } 472 n.cache.RUnlock() 473 n.config.IPCache.RemoveMetadataBatch(ipCacheUpdates...) 474 } 475 476 // LockName is used to serialize parallel end-to-end updates to the same name. 477 // 478 // It is needed due to some subtleties around NameManager locks and 479 // policy updates. Specifically, we unlock the NameManager after updates 480 // are queued to endpoints, but *before* changes are pushed to policy maps. 481 // So, if a second request comes in during this state, it may encounter 482 // policy drops until the policy updates are complete. 483 // 484 // Serializing on names prevents this. 485 // 486 // Rather than having a potentially unbounded set of per-name locks, this 487 // buckets names in to a set of locks. The lock count is configurable. 488 func (n *NameManager) LockName(name string) { 489 idx := nameLockIndex(name, option.Config.DNSProxyLockCount) 490 n.nameLocks[idx].Lock() 491 } 492 493 // UnlockName releases a lock previously acquired by LockName() 494 func (n *NameManager) UnlockName(name string) { 495 idx := nameLockIndex(name, option.Config.DNSProxyLockCount) 496 n.nameLocks[idx].Unlock() 497 } 498 499 // nameLockIndex hashes the DNS name to a uint32, then returns that 500 // mod the bucket count. 501 func nameLockIndex(name string, cnt int) uint32 { 502 h := fnv.New32() 503 _, _ = h.Write([]byte(name)) // cannot return error 504 return h.Sum32() % uint32(cnt) 505 } 506 507 type nameMetadata struct { 508 addrs []netip.Addr 509 labels labels.Labels // if empty, metadata will be removed for this name 510 } 511 512 // deriveLabelsForName derives what `fqdn:` labels we want to associate with 513 // IPs for this DNS name, i.e. what selectors match the DNS name. 514 func deriveLabelsForName(dnsName string, selectors map[api.FQDNSelector]*regexp.Regexp) labels.Labels { 515 lbls := labels.Labels{} 516 for fqdnSel, fqdnRegex := range selectors { 517 matches := fqdnRegex.MatchString(dnsName) 518 if matches { 519 l := fqdnSel.IdentityLabel() 520 lbls[l.Key] = l 521 } 522 } 523 return lbls 524 } 525 526 // deriveLabelsForNames derives the labels for all names found in nameToIPs 527 func deriveLabelsForNames(nameToIPs map[string][]netip.Addr, selectors map[api.FQDNSelector]*regexp.Regexp) (namesWithMetadata map[string]nameMetadata) { 528 namesWithMetadata = make(map[string]nameMetadata, len(nameToIPs)) 529 for dnsName, addrs := range nameToIPs { 530 namesWithMetadata[dnsName] = nameMetadata{ 531 addrs: addrs, 532 labels: deriveLabelsForName(dnsName, selectors), 533 } 534 } 535 return namesWithMetadata 536 }