1
#pragma once
2

            
3
#include <arpa/inet.h>
4
#include <fmt/format.h>
5
#include <netinet/in.h>
6
#include <sys/socket.h>
7

            
8
#include <cstddef>
9
#include <cstdint>
10
#include <functional>
11
#include <memory>
12
#include <string>
13
#include <utility>
14
#include <vector>
15

            
16
#include "envoy/common/exception.h"
17
#include "envoy/config/subscription.h"
18
#include "envoy/network/address.h"
19
#include "envoy/protobuf/message_validator.h"
20
#include "envoy/server/factory_context.h"
21
#include "envoy/singleton/instance.h"
22
#include "envoy/stats/scope.h"
23
#include "envoy/thread_local/thread_local.h"
24
#include "envoy/thread_local/thread_local_object.h"
25

            
26
#include "source/common/common/logger.h"
27
#include "source/common/common/macros.h"
28
#include "source/common/network/utility.h"
29
#include "source/common/protobuf/message_validator_impl.h"
30
#include "source/common/protobuf/protobuf.h"
31
#include "source/common/protobuf/utility.h"
32

            
33
#include "absl/container/flat_hash_map.h"
34
#include "absl/numeric/int128.h"
35
#include "absl/status/status.h"
36
#include "cilium/api/nphds.pb.h"
37
#include "cilium/api/nphds.pb.validate.h" // IWYU pragma: keep
38
#include "cilium/policy_id.h"
39

            
40
// std::hash specialization for Abseil uint128, needed for unordered_map key.
41
namespace std {
42
template <> struct hash<absl::uint128> {
43
  size_t operator()(const absl::uint128& x) const {
44
    return hash<uint64_t>{}(absl::Uint128Low64(x)) ^
45
           (hash<uint64_t>{}(absl::Uint128High64(x)) << 1);
46
  }
47
};
48
} // namespace std
49

            
50
namespace Envoy {
51
namespace Cilium {
52

            
53
template <typename I> I ntoh(I);
54
94
template <> inline uint32_t ntoh(uint32_t addr) { return ntohl(addr); }
55
64
template <> inline absl::uint128 ntoh(absl::uint128 addr) {
56
64
  return Network::Utility::Ip6ntohl(addr);
57
64
}
58
template <typename I> I hton(I);
59
378
template <> inline uint32_t hton(uint32_t addr) { return htonl(addr); }
60
14
template <> inline absl::uint128 hton(absl::uint128 addr) {
61
14
  return Network::Utility::Ip6htonl(addr);
62
14
}
63

            
64
399
template <typename I> I masked(I addr, unsigned int plen) {
65
399
  const unsigned int plen_max = sizeof(I) * 8;
66
399
  return plen == 0 ? I(0) : addr & ~hton((I(1) << (plen_max - plen)) - 1);
67
399
};
68

            
69
class PolicyHostDecoder : public Envoy::Config::OpaqueResourceDecoder {
70
public:
71
139
  PolicyHostDecoder() : validation_visitor_(ProtobufMessage::getNullValidationVisitor()) {}
72

            
73
  // Config::OpaqueResourceDecoder
74
193
  ProtobufTypes::MessagePtr decodeResource(const ProtobufWkt::Any& resource) override {
75
193
    auto typed_message = std::make_unique<cilium::NetworkPolicyHosts>();
76
    // If the Any is a synthetic empty message (e.g. because the resource field
77
    // was not set in Resource, this might be empty, so we shouldn't decode.
78
193
    if (!resource.type_url().empty()) {
79
193
      MessageUtil::anyConvertAndValidate<cilium::NetworkPolicyHosts>(resource, *typed_message,
80
193
                                                                     validation_visitor_);
81
193
    }
82
193
    return typed_message;
83
193
  }
84

            
85
193
  std::string resourceName(const Protobuf::Message& resource) override {
86
193
    return fmt::format("{}", dynamic_cast<const cilium::NetworkPolicyHosts&>(resource).policy());
87
193
  }
88

            
89
private:
90
  ProtobufMessage::ValidationVisitor& validation_visitor_;
91
};
92

            
93
class PolicyHostMap : public Singleton::Instance,
94
                      public Config::SubscriptionCallbacks,
95
                      public std::enable_shared_from_this<PolicyHostMap>,
96
                      public Logger::Loggable<Logger::Id::config> {
97
public:
98
  PolicyHostMap(Server::Configuration::CommonFactoryContext& context);
99
  PolicyHostMap(ThreadLocal::SlotAllocator& tls);
100
139
  ~PolicyHostMap() override {
101
139
    ENVOY_LOG(debug, "Cilium PolicyHostMap({}): PolicyHostMap is deleted NOW!", name_);
102
139
  }
103

            
104
  void startSubscription(Server::Configuration::CommonFactoryContext& context);
105

            
106
  // This is used for testing with a file-based subscription
107
129
  void startSubscription(std::unique_ptr<Envoy::Config::Subscription>&& subscription) {
108
129
    subscription_ = std::move(subscription);
109
129
    subscription_->start({});
110
129
  }
111

            
112
  // A shared pointer to a immutable copy is held by each thread. Changes are
113
  // done by creating a new version and assigning the new shared pointer to the
114
  // thread local slot on each thread.
115
  struct ThreadLocalHostMap : public ThreadLocal::ThreadLocalObject,
116
                              public Logger::Loggable<Logger::Id::config> {
117
  public:
118
130
    void logmaps(const std::string& msg) const {
119
130
      char buf[INET6_ADDRSTRLEN];
120
130
      std::string ip4, ip6, prefix;
121
130
      bool first = true;
122
197
      for (const auto& mask : ipv4_to_policy_) {
123
149
        std::string prefix = fmt::format("{}", mask.first);
124
183
        for (const auto& pair : mask.second) {
125
183
          if (!first) {
126
120
            ip4 += ", ";
127
120
          }
128
183
          first = false;
129
183
          ip4 += fmt::format("{}/{}->{}", inet_ntop(AF_INET, &pair.first, buf, sizeof(buf)), prefix,
130
183
                             pair.second);
131
183
        }
132
149
      }
133
130
      first = true;
134
165
      for (const auto& mask : ipv6_to_policy_) {
135
117
        std::string prefix = fmt::format("{}", mask.first);
136
166
        for (const auto& pair : mask.second) {
137
166
          if (!first) {
138
103
            ip6 += ", ";
139
103
          }
140
166
          first = false;
141
166
          ip6 += fmt::format("{}/{}->{}", inet_ntop(AF_INET6, &pair.first, buf, sizeof(buf)),
142
166
                             prefix, pair.second);
143
166
        }
144
117
      }
145
130
      ENVOY_LOG(debug, "PolicyHostMap::{}: IPv4: [{}], IPv6: [{}]", msg, ip4, ip6);
146
130
    }
147

            
148
    // Find the longest prefix match of the addr, return the matching policy id,
149
    // or ID::WORLD if there is no match.
150
222
    uint64_t resolve(uint32_t addr4) const {
151
403
      for (const auto& pair : ipv4_to_policy_) {
152
382
        auto it = pair.second.find(masked(addr4, pair.first));
153
382
        if (it != pair.second.end()) {
154
200
          return it->second;
155
200
        }
156
382
      }
157
22
      return ID::UNKNOWN;
158
222
    }
159

            
160
8
    uint64_t resolve(absl::uint128 addr6) const {
161
17
      for (const auto& pair : ipv6_to_policy_) {
162
17
        auto it = pair.second.find(masked(addr6, pair.first));
163
17
        if (it != pair.second.end()) {
164
8
          return it->second;
165
8
        }
166
17
      }
167
      return ID::UNKNOWN;
168
8
    }
169

            
170
228
    uint64_t resolve(const Network::Address::Ip* addr) const {
171
228
      auto* ipv4 = addr->ipv4();
172
228
      if (ipv4) {
173
220
        return resolve(ipv4->address());
174
220
      }
175
8
      auto* ipv6 = addr->ipv6();
176
8
      if (ipv6) {
177
8
        return resolve(ipv6->address());
178
8
      }
179
      return ID::WORLD;
180
8
    }
181

            
182
  protected:
183
    // Vectors of <prefix-len>, <address-map> pairs, ordered in the decreasing
184
    // prefix length, where map keys are addresses of the given prefix length.
185
    // Address bits outside of the prefix are zeroes.
186
    std::vector<std::pair<unsigned int, absl::flat_hash_map<uint32_t, uint64_t>>> ipv4_to_policy_;
187
    std::vector<std::pair<unsigned int, absl::flat_hash_map<absl::uint128, uint64_t>>>
188
        ipv6_to_policy_;
189
  };
190
  using ThreadLocalHostMapSharedPtr = std::shared_ptr<ThreadLocalHostMap>;
191

            
192
358
  const ThreadLocalHostMap* getHostMap() const {
193
358
    return tls_->get().get() ? &tls_->getTyped<ThreadLocalHostMap>() : nullptr;
194
358
  }
195

            
196
228
  uint64_t resolve(const Network::Address::Ip* addr) const {
197
228
    const ThreadLocalHostMap* hostmap = getHostMap();
198
228
    return (hostmap != nullptr) ? hostmap->resolve(addr) : ID::UNKNOWN;
199
228
  }
200

            
201
130
  void logmaps(const std::string& msg) {
202
130
    if (ENVOY_LOG_CHECK_LEVEL(debug)) {
203
130
      auto tlsmap = getHostMap();
204
130
      if (tlsmap) {
205
130
        tlsmap->logmaps(msg);
206
130
      } else {
207
        ENVOY_LOG(debug, "PolicyHostMap::{}: Error getting thread local map", msg);
208
      }
209
130
    }
210
130
  }
211

            
212
  // Config::SubscriptionCallbacks
213
  absl::Status onConfigUpdate(const std::vector<Envoy::Config::DecodedResourceRef>& resources,
214
                              const std::string& version_info) override;
215
  absl::Status onConfigUpdate(const std::vector<Envoy::Config::DecodedResourceRef>& added_resources,
216
                              const Protobuf::RepeatedPtrField<std::string>& removed_resources,
217
                              const std::string& system_version_info) override {
218
    // NOT IMPLEMENTED YET.
219
    UNREFERENCED_PARAMETER(added_resources);
220
    UNREFERENCED_PARAMETER(removed_resources);
221
    UNREFERENCED_PARAMETER(system_version_info);
222
    return absl::OkStatus();
223
  }
224
  void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason,
225
                            const EnvoyException* e) override;
226

            
227
private:
228
  ThreadLocal::SlotPtr tls_;
229
  Stats::ScopeSharedPtr scope_;
230
  std::unique_ptr<Envoy::Config::Subscription> subscription_;
231
  static uint64_t instance_id_;
232
  std::string name_;
233
};
234

            
235
} // namespace Cilium
236
} // namespace Envoy