blob: 81cf79d6805b239bbcceb16fd2ec662fdbcdb16e [file] [log] [blame]
/** @file
Plugin Traffic Dump captures traffic on a per session basis. A sampling ratio can be set via plugin.config or traffic_ctl to dump
one out of n sessions. The dump file schema can be found
https://github.com/apache/trafficserver/tree/master/tests/tools/lib/replay_schema.json
@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.
*/
#include <getopt.h>
#include "global_variables.h"
#include "session_data.h"
#include "transaction_data.h"
namespace traffic_dump
{
DbgCtl dbg_ctl{debug_tag};
/// Handle LIFECYCLE_MSG from traffic_ctl.
static int
global_message_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void *edata)
{
switch (event) {
case TS_EVENT_LIFECYCLE_MSG: {
TSPluginMsg *msg = static_cast<TSPluginMsg *>(edata);
static constexpr std::string_view PLUGIN_PREFIX("traffic_dump.");
std::string_view tag(msg->tag, strlen(msg->tag));
if (tag.substr(0, PLUGIN_PREFIX.size()) == PLUGIN_PREFIX) {
tag.remove_prefix(PLUGIN_PREFIX.size());
if (tag == "sample" && msg->data_size) {
const auto new_sample_size = static_cast<int64_t>(strtol(static_cast<char const *>(msg->data), nullptr, 0));
Dbg(dbg_ctl, "TS_EVENT_LIFECYCLE_MSG: Received Msg to change sample size to %" PRId64 "bytes", new_sample_size);
SessionData::set_sample_pool_size(new_sample_size);
} else if (tag == "reset") {
Dbg(dbg_ctl, "TS_EVENT_LIFECYCLE_MSG: Received Msg to reset disk usage counter");
SessionData::reset_disk_usage();
} else if (tag == "unlimit") {
Dbg(dbg_ctl, "TS_EVENT_LIFECYCLE_MSG: Received Msg to disable disk usage limit enforcement");
SessionData::disable_disk_limit_enforcement();
} else if (tag == "limit" && msg->data_size) {
const auto new_max_disk_usage = static_cast<int64_t>(strtol(static_cast<char const *>(msg->data), nullptr, 0));
Dbg(dbg_ctl, "TS_EVENT_LIFECYCLE_MSG: Received Msg to change max disk usage to %" PRId64 "bytes", new_max_disk_usage);
SessionData::set_max_disk_usage(new_max_disk_usage);
}
}
return TS_SUCCESS;
}
default:
Dbg(dbg_ctl, "session_aio_handler(): unhandled events %d", event);
return TS_ERROR;
}
}
} // namespace traffic_dump
void
TSPluginInit(int argc, char const *argv[])
{
Dbg(traffic_dump::dbg_ctl, "initializing plugin");
TSPluginRegistrationInfo info;
info.plugin_name = "traffic_dump";
info.vendor_name = "Apache Software Foundation";
info.support_email = "dev@trafficserver.apache.org";
if (TS_SUCCESS != TSPluginRegister(&info)) {
TSError("[%s] Unable to initialize plugin (disabled). Failed to register plugin.", traffic_dump::debug_tag);
return;
}
bool dump_body = false;
bool sensitive_fields_were_specified = false;
traffic_dump::sensitive_fields_t user_specified_fields;
swoc::file::path log_dir{traffic_dump::SessionData::default_log_directory};
int64_t sample_pool_size = traffic_dump::SessionData::default_sample_pool_size;
int64_t max_disk_usage = traffic_dump::SessionData::default_max_disk_usage;
bool enforce_disk_limit = traffic_dump::SessionData::default_enforce_disk_limit;
std::string sni_filter;
std::string client_ip_filter;
/// Commandline options
static const struct option longopts[] = {
{"dump_body", no_argument, nullptr, 'b'},
{"logdir", required_argument, nullptr, 'l'},
{"sample", required_argument, nullptr, 's'},
{"limit", required_argument, nullptr, 'm'},
{"sensitive-fields", required_argument, nullptr, 'f'},
{"sni-filter", required_argument, nullptr, 'n'},
{"client_ipv4", required_argument, nullptr, '4'},
{"client_ipv6", required_argument, nullptr, '6'},
{nullptr, no_argument, nullptr, 0 }
};
int opt = 0;
while (opt >= 0) {
opt = getopt_long(argc, const_cast<char *const *>(argv), "bf:l:s:m:n:4:6", longopts, nullptr);
switch (opt) {
case 'b': {
dump_body = true;
break;
}
case 'f': {
// --sensitive-fields takes a comma-separated list of HTTP fields that
// are sensitive. The field values for these fields will be replaced
// with generic traffic_dump generated data.
//
// If this option is not used, then the default values in
// default_sensitive_fields is used. If this option is used, then it
// replaced the default sensitive fields with the user-supplied list of
// sensitive fields.
sensitive_fields_were_specified = true;
swoc::TextView input_filter_fields{std::string_view{optarg}};
swoc::TextView filter_field;
while (!(filter_field = input_filter_fields.take_prefix_at(',')).empty()) {
filter_field.trim_if(&isspace);
if (filter_field.empty()) {
continue;
}
user_specified_fields.emplace(filter_field);
}
break;
}
case 'n': {
// --sni-filter is used to filter sessions based upon an SNI.
sni_filter = std::string(optarg);
break;
}
case 'l': {
log_dir = swoc::file::path{optarg};
break;
}
case 's': {
sample_pool_size = static_cast<int64_t>(std::strtol(optarg, nullptr, 0));
break;
}
case 'm': {
max_disk_usage = static_cast<int64_t>(std::strtol(optarg, nullptr, 0));
enforce_disk_limit = true;
break;
}
case '4':
case '6': {
client_ip_filter = std::string(optarg);
break;
}
case -1:
case '?':
break;
default:
Dbg(traffic_dump::dbg_ctl, "Unexpected options.");
TSError("[%s] Unexpected options error.", traffic_dump::debug_tag);
return;
}
}
if (!log_dir.is_absolute()) {
log_dir = swoc::file::path(TSInstallDirGet()) / log_dir;
}
if (sni_filter.empty()) {
if (!traffic_dump::SessionData::init(log_dir.view(), enforce_disk_limit, max_disk_usage, sample_pool_size, client_ip_filter)) {
TSError("[%s] Failed to initialize session state.", traffic_dump::debug_tag);
return;
}
} else {
if (!traffic_dump::SessionData::init(log_dir.view(), enforce_disk_limit, max_disk_usage, sample_pool_size, client_ip_filter,
sni_filter)) {
TSError("[%s] Failed to initialize session state with an SNI filter.", traffic_dump::debug_tag);
return;
}
}
if (sensitive_fields_were_specified) {
if (!traffic_dump::TransactionData::init(dump_body, std::move(user_specified_fields))) {
TSError("[%s] Failed to initialize transaction state with user-specified fields.", traffic_dump::debug_tag);
return;
}
} else {
// The user did not provide their own list of sensitive fields. Use the
// default.
if (!traffic_dump::TransactionData::init(dump_body)) {
TSError("[%s] Failed to initialize transaction state.", traffic_dump::debug_tag);
return;
}
}
TSCont message_continuation = TSContCreate(traffic_dump::global_message_handler, nullptr);
TSLifecycleHookAdd(TS_LIFECYCLE_MSG_HOOK, message_continuation);
return;
}