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