| /** @file |
| |
| Definitions for reverse proxy |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF 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. |
| |
| @section details Details |
| |
| Implements code necessary for Reverse Proxy which mostly consists of |
| general purpose hostname substitution in URLs. |
| |
| */ |
| |
| #include "tscore/ink_platform.h" |
| #include "tscore/Filenames.h" |
| #include "tscore/TSSystemState.h" |
| #include <dlfcn.h> |
| #include "iocore/cache/Cache.h" |
| #include "iocore/eventsystem/Freer.h" |
| #include "proxy/ReverseProxy.h" |
| #include "mgmt/config/ConfigContextDiags.h" |
| #include "mgmt/config/ConfigRegistry.h" |
| #include "tscore/MatcherUtils.h" |
| #include "tscore/Tokenizer.h" |
| #include "ts/remap.h" |
| #include "proxy/http/remap/RemapPluginInfo.h" |
| #include "proxy/http/remap/RemapProcessor.h" |
| #include "proxy/http/remap/UrlRewrite.h" |
| #include "proxy/http/remap/UrlMapping.h" |
| #include "proxy/http/remap/UrlMappingPathIndex.h" |
| |
| namespace |
| { |
| Ptr<ProxyMutex> reconfig_mutex; |
| |
| DbgCtl dbg_ctl_url_rewrite{"url_rewrite"}; |
| |
| // Steers UrlRewriteDeleter to inline-delete; see shutdown_url_rewrite(). |
| std::atomic<bool> rewrite_table_shutdown{false}; |
| |
| // Defer teardown to ET_TASK; UrlRewrite destruction can be slow. |
| struct UrlRewriteDeleter { |
| void |
| operator()(UrlRewrite *p) const noexcept |
| { |
| if (!p) { |
| return; |
| } |
| if (rewrite_table_shutdown.load(std::memory_order_acquire) || TSSystemState::is_event_system_shut_down()) { |
| // Leak; plugin teardown is unsafe post-shutdown. |
| return; |
| } |
| // new_Deleter allocates; fall back to inline delete so we don't escape noexcept. |
| try { |
| new_Deleter(p, 0); |
| } catch (...) { |
| delete p; |
| } |
| } |
| }; |
| |
| } // end anonymous namespace |
| |
| // Global Ptrs |
| AtomicSharedPtr<UrlRewrite> rewrite_table; |
| thread_local PluginThreadContext *pluginThreadContext = nullptr; |
| |
| void |
| shutdown_url_rewrite() |
| { |
| // Drain before flag: this ref destructs normally; later drops leak. |
| rewrite_table.exchange(nullptr); |
| rewrite_table_shutdown.store(true, std::memory_order_release); |
| } |
| |
| // Tokens for the Callback function |
| #define FILE_CHANGED 0 |
| #define REVERSE_CHANGED 1 |
| #define TSNAME_CHANGED 2 |
| #define TRANS_CHANGED 4 |
| #define URL_REMAP_MODE_CHANGED 8 |
| #define HTTP_DEFAULT_REDIRECT_CHANGED 9 |
| |
| static void init_table_volume_host_records(UrlRewrite &table); |
| |
| // |
| // Begin API Functions |
| // |
| int |
| init_reverse_proxy() |
| { |
| ink_assert(rewrite_table.load(std::memory_order_acquire) == nullptr); |
| reconfig_mutex = new_ProxyMutex(); |
| auto initial_table = std::make_unique<UrlRewrite>(); |
| auto &config_reg = config::ConfigRegistry::Get_Instance(); |
| |
| // Register with ConfigRegistry BEFORE load() so that remap.config and remap.yaml are in |
| // FileManager's bindings when .include directives call configFileChild() |
| // to register child files (e.g. test.inc). |
| config_reg.register_config( |
| "remap", // registry key |
| ts::filename::REMAP, // default filename |
| "proxy.config.url_remap.filename", // record holding the filename |
| [](ConfigContext ctx) { reloadUrlRewrite(ctx); }, // reload handler |
| config::ConfigSource::FileOnly); // file-based only |
| |
| config_reg.register_config( |
| "remap_yaml", // registry key |
| ts::filename::REMAP_YAML, // default filename |
| "proxy.config.url_remap_yaml.filename", // record holding the filename |
| [](ConfigContext ctx) { reloadUrlRewrite(ctx); }, // reload handler |
| config::ConfigSource::FileOnly); // file-based only |
| |
| Note("%s loading (checking first) ...", ts::filename::REMAP_YAML); |
| Note("%s loading ...", ts::filename::REMAP); |
| bool status = initial_table->load(); |
| bool is_yaml = initial_table->is_remap_yaml(); |
| |
| if (!status) { |
| Emergency("%s failed to load", is_yaml ? ts::filename::REMAP_YAML : ts::filename::REMAP); |
| } else { |
| Note("%s finished loading", is_yaml ? ts::filename::REMAP_YAML : ts::filename::REMAP); |
| } |
| |
| if (initial_table->is_valid() && CacheProcessor::IsCacheEnabled() == CacheInitState::INITIALIZED) { |
| // Initialize deferred @volume= mappings before publishing so startup-only |
| // remap walks cannot race a reload. |
| init_table_volume_host_records(*initial_table); |
| } |
| |
| rewrite_table.store(std::shared_ptr<UrlRewrite>(initial_table.release(), UrlRewriteDeleter{}), std::memory_order_release); |
| ink_assert(0 == config_reg.attach("remap", "proxy.config.url_remap.filename")); |
| ink_assert(0 == config_reg.attach("remap", "proxy.config.proxy_name")); |
| ink_assert(0 == config_reg.attach("remap", "proxy.config.http.referer_default_redirect")); |
| ink_assert(0 == config_reg.attach("remap_yaml", "proxy.config.url_remap_yaml.filename")); |
| ink_assert(0 == config_reg.attach("remap_yaml", "proxy.config.proxy_name")); |
| ink_assert(0 == config_reg.attach("remap_yaml", "proxy.config.http.referer_default_redirect")); |
| RecRegisterConfigUpdateCb("proxy.config.reverse_proxy.enabled", url_rewrite_CB, (void *)REVERSE_CHANGED); |
| |
| return 0; |
| } |
| |
| /** |
| This function is used to figure out if a URL needs to be remapped |
| according to the rules in remap.config. |
| */ |
| mapping_type |
| request_url_remap_redirect(HTTPHdr *request_header, URL *redirect_url, UrlRewrite *table) |
| { |
| return table ? table->Remap_redirect(request_header, redirect_url) : mapping_type::NONE; |
| } |
| |
| bool |
| response_url_remap(HTTPHdr *response_header, UrlRewrite *table) |
| { |
| return table ? table->ReverseMap(response_header) : false; |
| } |
| |
| // |
| // |
| // End API Functions |
| // |
| |
| bool |
| urlRewriteVerify() |
| { |
| return UrlRewrite().load(); |
| } |
| |
| /** |
| Called when the remap.config or remap.yaml file changes. Since it called infrequently, |
| we do the load of new file as blocking I/O and lock acquire is also |
| blocking. |
| |
| */ |
| bool |
| reloadUrlRewrite(ConfigContext ctx) |
| { |
| std::string msg_buffer; |
| |
| msg_buffer.reserve(1024); |
| |
| CfgLoadLog(ctx, DL_Note, "%s loading (checking first) ...", ts::filename::REMAP_YAML); |
| CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::REMAP); |
| Dbg(dbg_ctl_url_rewrite, "%s updated, reloading...", ts::filename::REMAP_YAML); |
| Dbg(dbg_ctl_url_rewrite, "%s updated, reloading...", ts::filename::REMAP); |
| auto newTable = std::make_unique<UrlRewrite>(); |
| |
| bool status = newTable->load(ctx); |
| bool is_yaml = (newTable->is_remap_yaml()); |
| |
| if (status) { |
| swoc::bwprint(msg_buffer, "{} finished loading", is_yaml ? ts::filename::REMAP_YAML : ts::filename::REMAP); |
| |
| rewrite_table.exchange(std::shared_ptr<UrlRewrite>(newTable.release(), UrlRewriteDeleter{}), std::memory_order_acq_rel); |
| |
| Dbg(dbg_ctl_url_rewrite, "%s", msg_buffer.c_str()); |
| CfgLoadComplete(ctx, "%s finished loading", is_yaml ? ts::filename::REMAP_YAML : ts::filename::REMAP); |
| return true; |
| } else { |
| swoc::bwprint(msg_buffer, "{} failed to load", is_yaml ? ts::filename::REMAP_YAML : ts::filename::REMAP); |
| |
| Dbg(dbg_ctl_url_rewrite, "%s", msg_buffer.c_str()); |
| CfgLoadFail(ctx, "%s failed to load", is_yaml ? ts::filename::REMAP_YAML : ts::filename::REMAP); |
| return false; |
| } |
| } |
| |
| /** |
| * Helper function to initialize volume_host_rec for a single url_mapping. |
| * This is a no-op if the mapping has no volume string or is already initialized. |
| */ |
| static void |
| init_mapping_volume_host_rec(url_mapping &mapping) |
| { |
| char errbuf[256]; |
| |
| if (!mapping.initVolumeHostRec(errbuf, sizeof(errbuf))) { |
| Error("Failed to initialize volume record for @volume=%s: %s", mapping.getVolume().c_str(), errbuf); |
| } |
| } |
| |
| static void |
| init_store_volume_host_records(UrlRewrite::MappingsStore &store) |
| { |
| if (store.hash_lookup) { |
| for (auto &entry : *store.hash_lookup) { |
| UrlMappingPathIndex *path_index = entry.second; |
| |
| if (path_index) { |
| path_index->foreach_mapping(init_mapping_volume_host_rec); |
| } |
| } |
| } |
| |
| for (UrlRewrite::RegexMapping *reg_map = store.regex_list.head; reg_map; reg_map = reg_map->link.next) { |
| if (reg_map->url_map) { |
| init_mapping_volume_host_rec(*reg_map->url_map); |
| } |
| } |
| } |
| |
| static void |
| init_table_volume_host_records(UrlRewrite &table) |
| { |
| Dbg(dbg_ctl_url_rewrite, "Initializing volume_host_rec for all remap rules after cache init"); |
| |
| init_store_volume_host_records(table.forward_mappings); |
| init_store_volume_host_records(table.reverse_mappings); |
| init_store_volume_host_records(table.permanent_redirects); |
| init_store_volume_host_records(table.temporary_redirects); |
| init_store_volume_host_records(table.forward_mappings_with_recv_port); |
| } |
| |
| // This is called after the cache is initialized, since we may need the volume_host_records. |
| // Must only be called during startup before any remap reload can occur. |
| void |
| init_remap_volume_host_records() |
| { |
| if (CacheProcessor::IsCacheEnabled() != CacheInitState::INITIALIZED) { |
| return; |
| } |
| |
| auto table = rewrite_table.load(std::memory_order_acquire); |
| |
| if (!table) { |
| return; |
| } |
| |
| if (table->is_valid()) { |
| init_table_volume_host_records(*table); |
| } |
| } |
| |
| int |
| url_rewrite_CB(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, RecData data, |
| void * /* cookie ATS_UNUSED */) |
| { |
| if (auto table = rewrite_table.load(std::memory_order_acquire)) { |
| table->SetReverseFlag(data.rec_int); |
| } |
| return 0; |
| } |