1 /** @file
2
3 Traffic Dump session handling implementation
4
5 @section license License
6
7 Licensed to the Apache Software Foundation (ASF) under one
8 or more contributor license agreements. See the NOTICE file
9 distributed with this work for additional information
10 regarding copyright ownership. The ASF licenses this file
11 to you under the Apache License, Version 2.0 (the
12 "License"); you may not use this file except in compliance
13 with the License. You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22 */
23
24 #include <arpa/inet.h>
25 #include <chrono>
26 #include <fcntl.h>
27 #include <iomanip>
28 #include <netinet/in.h>
29 #include <openssl/ssl.h>
30 #include <sstream>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <unordered_map>
34
35 #include <tscore/ink_inet.h>
36
37 #include "session_data.h"
38 #include "global_variables.h"
39 #include "transaction_data.h"
40
41 namespace
42 {
43 /** The final string used to close a JSON session. */
44 char const constexpr *const json_closing = "]}]}";
45
46 /**
47 * A mapping from IP_PROTO_TAG to the string describing the JSON protocol node.
48 */
49 std::unordered_map<std::string_view, std::string> tag_to_node = {
50 {IP_PROTO_TAG_IPV4, R"("name":"ip","version":"4")"},
51 {IP_PROTO_TAG_IPV6, R"("name":"ip","version":"6")"},
52
53 {IP_PROTO_TAG_TCP, R"("name":"tcp")"},
54 {IP_PROTO_TAG_UDP, R"("name":"udp")"},
55
56 {IP_PROTO_TAG_QUIC, R"("name:":"quic")"},
57
58 {IP_PROTO_TAG_TLS_1_0, R"("name":"tls","version":"1.0")"},
59 {IP_PROTO_TAG_TLS_1_1, R"("name":"tls","version":"1.1")"},
60 {IP_PROTO_TAG_TLS_1_2, R"("name":"tls","version":"1.2")"},
61 {IP_PROTO_TAG_TLS_1_3, R"("name":"tls","version":"1.3")"},
62
63 {IP_PROTO_TAG_HTTP_0_9, R"("name":"http","version":"0.9")"},
64 {IP_PROTO_TAG_HTTP_1_0, R"("name":"http","version":"1.0")"},
65 {IP_PROTO_TAG_HTTP_1_1, R"("name":"http","version":"1.1")"},
66 {IP_PROTO_TAG_HTTP_2_0, R"("name":"http","version":"2")"},
67
68 {IP_PROTO_TAG_HTTP_QUIC, R"("name":"http","version":"0.9")"},
69 {IP_PROTO_TAG_HTTP_3, R"("name":"http","version":"3")"},
70 };
71
72 std::unordered_map<std::string_view, std::string> http_tag_to_version = {
73 {IP_PROTO_TAG_HTTP_0_9, "0.9"}, {IP_PROTO_TAG_HTTP_1_0, "1.0"}, {IP_PROTO_TAG_HTTP_1_1, "1.1"},
74 {IP_PROTO_TAG_HTTP_2_0, "2"}, {IP_PROTO_TAG_HTTP_QUIC, "0.9"}, {IP_PROTO_TAG_HTTP_3, "3"},
75 };
76
77 /** Create a TLS characteristics node.
78 *
79 * This function encapsulates the logic common between the client-side and
80 * server-side logic for populating a "tls" node.
81 *
82 * @param[in] vconn The virtual connection for the session.
83 *
84 * @return The node describing the TLS properties of this session.
85 */
86 std::string
get_tls_description_helper(TSVConn vconn)87 get_tls_description_helper(TSVConn vconn)
88 {
89 if (vconn == nullptr) {
90 return "";
91 }
92 SSL *ssl_obj = reinterpret_cast<SSL *>(TSVConnSslConnectionGet(vconn));
93 if (ssl_obj == nullptr) {
94 return "";
95 }
96 std::ostringstream tls_description;
97 tls_description << R"("name":"tls",)";
98 char const *version_ptr = SSL_get_version(ssl_obj);
99 if (version_ptr != nullptr) {
100 std::string_view version{version_ptr};
101 if (!version.empty()) {
102 tls_description << R"("version":")" << version << R"(",)";
103 }
104 }
105 char const *sni_ptr = SSL_get_servername(ssl_obj, TLSEXT_NAMETYPE_host_name);
106 if (sni_ptr != nullptr) {
107 std::string_view sni{sni_ptr};
108 if (!sni.empty()) {
109 tls_description << R"("sni":")" << sni << R"(",)";
110 }
111 }
112
113 int verify_mode = SSL_get_verify_mode(ssl_obj);
114 tls_description << R"("proxy-verify-mode":)" << std::to_string(verify_mode) << ",";
115 bool provided_cert = TSVConnProvidedSslCert(vconn);
116 tls_description << R"("proxy-provided-cert":)" << (provided_cert ? "true" : "false");
117 return tls_description.str();
118 }
119
120 /** Create a client-side TLS characteristics node.
121 *
122 * @param[in] ssnp The pointer for this session.
123 *
124 * @return The node describing the TLS properties of this session.
125 */
126 std::string
get_client_tls_description(TSHttpSsn ssnp)127 get_client_tls_description(TSHttpSsn ssnp)
128 {
129 TSVConn client_ssn_vc = TSHttpSsnClientVConnGet(ssnp);
130 return get_tls_description_helper(client_ssn_vc);
131 }
132
133 /** Create a server-side TLS characteristics node.
134 *
135 * @param[in] ssnp The pointer for this session.
136 *
137 * @return The node describing the TLS properties of this session.
138 */
139 std::string
get_server_tls_description(TSHttpTxn txnp)140 get_server_tls_description(TSHttpTxn txnp)
141 {
142 TSVConn server_ssn_vc = TSHttpTxnServerVConnGet(txnp);
143 return get_tls_description_helper(server_ssn_vc);
144 }
145 } // namespace
146
147 namespace traffic_dump
148 {
149 // Static member initialization.
150 int SessionData::session_arg_index = -1;
151 std::atomic<int64_t> SessionData::sample_pool_size = default_sample_pool_size;
152 std::atomic<int64_t> SessionData::max_disk_usage = default_max_disk_usage;
153 std::atomic<int64_t> SessionData::disk_usage = 0;
154 ts::file::path SessionData::log_directory{default_log_directory};
155 uint64_t SessionData::session_counter = 0;
156 std::string SessionData::sni_filter;
157
158 int
get_session_arg_index()159 SessionData::get_session_arg_index()
160 {
161 return session_arg_index;
162 }
163
164 void
set_sample_pool_size(int64_t new_sample_size)165 SessionData::set_sample_pool_size(int64_t new_sample_size)
166 {
167 sample_pool_size = new_sample_size;
168 }
169
170 void
reset_disk_usage()171 SessionData::reset_disk_usage()
172 {
173 disk_usage = 0;
174 }
175
176 void
set_max_disk_usage(int64_t new_max_disk_usage)177 SessionData::set_max_disk_usage(int64_t new_max_disk_usage)
178 {
179 max_disk_usage = new_max_disk_usage;
180 }
181
182 bool
init(std::string_view log_directory,int64_t max_disk_usage,int64_t sample_size)183 SessionData::init(std::string_view log_directory, int64_t max_disk_usage, int64_t sample_size)
184 {
185 SessionData::log_directory = log_directory;
186 SessionData::max_disk_usage = max_disk_usage;
187 SessionData::sample_pool_size = sample_size;
188
189 if (TS_SUCCESS != TSUserArgIndexReserve(TS_USER_ARGS_SSN, debug_tag, "Track log related data", &session_arg_index)) {
190 TSError("[%s] Unable to initialize plugin (disabled). Failed to reserve ssn arg.", traffic_dump::debug_tag);
191 return false;
192 }
193
194 TSCont ssncont = TSContCreate(global_session_handler, nullptr);
195 TSHttpHookAdd(TS_HTTP_SSN_START_HOOK, ssncont);
196 TSHttpHookAdd(TS_HTTP_SSN_CLOSE_HOOK, ssncont);
197
198 TSDebug(debug_tag, "Initialized with log directory: %s", SessionData::log_directory.c_str());
199 TSDebug(debug_tag, "Initialized with sample pool size %" PRId64 " bytes and disk limit %" PRId64 " bytes", sample_size,
200 max_disk_usage);
201 return true;
202 }
203
204 bool
init(std::string_view log_directory,int64_t max_disk_usage,int64_t sample_size,std::string_view sni_filter)205 SessionData::init(std::string_view log_directory, int64_t max_disk_usage, int64_t sample_size, std::string_view sni_filter)
206 {
207 if (!init(log_directory, max_disk_usage, sample_size)) {
208 return false;
209 }
210 SessionData::sni_filter = sni_filter;
211 TSDebug(debug_tag, "Filtering to only dump connections with SNI: %s", SessionData::sni_filter.c_str());
212 return true;
213 }
214
215 std::string
get_protocol_stack_helper(const get_protocol_stack_f & get_protocol_stack,const get_tls_description_f & get_tls_node,const handle_http_version_f & handle_http_version)216 SessionData::get_protocol_stack_helper(const get_protocol_stack_f &get_protocol_stack, const get_tls_description_f &get_tls_node,
217 const handle_http_version_f &handle_http_version)
218 {
219 std::ostringstream protocol_description;
220 protocol_description << R"("protocol":[)";
221 char const *protocol[10];
222 int count = -1;
223 TSAssert(TS_SUCCESS == get_protocol_stack(10, protocol, &count));
224 bool is_first_printed_protocol = true;
225 for (int i = 0; i < count; ++i) {
226 std::string_view protocol_string(protocol[i]);
227 if (!is_first_printed_protocol) {
228 protocol_description << ",";
229 }
230 is_first_printed_protocol = false;
231 if (protocol_string.find("tls") != std::string::npos) {
232 protocol_description << '{' << get_tls_node() << '}';
233 } else {
234 auto search = tag_to_node.find(std::string(protocol_string));
235 if (search == tag_to_node.end()) {
236 // If the tag from get_protocol_stack is not in our list, then our
237 // tag_to_node has not been updated with the new tag. Update tag_to_node.
238 TSError("[%s] Missing tag node description: '%.*s'", traffic_dump::debug_tag, static_cast<int>(protocol_string.length()),
239 protocol_string.data());
240 protocol_description << R"({"name":")" << protocol_string << R"("})";
241 } else {
242 protocol_description << '{' << search->second << '}';
243 }
244
245 // See whether an HTTP version is provided. If so, record it.
246 auto const it = http_tag_to_version.find(std::string(protocol_string));
247 if (it != http_tag_to_version.end()) {
248 handle_http_version(it->second);
249 }
250 }
251 }
252 protocol_description << "]"; // Close the "protocol" sequence.
253 return protocol_description.str();
254 }
255
256 std::string
get_client_protocol_description(TSHttpSsn client_ssnp)257 SessionData::get_client_protocol_description(TSHttpSsn client_ssnp)
258 {
259 return get_protocol_stack_helper(
260 [&client_ssnp](int n, const char **result, int *actual) {
261 return TSHttpSsnClientProtocolStackGet(client_ssnp, n, result, actual);
262 },
263 [&client_ssnp]() { return get_client_tls_description(client_ssnp); },
264 [this](std::string_view http_version) { this->http_version_in_client_stack = http_version; });
265 }
266
267 std::string
get_server_protocol_description(TSHttpTxn server_txnp)268 SessionData::get_server_protocol_description(TSHttpTxn server_txnp)
269 {
270 return get_protocol_stack_helper(
271 [&server_txnp](int n, const char **result, int *actual) {
272 return TSHttpTxnServerProtocolStackGet(server_txnp, n, result, actual);
273 },
274 [&server_txnp]() { return get_server_tls_description(server_txnp); }, [](std::string_view http_version) {});
275 }
276
SessionData()277 SessionData::SessionData()
278 {
279 aio_cont = TSContCreate(session_aio_handler, TSMutexCreate());
280 txn_cont = TSContCreate(TransactionData::global_transaction_handler, nullptr);
281 }
282
~SessionData()283 SessionData::~SessionData()
284 {
285 if (aio_cont) {
286 TSContDestroy(aio_cont);
287 }
288 if (txn_cont) {
289 TSContDestroy(txn_cont);
290 }
291 }
292
293 /*
294 * Note this assumes that the caller holds the disk_io_mutex lock. This is a
295 * private member function. The two publicly accessible functions hold the
296 * lock before calling this.
297 */
298 int
write_to_disk_no_lock(std::string_view content)299 SessionData::write_to_disk_no_lock(std::string_view content)
300 {
301 char *pBuf = nullptr;
302 // Allocate a buffer for aio writing
303 if ((pBuf = static_cast<char *>(TSmalloc(sizeof(char) * content.size())))) {
304 memcpy(pBuf, content.data(), content.size());
305 if (TS_SUCCESS == TSAIOWrite(log_fd, write_offset, pBuf, content.size(), aio_cont)) {
306 // Update offset within file and aio events count
307 write_offset += content.size();
308 aio_count += 1;
309
310 return TS_SUCCESS;
311 }
312 TSfree(pBuf);
313 }
314 return TS_ERROR;
315 }
316
317 int
write_to_disk(std::string_view content)318 SessionData::write_to_disk(std::string_view content)
319 {
320 const std::lock_guard<std::recursive_mutex> _(disk_io_mutex);
321 const int result = write_to_disk_no_lock(content);
322 return result;
323 }
324
325 int
write_transaction_to_disk(std::string_view content)326 SessionData::write_transaction_to_disk(std::string_view content)
327 {
328 const std::lock_guard<std::recursive_mutex> _(disk_io_mutex);
329
330 int result = TS_SUCCESS;
331 if (has_written_first_transaction) {
332 // Prepend a comma.
333 std::string with_comma;
334 with_comma.reserve(content.size() + 1);
335 with_comma.insert(0, ",");
336 with_comma.insert(1, content);
337 result = write_to_disk_no_lock(with_comma);
338 } else {
339 result = write_to_disk_no_lock(content);
340 has_written_first_transaction = true;
341 }
342 return result;
343 }
344
345 std::string
get_http_version_in_client_stack() const346 SessionData::get_http_version_in_client_stack() const
347 {
348 return http_version_in_client_stack;
349 }
350
351 int
session_aio_handler(TSCont contp,TSEvent event,void * edata)352 SessionData::session_aio_handler(TSCont contp, TSEvent event, void *edata)
353 {
354 switch (event) {
355 case TS_EVENT_AIO_DONE: {
356 TSAIOCallback cb = static_cast<TSAIOCallback>(edata);
357 SessionData *ssnData = static_cast<SessionData *>(TSContDataGet(contp));
358 if (!ssnData) {
359 TSDebug(debug_tag, "session_aio_handler(): No valid ssnData. Abort.");
360 return TS_ERROR;
361 }
362 char *buf = TSAIOBufGet(cb);
363 const std::lock_guard<std::recursive_mutex> _(ssnData->disk_io_mutex);
364
365 // Free the allocated buffer and update aio_count
366 if (buf) {
367 TSfree(buf);
368 if (--ssnData->aio_count == 0 && ssnData->ssn_closed) {
369 // check for ssn close, if closed, do clean up
370 TSContDataSet(contp, nullptr);
371 close(ssnData->log_fd);
372 std::error_code ec;
373 ts::file::file_status st = ts::file::status(ssnData->log_name, ec);
374 if (!ec) {
375 disk_usage += ts::file::file_size(st);
376 TSDebug(debug_tag, "Finish a session with log file of %" PRIuMAX "bytes", ts::file::file_size(st));
377 }
378 delete ssnData;
379 return TS_SUCCESS;
380 }
381 }
382 return TS_SUCCESS;
383 }
384 default:
385 TSDebug(debug_tag, "session_aio_handler(): unhandled events %d", event);
386 return TS_ERROR;
387 }
388 return TS_SUCCESS;
389 }
390
391 int
global_session_handler(TSCont contp,TSEvent event,void * edata)392 SessionData::global_session_handler(TSCont contp, TSEvent event, void *edata)
393 {
394 TSHttpSsn ssnp = static_cast<TSHttpSsn>(edata);
395
396 switch (event) {
397 case TS_EVENT_HTTP_SSN_START: {
398 // Grab session id for logging against a global value rather than the local
399 // session_counter.
400 int64_t id = TSHttpSsnIdGet(ssnp);
401
402 // If the user has asked for SNI filtering, filter on that first because
403 // any sampling will apply just to that subset of connections that match
404 // that SNI.
405 if (!sni_filter.empty()) {
406 TSVConn ssn_vc = TSHttpSsnClientVConnGet(ssnp);
407 TSSslConnection ssl_conn = TSVConnSslConnectionGet(ssn_vc);
408 SSL *ssl_obj = reinterpret_cast<SSL *>(ssl_conn);
409 if (ssl_obj == nullptr) {
410 TSDebug(debug_tag, "global_session_handler(): Ignore non-HTTPS session %" PRId64 "...", id);
411 break;
412 }
413 char const *sni_ptr = SSL_get_servername(ssl_obj, TLSEXT_NAMETYPE_host_name);
414 if (sni_ptr == nullptr) {
415 TSDebug(debug_tag, "global_session_handler(): Ignore HTTPS session with non-existent SNI.");
416 break;
417 } else {
418 const std::string_view sni{sni_ptr};
419 if (sni != sni_filter) {
420 TSDebug(debug_tag, "global_session_handler(): Ignore HTTPS session with non-filtered SNI: %s", sni_ptr);
421 break;
422 }
423 }
424 }
425 const auto this_session_count = session_counter++;
426 if (this_session_count % sample_pool_size != 0) {
427 TSDebug(debug_tag, "global_session_handler(): Ignore session %" PRId64 "...", id);
428 break;
429 } else if (disk_usage >= max_disk_usage) {
430 TSDebug(debug_tag, "global_session_handler(): Ignore session %" PRId64 "due to disk usage %" PRId64 "bytes", id,
431 disk_usage.load());
432 break;
433 }
434 // Beginning of a new session
435 /// Get epoch time
436 auto start = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch());
437
438 // Create new per session data
439 SessionData *ssnData = new SessionData;
440 TSUserArgSet(ssnp, session_arg_index, ssnData);
441
442 TSContDataSet(ssnData->aio_cont, ssnData);
443
444 // "protocol":
445 // This is the protocol stack for the client side of the session.
446 std::string protocol_description = ssnData->get_client_protocol_description(ssnp);
447 std::string beginning = R"({"meta":{"version":"1.0"},"sessions":[{)" + protocol_description + R"(,"connection-time":)" +
448 std::to_string(start.count()) + R"(,"transactions":[)";
449
450 // Use the session count's hex string as the filename.
451 std::stringstream stream;
452 stream << std::setw(16) << std::setfill('0') << std::hex << this_session_count;
453 std::string session_hex_name = stream.str();
454
455 // Use client ip as sub directory name
456 char client_str[INET6_ADDRSTRLEN];
457 sockaddr const *client_ip = TSHttpSsnClientAddrGet(ssnp);
458 if (AF_INET == client_ip->sa_family) {
459 inet_ntop(AF_INET, &(reinterpret_cast<sockaddr_in const *>(client_ip)->sin_addr), client_str, INET_ADDRSTRLEN);
460 } else if (AF_INET6 == client_ip->sa_family) {
461 inet_ntop(AF_INET6, &(reinterpret_cast<sockaddr_in6 const *>(client_ip)->sin6_addr), client_str, INET6_ADDRSTRLEN);
462 } else {
463 TSDebug(debug_tag, "global_session_handler(): Unknown address family.");
464 snprintf(client_str, INET6_ADDRSTRLEN, "unknown");
465 }
466
467 // Initialize AIO file
468 const std::lock_guard<std::recursive_mutex> _(ssnData->disk_io_mutex);
469 if (ssnData->log_fd < 0) {
470 ts::file::path log_p = log_directory / ts::file::path(std::string(client_str, 3));
471 ts::file::path log_f = log_p / ts::file::path(session_hex_name);
472
473 // Create subdir if not existing
474 std::error_code ec;
475 ts::file::status(log_p, ec);
476 if (ec && mkdir(log_p.c_str(), 0755) == -1) {
477 TSDebug(debug_tag, "global_session_handler(): Failed to create dir %s", log_p.c_str());
478 TSError("[%s] Failed to create dir %s", debug_tag, log_p.c_str());
479 }
480
481 // Try to open log files for AIO
482 ssnData->log_fd = open(log_f.c_str(), O_RDWR | O_CREAT, S_IRWXU);
483 if (ssnData->log_fd < 0) {
484 TSDebug(debug_tag, "global_session_handler(): Failed to open log files %s. Abort.", log_f.c_str());
485 TSHttpSsnReenable(ssnp, TS_EVENT_HTTP_CONTINUE);
486 return TS_EVENT_HTTP_CONTINUE;
487 }
488 ssnData->log_name = log_f;
489 // Write log file beginning to disk
490 ssnData->write_to_disk(beginning);
491 }
492
493 TSHttpSsnHookAdd(ssnp, TS_HTTP_TXN_START_HOOK, ssnData->txn_cont);
494 TSHttpSsnHookAdd(ssnp, TS_HTTP_TXN_CLOSE_HOOK, ssnData->txn_cont);
495 break;
496 }
497 case TS_EVENT_HTTP_SSN_CLOSE: {
498 // Write session and close the log file.
499 int64_t id = TSHttpSsnIdGet(ssnp);
500 TSDebug(debug_tag, "global_session_handler(): Closing session %" PRId64 "...", id);
501 // Retrieve SessionData
502 SessionData *ssnData = static_cast<SessionData *>(TSUserArgGet(ssnp, session_arg_index));
503 // If no valid ssnData, continue transaction as if nothing happened
504 if (!ssnData) {
505 TSDebug(debug_tag, "global_session_handler(): [TS_EVENT_HTTP_SSN_CLOSE] No ssnData found. Abort.");
506 TSHttpSsnReenable(ssnp, TS_EVENT_HTTP_CONTINUE);
507 return TS_SUCCESS;
508 }
509 ssnData->write_to_disk(json_closing);
510 {
511 const std::lock_guard<std::recursive_mutex> _(ssnData->disk_io_mutex);
512 ssnData->ssn_closed = true;
513 }
514
515 break;
516 }
517 default:
518 break;
519 }
520 TSHttpSsnReenable(ssnp, TS_EVENT_HTTP_CONTINUE);
521 return TS_SUCCESS;
522 }
523
524 } // namespace traffic_dump
525