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