// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package socket import ( "fmt" "net" "time" "golang.org/x/net/publicsuffix" ) type ptrRecord struct { hostname string error error expires time.Time } func (r ptrRecord) IsExpired(now time.Time) bool { return now.After(r.expires) } // ReverseLookupCache is a cache for storing and retrieving the results of // reverse DNS queries. It caches the results of queries regardless of their // outcome (success or failure). The result is cached for the amount of time // specified by parameters and not based on the TTL from the PTR record. type ReverseLookupCache struct { data map[string]ptrRecord successTTL, failureTTL time.Duration } // NewReverseLookupCache returns a new cache. func NewReverseLookupCache(successTTL, failureTTL time.Duration) *ReverseLookupCache { c := &ReverseLookupCache{ data: map[string]ptrRecord{}, successTTL: successTTL, failureTTL: failureTTL, } return c } // Cleanup removes expired entries from the cache. func (c *ReverseLookupCache) Cleanup() { now := time.Now() for k, ptr := range c.data { if ptr.IsExpired(now) { delete(c.data, k) } } } // Lookup performs a reverse lookup on the given IP address. A cached result // will be returned if it is contained in the cache, otherwise a lookup is // performed. func (c ReverseLookupCache) Lookup(ip net.IP) (string, error) { // Go doesn't expose a lookup method that accepts net.IP so // unfortunately we must convert the IP to a string. ipStr := ip.String() // XXX: This could be implemented using common.Cache with a separate // cleanup thread. c.Cleanup() // Check the cache. now := time.Now() if ptr, found := c.data[ipStr]; found && !ptr.IsExpired(now) { return ptr.hostname, ptr.error } // Do a new lookup. names, err := net.LookupAddr(ipStr) now = time.Now() var ptr ptrRecord switch { case err != nil: ptr.expires = now.Add(c.failureTTL) ptr.error = err case len(names) == 0: ptr.expires = now.Add(c.failureTTL) ptr.error = fmt.Errorf("empty dns response") default: ptr.expires = now.Add(c.successTTL) ptr.hostname = names[0] } c.data[ipStr] = ptr return ptr.hostname, ptr.error } // etldPlusOne returns the effective top-level domain plus one domain for the // given hostname. func etldPlusOne(hostname string) (string, error) { if hostname == "" { return "", nil } trimmed := false if hostname[len(hostname)-1] == '.' { hostname = hostname[:len(hostname)-1] trimmed = true } domain, err := publicsuffix.EffectiveTLDPlusOne(hostname) if err != nil { return "", err } if trimmed { return domain + ".", nil } return domain, nil }