github.com/elfadel/cilium@v1.6.12/pkg/fqdn/name_manager.go (about) 1 // Copyright 2018 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 fqdn 16 17 import ( 18 "context" 19 "net" 20 "regexp" 21 "sync" 22 "time" 23 24 "github.com/cilium/cilium/pkg/identity" 25 "github.com/cilium/cilium/pkg/ipcache" 26 "github.com/cilium/cilium/pkg/lock" 27 "github.com/cilium/cilium/pkg/logging/logfields" 28 "github.com/cilium/cilium/pkg/policy/api" 29 30 "github.com/sirupsen/logrus" 31 ) 32 33 // NameManager maintains state DNS names, via FQDNSelector or exact match for 34 // polling, need to be tracked. It is the main structure which relates the FQDN 35 // subsystem to the policy subsystem for plumbing the relation between a DNS 36 // name and the corresponding IPs which have been returned via DNS lookups. 37 // When DNS updates are given to a NameManager it update cached selectors as 38 // required via UpdateSelectors. 39 // DNS information is cached, respecting TTL. 40 type NameManager struct { 41 lock.Mutex 42 43 // config is a copy from when this instance was initialized. 44 // It is read-only once set 45 config Config 46 47 // namesToPoll is the set of names that need to be polled. These do not 48 // include regexes, as those are not polled directly. 49 namesToPoll map[string]struct{} 50 51 // allSelectors contains all FQDNSelectors which are present in all policy. We 52 // use these selectors to map selectors --> IPs. 53 allSelectors map[api.FQDNSelector]*regexp.Regexp 54 55 // cache is a private copy of the pointer from config. 56 cache *DNSCache 57 58 bootstrapCompleted bool 59 } 60 61 // Lock must be held during any calls to RegisterForIdentityUpdatesLocked or 62 // UnregisterForIdentityUpdatesLocked. 63 func (n *NameManager) Lock() { 64 n.Mutex.Lock() 65 } 66 67 // Unlock must be called after calls to RegisterForIdentityUpdatesLocked or 68 // UnregisterForIdentityUpdatesLocked are done. 69 func (n *NameManager) Unlock() { 70 n.Mutex.Unlock() 71 } 72 73 // RegisterForIdentityUpdatesLocked exposes this FQDNSelector so that identities 74 // for IPs contained in a DNS response that matches said selector can be 75 // propagated back to the SelectorCache via `UpdateFQDNSelector`. All DNS names 76 // contained within the NameManager's cache are iterated over to see if they match 77 // the FQDNSelector. All IPs which correspond to the DNS names which match this 78 // Selector will be returned as CIDR identities, as other DNS Names which have 79 // already been resolved may match this FQDNSelector. 80 func (n *NameManager) RegisterForIdentityUpdatesLocked(selector api.FQDNSelector) []identity.NumericIdentity { 81 _, exists := n.allSelectors[selector] 82 if exists { 83 log.WithField("fqdnSelector", selector).Warning("FQDNSelector was already registered for updates, returning without any identities") 84 return nil 85 } 86 87 // This error should never occur since the FQDNSelector has already been 88 // validated, but account for it for good measure. 89 regex, err := selector.ToRegex() 90 if err != nil { 91 log.WithError(err).WithField("fqdnSelector", selector).Error("FQDNSelector did not compile to valid regex") 92 return nil 93 } 94 95 // Update names to poll for DNS poller since we now care about this selector. 96 if len(selector.MatchName) > 0 { 97 n.namesToPoll[prepareMatchName(selector.MatchName)] = struct{}{} 98 } 99 100 n.allSelectors[selector] = regex 101 _, selectorIPMapping := mapSelectorsToIPs(map[api.FQDNSelector]struct{}{selector: {}}, n.cache) 102 103 // Allocate identities for each IPNet and then map to selector 104 selectorIPs := selectorIPMapping[selector] 105 log.WithFields(logrus.Fields{ 106 "fqdnSelector": selector, 107 "ips": selectorIPs, 108 }).Debug("getting identities for IPs associated with FQDNSelector") 109 var currentlyAllocatedIdentities []*identity.Identity 110 if currentlyAllocatedIdentities, err = ipcache.AllocateCIDRsForIPs(selectorIPs); err != nil { 111 log.WithError(err).WithField("prefixes", selectorIPs).Warn( 112 "failed to allocate identities for IPs") 113 return nil 114 } 115 numIDs := make([]identity.NumericIdentity, 0, len(currentlyAllocatedIdentities)) 116 for i := range currentlyAllocatedIdentities { 117 numIDs = append(numIDs, currentlyAllocatedIdentities[i].ID) 118 } 119 120 return numIDs 121 } 122 123 // UnregisterForIdentityUpdatesLocked removes this FQDNSelector from the set of 124 // FQDNSelectors which are being tracked by the NameManager. No more updates for IPs 125 // which correspond to said selector are propagated. 126 func (n *NameManager) UnregisterForIdentityUpdatesLocked(selector api.FQDNSelector) { 127 delete(n.allSelectors, selector) 128 if len(selector.MatchName) > 0 { 129 delete(n.namesToPoll, prepareMatchName(selector.MatchName)) 130 } 131 } 132 133 // NewNameManager creates an initialized NameManager. 134 // When config.Cache is nil, the global fqdn.DefaultDNSCache is used. 135 func NewNameManager(config Config) *NameManager { 136 137 if config.Cache == nil { 138 config.Cache = NewDNSCache(0) 139 } 140 141 if config.UpdateSelectors == nil { 142 config.UpdateSelectors = func(ctx context.Context, selectorIPMapping map[api.FQDNSelector][]net.IP, namesMissingIPs []api.FQDNSelector) (*sync.WaitGroup, error) { 143 return &sync.WaitGroup{}, nil 144 } 145 } 146 147 return &NameManager{ 148 config: config, 149 namesToPoll: make(map[string]struct{}), 150 allSelectors: make(map[api.FQDNSelector]*regexp.Regexp), 151 cache: config.Cache, 152 } 153 154 } 155 156 // GetDNSCache returns the DNSCache used by the NameManager 157 func (n *NameManager) GetDNSCache() *DNSCache { 158 return n.cache 159 } 160 161 // GetDNSNames returns a snapshot of the DNS names managed by this NameManager 162 func (n *NameManager) GetDNSNames() (dnsNames []string) { 163 n.Lock() 164 defer n.Unlock() 165 166 for name := range n.namesToPoll { 167 dnsNames = append(dnsNames, name) 168 } 169 170 return dnsNames 171 } 172 173 // UpdateGenerateDNS inserts the new DNS information into the cache. If the IPs 174 // have changed for a name, store which rules must be updated in rulesToUpdate, 175 // regenerate them, and emit via UpdateSelectors. 176 func (n *NameManager) UpdateGenerateDNS(ctx context.Context, lookupTime time.Time, updatedDNSIPs map[string]*DNSIPRecords) (wg *sync.WaitGroup, err error) { 177 n.Mutex.Lock() 178 defer n.Mutex.Unlock() 179 180 // Update IPs in n 181 fqdnSelectorsToUpdate, updatedDNSNames := n.updateDNSIPs(lookupTime, updatedDNSIPs) 182 for dnsName, IPs := range updatedDNSNames { 183 log.WithFields(logrus.Fields{ 184 "matchName": dnsName, 185 "IPs": IPs, 186 "fqdnSelectorsToUpdate": fqdnSelectorsToUpdate, 187 }).Debug("Updated FQDN with new IPs") 188 } 189 190 namesMissingIPs, selectorIPMapping := n.generateSelectorUpdates(fqdnSelectorsToUpdate) 191 if len(namesMissingIPs) != 0 { 192 log.WithField(logfields.DNSName, namesMissingIPs). 193 Debug("No IPs to insert when generating DNS name selected by ToFQDN rule") 194 } 195 196 return n.config.UpdateSelectors(ctx, selectorIPMapping, namesMissingIPs) 197 } 198 199 // ForceGenerateDNS unconditionally regenerates all rules that refer to DNS 200 // names in namesToRegen. These names are FQDNs and toFQDNs.matchPatterns or 201 // matchNames that match them will cause these rules to regenerate. 202 func (n *NameManager) ForceGenerateDNS(ctx context.Context, namesToRegen []string) (wg *sync.WaitGroup, err error) { 203 n.Mutex.Lock() 204 defer n.Mutex.Unlock() 205 206 affectedFQDNSels := make(map[api.FQDNSelector]struct{}, 0) 207 for _, dnsName := range namesToRegen { 208 for fqdnSel, fqdnRegEx := range n.allSelectors { 209 if fqdnRegEx.MatchString(dnsName) { 210 affectedFQDNSels[fqdnSel] = struct{}{} 211 } 212 } 213 } 214 215 namesMissingIPs, selectorIPMapping := mapSelectorsToIPs(affectedFQDNSels, n.cache) 216 if len(namesMissingIPs) != 0 { 217 log.WithField(logfields.DNSName, namesMissingIPs). 218 Debug("No IPs to insert when generating DNS name selected by ToFQDN rule") 219 } 220 221 // emit the new rules 222 return n.config. 223 UpdateSelectors(ctx, selectorIPMapping, namesMissingIPs) 224 } 225 226 func (n *NameManager) CompleteBootstrap() { 227 n.Lock() 228 n.bootstrapCompleted = true 229 n.Unlock() 230 } 231 232 // updateDNSIPs updates the IPs for each DNS name in updatedDNSIPs. 233 // It returns: 234 // affectedSelectors: a set of all FQDNSelectors which match DNS Names whose 235 // corresponding set of IPs has changed. 236 // updatedNames: a map of DNS names to all the valid IPs we store for each. 237 func (n *NameManager) updateDNSIPs(lookupTime time.Time, updatedDNSIPs map[string]*DNSIPRecords) (affectedSelectors map[api.FQDNSelector]struct{}, updatedNames map[string][]net.IP) { 238 updatedNames = make(map[string][]net.IP, len(updatedDNSIPs)) 239 affectedSelectors = make(map[api.FQDNSelector]struct{}, len(updatedDNSIPs)) 240 241 perDNSName: 242 for dnsName, lookupIPs := range updatedDNSIPs { 243 updated := n.updateIPsForName(lookupTime, dnsName, lookupIPs.IPs, lookupIPs.TTL) 244 245 // The IPs didn't change. No more to be done for this dnsName 246 if !updated && n.bootstrapCompleted { 247 log.WithFields(logrus.Fields{ 248 "dnsName": dnsName, 249 "lookupIPs": lookupIPs, 250 }).Debug("FQDN: IPs didn't change for DNS name") 251 continue perDNSName 252 } 253 254 // record the IPs that were different 255 updatedNames[dnsName] = lookupIPs.IPs 256 257 // accumulate the new selectors affected by new IPs 258 if len(n.allSelectors) == 0 { 259 log.WithFields(logrus.Fields{ 260 "dnsName": dnsName, 261 "lookupIPs": lookupIPs, 262 }).Debug("FQDN: No selectors registered for updates") 263 } 264 for fqdnSel, fqdnRegex := range n.allSelectors { 265 matches := fqdnRegex.MatchString(dnsName) 266 if matches { 267 affectedSelectors[fqdnSel] = struct{}{} 268 } 269 } 270 } 271 272 return affectedSelectors, updatedNames 273 } 274 275 // generateSelectorUpdates iterates over all names in the DNS cache managed by 276 // gen and figures out to which FQDNSelectors managed by the cache these names 277 // map. Returns the set of FQDNSelectors which map to no IPs, and a mapping 278 // of FQDNSelectors to IPs. 279 func (n *NameManager) generateSelectorUpdates(fqdnSelectors map[api.FQDNSelector]struct{}) (namesMissingIPs []api.FQDNSelector, selectorIPMapping map[api.FQDNSelector][]net.IP) { 280 return mapSelectorsToIPs(fqdnSelectors, n.cache) 281 } 282 283 // updateIPsName will update the IPs for dnsName. It always retains a copy of 284 // newIPs. 285 // updated is true when the new IPs differ from the old IPs 286 func (n *NameManager) updateIPsForName(lookupTime time.Time, dnsName string, newIPs []net.IP, ttl int) (updated bool) { 287 cacheIPs := n.cache.Lookup(dnsName) 288 289 if n.config.MinTTL > ttl { 290 ttl = n.config.MinTTL 291 } 292 293 n.cache.Update(lookupTime, dnsName, newIPs, ttl) 294 sortedNewIPs := n.cache.Lookup(dnsName) // DNSCache returns IPs sorted 295 296 // The 0 checks below account for an unlike race condition where this 297 // function is called with already expired data and if other cache data 298 // from before also expired. 299 return (len(cacheIPs) == 0 && len(sortedNewIPs) == 0) || !sortedIPsAreEqual(sortedNewIPs, cacheIPs) 300 }