|
4 | 4 | #include <fmt/format.h> |
5 | 5 | #include <sys/socket.h> |
6 | 6 |
|
| 7 | +#include <charconv> |
7 | 8 | #include <cstdint> |
8 | 9 | #include <cstring> |
9 | 10 | #include <memory> |
10 | 11 | #include <string> |
| 12 | +#include <system_error> |
11 | 13 | #include <utility> |
12 | 14 | #include <vector> |
13 | 15 |
|
14 | 16 | #include "envoy/common/exception.h" |
15 | 17 | #include "envoy/config/core/v3/config_source.pb.h" |
| 18 | +#include "envoy/config/grpc_mux.h" |
16 | 19 | #include "envoy/config/subscription.h" |
17 | 20 | #include "envoy/event/dispatcher.h" |
18 | 21 | #include "envoy/server/factory_context.h" |
|
21 | 24 | #include "envoy/thread_local/thread_local.h" |
22 | 25 | #include "envoy/thread_local/thread_local_object.h" |
23 | 26 |
|
| 27 | +#include "source/common/common/assert.h" |
24 | 28 | #include "source/common/common/logger.h" |
25 | 29 | #include "source/common/common/macros.h" |
26 | 30 |
|
| 31 | +#include "absl/container/flat_hash_set.h" |
27 | 32 | #include "absl/numeric/int128.h" |
28 | 33 | #include "absl/status/status.h" |
29 | 34 | #include "absl/strings/str_cat.h" |
@@ -58,9 +63,18 @@ unsigned int checkPrefix(T addr, bool have_prefix, unsigned int plen, absl::stri |
58 | 63 | } // namespace |
59 | 64 |
|
60 | 65 | struct ThreadLocalHostMapInitializer : public PolicyHostMap::ThreadLocalHostMap { |
61 | | -protected: |
| 66 | +public: |
62 | 67 | friend class PolicyHostMap; // PolicyHostMap can insert(); |
63 | 68 |
|
| 69 | + ThreadLocalHostMapInitializer() = default; |
| 70 | + |
| 71 | + explicit ThreadLocalHostMapInitializer(const PolicyHostMap::ThreadLocalHostMap* host_map) { |
| 72 | + if (host_map != nullptr) { |
| 73 | + static_cast<PolicyHostMap::ThreadLocalHostMap&>(*this) = *host_map; |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | +protected: |
64 | 78 | // find the map of the given prefix length, insert in the decreasing order if |
65 | 79 | // it does not exist |
66 | 80 | template <typename M> |
@@ -159,6 +173,29 @@ struct ThreadLocalHostMapInitializer : public PolicyHostMap::ThreadLocalHostMap |
159 | 173 | fmt::format("NetworkPolicyHosts: Invalid host entry \'{}\' for policy {}", host, policy)); |
160 | 174 | } |
161 | 175 | } |
| 176 | + |
| 177 | + template <typename MapVec> |
| 178 | + void prunePolicyMapVec(MapVec& maps, const absl::flat_hash_set<uint64_t>& nids) { |
| 179 | + for (auto vec_it = maps.begin(); vec_it != maps.end();) { |
| 180 | + auto& map = vec_it->second; |
| 181 | + for (auto map_it = map.begin(); map_it != map.end();) { |
| 182 | + auto it = map_it++; |
| 183 | + if (nids.contains(it->second)) { |
| 184 | + map.erase(it); |
| 185 | + } |
| 186 | + } |
| 187 | + if (map.empty()) { |
| 188 | + vec_it = maps.erase(vec_it); |
| 189 | + } else { |
| 190 | + ++vec_it; |
| 191 | + } |
| 192 | + } |
| 193 | + } |
| 194 | + |
| 195 | + void remove(const absl::flat_hash_set<uint64_t>& removed_nids) { |
| 196 | + prunePolicyMapVec(ipv4_to_policy_, removed_nids); |
| 197 | + prunePolicyMapVec(ipv6_to_policy_, removed_nids); |
| 198 | + } |
162 | 199 | }; |
163 | 200 |
|
164 | 201 | uint64_t PolicyHostMap::instance_id_ = 0; |
@@ -196,22 +233,155 @@ PolicyHostMap::PolicyHostMap(Server::Configuration::CommonFactoryContext& contex |
196 | 233 | } |
197 | 234 |
|
198 | 235 | void PolicyHostMap::startSubscription(Server::Configuration::CommonFactoryContext& context, |
199 | | - const envoy::config::core::v3::ConfigSource& config_source) { |
200 | | - if (config_source.config_source_specifier_case() == envoy::config::core::v3::ConfigSource::kAds) { |
201 | | - auto ads_mux = context.xdsManager().adsMux(); |
202 | | - subscription_ = THROW_OR_RETURN_VALUE( |
203 | | - context.clusterManager().subscriptionFactory().subscriptionOverAdsGrpcMux( |
204 | | - ads_mux, config_source, NetworkPolicyHostsTypeUrl, *scope_, *this, |
205 | | - std::make_shared<Cilium::PolicyHostDecoder>(), {}), |
206 | | - Config::SubscriptionPtr); |
207 | | - } else { |
208 | | - subscription_ = subscribe(NetworkPolicyHostsTypeUrl, config_source, context, *scope_, *this, |
209 | | - std::make_shared<Cilium::PolicyHostDecoder>()); |
| 236 | + const envoy::config::core::v3::ConfigSource& npds_config) { |
| 237 | + context_ = &context; |
| 238 | + desired_config_source_ = npds_config; |
| 239 | + subscribe(); |
| 240 | +} |
| 241 | + |
| 242 | +void PolicyHostMap::setConfigSource(const envoy::config::core::v3::ConfigSource& config_source) { |
| 243 | + desired_config_source_ = config_source; |
| 244 | + if (context_ != nullptr) { |
| 245 | + maybeRecreateSubscriptionInDesiredMode(/*transport_closed=*/false); |
| 246 | + } |
| 247 | +} |
| 248 | + |
| 249 | +bool PolicyHostMap::subscriptionUseDeltaXds() const { |
| 250 | + if (!config_source_.has_api_config_source()) { |
| 251 | + return false; |
210 | 252 | } |
| 253 | + const auto& api_type = config_source_.api_config_source().api_type(); |
| 254 | + return api_type == envoy::config::core::v3::ApiConfigSource::DELTA_GRPC || |
| 255 | + api_type == envoy::config::core::v3::ApiConfigSource::AGGREGATED_DELTA_GRPC; |
| 256 | +} |
211 | 257 |
|
| 258 | +void PolicyHostMap::subscribe() { |
| 259 | + ASSERT(context_ != nullptr); |
| 260 | + subscription_connected_ = false; |
| 261 | + config_source_ = desired_config_source_; |
| 262 | + ++subscription_id_; |
| 263 | + |
| 264 | + auto on_stream_event = [weak_this = weak_from_this(), |
| 265 | + id = subscription_id_](Config::GrpcMuxStreamEvent event) { |
| 266 | + if (auto shared_this = weak_this.lock()) { |
| 267 | + shared_this->onSubscriptionStreamEvent(id, event); |
| 268 | + } |
| 269 | + }; |
| 270 | + |
| 271 | + subscription_ = |
| 272 | + Cilium::subscribe(NetworkPolicyHostsTypeUrl, config_source_, *context_, *scope_, *this, |
| 273 | + std::make_shared<Cilium::PolicyHostDecoder>(), std::move(on_stream_event)); |
212 | 274 | subscription_->start({}); |
213 | 275 | } |
214 | 276 |
|
| 277 | +void PolicyHostMap::onSubscriptionStreamEvent(uint64_t subscription_id, |
| 278 | + Config::GrpcMuxStreamEvent event) { |
| 279 | + if (subscription_id != subscription_id_) { |
| 280 | + return; |
| 281 | + } |
| 282 | + |
| 283 | + switch (event) { |
| 284 | + case Config::GrpcMuxStreamEvent::Established: |
| 285 | + subscription_connected_ = true; |
| 286 | + break; |
| 287 | + case Config::GrpcMuxStreamEvent::Closed: |
| 288 | + if (!subscription_connected_) { |
| 289 | + return; |
| 290 | + } |
| 291 | + subscription_connected_ = false; |
| 292 | + |
| 293 | + if (context_ == nullptr) { |
| 294 | + return; |
| 295 | + } |
| 296 | + |
| 297 | + context_->mainThreadDispatcher().post( |
| 298 | + [weak_this = weak_from_this(), subscription_id = subscription_id_]() { |
| 299 | + if (auto shared_this = weak_this.lock()) { |
| 300 | + if (subscription_id != shared_this->subscription_id_) { |
| 301 | + return; |
| 302 | + } |
| 303 | + shared_this->maybeRecreateSubscriptionInDesiredMode(/*transport_closed=*/true); |
| 304 | + } |
| 305 | + }); |
| 306 | + break; |
| 307 | + } |
| 308 | +} |
| 309 | + |
| 310 | +void PolicyHostMap::maybeRecreateSubscriptionInDesiredMode(bool transport_closed) { |
| 311 | + if (subscription_ && (subscription_connected_ || !transport_closed)) { |
| 312 | + if (subscription_connected_ && subscriptionUseDeltaXds()) { |
| 313 | + return; |
| 314 | + } |
| 315 | + if (Protobuf::util::MessageDifferencer::Equals(config_source_, desired_config_source_)) { |
| 316 | + return; |
| 317 | + } |
| 318 | + } |
| 319 | + |
| 320 | + subscribe(); |
| 321 | +} |
| 322 | + |
| 323 | +absl::Status |
| 324 | +PolicyHostMap::onConfigUpdate(const std::vector<Envoy::Config::DecodedResourceRef>& added_resources, |
| 325 | + const Protobuf::RepeatedPtrField<std::string>& removed_resources, |
| 326 | + const std::string& system_version_info) { |
| 327 | + const bool is_new_stream = subscription_id_ != accepted_subscription_id_; |
| 328 | + ENVOY_LOG( |
| 329 | + debug, |
| 330 | + "PolicyHostMap::onConfigUpdate({}), {} added_resources, {} removed_resources, version: {}, " |
| 331 | + "subscription_id: {}, accepted_subscription_id: {}, is_new_stream: {}", |
| 332 | + name_, added_resources.size(), removed_resources.size(), system_version_info, |
| 333 | + subscription_id_, accepted_subscription_id_, is_new_stream); |
| 334 | + |
| 335 | + auto newmap = |
| 336 | + std::make_shared<ThreadLocalHostMapInitializer>(is_new_stream ? nullptr : getHostMap()); |
| 337 | + |
| 338 | + absl::flat_hash_set<uint64_t> to_remove; |
| 339 | + to_remove.reserve(added_resources.size() + removed_resources.size()); |
| 340 | + |
| 341 | + for (const auto& name : removed_resources) { |
| 342 | + uint64_t nid = 0; |
| 343 | + auto [ptr, ec] = std::from_chars(name.data(), name.data() + name.size(), nid); |
| 344 | + if (ec != std::errc{} || ptr != name.data() + name.size()) { |
| 345 | + throw EnvoyException(fmt::format("Invalid removed resource name '{}'", name)); |
| 346 | + } |
| 347 | + ENVOY_LOG(trace, |
| 348 | + "Removing NetworkPolicyHosts for policy {} in delta onConfigUpdate() version {}", nid, |
| 349 | + system_version_info); |
| 350 | + to_remove.insert(nid); |
| 351 | + } |
| 352 | + for (const auto& resource : added_resources) { |
| 353 | + const auto& config = dynamic_cast<const cilium::NetworkPolicyHosts&>(resource.get().resource()); |
| 354 | + to_remove.insert(config.policy()); |
| 355 | + } |
| 356 | + newmap->remove(to_remove); |
| 357 | + |
| 358 | + for (const auto& resource : added_resources) { |
| 359 | + const auto& config = dynamic_cast<const cilium::NetworkPolicyHosts&>(resource.get().resource()); |
| 360 | + ENVOY_LOG(trace, |
| 361 | + "Received NetworkPolicyHosts for policy {} in delta onConfigUpdate() version {}", |
| 362 | + config.policy(), system_version_info); |
| 363 | + newmap->insert(config); |
| 364 | + } |
| 365 | + |
| 366 | + // Force 'this' to be not deleted for as long as the lambda stays |
| 367 | + // alive. Note that generally capturing a shared pointer is |
| 368 | + // dangerous as it may happen that there is a circular reference |
| 369 | + // from 'this' to itself via the lambda capture, leading to 'this' |
| 370 | + // never being released. It should happen in this case, though. |
| 371 | + std::shared_ptr<PolicyHostMap> shared_this = shared_from_this(); |
| 372 | + |
| 373 | + // Assign the new map to all threads. |
| 374 | + tls_->set([shared_this, newmap](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { |
| 375 | + UNREFERENCED_PARAMETER(shared_this); |
| 376 | + ENVOY_LOG(trace, "PolicyHostMap: Assigning new map"); |
| 377 | + return newmap; |
| 378 | + }); |
| 379 | + logmaps("delta onConfigUpdate"); |
| 380 | + accepted_subscription_id_ = subscription_id_; |
| 381 | + stats_.update_success_.inc(); |
| 382 | + return absl::OkStatus(); |
| 383 | +} |
| 384 | + |
215 | 385 | absl::Status |
216 | 386 | PolicyHostMap::onConfigUpdate(const std::vector<Envoy::Config::DecodedResourceRef>& resources, |
217 | 387 | const std::string& version_info) { |
|
0 commit comments