1 /** @file
2
3 Plugin Traffic Dump captures traffic on a per session basis. A sampling ratio can be set via plugin.config or traffic_ctl to dump
4 one out of n sessions. The dump file schema can be found
5 https://github.com/apache/trafficserver/tree/master/tests/tools/lib/replay_schema.json
6
7 @section license License
8
9 Licensed to the Apache Software Foundation (ASF) under one
10 or more contributor license agreements. See the NOTICE file
11 distributed with this work for additional information
12 regarding copyright ownership. The ASF licenses this file
13 to you under the Apache License, Version 2.0 (the
14 "License"); you may not use this file except in compliance
15 with the License. You may obtain a copy of the License at
16
17 http://www.apache.org/licenses/LICENSE-2.0
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24 */
25
26 #include <getopt.h>
27
28 #include "global_variables.h"
29 #include "session_data.h"
30 #include "transaction_data.h"
31
32 namespace traffic_dump
33 {
34 /// Handle LIFECYCLE_MSG from traffic_ctl.
35 static int
global_message_handler(TSCont contp,TSEvent event,void * edata)36 global_message_handler(TSCont contp, TSEvent event, void *edata)
37 {
38 switch (event) {
39 case TS_EVENT_LIFECYCLE_MSG: {
40 TSPluginMsg *msg = static_cast<TSPluginMsg *>(edata);
41 static constexpr std::string_view PLUGIN_PREFIX("traffic_dump."_sv);
42
43 std::string_view tag(msg->tag, strlen(msg->tag));
44 if (tag.substr(0, PLUGIN_PREFIX.size()) == PLUGIN_PREFIX) {
45 tag.remove_prefix(PLUGIN_PREFIX.size());
46 if (tag == "sample" && msg->data_size) {
47 const auto new_sample_size = static_cast<int64_t>(strtol(static_cast<char const *>(msg->data), nullptr, 0));
48 TSDebug(debug_tag, "TS_EVENT_LIFECYCLE_MSG: Received Msg to change sample size to %" PRId64 "bytes", new_sample_size);
49 SessionData::set_sample_pool_size(new_sample_size);
50 } else if (tag == "reset") {
51 TSDebug(debug_tag, "TS_EVENT_LIFECYCLE_MSG: Received Msg to reset disk usage counter");
52 SessionData::reset_disk_usage();
53 } else if (tag == "limit" && msg->data_size) {
54 const auto new_max_disk_usage = static_cast<int64_t>(strtol(static_cast<char const *>(msg->data), nullptr, 0));
55 TSDebug(debug_tag, "TS_EVENT_LIFECYCLE_MSG: Received Msg to change max disk usage to %" PRId64 "bytes", new_max_disk_usage);
56 SessionData::set_max_disk_usage(new_max_disk_usage);
57 }
58 }
59 return TS_SUCCESS;
60 }
61 default:
62 TSDebug(debug_tag, "session_aio_handler(): unhandled events %d", event);
63 return TS_ERROR;
64 }
65 }
66
67 } // namespace traffic_dump
68
69 void
TSPluginInit(int argc,char const * argv[])70 TSPluginInit(int argc, char const *argv[])
71 {
72 TSDebug(traffic_dump::debug_tag, "initializing plugin");
73 TSPluginRegistrationInfo info;
74
75 info.plugin_name = "traffic_dump";
76 info.vendor_name = "Apache Software Foundation";
77 info.support_email = "dev@trafficserver.apache.org";
78
79 if (TS_SUCCESS != TSPluginRegister(&info)) {
80 TSError("[%s] Unable to initialize plugin (disabled). Failed to register plugin.", traffic_dump::debug_tag);
81 return;
82 }
83
84 bool sensitive_fields_were_specified = false;
85 traffic_dump::sensitive_fields_t user_specified_fields;
86 ts::file::path log_dir{traffic_dump::SessionData::default_log_directory};
87 int64_t sample_pool_size = traffic_dump::SessionData::default_sample_pool_size;
88 int64_t max_disk_usage = traffic_dump::SessionData::default_max_disk_usage;
89 std::string sni_filter;
90
91 /// Commandline options
92 static const struct option longopts[] = {
93 {"logdir", required_argument, nullptr, 'l'}, {"sample", required_argument, nullptr, 's'},
94 {"limit", required_argument, nullptr, 'm'}, {"sensitive-fields", required_argument, nullptr, 'f'},
95 {"sni-filter", required_argument, nullptr, 'n'}, {nullptr, no_argument, nullptr, 0}};
96 int opt = 0;
97 while (opt >= 0) {
98 opt = getopt_long(argc, const_cast<char *const *>(argv), "l:", longopts, nullptr);
99 switch (opt) {
100 case 'f': {
101 // --sensitive-fields takes a comma-separated list of HTTP fields that
102 // are sensitive. The field values for these fields will be replaced
103 // with generic traffic_dump generated data.
104 //
105 // If this option is not used, then the default values in
106 // default_sensitive_fields is used. If this option is used, then it
107 // replaced the default sensitive fields with the user-supplied list of
108 // sensitive fields.
109 sensitive_fields_were_specified = true;
110 ts::TextView input_filter_fields{std::string_view{optarg}};
111 ts::TextView filter_field;
112 while (!(filter_field = input_filter_fields.take_prefix_at(',')).empty()) {
113 filter_field.trim_if(&isspace);
114 if (filter_field.empty()) {
115 continue;
116 }
117 user_specified_fields.emplace(filter_field);
118 }
119 break;
120 }
121 case 'n': {
122 // --sni-filter is used to filter sessions based upon an SNI.
123 sni_filter = std::string(optarg);
124 break;
125 }
126 case 'l': {
127 log_dir = ts::file::path{optarg};
128 break;
129 }
130 case 's': {
131 sample_pool_size = static_cast<int64_t>(std::strtol(optarg, nullptr, 0));
132 break;
133 }
134 case 'm': {
135 max_disk_usage = static_cast<int64_t>(std::strtol(optarg, nullptr, 0));
136 }
137 case -1:
138 case '?':
139 break;
140
141 default:
142 TSDebug(traffic_dump::debug_tag, "Unexpected options.");
143 TSError("[%s] Unexpected options error.", traffic_dump::debug_tag);
144 return;
145 }
146 }
147 if (!log_dir.is_absolute()) {
148 log_dir = ts::file::path(TSInstallDirGet()) / log_dir;
149 }
150 if (sni_filter.empty()) {
151 if (!traffic_dump::SessionData::init(log_dir.view(), max_disk_usage, sample_pool_size)) {
152 TSError("[%s] Failed to initialize session state.", traffic_dump::debug_tag);
153 return;
154 }
155 } else {
156 if (!traffic_dump::SessionData::init(log_dir.view(), max_disk_usage, sample_pool_size, sni_filter)) {
157 TSError("[%s] Failed to initialize session state with an SNI filter.", traffic_dump::debug_tag);
158 return;
159 }
160 }
161
162 if (sensitive_fields_were_specified) {
163 if (!traffic_dump::TransactionData::init(std::move(user_specified_fields))) {
164 TSError("[%s] Failed to initialize transaction state with user-specified fields.", traffic_dump::debug_tag);
165 return;
166 }
167 } else {
168 // The user did not provide their own list of sensitive fields. Use the
169 // default.
170 if (!traffic_dump::TransactionData::init()) {
171 TSError("[%s] Failed to initialize transaction state.", traffic_dump::debug_tag);
172 return;
173 }
174 }
175
176 TSCont message_continuation = TSContCreate(traffic_dump::global_message_handler, nullptr);
177 TSLifecycleHookAdd(TS_LIFECYCLE_MSG_HOOK, message_continuation);
178 return;
179 }
180