]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Convert LRU map into a CLP map (#593)
authorAmos Jeffries <yadij@users.noreply.github.com>
Sun, 26 Jul 2020 02:21:58 +0000 (02:21 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Sun, 26 Jul 2020 06:28:17 +0000 (06:28 +0000)
Revamped LruMap implementation, adding support for value-specific TTLs,
value-specific memory consumption estimates, memory pooling for value
wrappers, and polishing code. Renamed the result to ClpMap.

Fixed memory leak of generated fake SSL certificates in Squids
configured with dynamic_cert_mem_cache_size=0 (the default is 4MB).

Controversially changed sslcrtvalidator_program cache and TTL defaults:

* After we started accounting for key lengths in validator cache
  entries, the old 2048 bytes default size became not just ridiculously
  small but insufficient to cache the vast majority of cache entries
  because an entry key contains a PEM certificate sent to the validator
  (among other validation parameters). A PEM certificate usually
  consumes at least a few kilobytes. Living the old 2KB default would
  essentially mean that we are disabling caching (by default).

* And if we fix one default, it makes sense to fix the other as well.
  The old 60 second TTL default felt too conservative. Most certificate
  invalidations last days, not seconds. We picked one hour because that
  value is still fairly conservative, used as the default by several
  other Squid caches, and should allow for decent cache hit ratio.

Dropped support for the undocumented "sslcrtvalidator_program ttl=-1"
hack that meant unlimited TTL. Large TTL values should be used instead.
TODO: The option parser should be revamped to reject too-large values.

18 files changed:
src/SquidMath.h
src/base/ClpMap.h [new file with mode: 0644]
src/base/LruMap.h [deleted file]
src/base/Makefile.am
src/base/Optional.h [new file with mode: 0644]
src/base/forward.h
src/cf.data.pre
src/client_side.cc
src/mem/PoolingAllocator.h
src/ssl/cert_validate_message.cc
src/ssl/cert_validate_message.h
src/ssl/context_storage.cc
src/ssl/context_storage.h
src/ssl/helper.cc
src/ssl/helper.h
src/ssl/support.cc
src/ssl/support.h
src/tests/stub_libsslsquid.cc

index 12bb57c9ab108dccda9ad6c0c73118d87d3aadec..480d7b931bb19fd3d46e22191071c2ba71069cda 100644 (file)
@@ -9,6 +9,13 @@
 #ifndef _SQUID_SRC_SQUIDMATH_H
 #define _SQUID_SRC_SQUIDMATH_H
 
+#include "base/forward.h"
+
+#include <limits>
+#include <type_traits>
+
+// TODO: Move to src/base/Math.h and drop the Math namespace
+
 /* Math functions we define locally for Squid */
 namespace Math
 {
@@ -21,5 +28,64 @@ double doubleAverage(const double, const double, int, const int);
 
 } // namespace Math
 
+// If Sum() performance becomes important, consider using GCC and clang
+// built-ins like __builtin_add_overflow() instead of manual overflow checks.
+
+/// std::enable_if_t replacement until C++14
+/// simplifies Sum() declarations below
+template <bool B, class T = void>
+using EnableIfType = typename std::enable_if<B,T>::type;
+
+/// detects a pair of unsigned types
+/// reduces code duplication in Sum() declarations below
+template <typename T, typename U>
+using AllUnsigned = typename std::conditional<
+    std::is_unsigned<T>::value && std::is_unsigned<U>::value,
+    std::true_type,
+    std::false_type
+    >::type;
+
+/// \returns a non-overflowing sum of the two unsigned arguments (or nothing)
+template <typename T, typename U, EnableIfType<AllUnsigned<T,U>::value, int> = 0>
+Optional<T>
+Sum(const T a, const U b) {
+    // Instead of computing the largest type dynamically, we simply go by T and
+    // reject cases like Sum(0, ULLONG_MAX) that would overflow on return.
+    // TODO: Consider using std::common_type<T, U> in the return type instead.
+    static_assert(sizeof(T) >= sizeof(U), "Sum() return type can fit its (unsigned) result");
+
+    // this optimized implementation relies on unsigned overflows
+    static_assert(std::is_unsigned<T>::value, "the first Sum(a,b) argument is unsigned");
+    static_assert(std::is_unsigned<U>::value, "the second Sum(a,b) argument is unsigned");
+    const auto sum = a + b;
+    // when a+b overflows, the result becomes smaller than any operand
+    return (sum < a) ? Optional<T>() : Optional<T>(sum);
+}
+
+/// \returns a non-overflowing sum of the two signed arguments (or nothing)
+template <typename T, typename U, EnableIfType<!AllUnsigned<T,U>::value, int> = 0>
+Optional<T> constexpr
+Sum(const T a, const U b) {
+    // Instead of computing the largest type dynamically, we simply go by T and
+    // reject cases like Sum(0, LLONG_MAX) that would overflow on return.
+    static_assert(sizeof(T) >= sizeof(U), "Sum() return type can fit its (signed) result");
+
+    // tests below avoid undefined behavior of signed under/overflows
+    return b >= 0 ?
+        ((a > std::numeric_limits<U>::max() - b) ? Optional<T>() : Optional<T>(a + b)):
+        ((a < std::numeric_limits<U>::min() - b) ? Optional<T>() : Optional<T>(a + b));
+}
+
+/// \returns a non-overflowing sum of the arguments (or nothing)
+template <typename T, typename... Args>
+Optional<T>
+Sum(const T first, Args... args) {
+    if (const auto others = Sum(args...)) {
+        return Sum(first, others.value());
+    } else {
+        return Optional<T>();
+    }
+}
+
 #endif /* _SQUID_SRC_SQUIDMATH_H */
 
diff --git a/src/base/ClpMap.h b/src/base/ClpMap.h
new file mode 100644 (file)
index 0000000..75e47a1
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef SQUID__SRC_BASE_CLPMAP_H
+#define SQUID__SRC_BASE_CLPMAP_H
+
+#include "base/Optional.h"
+#include "mem/PoolingAllocator.h"
+#include "SquidMath.h"
+#include "SquidTime.h"
+
+#include <functional>
+#include <limits>
+#include <list>
+#include <unordered_map>
+
+template<class Value>
+uint64_t
+DefaultMemoryUsage(const Value &e)
+{
+    return sizeof(e);
+}
+
+/// An in-memory associative container enforcing three primary caching policies:
+/// * Capacity: The memory used by cached entries has a configurable limit;
+/// * Lifetime: Entries are hidden (and may be deleted) after their TTL expires;
+/// * Priority: Capacity victims are purged in LRU order.
+/// Individual cache entry operations have average constant-time complexity.
+///
+/// Value must meet STL requirements of Erasable and EmplaceConstructible.
+/// Key must come with std::hash<Key> and std::equal_to<Key> instantiations.
+/// Key::length() must return the number of memory bytes in use by the key.
+/// MemoryUsedBy() must return the number of memory bytes in use by the value.
+template <class Key, class Value, uint64_t MemoryUsedBy(const Value &) = DefaultMemoryUsage>
+class ClpMap
+{
+public:
+    /// maximum desired entry caching duration (a.k.a. TTL), in seconds
+    using Ttl = int;
+
+    explicit ClpMap(const uint64_t capacity) { setMemLimit(capacity); }
+    ClpMap(uint64_t capacity, Ttl defaultTtl);
+    ~ClpMap() = default;
+
+    // copying disabled because it may be expensive for large maps
+    // moving (implicitly) disabled for simplicity sake
+    ClpMap(const ClpMap &) = delete;
+    ClpMap &operator =(const ClpMap &) = delete;
+
+    /// \return a pointer to a fresh cached value (or nil)
+    /// The underlying value is owned by the map, so the pointer may be
+    /// invalidated by any non-constant method call, including another get().
+    /// Also moves the found entry to the end of the purging queue.
+    const Value *get(const Key &);
+
+    /// Copy the given value into the map (with the given key and TTL)
+    /// \retval true the value was successfully copied into the map
+    /// \retval false caching was rejected (the map remains unchanged)
+    bool add(const Key &, const Value &, Ttl);
+
+    /// Copy the given value into the map (with the given key and default TTL)
+    bool add(const Key &key, const Value &v) { return add(key, v, defaultTtl_); }
+
+    /// Remove the corresponding entry (if any)
+    void del(const Key &);
+
+    /// Reset the memory capacity for this map, purging if needed
+    void setMemLimit(uint64_t newLimit);
+
+    /// The memory capacity for the map
+    uint64_t memLimit() const { return memLimit_; }
+
+    /// The free space of the map
+    uint64_t freeMem() const { return memLimit() - memoryUsed(); }
+
+    /// The current (approximate) memory usage of the map
+    uint64_t memoryUsed() const { return memUsed_; }
+
+    /// The number of currently stored entries, including expired ones
+    size_t entries() const { return entries_.size(); }
+
+private:
+    /// the keeper of cache entry Key, Value, and caching-related entry metadata
+    class Entry
+    {
+    public:
+        Entry(const Key &, const Value &, const Ttl);
+
+        /// whether the entry is stale
+        bool expired() const { return expires < squid_curtime; }
+
+    public:
+        Key key; ///< the entry search key; see ClpMap::get()
+        Value value; ///< cached value provided by the map user
+        time_t expires = 0; ///< get() stops returning the entry after this time
+        uint64_t memCounted = 0; ///< memory accounted for this entry in our ClpMap
+    };
+
+    /// Entries in LRU order
+    using Entries = std::list<Entry, PoolingAllocator<Entry> >;
+    using EntriesIterator = typename Entries::iterator;
+
+    using IndexItem = std::pair<Key, EntriesIterator>;
+    /// key:entry_position mapping for fast entry lookups by key
+    using Index = std::unordered_map<Key, EntriesIterator, std::hash<Key>, std::equal_to<Key>, PoolingAllocator<IndexItem> >;
+    using IndexIterator = typename Index::iterator;
+
+    static Optional<uint64_t> MemoryCountedFor(const Key &, const Value &);
+
+    void trim(uint64_t wantSpace);
+    void erase(const IndexIterator &);
+    IndexIterator find(const Key &);
+
+    /// cached entries, including expired ones, in LRU order
+    Entries entries_;
+
+    /// entries_ positions indexed by the entry key
+    Index index_;
+
+    /// entry TTL to use if none provided to add()
+    Ttl defaultTtl_ = std::numeric_limits<Ttl>::max();
+
+    /// the maximum memory we are allowed to use for all cached entries
+    uint64_t memLimit_ = 0;
+
+    /// the total amount of memory we currently use for all cached entries
+    uint64_t memUsed_ = 0;
+};
+
+template <class Key, class Value, uint64_t MemoryUsedBy(const Value &)>
+ClpMap<Key, Value, MemoryUsedBy>::ClpMap(const uint64_t capacity, const Ttl defaultTtl):
+    defaultTtl_(defaultTtl)
+{
+    assert(defaultTtl >= 0);
+    setMemLimit(capacity);
+}
+
+template <class Key, class Value, uint64_t MemoryUsedBy(const Value &)>
+void
+ClpMap<Key, Value, MemoryUsedBy>::setMemLimit(const uint64_t newLimit)
+{
+    assert(newLimit >= 0);
+    if (memUsed_ > newLimit)
+        trim(memLimit_ - newLimit);
+    memLimit_ = newLimit;
+}
+
+/// \returns the index position of an entry identified by its key (or end())
+template <class Key, class Value, uint64_t MemoryUsedBy(const Value &)>
+typename ClpMap<Key, Value, MemoryUsedBy>::IndexIterator
+ClpMap<Key, Value, MemoryUsedBy>::find(const Key &key)
+{
+    const auto i = index_.find(key);
+    if (i == index_.end())
+        return i;
+
+    const auto entryPosition = i->second;
+    if (!entryPosition->expired()) {
+        if (entryPosition != entries_.begin())
+            entries_.splice(entries_.begin(), entries_, entryPosition);
+        return i;
+    }
+    // else fall through to cleanup
+
+    erase(i);
+    return index_.end();
+}
+
+template <class Key, class Value, uint64_t MemoryUsedBy(const Value &)>
+const Value *
+ClpMap<Key, Value, MemoryUsedBy>::get(const Key &key)
+{
+    const auto i = find(key);
+    if (i != index_.end()) {
+        const auto &entry = *(i->second);
+        return &entry.value;
+    }
+    return nullptr;
+}
+
+template <class Key, class Value, uint64_t MemoryUsedBy(const Value &)>
+Optional<uint64_t>
+ClpMap<Key, Value, MemoryUsedBy>::MemoryCountedFor(const Key &k, const Value &v)
+{
+    // Both storage and index store keys, but we count keySz once, assuming that
+    // copying a Key does not consume more memory. This assumption holds for
+    // Key=SBuf, but, ideally, we should be outsourcing this decision to another
+    // configurable function, storing each key once, or hard-coding Key=SBuf.
+    const auto keySz = k.length();
+
+    // approximate calculation (e.g., containers store wrappers not value_types)
+    return Sum<uint64_t>(
+        keySz,
+        // storage
+        sizeof(typename Entries::value_type),
+        MemoryUsedBy(v),
+        // index
+        sizeof(typename Index::value_type));
+}
+
+template <class Key, class Value, uint64_t MemoryUsedBy(const Value &)>
+bool
+ClpMap<Key, Value, MemoryUsedBy>::add(const Key &key, const Value &v, const Ttl ttl)
+{
+    // optimization: avoid del() search, MemoryCountedFor() in always-empty maps
+    if (memLimit() == 0)
+        return false;
+
+    del(key);
+
+    if (ttl < 0)
+        return false; // already expired; will never be returned by get()
+
+    const auto memoryRequirements = MemoryCountedFor(key, v);
+    if (!memoryRequirements)
+        return false; // cannot even compute memory requirements
+
+    const auto wantSpace = memoryRequirements.value();
+    if (wantSpace > memLimit() || wantSpace == 0) // 0 is 64-bit integer overflow
+        return false; // will never fit
+    trim(wantSpace);
+
+    entries_.emplace_front(key, v, ttl); // TODO: After C++17 migration, use the return value
+    index_.emplace(key, entries_.begin());
+
+    entries_.begin()->memCounted = wantSpace;
+    memUsed_ += wantSpace;
+    assert(memUsed_ >= wantSpace); // no overflows
+    return true;
+}
+
+/// removes the cached entry (identified by its index) from the map
+template <class Key, class Value, uint64_t MemoryUsedBy(const Value &)>
+void
+ClpMap<Key, Value, MemoryUsedBy>::erase(const IndexIterator &i)
+{
+    assert(i != index_.end());
+    const auto entryPosition = i->second;
+
+    assert(entryPosition != entries_.end());
+    const auto sz = entryPosition->memCounted;
+    assert(memUsed_ >= sz);
+    memUsed_ -= sz;
+
+    index_.erase(i); // destroys a "pointer" to our Entry
+    entries_.erase(entryPosition); // destroys our Entry
+}
+
+template <class Key, class Value, uint64_t MemoryUsedBy(const Value &)>
+void
+ClpMap<Key, Value, MemoryUsedBy>::del(const Key &key)
+{
+    const auto i = find(key);
+    if (i != index_.end())
+        erase(i);
+}
+
+/// purges entries to make free memory large enough to fit wantSpace bytes
+template <class Key, class Value, uint64_t MemoryUsedBy(const Value &)>
+void
+ClpMap<Key, Value, MemoryUsedBy>::trim(const uint64_t wantSpace)
+{
+    assert(wantSpace <= memLimit()); // no infinite loops and in-vain trimming
+    while (freeMem() < wantSpace) {
+        assert(!entries_.empty());
+        // TODO: Purge expired entries first. They are useless, but their
+        // presence may lead to purging potentially useful fresh entries here.
+        del(entries_.rbegin()->key);
+    }
+}
+
+template <class Key, class Value, uint64_t MemoryUsedBy(const Value &)>
+ClpMap<Key, Value, MemoryUsedBy>::Entry::Entry(const Key &aKey, const Value &v, const Ttl ttl) :
+        key(aKey),
+        value(v),
+        expires(Sum(squid_curtime, ttl).value_or(std::numeric_limits<time_t>::max()))
+{
+}
+
+#endif /* SQUID__SRC_BASE_CLPMAP_H */
diff --git a/src/base/LruMap.h b/src/base/LruMap.h
deleted file mode 100644 (file)
index 803f477..0000000
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
- *
- * Squid software is distributed under GPLv2+ license and includes
- * contributions from numerous individuals and organizations.
- * Please see the COPYING and CONTRIBUTORS files for details.
- */
-
-#ifndef SQUID_LRUMAP_H
-#define SQUID_LRUMAP_H
-
-#include "SquidTime.h"
-
-#include <list>
-#include <map>
-
-template <class Key, class EntryValue, size_t EntryCost = sizeof(EntryValue)> class LruMap
-{
-public:
-    class Entry
-    {
-    public:
-        Entry(const Key &aKey, EntryValue *t): key(aKey), value(t), date(squid_curtime) {}
-        ~Entry() {delete value;}
-    private:
-        Entry(Entry &);
-        Entry & operator = (Entry &);
-    public:
-        Key key; ///< the key of entry
-        EntryValue *value; ///< A pointer to the stored value
-        time_t date; ///< The date the entry created
-    };
-    typedef std::list<Entry *> Queue;
-    typedef typename std::list<Entry *>::iterator QueueIterator;
-
-    /// key:queue_item mapping for fast lookups by key
-    typedef std::map<Key, QueueIterator> Map;
-    typedef typename Map::iterator MapIterator;
-    typedef std::pair<Key, QueueIterator> MapPair;
-
-    LruMap(int ttl, size_t size);
-    ~LruMap();
-    /// Search for an entry, and return a pointer
-    EntryValue *get(const Key &key);
-    /// Add an entry to the map
-    bool add(const Key &key, EntryValue *t);
-    /// Delete an entry from the map
-    bool del(const Key &key);
-    /// (Re-)set the maximum size for this map
-    void setMemLimit(size_t aSize);
-    /// The available size for the map
-    size_t memLimit() const {return memLimit_;}
-    /// The free space of the map
-    size_t freeMem() const { return (memLimit() > size() ? memLimit() - size() : 0);}
-    /// The current size of the map
-    size_t size() const {return (entries_ * EntryCost);}
-    /// The number of stored entries
-    int entries() const {return entries_;}
-private:
-    LruMap(LruMap const &);
-    LruMap & operator = (LruMap const &);
-
-    bool expired(const Entry &e) const;
-    void trim();
-    void touch(const MapIterator &i);
-    bool del(const MapIterator &i);
-    void findEntry(const Key &key, LruMap::MapIterator &i);
-
-    Map storage; ///< The Key/value * pairs
-    Queue index; ///< LRU cache index
-    int ttl;///< >0 ttl for caching, == 0 cache is disabled, < 0 store for ever
-    size_t memLimit_; ///< The maximum memory to use
-    int entries_; ///< The stored entries
-};
-
-template <class Key, class EntryValue, size_t EntryCost>
-LruMap<Key, EntryValue, EntryCost>::LruMap(int aTtl, size_t aSize): entries_(0)
-{
-    ttl = aTtl;
-
-    setMemLimit(aSize);
-}
-
-template <class Key, class EntryValue, size_t EntryCost>
-LruMap<Key, EntryValue, EntryCost>::~LruMap()
-{
-    for (QueueIterator i = index.begin(); i != index.end(); ++i) {
-        delete *i;
-    }
-}
-
-template <class Key, class EntryValue, size_t EntryCost>
-void
-LruMap<Key, EntryValue, EntryCost>::setMemLimit(size_t aSize)
-{
-    if (aSize > 0)
-        memLimit_ = aSize;
-    else
-        memLimit_ = 0;
-}
-
-template <class Key, class EntryValue, size_t EntryCost>
-void
-LruMap<Key, EntryValue, EntryCost>::findEntry(const Key &key, LruMap::MapIterator &i)
-{
-    i = storage.find(key);
-    if (i == storage.end()) {
-        return;
-    }
-    index.push_front(*(i->second));
-    index.erase(i->second);
-    i->second = index.begin();
-
-    if (const Entry *e = *i->second) {
-        if (!expired(*e))
-            return;
-        // else fall through to cleanup
-    }
-
-    del(i);
-    i = storage.end();
-}
-
-template <class Key, class EntryValue, size_t EntryCost>
-EntryValue *
-LruMap<Key, EntryValue, EntryCost>::get(const Key &key)
-{
-    MapIterator i;
-    findEntry(key, i);
-    if (i != storage.end()) {
-        touch(i);
-        Entry *e = *i->second;
-        return e->value;
-    }
-    return NULL;
-}
-
-template <class Key, class EntryValue, size_t EntryCost>
-bool
-LruMap<Key, EntryValue, EntryCost>::add(const Key &key, EntryValue *t)
-{
-    if (ttl == 0)
-        return false;
-
-    del(key);
-    trim();
-
-    if (memLimit() == 0)
-        return false;
-
-    index.push_front(new Entry(key, t));
-    storage.insert(MapPair(key, index.begin()));
-
-    ++entries_;
-    return true;
-}
-
-template <class Key, class EntryValue, size_t EntryCost>
-bool
-LruMap<Key, EntryValue, EntryCost>::expired(const LruMap::Entry &entry) const
-{
-    if (ttl < 0)
-        return false;
-
-    return (entry.date + ttl < squid_curtime);
-}
-
-template <class Key, class EntryValue, size_t EntryCost>
-bool
-LruMap<Key, EntryValue, EntryCost>::del(LruMap::MapIterator const &i)
-{
-    if (i != storage.end()) {
-        Entry *e = *i->second;
-        index.erase(i->second);
-        storage.erase(i);
-        delete e;
-        --entries_;
-        return true;
-    }
-    return false;
-}
-
-template <class Key, class EntryValue, size_t EntryCost>
-bool
-LruMap<Key, EntryValue, EntryCost>::del(const Key &key)
-{
-    MapIterator i;
-    findEntry(key, i);
-    return del(i);
-}
-
-template <class Key, class EntryValue, size_t EntryCost>
-void
-LruMap<Key, EntryValue, EntryCost>::trim()
-{
-    while (size() >= memLimit()) {
-        QueueIterator i = index.end();
-        --i;
-        if (i != index.end()) {
-            del((*i)->key);
-        }
-    }
-}
-
-template <class Key, class EntryValue, size_t EntryCost>
-void
-LruMap<Key, EntryValue, EntryCost>::touch(LruMap::MapIterator const &i)
-{
-    // this must not be done when nothing is being cached.
-    if (ttl == 0)
-        return;
-
-    index.push_front(*(i->second));
-    index.erase(i->second);
-    i->second = index.begin();
-}
-
-#endif
-
index 3d1cc4ac2233f94dfeeb1d119c642191f21cc230..4792522f8dfe9c98f8470d79df70124f5a2528df 100644 (file)
@@ -24,6 +24,7 @@ libbase_la_SOURCES = \
        CbcPointer.h \
        CharacterSet.cc \
        CharacterSet.h \
+       ClpMap.h \
        CodeContext.cc \
        CodeContext.h \
        EnumIterator.h \
@@ -36,7 +37,7 @@ libbase_la_SOURCES = \
        InstanceId.h \
        Lock.h \
        LookupTable.h \
-       LruMap.h \
+       Optional.h \
        Packable.h \
        PackableStream.h \
        Range.h \
diff --git a/src/base/Optional.h b/src/base/Optional.h
new file mode 100644 (file)
index 0000000..f7c1e58
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef SQUID__SRC_BASE_OPTIONAL_H
+#define SQUID__SRC_BASE_OPTIONAL_H
+
+#include <exception>
+
+/// std::bad_optional_access replacement (until we upgrade to C++17)
+class BadOptionalAccess: public std::exception
+{
+public:
+    BadOptionalAccess() {}
+    /* std::exception API */
+    virtual const char* what() const noexcept override { return "bad-optional-access"; }
+    virtual ~BadOptionalAccess() noexcept = default;
+};
+
+/// (limited) std::optional replacement (until we upgrade to C++17)
+template <typename Value>
+class Optional
+{
+public:
+    // std::optional supports non-trivial types as well, but we
+    // do not want to fiddle with unions to disable default Value constructor
+    // until that work becomes necessary
+    static_assert(std::is_trivial<Value>::value, "Value is trivial");
+
+    constexpr Optional() noexcept {}
+    constexpr explicit Optional(const Value &v): value_(v), hasValue_(true) {}
+
+    constexpr explicit operator bool() const noexcept { return hasValue_; }
+    constexpr bool has_value() const noexcept { return hasValue_; }
+
+    const Value &value() const &
+    {
+        if (!hasValue_)
+            throw BadOptionalAccess();
+        return value_;
+    }
+
+    template <class Other>
+    constexpr Value value_or(Other &&defaultValue) const &
+    {
+        return hasValue_ ? value_ : static_cast<Value>(std::forward<Other>(defaultValue));
+    }
+
+private:
+    Value value_; // stored value; inaccessible/uninitialized unless hasValue_
+    bool hasValue_ = false;
+};
+
+#endif /* SQUID__SRC_BASE_OPTIONAL_H */
index 6edd65e451b7ebdb93df2bfb0f98d5198b1acc52..2527025646262010b1cc3672064aff7cec8bc34b 100644 (file)
@@ -14,6 +14,9 @@ class AsyncJob;
 class CallDialer;
 class CodeContext;
 class ScopedId;
+class BadOptionalAccess;
+
+template <typename Value> class Optional;
 
 template<class Cbc> class CbcPointer;
 template<class RefCountableKid> class RefCount;
index 0b26d219e0945f9493e703c520f6b1ce34ee336a..e43e875a4a0e64a51ad8e20c69d25ba148d55d81 100644 (file)
@@ -3406,11 +3406,26 @@ DOC_START
        Specify the location and options of the executable for ssl_crt_validator
        process.
 
-       Usage:  sslcrtvalidator_program [ttl=n] [cache=n] path ...
+       Usage:  sslcrtvalidator_program [ttl=...] [cache=n] path ...
 
        Options:
-         ttl=n         TTL in seconds for cached results. The default is 60 secs
-         cache=n       limit the result cache size. The default value is 2048
+
+       cache=bytes
+               Limits how much memory Squid can use for caching validator
+               responses. The default is 67108864 (i.e. 64 MB).
+               Reconfiguration purges any excess entries. To disable caching,
+               use cache=0. Currently, cache entry sizes are seriously
+               underestimated. Even with that bug, a typical estimate for a
+               single cache entry size would be at least a few kilobytes (the
+               size of the PEM certificates sent to the validator).
+
+       ttl=<seconds|"infinity">
+               Approximately how long Squid may reuse the validator results
+               for. The default is 3600 (i.e. 1 hour). Using ttl=infinity
+               disables TTL checks. Reconfiguration does not affect TTLs of
+               the already cached entries. To disable caching, use zero cache
+               size, not zero TTL -- zero TTL allows reuse for the remainder
+               of the second when the result was cached.
 DOC_END
 
 NAME: sslcrtvalidator_children
index 3a502639e4983bf6168bb88552f0ee6ebf2e7a86..a026a114a052067e4ba3a8b77d05533178e380fc 100644 (file)
@@ -2802,7 +2802,7 @@ ConnStateData::getTlsContextFromCache(const SBuf &cacheKey, const Ssl::Certifica
 {
     debugs(33, 5, "Finding SSL certificate for " << cacheKey << " in cache");
     Ssl::LocalContextStorage * ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s);
-    if (Security::ContextPointer *ctx = ssl_ctx_cache ? ssl_ctx_cache->get(cacheKey) : nullptr) {
+    if (const auto ctx = ssl_ctx_cache ? ssl_ctx_cache->get(cacheKey) : nullptr) {
         if (Ssl::verifySslCertificate(*ctx, certProperties)) {
             debugs(33, 5, "Cached SSL certificate for " << certProperties.commonName << " is valid");
             return *ctx;
@@ -2819,7 +2819,7 @@ void
 ConnStateData::storeTlsContextToCache(const SBuf &cacheKey, Security::ContextPointer &ctx)
 {
     Ssl::LocalContextStorage *ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s);
-    if (!ssl_ctx_cache || !ssl_ctx_cache->add(cacheKey, new Security::ContextPointer(ctx))) {
+    if (!ssl_ctx_cache || !ssl_ctx_cache->add(cacheKey, ctx)) {
         // If it is not in storage delete after using. Else storage deleted it.
         fd_table[clientConnection->fd].dynamicTlsContext = ctx;
     }
index ff5de1094c0d9dd674ce58236a55cda676519cfc..e1f236b28491b215acbb2f97dc4ed1c6d869a8b2 100644 (file)
@@ -25,7 +25,8 @@ public:
 
     // The following declarations are only necessary for compilers that do not
     // fully support C++11 Allocator-related APIs, such as GCC v4.8.
-    // TODO: Remove after dropping support for such compilers.
+    // The corresponding std::allocator declarations are deprecated in C++17.
+    // TODO: Remove after dropping support for deficient compilers.
 
     using size_type = size_t;
     using pointer = Value*;
@@ -38,6 +39,7 @@ public:
       typedef PoolingAllocator<OtherValue> other;
     };
 
+    template<class U, class ... Args> void construct(U *p, Args && ... args) { new((void *)p) U(std::forward<Args>(args)...); }
     template<typename OtherValue> void destroy(OtherValue *p) { p->~OtherValue(); }
 };
 
index a8184ef9c516c09bca3ef9c5c0fdbf703dee7005..602d1b10588e647ee1696ea20dace26460969ec9 100644 (file)
@@ -197,6 +197,13 @@ Ssl::CertValidationMsg::getCertByName(std::vector<CertItem> const &certs, std::s
     return NULL;
 }
 
+uint64_t
+Ssl::CertValidationResponse::MemoryUsedByResponse(const CertValidationResponse::Pointer &)
+{
+    // XXX: This math does not account for most of the response size!
+    return sizeof(CertValidationResponse);
+}
+
 Ssl::CertValidationResponse::RecvdError &
 Ssl::CertValidationResponse::getError(int errorId)
 {
index 822ecde3c0b277992ad48a199e987e7fd9ad7ebe..58c8f51bd8501cd73a3a5e460004539d75b9e3e0 100644 (file)
@@ -60,6 +60,9 @@ public:
 
     typedef std::vector<RecvdError> RecvdErrors;
     explicit CertValidationResponse(const Security::SessionPointer &aSession) : ssl(aSession) {}
+
+    static uint64_t MemoryUsedByResponse(const CertValidationResponse::Pointer &);
+
     /// Search in errors list for the error item with id=errorId.
     /// If none found a new RecvdError item added with the given id;
     RecvdError &getError(int errorId);
index 20dc491adb85b755078ac27f393ba682dc34f40e..4637b57b3331471e635d006aa0303ff86ed46e0f 100644 (file)
@@ -44,10 +44,12 @@ void Ssl::CertificateStorageAction::dump (StoreEntry *sentry)
     for (std::map<Ip::Address, LocalContextStorage *>::iterator i = TheGlobalContextStorage.storage.begin(); i != TheGlobalContextStorage.storage.end(); ++i) {
         stream << i->first << delimiter;
         LocalContextStorage & ssl_store_policy(*(i->second));
+        const auto memoryPerEntry = ssl_store_policy.entries() ?
+            ssl_store_policy.memoryUsed() / ssl_store_policy.entries() : 0;
         stream << ssl_store_policy.memLimit() / 1024 << delimiter;
         stream << ssl_store_policy.entries() << delimiter;
-        stream << SSL_CTX_SIZE / 1024 << delimiter;
-        stream << ssl_store_policy.size() / 1024 << delimiter;
+        stream << memoryPerEntry / 1024 << delimiter;
+        stream << ssl_store_policy.memoryUsed() / 1024 << delimiter;
         stream << ssl_store_policy.freeMem() / 1024 << endString;
     }
     stream << endString;
@@ -112,7 +114,7 @@ void Ssl::GlobalContextStorage::reconfigureFinish()
         // add new local storages.
         for (std::map<Ip::Address, size_t>::iterator conf_i = configureStorage.begin(); conf_i != configureStorage.end(); ++conf_i ) {
             if (storage.find(conf_i->first) == storage.end() && conf_i->second > 0) {
-                storage.insert(std::pair<Ip::Address, LocalContextStorage *>(conf_i->first, new LocalContextStorage(-1, conf_i->second)));
+                storage.insert(std::pair<Ip::Address, LocalContextStorage *>(conf_i->first, new LocalContextStorage(conf_i->second)));
             }
         }
     }
index 73b3a0895b6bbbd84c1c3e589f8c53f9e3d61c8d..6df979ae892604d975e4b9252bcd046a80c8d7ee 100644 (file)
@@ -11,7 +11,7 @@
 
 #if USE_OPENSSL
 
-#include "base/LruMap.h"
+#include "base/ClpMap.h"
 #include "CacheManager.h"
 #include "compat/openssl.h"
 #include "ip/Address.h"
@@ -27,9 +27,6 @@
 #include <openssl/ssl.h>
 #endif
 
-/// TODO: Replace on real size.
-#define SSL_CTX_SIZE 1024
-
 namespace  Ssl
 {
 
@@ -49,7 +46,10 @@ public:
     virtual bool aggregatable() const { return false; }
 };
 
-typedef LruMap<SBuf, Security::ContextPointer, SSL_CTX_SIZE> LocalContextStorage;
+inline uint64_t MemoryUsedByContext(const Security::ContextPointer &) {
+    return 1024; // TODO: Calculate approximate memory usage by the context.
+}
+using LocalContextStorage = ClpMap<SBuf, Security::ContextPointer, MemoryUsedByContext>;
 
 /// Class for storing/manipulating LocalContextStorage per local listening address/port.
 class GlobalContextStorage
index f9ae2cd1cb7c1ab8bc1fa21a98ca439a713a6667..70721a489cffa7693d31e8ac115359c55b31f17d 100644 (file)
@@ -9,17 +9,20 @@
 #include "squid.h"
 #include "../helper.h"
 #include "anyp/PortCfg.h"
+#include "cache_cf.h"
 #include "fs_io.h"
 #include "helper/Reply.h"
+#include "Parsing.h"
 #include "SquidConfig.h"
 #include "SquidString.h"
 #include "SquidTime.h"
+#include "sbuf/Stream.h"
 #include "ssl/cert_validate_message.h"
 #include "ssl/Config.h"
 #include "ssl/helper.h"
 #include "wordlist.h"
 
-Ssl::CertValidationHelper::LruCache *Ssl::CertValidationHelper::HelperCache = nullptr;
+Ssl::CertValidationHelper::CacheType *Ssl::CertValidationHelper::HelperCache = nullptr;
 
 #if USE_SSL_CRTD
 
@@ -186,20 +189,30 @@ void Ssl::CertValidationHelper::Init()
     ssl_crt_validator->eom = '\1';
     assert(ssl_crt_validator->cmdline == NULL);
 
-    int ttl = 60;
-    size_t cache = 2048;
+    /* defaults */
+    int ttl = 3600; // 1 hour
+    size_t cache = 64*1024*1024; // 64 MB
     {
+        // TODO: Do this during parseConfigFile() for proper parsing, error handling
         char *tmp = xstrdup(Ssl::TheConfig.ssl_crt_validator);
         char *tmp_begin = tmp;
         char * token = NULL;
         bool parseParams = true;
         while ((token = strwordtok(NULL, &tmp))) {
             if (parseParams) {
-                if (strncmp(token, "ttl=", 4) == 0) {
-                    ttl = atoi(token + 4);
+                if (strcmp(token, "ttl=infinity") == 0) {
+                    ttl = std::numeric_limits<CacheType::Ttl>::max();
+                    continue;
+                } else if (strncmp(token, "ttl=", 4) == 0) {
+                    ttl = xatoi(token + 4);
+                    if (ttl < 0) {
+                        throw TextException(ToSBuf("Negative TTL in sslcrtvalidator_program ", Ssl::TheConfig.ssl_crt_validator,
+                            Debug::Extra, "For unlimited TTL, use ttl=infinity"),
+                            Here());
+                    }
                     continue;
                 } else if (strncmp(token, "cache=", 6) == 0) {
-                    cache = atoi(token + 6);
+                    cache = xatoi(token + 6);
                     continue;
                 } else
                     parseParams = false;
@@ -212,7 +225,7 @@ void Ssl::CertValidationHelper::Init()
 
     //WARNING: initializing static member in an object initialization method
     assert(HelperCache == NULL);
-    HelperCache = new Ssl::CertValidationHelper::LruCache(ttl, cache);
+    HelperCache = new CacheType(cache, ttl);
 }
 
 void Ssl::CertValidationHelper::Shutdown()
@@ -279,9 +292,7 @@ sslCrtvdHandleReplyWrapper(void *data, const ::Helper::Reply &reply)
 
     if (Ssl::CertValidationHelper::HelperCache &&
             (validationResponse->resultCode == ::Helper::Okay || validationResponse->resultCode == ::Helper::Error)) {
-        Ssl::CertValidationResponse::Pointer *item = new Ssl::CertValidationResponse::Pointer(validationResponse);
-        if (!Ssl::CertValidationHelper::HelperCache->add(crtdvdData->query, item))
-            delete item;
+        (void)Ssl::CertValidationHelper::HelperCache->add(crtdvdData->query, validationResponse);
     }
 
     delete crtdvdData;
index f53a71e1275c5524e0111a24881e8f9dc284a2b6..97949288b9f0f2728231f6f0fbd48423065b9a51 100644 (file)
@@ -12,7 +12,7 @@
 #if USE_OPENSSL
 
 #include "base/AsyncJobCalls.h"
-#include "base/LruMap.h"
+#include "base/ClpMap.h"
 #include "helper/forward.h"
 #include "security/forward.h"
 #include "ssl/cert_validate_message.h"
@@ -54,8 +54,8 @@ public:
 private:
     static helper * ssl_crt_validator; ///< helper for management of ssl_crtd.
 public:
-    typedef LruMap<SBuf, Ssl::CertValidationResponse::Pointer, sizeof(Ssl::CertValidationResponse::Pointer) + sizeof(Ssl::CertValidationResponse)> LruCache;
-    static LruCache *HelperCache; ///< cache for cert validation helper
+    typedef ClpMap<SBuf, CertValidationResponse::Pointer, CertValidationResponse::MemoryUsedByResponse> CacheType;
+    static CacheType *HelperCache; ///< cache for cert validation helper
 };
 
 } //namespace Ssl
index c3974c8c9a682fdd4b844ff87b62438a7f22f0c3..2b5bec1621c42aa75f177fd94cd7da2e22d9b536 100644 (file)
@@ -869,7 +869,7 @@ Ssl::configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::Po
 }
 
 bool
-Ssl::verifySslCertificate(Security::ContextPointer &ctx, CertificateProperties const &properties)
+Ssl::verifySslCertificate(const Security::ContextPointer &ctx, CertificateProperties const &properties)
 {
 #if HAVE_SSL_CTX_GET0_CERTIFICATE
     X509 * cert = SSL_CTX_get0_certificate(ctx.get());
index 7401149b81e1c0407ae2b1232679919789a2ec17..28c6e5b3aebc9b18caceba504f6e6fa85ceac2f9 100644 (file)
@@ -231,7 +231,7 @@ Security::ContextPointer GenerateSslContext(CertificateProperties const &, Secur
   \param properties Check if the context certificate matches the given properties
   \return true if the contexts certificate is valid, false otherwise
  */
-bool verifySslCertificate(Security::ContextPointer &, CertificateProperties const &);
+bool verifySslCertificate(const Security::ContextPointer &, CertificateProperties const &);
 
 /**
   \ingroup ServerProtocolSSLAPI
index 9d9eb9cf33077a1d36a65e22387b25683cad2149..4643032cbb5c1a1a9665f360be303662f3f4441c 100644 (file)
@@ -11,6 +11,7 @@
 #if USE_OPENSSL
 
 #include "fatal.h"
+#include "sbuf/Algorithms.h"
 #include "sbuf/SBuf.h"
 
 /* Stub File for the ssl/libsslsquid.la convenience library */
@@ -37,7 +38,7 @@ Ssl::CertificateStorageAction::Pointer Ssl::CertificateStorageAction::Create(con
 void Ssl::CertificateStorageAction::dump(StoreEntry *sentry) STUB
 void Ssl::GlobalContextStorage::addLocalStorage(Ip::Address const & address, size_t size_of_store) STUB
 Ssl::LocalContextStorage *Ssl::GlobalContextStorage::getLocalStorage(Ip::Address const & address)
-{ fatal(STUB_API " required"); static Ssl::LocalContextStorage v(0,0); return &v; }
+{ fatal(STUB_API " required"); static LocalContextStorage v(0); return &v; }
 void Ssl::GlobalContextStorage::reconfigureStart() STUB
 //Ssl::GlobalContextStorage Ssl::TheGlobalContextStorage;