Skip to content

Commit f2ae2e8

Browse files
author
bneradt
committed
traffic_dump: print request body data and client address filtering
This adds the -b option to traffic dump to dump client request body data. It also adds the -4 and -6 options so that the client can filter what is dumped based upon a client IP address.
1 parent a6a4d3b commit f2ae2e8

17 files changed

Lines changed: 672 additions & 71 deletions

include/tscore/ink_inet.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,7 @@ struct IpAddr {
11791179

11801180
/// Construct from @c sockaddr.
11811181
explicit IpAddr(sockaddr const *addr) { this->assign(addr); }
1182+
explicit IpAddr(sockaddr const &addr) { this->assign(&addr); }
11821183
/// Construct from @c sockaddr_in6.
11831184
explicit IpAddr(sockaddr_in6 const &addr) { this->assign(ats_ip_sa_cast(&addr)); }
11841185
/// Construct from @c sockaddr_in6.

plugins/experimental/traffic_dump/json_utils.cc

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -127,31 +127,6 @@ esc_json_out(const char *buf, int64_t len, std::ostream &jsonfile)
127127

128128
return len;
129129
}
130-
/** Escape characters in a string as needed and return the resultant escaped string.
131-
*
132-
* @param[in] s The characters that need to be escaped.
133-
*/
134-
std::string
135-
escape_json(std::string_view s)
136-
{
137-
std::ostringstream o;
138-
esc_json_out(s.data(), s.length(), o);
139-
return o.str();
140-
}
141-
142-
/** An escape_json overload for a char buffer.
143-
*
144-
* @param[in] buf The char buffer pointer with characters that need to be escaped.
145-
*
146-
* @param[in] size The size of the buf char array.
147-
*/
148-
std::string
149-
escape_json(char const *buf, int64_t size)
150-
{
151-
std::ostringstream o;
152-
esc_json_out(buf, size, o);
153-
return o.str();
154-
}
155130

156131
} // anonymous namespace
157132

@@ -175,4 +150,20 @@ json_entry_array(std::string_view name, std::string_view value)
175150
return "[\"" + escape_json(name) + "\",\"" + escape_json(value) + "\"]";
176151
}
177152

153+
std::string
154+
escape_json(std::string_view s)
155+
{
156+
std::ostringstream o;
157+
esc_json_out(s.data(), s.length(), o);
158+
return o.str();
159+
}
160+
161+
std::string
162+
escape_json(char const *buf, int64_t size)
163+
{
164+
std::ostringstream o;
165+
esc_json_out(buf, size, o);
166+
return o.str();
167+
}
168+
178169
} // namespace traffic_dump

plugins/experimental/traffic_dump/json_utils.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,18 @@ std::string json_entry(std::string_view name, char const *value, int64_t size);
5555
*/
5656
std::string json_entry_array(std::string_view name, std::string_view value);
5757

58+
/** Escape characters in a string as needed and return the resultant escaped string.
59+
*
60+
* @param[in] s The characters that need to be escaped.
61+
*/
62+
std::string escape_json(std::string_view s);
63+
64+
/** An escape_json overload for a char buffer.
65+
*
66+
* @param[in] buf The char buffer pointer with characters that need to be escaped.
67+
*
68+
* @param[in] size The size of the buf char array.
69+
*/
70+
std::string escape_json(char const *buf, int64_t size);
71+
5872
} // namespace traffic_dump

plugins/experimental/traffic_dump/session_data.cc

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ std::atomic<int64_t> SessionData::disk_usage = 0;
191191
ts::file::path SessionData::log_directory{default_log_directory};
192192
uint64_t SessionData::session_counter = 0;
193193
std::string SessionData::sni_filter;
194+
std::optional<IpAddr> SessionData::client_ip_filter = std::nullopt;
194195

195196
int
196197
SessionData::get_session_arg_index()
@@ -217,12 +218,25 @@ SessionData::set_max_disk_usage(int64_t new_max_disk_usage)
217218
}
218219

219220
bool
220-
SessionData::init(std::string_view log_directory, int64_t max_disk_usage, int64_t sample_size)
221+
SessionData::init(std::string_view log_directory, int64_t max_disk_usage, int64_t sample_size, std::string_view ip_filter)
221222
{
222223
SessionData::log_directory = log_directory;
223224
SessionData::max_disk_usage = max_disk_usage;
224225
SessionData::sample_pool_size = sample_size;
225226

227+
if (!ip_filter.empty()) {
228+
client_ip_filter.emplace();
229+
if (client_ip_filter->load(ip_filter)) {
230+
TSDebug(debug_tag, "Problems parsing IP filter address argument: %.*s", static_cast<int>(ip_filter.size()), ip_filter.data());
231+
TSError("[%s] Problems parsing IP filter address argument: %.*s", debug_tag, static_cast<int>(ip_filter.size()),
232+
ip_filter.data());
233+
client_ip_filter = std::nullopt;
234+
return false;
235+
} else {
236+
TSDebug(debug_tag, "Filtering to only dump connections with ip: %.*s", static_cast<int>(ip_filter.size()), ip_filter.data());
237+
}
238+
}
239+
226240
if (TS_SUCCESS != TSUserArgIndexReserve(TS_USER_ARGS_SSN, debug_tag, "Track log related data", &session_arg_index)) {
227241
TSError("[%s] Unable to initialize plugin (disabled). Failed to reserve ssn arg.", traffic_dump::debug_tag);
228242
return false;
@@ -239,9 +253,10 @@ SessionData::init(std::string_view log_directory, int64_t max_disk_usage, int64_
239253
}
240254

241255
bool
242-
SessionData::init(std::string_view log_directory, int64_t max_disk_usage, int64_t sample_size, std::string_view sni_filter)
256+
SessionData::init(std::string_view log_directory, int64_t max_disk_usage, int64_t sample_size, std::string_view ip_filter,
257+
std::string_view sni_filter)
243258
{
244-
if (!init(log_directory, max_disk_usage, sample_size)) {
259+
if (!init(log_directory, max_disk_usage, sample_size, ip_filter)) {
245260
return false;
246261
}
247262
SessionData::sni_filter = sni_filter;
@@ -337,6 +352,25 @@ SessionData::write_transaction_to_disk(std::string_view content)
337352
return result;
338353
}
339354

355+
bool
356+
SessionData::is_filtered_out(const sockaddr *session_client_ip)
357+
{
358+
if (!client_ip_filter) {
359+
// The user did not configure an IP by which to filter.
360+
return false;
361+
}
362+
if (session_client_ip == nullptr) {
363+
TSDebug(debug_tag, "Found no client IP address for session. Abort.");
364+
return true;
365+
}
366+
if (session_client_ip->sa_family != AF_INET && session_client_ip->sa_family != AF_INET6) {
367+
TSDebug(debug_tag, "IP family is not v4 nor v6. Abort.");
368+
return true;
369+
}
370+
371+
IpAddr session_address(*session_client_ip);
372+
return session_address != *client_ip_filter;
373+
}
340374
int
341375
SessionData::session_aio_handler(TSCont contp, TSEvent event, void *edata)
342376
{
@@ -413,12 +447,17 @@ SessionData::global_session_handler(TSCont contp, TSEvent event, void *edata)
413447
}
414448
const auto this_session_count = session_counter++;
415449
if (this_session_count % sample_pool_size != 0) {
416-
TSDebug(debug_tag, "global_session_handler(): Ignore session %" PRId64 "...", id);
450+
TSDebug(debug_tag, "Ignore session %" PRId64 " per the random sampling mechanism", id);
417451
break;
418452
} else if (disk_usage >= max_disk_usage) {
419-
TSDebug(debug_tag, "global_session_handler(): Ignore session %" PRId64 "due to disk usage %" PRId64 "bytes", id,
420-
disk_usage.load());
453+
TSDebug(debug_tag, "Ignore session %" PRId64 "due to disk usage %" PRId64 "bytes", id, disk_usage.load());
421454
break;
455+
} else {
456+
const sockaddr *client_ip = TSHttpSsnClientAddrGet(ssnp);
457+
if (SessionData::is_filtered_out(client_ip)) {
458+
TSDebug(debug_tag, "Ignore session %" PRId64 " per the client's IP filter", id);
459+
break;
460+
}
422461
}
423462
// Beginning of a new session
424463
/// Get epoch time

plugins/experimental/traffic_dump/session_data.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <string_view>
2525

2626
#include "ts/ts.h"
27+
#include "tscore/ink_inet.h"
2728
#include "tscore/ts_file.h"
2829

2930
namespace traffic_dump
@@ -95,6 +96,9 @@ class SessionData
9596
/// The running counter of all sessions dumped by traffic_dump.
9697
static uint64_t session_counter;
9798

99+
/// Only addresses with this IP will be dumped (if set).
100+
static std::optional<IpAddr> client_ip_filter;
101+
98102
public:
99103
SessionData();
100104
~SessionData();
@@ -106,8 +110,9 @@ class SessionData
106110
*
107111
* @return True if initialization is successful, false otherwise.
108112
*/
109-
static bool init(std::string_view log_directory, int64_t max_disk_usage, int64_t sample_size);
110-
static bool init(std::string_view log_directory, int64_t max_disk_usage, int64_t sample_size, std::string_view sni_filter);
113+
static bool init(std::string_view log_directory, int64_t max_disk_usage, int64_t sample_size, std::string_view ip_filter);
114+
static bool init(std::string_view log_directory, int64_t max_disk_usage, int64_t sample_size, std::string_view ip_filter,
115+
std::string_view sni_filter);
111116

112117
/** Set the sample_pool_size to a new value.
113118
*
@@ -168,6 +173,22 @@ class SessionData
168173
*/
169174
int write_to_disk_no_lock(std::string_view content);
170175

176+
/** Determine whether the user configured IP filter indicates this transaction
177+
* should not be dumped.
178+
*
179+
* @note This also does some validity verification on the IP, making sure it is
180+
* v4 or v6, and returns true (i.e., it should be filtered out) if it does not
181+
* match these checks. These checks are required in order to perform the IP
182+
* filtering logic. This function will only return true if the client has
183+
* enabled client filtering.
184+
*
185+
* @param[in] session_client_ip The session's client address.
186+
*
187+
* @return True if the provided address should not be dumped per the user's
188+
* configuration, false otherwise.
189+
*/
190+
static bool is_filtered_out(const sockaddr *session_client_ip);
191+
171192
/** Get the JSON string that describes the client session stack.
172193
*
173194
* @param[in] ssnp The reference to the client session.

plugins/experimental/traffic_dump/traffic_dump.cc

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,22 +76,33 @@ TSPluginInit(int argc, char const *argv[])
7676
return;
7777
}
7878

79+
bool dump_body = false;
7980
bool sensitive_fields_were_specified = false;
8081
traffic_dump::sensitive_fields_t user_specified_fields;
8182
ts::file::path log_dir{traffic_dump::SessionData::default_log_directory};
8283
int64_t sample_pool_size = traffic_dump::SessionData::default_sample_pool_size;
8384
int64_t max_disk_usage = traffic_dump::SessionData::default_max_disk_usage;
8485
std::string sni_filter;
86+
std::string client_ip_filter;
8587

8688
/// Commandline options
87-
static const struct option longopts[] = {
88-
{"logdir", required_argument, nullptr, 'l'}, {"sample", required_argument, nullptr, 's'},
89-
{"limit", required_argument, nullptr, 'm'}, {"sensitive-fields", required_argument, nullptr, 'f'},
90-
{"sni-filter", required_argument, nullptr, 'n'}, {nullptr, no_argument, nullptr, 0}};
91-
int opt = 0;
89+
static const struct option longopts[] = {{"dump_body", no_argument, nullptr, 'b'},
90+
{"logdir", required_argument, nullptr, 'l'},
91+
{"sample", required_argument, nullptr, 's'},
92+
{"limit", required_argument, nullptr, 'm'},
93+
{"sensitive-fields", required_argument, nullptr, 'f'},
94+
{"sni-filter", required_argument, nullptr, 'n'},
95+
{"client_ipv4", required_argument, nullptr, '4'},
96+
{"client_ipv6", required_argument, nullptr, '6'},
97+
{nullptr, no_argument, nullptr, 0}};
98+
int opt = 0;
9299
while (opt >= 0) {
93-
opt = getopt_long(argc, const_cast<char *const *>(argv), "l:", longopts, nullptr);
100+
opt = getopt_long(argc, const_cast<char *const *>(argv), "bf:l:s:m:n:4:6", longopts, nullptr);
94101
switch (opt) {
102+
case 'b': {
103+
dump_body = true;
104+
break;
105+
}
95106
case 'f': {
96107
// --sensitive-fields takes a comma-separated list of HTTP fields that
97108
// are sensitive. The field values for these fields will be replaced
@@ -128,6 +139,12 @@ TSPluginInit(int argc, char const *argv[])
128139
}
129140
case 'm': {
130141
max_disk_usage = static_cast<int64_t>(std::strtol(optarg, nullptr, 0));
142+
break;
143+
}
144+
case '4':
145+
case '6': {
146+
client_ip_filter = std::string(optarg);
147+
break;
131148
}
132149
case -1:
133150
case '?':
@@ -143,26 +160,26 @@ TSPluginInit(int argc, char const *argv[])
143160
log_dir = ts::file::path(TSInstallDirGet()) / log_dir;
144161
}
145162
if (sni_filter.empty()) {
146-
if (!traffic_dump::SessionData::init(log_dir.view(), max_disk_usage, sample_pool_size)) {
163+
if (!traffic_dump::SessionData::init(log_dir.view(), max_disk_usage, sample_pool_size, client_ip_filter)) {
147164
TSError("[%s] Failed to initialize session state.", traffic_dump::debug_tag);
148165
return;
149166
}
150167
} else {
151-
if (!traffic_dump::SessionData::init(log_dir.view(), max_disk_usage, sample_pool_size, sni_filter)) {
168+
if (!traffic_dump::SessionData::init(log_dir.view(), max_disk_usage, sample_pool_size, client_ip_filter, sni_filter)) {
152169
TSError("[%s] Failed to initialize session state with an SNI filter.", traffic_dump::debug_tag);
153170
return;
154171
}
155172
}
156173

157174
if (sensitive_fields_were_specified) {
158-
if (!traffic_dump::TransactionData::init(std::move(user_specified_fields))) {
175+
if (!traffic_dump::TransactionData::init(dump_body, std::move(user_specified_fields))) {
159176
TSError("[%s] Failed to initialize transaction state with user-specified fields.", traffic_dump::debug_tag);
160177
return;
161178
}
162179
} else {
163180
// The user did not provide their own list of sensitive fields. Use the
164181
// default.
165-
if (!traffic_dump::TransactionData::init()) {
182+
if (!traffic_dump::TransactionData::init(dump_body)) {
166183
TSError("[%s] Failed to initialize transaction state.", traffic_dump::debug_tag);
167184
return;
168185
}

0 commit comments

Comments
 (0)