github.com/looshlee/beatles@v0.0.0-20220727174639-742810ab631c/pkg/ipcache/ipcache.go (about) 1 // Copyright 2018-2019 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package ipcache 16 17 import ( 18 "fmt" 19 "net" 20 21 "github.com/cilium/cilium/pkg/identity" 22 "github.com/cilium/cilium/pkg/lock" 23 "github.com/cilium/cilium/pkg/logging/logfields" 24 "github.com/cilium/cilium/pkg/option" 25 "github.com/cilium/cilium/pkg/source" 26 27 "github.com/sirupsen/logrus" 28 ) 29 30 var ( 31 // IPIdentityCache caches the mapping of endpoint IPs to their corresponding 32 // security identities across the entire cluster in which this instance of 33 // Cilium is running. 34 IPIdentityCache = NewIPCache() 35 ) 36 37 // Identity is the identity representation of an IP<->Identity cache. 38 type Identity struct { 39 // ID is the numeric identity 40 ID identity.NumericIdentity 41 42 // Source is the source of the identity in the cache 43 Source source.Source 44 45 // shadowed determines if another entry overlaps with this one. 46 // Shadowed identities are not propagated to listeners by default. 47 // Most commonly set for Identity with Source = source.Generated when 48 // a pod IP (other source) has the same IP. 49 shadowed bool 50 } 51 52 // IPKeyPair is the (IP, key) pair used of the identity 53 type IPKeyPair struct { 54 IP net.IP 55 Key uint8 56 } 57 58 // IPCache is a collection of mappings: 59 // - mapping of endpoint IP or CIDR to security identities of all endpoints 60 // which are part of the same cluster, and vice-versa 61 // - mapping of endpoint IP or CIDR to host IP (maybe nil) 62 type IPCache struct { 63 mutex lock.SemaphoredMutex 64 ipToIdentityCache map[string]Identity 65 identityToIPCache map[identity.NumericIdentity]map[string]struct{} 66 ipToHostIPCache map[string]IPKeyPair 67 68 // prefixLengths reference-count the number of CIDRs that use 69 // particular prefix lengths for the mask. 70 v4PrefixLengths map[int]int 71 v6PrefixLengths map[int]int 72 73 listeners []IPIdentityMappingListener 74 } 75 76 // Implementation represents a concrete datapath implementation of the IPCache 77 // which may restrict the ability to apply IPCache mappings, depending on the 78 // underlying details of that implementation. 79 type Implementation interface { 80 GetMaxPrefixLengths(ipv6 bool) int 81 } 82 83 // NewIPCache returns a new IPCache with the mappings of endpoint IP to security 84 // identity (and vice-versa) initialized. 85 func NewIPCache() *IPCache { 86 return &IPCache{ 87 mutex: lock.NewSemaphoredMutex(), 88 ipToIdentityCache: map[string]Identity{}, 89 identityToIPCache: map[identity.NumericIdentity]map[string]struct{}{}, 90 ipToHostIPCache: map[string]IPKeyPair{}, 91 v4PrefixLengths: map[int]int{}, 92 v6PrefixLengths: map[int]int{}, 93 } 94 } 95 96 // Lock locks the IPCache's mutex. 97 func (ipc *IPCache) Lock() { 98 ipc.mutex.Lock() 99 } 100 101 // Unlock unlocks the IPCache's mutex. 102 func (ipc *IPCache) Unlock() { 103 ipc.mutex.Unlock() 104 } 105 106 // RLock RLocks the IPCache's mutex. 107 func (ipc *IPCache) RLock() { 108 ipc.mutex.RLock() 109 } 110 111 // RUnlock RUnlocks the IPCache's mutex. 112 func (ipc *IPCache) RUnlock() { 113 ipc.mutex.RUnlock() 114 } 115 116 // SetListeners sets the listeners for this IPCache. 117 func (ipc *IPCache) SetListeners(listeners []IPIdentityMappingListener) { 118 ipc.mutex.Lock() 119 ipc.listeners = listeners 120 ipc.mutex.Unlock() 121 } 122 123 // AddListener adds a listener for this IPCache. 124 func (ipc *IPCache) AddListener(listener IPIdentityMappingListener) { 125 // We need to acquire the semaphored mutex as we Write Lock as we are 126 // modifying the listeners slice. 127 ipc.mutex.Lock() 128 ipc.listeners = append(ipc.listeners, listener) 129 // We will release the semaphore mutex with UnlockToRLock, *and not Unlock* 130 // because want to prevent a race across an Upsert or Delete. By doing this 131 // we are sure no other writers are performing any operation while we are 132 // still reading. 133 ipc.mutex.UnlockToRLock() 134 defer ipc.mutex.RUnlock() 135 // Initialize new listener with the current mappings 136 ipc.DumpToListenerLocked(listener) 137 } 138 139 func checkPrefixLengthsAgainstMap(impl Implementation, prefixes []*net.IPNet, existingPrefixes map[int]int, isIPv6 bool) error { 140 prefixLengths := make(map[int]struct{}) 141 142 for i := range existingPrefixes { 143 prefixLengths[i] = struct{}{} 144 } 145 146 for _, prefix := range prefixes { 147 ones, bits := prefix.Mask.Size() 148 if _, ok := prefixLengths[ones]; !ok { 149 if bits == net.IPv6len*8 && isIPv6 || bits == net.IPv4len*8 && !isIPv6 { 150 prefixLengths[ones] = struct{}{} 151 } 152 } 153 } 154 155 maxPrefixLengths := impl.GetMaxPrefixLengths(isIPv6) 156 if len(prefixLengths) > maxPrefixLengths { 157 existingPrefixLengths := len(existingPrefixes) 158 return fmt.Errorf("adding specified CIDR prefixes would result in too many prefix lengths (current: %d, result: %d, max: %d)", 159 existingPrefixLengths, len(prefixLengths), maxPrefixLengths) 160 } 161 return nil 162 } 163 164 // checkPrefixes ensures that we will reject rules if the import of those 165 // rules would cause the underlying implementation of the ipcache to exceed 166 // the maximum number of supported CIDR prefix lengths. 167 func checkPrefixes(impl Implementation, prefixes []*net.IPNet) (err error) { 168 IPIdentityCache.RLock() 169 defer IPIdentityCache.RUnlock() 170 171 if err = checkPrefixLengthsAgainstMap(impl, prefixes, IPIdentityCache.v4PrefixLengths, false); err != nil { 172 return 173 } 174 return checkPrefixLengthsAgainstMap(impl, prefixes, IPIdentityCache.v6PrefixLengths, true) 175 } 176 177 // refPrefixLength adds one reference to the prefix length in the map. 178 func refPrefixLength(prefixLengths map[int]int, length int) { 179 if _, ok := prefixLengths[length]; ok { 180 prefixLengths[length]++ 181 } else { 182 prefixLengths[length] = 1 183 } 184 } 185 186 // refPrefixLength removes one reference from the prefix length in the map. 187 func unrefPrefixLength(prefixLengths map[int]int, length int) { 188 value := prefixLengths[length] 189 if value <= 1 { 190 delete(prefixLengths, length) 191 } else { 192 prefixLengths[length]-- 193 } 194 } 195 196 // endpointIPToCIDR converts the endpoint IP into an equivalent full CIDR. 197 func endpointIPToCIDR(ip net.IP) *net.IPNet { 198 bits := net.IPv6len * 8 199 if ip.To4() != nil { 200 bits = net.IPv4len * 8 201 } 202 return &net.IPNet{ 203 IP: ip, 204 Mask: net.CIDRMask(bits, bits), 205 } 206 } 207 208 func (ipc *IPCache) getHostIPCache(ip string) (net.IP, uint8) { 209 ipKeyPair := ipc.ipToHostIPCache[ip] 210 return ipKeyPair.IP, ipKeyPair.Key 211 } 212 213 // Upsert adds / updates the provided IP (endpoint or CIDR prefix) and identity 214 // into the IPCache. 215 // 216 // Returns false if the entry is not owned by the self declared source, i.e. 217 // returns false if the kubernetes layer is trying to upsert an entry now 218 // managed by the kvstore layer. See source.AllowOverwrite() for rules on 219 // ownership. hostIP is the location of the given IP. It is optional (may be 220 // nil) and is propagated to the listeners. 221 func (ipc *IPCache) Upsert(ip string, hostIP net.IP, hostKey uint8, newIdentity Identity) bool { 222 scopedLog := log 223 if option.Config.Debug { 224 scopedLog = log.WithFields(logrus.Fields{ 225 logfields.IPAddr: ip, 226 logfields.Identity: newIdentity, 227 logfields.Key: hostKey, 228 }) 229 } 230 231 ipc.mutex.Lock() 232 defer ipc.mutex.Unlock() 233 234 var cidr *net.IPNet 235 var oldIdentity *identity.NumericIdentity 236 callbackListeners := true 237 238 oldHostIP, oldHostKey := ipc.getHostIPCache(ip) 239 240 cachedIdentity, found := ipc.ipToIdentityCache[ip] 241 if found { 242 if !source.AllowOverwrite(cachedIdentity.Source, newIdentity.Source) { 243 return false 244 } 245 246 // Skip update if IP is already mapped to the given identity 247 // and the host IP hasn't changed. 248 if cachedIdentity == newIdentity && oldHostIP.Equal(hostIP) && hostKey == oldHostKey { 249 return true 250 } 251 252 oldIdentity = &cachedIdentity.ID 253 } 254 255 // Endpoint IP identities take precedence over CIDR identities, so if the 256 // IP is a full CIDR prefix and there's an existing equivalent endpoint IP, 257 // don't notify the listeners. 258 var err error 259 if _, cidr, err = net.ParseCIDR(ip); err == nil { 260 // Add a reference for the prefix length if this is a CIDR. 261 pl, bits := cidr.Mask.Size() 262 switch bits { 263 case net.IPv6len * 8: 264 refPrefixLength(ipc.v6PrefixLengths, pl) 265 case net.IPv4len * 8: 266 refPrefixLength(ipc.v4PrefixLengths, pl) 267 } 268 269 ones, bits := cidr.Mask.Size() 270 if ones == bits { 271 if _, endpointIPFound := ipc.ipToIdentityCache[cidr.IP.String()]; endpointIPFound { 272 scopedLog.Debug("Ignoring CIDR to identity mapping as it is shadowed by an endpoint IP") 273 // Skip calling back the listeners, since the endpoint IP has 274 // precedence over the new CIDR. 275 newIdentity.shadowed = true 276 } 277 } 278 } else if endpointIP := net.ParseIP(ip); endpointIP != nil { // Endpoint IP. 279 cidr = endpointIPToCIDR(endpointIP) 280 281 // Check whether the upserted endpoint IP will shadow that CIDR, and 282 // replace its mapping with the listeners if that was the case. 283 if !found { 284 cidrStr := cidr.String() 285 if cidrIdentity, cidrFound := ipc.ipToIdentityCache[cidrStr]; cidrFound { 286 oldHostIP, _ = ipc.getHostIPCache(cidrStr) 287 if cidrIdentity.ID != newIdentity.ID || !oldHostIP.Equal(hostIP) { 288 scopedLog.Debug("New endpoint IP started shadowing existing CIDR to identity mapping") 289 cidrIdentity.shadowed = true 290 ipc.ipToIdentityCache[cidrStr] = cidrIdentity 291 oldIdentity = &cidrIdentity.ID 292 } else { 293 // The endpoint IP and the CIDR are associated with the 294 // same identity and host IP. Nothing changes for the 295 // listeners. 296 callbackListeners = false 297 } 298 } 299 } 300 } else { 301 log.WithFields(logrus.Fields{ 302 logfields.IPAddr: ip, 303 logfields.Identity: newIdentity, 304 logfields.Key: hostKey, 305 }).Error("Attempt to upsert invalid IP into ipcache layer") 306 return false 307 } 308 309 scopedLog.Debug("Upserting IP into ipcache layer") 310 311 // Update both maps. 312 ipc.ipToIdentityCache[ip] = newIdentity 313 // Delete the old identity, if any. 314 if found { 315 delete(ipc.identityToIPCache[cachedIdentity.ID], ip) 316 if len(ipc.identityToIPCache[cachedIdentity.ID]) == 0 { 317 delete(ipc.identityToIPCache, cachedIdentity.ID) 318 } 319 } 320 if _, ok := ipc.identityToIPCache[newIdentity.ID]; !ok { 321 ipc.identityToIPCache[newIdentity.ID] = map[string]struct{}{} 322 } 323 ipc.identityToIPCache[newIdentity.ID][ip] = struct{}{} 324 325 if hostIP == nil { 326 delete(ipc.ipToHostIPCache, ip) 327 } else { 328 ipc.ipToHostIPCache[ip] = IPKeyPair{IP: hostIP, Key: hostKey} 329 } 330 331 if callbackListeners && !newIdentity.shadowed { 332 for _, listener := range ipc.listeners { 333 listener.OnIPIdentityCacheChange(Upsert, *cidr, oldHostIP, hostIP, oldIdentity, newIdentity.ID, hostKey) 334 } 335 } 336 337 return true 338 } 339 340 // DumpToListenerLocked dumps the entire contents of the IPCache by triggering 341 // the listener's "OnIPIdentityCacheChange" method for each entry in the cache. 342 func (ipc *IPCache) DumpToListenerLocked(listener IPIdentityMappingListener) { 343 for ip, identity := range ipc.ipToIdentityCache { 344 if identity.shadowed { 345 continue 346 } 347 hostIP, encryptKey := ipc.getHostIPCache(ip) 348 _, cidr, err := net.ParseCIDR(ip) 349 if err != nil { 350 endpointIP := net.ParseIP(ip) 351 cidr = endpointIPToCIDR(endpointIP) 352 } 353 listener.OnIPIdentityCacheChange(Upsert, *cidr, nil, hostIP, nil, identity.ID, encryptKey) 354 } 355 } 356 357 // deleteLocked removes the provided IP-to-security-identity mapping 358 // from ipc with the assumption that the IPCache's mutex is held. 359 func (ipc *IPCache) deleteLocked(ip string, source source.Source) { 360 scopedLog := log.WithFields(logrus.Fields{ 361 logfields.IPAddr: ip, 362 }) 363 364 cachedIdentity, found := ipc.ipToIdentityCache[ip] 365 if !found { 366 scopedLog.Debug("Attempt to remove non-existing IP from ipcache layer") 367 return 368 } 369 370 if cachedIdentity.Source != source { 371 scopedLog.WithField("source", cachedIdentity.Source). 372 Debugf("Skipping delete of identity from source %s", source) 373 return 374 } 375 376 var cidr *net.IPNet 377 cacheModification := Delete 378 oldHostIP, encryptKey := ipc.getHostIPCache(ip) 379 var newHostIP net.IP 380 var oldIdentity *identity.NumericIdentity 381 newIdentity := cachedIdentity 382 callbackListeners := true 383 384 var err error 385 if _, cidr, err = net.ParseCIDR(ip); err == nil { 386 // Remove a reference for the prefix length if this is a CIDR. 387 pl, bits := cidr.Mask.Size() 388 switch bits { 389 case net.IPv6len * 8: 390 unrefPrefixLength(ipc.v6PrefixLengths, pl) 391 case net.IPv4len * 8: 392 unrefPrefixLength(ipc.v4PrefixLengths, pl) 393 } 394 395 // Check whether the deleted CIDR was shadowed by an endpoint IP. In 396 // this case, skip calling back the listeners since they don't know 397 // about its mapping. 398 if _, endpointIPFound := ipc.ipToIdentityCache[cidr.IP.String()]; endpointIPFound { 399 scopedLog.Debug("Deleting CIDR shadowed by endpoint IP") 400 callbackListeners = false 401 } 402 } else if endpointIP := net.ParseIP(ip); endpointIP != nil { // Endpoint IP. 403 // Convert the endpoint IP into an equivalent full CIDR. 404 bits := net.IPv6len * 8 405 if endpointIP.To4() != nil { 406 bits = net.IPv4len * 8 407 } 408 cidr = &net.IPNet{ 409 IP: endpointIP, 410 Mask: net.CIDRMask(bits, bits), 411 } 412 413 // Check whether the deleted endpoint IP was shadowing that CIDR, and 414 // restore its mapping with the listeners if that was the case. 415 cidrStr := cidr.String() 416 if cidrIdentity, cidrFound := ipc.ipToIdentityCache[cidrStr]; cidrFound { 417 newHostIP, _ = ipc.getHostIPCache(cidrStr) 418 if cidrIdentity.ID != cachedIdentity.ID || !oldHostIP.Equal(newHostIP) { 419 scopedLog.Debug("Removal of endpoint IP revives shadowed CIDR to identity mapping") 420 cacheModification = Upsert 421 cidrIdentity.shadowed = false 422 ipc.ipToIdentityCache[cidrStr] = cidrIdentity 423 oldIdentity = &cachedIdentity.ID 424 newIdentity = cidrIdentity 425 } else { 426 // The endpoint IP and the CIDR were associated with the same 427 // identity and host IP. Nothing changes for the listeners. 428 callbackListeners = false 429 } 430 } 431 } else { 432 scopedLog.Error("Attempt to delete invalid IP from ipcache layer") 433 return 434 } 435 436 scopedLog.Debug("Deleting IP from ipcache layer") 437 438 delete(ipc.ipToIdentityCache, ip) 439 delete(ipc.identityToIPCache[cachedIdentity.ID], ip) 440 if len(ipc.identityToIPCache[cachedIdentity.ID]) == 0 { 441 delete(ipc.identityToIPCache, cachedIdentity.ID) 442 } 443 delete(ipc.ipToHostIPCache, ip) 444 445 if callbackListeners { 446 for _, listener := range ipc.listeners { 447 listener.OnIPIdentityCacheChange(cacheModification, *cidr, oldHostIP, newHostIP, 448 oldIdentity, newIdentity.ID, encryptKey) 449 } 450 } 451 } 452 453 // Delete removes the provided IP-to-security-identity mapping from the IPCache. 454 func (ipc *IPCache) Delete(IP string, source source.Source) { 455 ipc.mutex.Lock() 456 defer ipc.mutex.Unlock() 457 ipc.deleteLocked(IP, source) 458 } 459 460 // LookupByIP returns the corresponding security identity that endpoint IP maps 461 // to within the provided IPCache, as well as if the corresponding entry exists 462 // in the IPCache. 463 func (ipc *IPCache) LookupByIP(IP string) (Identity, bool) { 464 ipc.mutex.RLock() 465 defer ipc.mutex.RUnlock() 466 return ipc.LookupByIPRLocked(IP) 467 } 468 469 // LookupByIPRLocked returns the corresponding security identity that endpoint IP maps 470 // to within the provided IPCache, as well as if the corresponding entry exists 471 // in the IPCache. 472 func (ipc *IPCache) LookupByIPRLocked(IP string) (Identity, bool) { 473 474 identity, exists := ipc.ipToIdentityCache[IP] 475 return identity, exists 476 } 477 478 // LookupByPrefixRLocked looks for either the specified CIDR prefix, or if the 479 // prefix is fully specified (ie, w.x.y.z/32 for IPv4), find the host for the 480 // identity in the provided IPCache, and returns the corresponding security 481 // identity as well as whether the entry exists in the IPCache. 482 func (ipc *IPCache) LookupByPrefixRLocked(prefix string) (identity Identity, exists bool) { 483 if _, cidr, err := net.ParseCIDR(prefix); err == nil { 484 // If it's a fully specfied prefix, attempt to find the host 485 ones, bits := cidr.Mask.Size() 486 if ones == bits { 487 identity, exists = ipc.ipToIdentityCache[cidr.IP.String()] 488 if exists { 489 return 490 } 491 } 492 } 493 identity, exists = ipc.ipToIdentityCache[prefix] 494 return 495 } 496 497 // LookupByPrefix returns the corresponding security identity that endpoint IP 498 // maps to within the provided IPCache, as well as if the corresponding entry 499 // exists in the IPCache. 500 func (ipc *IPCache) LookupByPrefix(IP string) (Identity, bool) { 501 ipc.mutex.RLock() 502 defer ipc.mutex.RUnlock() 503 return ipc.LookupByPrefixRLocked(IP) 504 } 505 506 // LookupByIdentity returns the set of IPs (endpoint or CIDR prefix) that have 507 // security identity ID, as well as whether the corresponding entry exists in 508 // the IPCache. 509 func (ipc *IPCache) LookupByIdentity(id identity.NumericIdentity) (map[string]struct{}, bool) { 510 ipc.mutex.RLock() 511 defer ipc.mutex.RUnlock() 512 ips, exists := ipc.identityToIPCache[id] 513 return ips, exists 514 } 515 516 // GetIPIdentityMapModel returns all known endpoint IP to security identity mappings 517 // stored in the key-value store. 518 func GetIPIdentityMapModel() { 519 // TODO (ianvernon) return model of ip to identity mapping. For use in CLI. 520 // see GH-2555 521 }