1
#include "ipcache.h"
2

            
3
#include <cerrno> // IWYU pragma: keep
4
#include <chrono>
5
#include <cstring>
6
#include <memory>
7
#include <string>
8

            
9
#include "envoy/network/address.h"
10
#include "envoy/server/factory_context.h"
11
#include "envoy/singleton/manager.h"
12

            
13
#include "source/common/common/logger.h"
14
#include "source/common/common/utility.h"
15

            
16
#include "absl/numeric/int128.h"
17
#include "absl/synchronization/mutex.h"
18
#include "cilium/bpf.h"
19
#include "linux/bpf.h"
20

            
21
namespace Envoy {
22
namespace Cilium {
23

            
24
bool operator==(const IpCacheKey& a, const IpCacheKey& b) { return memcmp(&a, &b, sizeof(a)) == 0; }
25

            
26
template <typename H> H AbslHashValue(H state, const IpCacheKey& key) {
27
  // Combine the hash of each member into the state
28
  H h = H::combine_contiguous(std::move(state), reinterpret_cast<const char*>(&key), sizeof(key));
29
  return h;
30
}
31

            
32
SINGLETON_MANAGER_REGISTRATION(cilium_ipcache);
33

            
34
IpCacheSharedPtr IpCache::newIpCache(Server::Configuration::ServerFactoryContext& context,
35
                                     const std::string& path,
36
                                     std::chrono::milliseconds cache_gc_interval) {
37
  auto ipcache = context.singletonManager().getTyped<Cilium::IpCache>(
38
      SINGLETON_MANAGER_REGISTERED_NAME(cilium_ipcache), [&path, &context, &cache_gc_interval] {
39
        auto ipcache = std::make_shared<Cilium::IpCache>(context, path, cache_gc_interval);
40
        if (!ipcache->open()) {
41
          ipcache.reset();
42
        }
43
        return ipcache;
44
      });
45

            
46
  // Override the current path even on an existing singleton
47
  if (ipcache) {
48
    ipcache->setConfig(path, cache_gc_interval);
49
  }
50
  return ipcache;
51
}
52

            
53
IpCacheSharedPtr IpCache::getIpCache(Server::Configuration::ServerFactoryContext& context) {
54
  return context.singletonManager().getTyped<Cilium::IpCache>(
55
      SINGLETON_MANAGER_REGISTERED_NAME(cilium_ipcache));
56
}
57

            
58
IpCache::IpCache(Server::Configuration::ServerFactoryContext& context, const std::string& path,
59
                 std::chrono::milliseconds cache_gc_interval)
60
    : Bpf(BPF_MAP_TYPE_LPM_TRIE, sizeof(struct IpCacheKey), sizeof(SecLabelType),
61
          sizeof(struct RemoteEndpointInfo)),
62
      dispatcher_(context.mainThreadDispatcher()),
63
      cache_gc_timer_(dispatcher_.createTimer([this]() { cacheGc(); })),
64
      cache_gc_interval_(cache_gc_interval), time_source_(context.timeSource()), path_(path) {
65
  // Timer for cache GC
66
  if (cache_gc_interval_ != std::chrono::milliseconds(0)) {
67
    cache_gc_timer_->enableTimer(cache_gc_interval_);
68
  }
69
}
70

            
71
void IpCache::cacheGc() {
72
  {
73
    absl::WriterMutexLock lock(&mutex_);
74
    for (auto it = cache_.begin(); it != cache_.end(); it++) {
75
      auto age = time_source_.monotonicTime() - it->second.time_stamp;
76
      if (age >= std::chrono::milliseconds(1)) {
77
        ENVOY_LOG(trace, "cilium.ipcache: local cache GC deleting old entry {}:{}",
78
                  it->first.asString(), it->second.sec_label);
79
        cache_.erase(it);
80
      }
81
    }
82
  }
83
  cache_gc_timer_->enableTimer(cache_gc_interval_);
84
}
85

            
86
void IpCache::setConfig(const std::string& path, std::chrono::milliseconds cache_gc_interval) {
87
  absl::WriterMutexLock lock(&mutex_);
88
  // update GC interval?
89
  if (cache_gc_interval != cache_gc_interval_) {
90
    cache_gc_timer_->disableTimer();
91
    cache_gc_interval_ = cache_gc_interval;
92
    if (cache_gc_interval_ != std::chrono::milliseconds(0)) {
93
      cache_gc_timer_->enableTimer(cache_gc_interval_);
94
    }
95
  }
96
  // re-open on path change
97
  if (path != path_) {
98
    path_ = path;
99
    openLocked();
100
  }
101
}
102

            
103
bool IpCache::open() {
104
  absl::WriterMutexLock lock(&mutex_);
105
  return openLocked();
106
}
107

            
108
bool IpCache::openLocked() {
109
  if (Bpf::open(path_)) {
110
    ENVOY_LOG(debug, "cilium.ipcache: Opened ipcache at {}", path_);
111
    return true;
112
  }
113
  ENVOY_LOG(warn, "cilium.ipcache: Cannot open ipcache at {}", path_);
114
  return false;
115
}
116

            
117
uint32_t IpCache::resolve(const Network::Address::Ip* ip, std::chrono::microseconds cache_ttl) {
118
  struct IpCacheKey key {};
119
  struct RemoteEndpointInfo value {};
120

            
121
  if (ip->version() == Network::Address::IpVersion::v4) {
122
    key.lpm_key = {32 + 32, {}};
123
    key.family = ENDPOINT_KEY_IPV4;
124
    key.ip4 = ip->ipv4()->address();
125
  } else {
126
    key.lpm_key = {32 + 128, {}};
127
    key.family = ENDPOINT_KEY_IPV6;
128
    absl::uint128 ip6 = ip->ipv6()->address();
129
    memcpy(&key.ip6, &ip6, sizeof key.ip6); // NOLINT(safe-memcpy)
130
  }
131

            
132
  bool ok;
133
  bool use_cache = cache_ttl > std::chrono::microseconds(0);
134
  {
135
    // Read lock prevents ipcache lookups while ipcache is being reopened.
136
    absl::ReaderMutexLock lock(&mutex_);
137

            
138
    // local cache lookup
139
    if (use_cache) {
140
      const auto it = cache_.find(key);
141
      if (it != cache_.cend()) {
142
        auto age = time_source_.monotonicTime() - it->second.time_stamp;
143
        if (age < cache_ttl) {
144
          // use cached value
145
          ENVOY_LOG(trace, "cilium.ipcache: {} has cached ID {}", ip->addressAsString(),
146
                    it->second.sec_label);
147
          return it->second.sec_label;
148
        }
149
      }
150
    }
151

            
152
    ENVOY_LOG(trace, "cilium.ipcache: Looking up key: {}", key.asString());
153
    ok = lookup(&key, &value);
154
  }
155

            
156
  if (ok) {
157
    ENVOY_LOG(debug, "cilium.ipcache: {} has ID {}", ip->addressAsString(), value.sec_label);
158

            
159
    // cache result
160
    if (use_cache) {
161
      absl::WriterMutexLock lock(&mutex_);
162
      cache_.insert_or_assign(key,
163
                              CachedEndpointInfo{value.sec_label, time_source_.monotonicTime()});
164
    }
165
    return value.sec_label;
166
  }
167
  ENVOY_LOG(info, "cilium.ipcache: bpf map lookup failed: {}", Envoy::errorDetails(errno));
168
  return 0;
169
}
170

            
171
} // namespace Cilium
172
} // namespace Envoy