blob: da47dc08e52dada97e43f6faabdbac7385884cd3 [file]
/** @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;
}