blob: 8a9d98b484776d498a7e274feab9fdaa16bd3260 [file] [log] [blame]
/** @file
A brief file description
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* tcp_info.cc: logs the tcp_info data struture to a file
*/
#include <stdio.h>
#include <stdlib.h>
#include <ts/ts.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <string.h>
#include <sys/time.h>
#include <arpa/inet.h>
struct Config {
int sample;
const char* log_file;
int log_fd;
int log_level;
int hook;
};
static Config config;
static void
load_config() {
char config_file[PATH_MAX];
config.sample = 1000;
config.log_level = 1;
config.log_file = NULL;
config.hook = 1;
// get the install directory
const char* install_dir = TSInstallDirGet();
// figure out the config file and open it
snprintf(config_file, sizeof(config_file), "%s/%s/%s", install_dir, "etc", "tcp_info.config");
FILE *file = fopen(config_file, "r");
if (file == NULL) {
snprintf(config_file, sizeof(config_file), "%s/%s/%s", install_dir, "conf", "tcp_info.config");
file = fopen(config_file, "r");
}
TSDebug("tcp_info", "config file name: %s", config_file);
assert(file != NULL);
// read and parse the lines
char line[256];
while (fgets(line, sizeof(line), file) != NULL) {
char *pos = strchr(line, '=');
*pos = '\0';
char *value = pos + 1;
// remove the new line
pos = strchr(value, '\n');
if (pos != NULL) {
*pos = '\0';
}
if (value != NULL) {
TSDebug("tcp_info", "config key: %s", line);
TSDebug("tcp_info", "config value: %s", value);
if (strcmp(line, "sample") == 0) {
config.sample = atoi(value);
} else if (strcmp(line, "log_file") == 0) {
config.log_file = strdup(value);
} else if (strcmp(line, "log_level") == 0) {
config.log_level = atoi(value);
} else if (strcmp(line, "hook") == 0) {
config.hook = atoi(value);
}
}
}
TSDebug("tcp_info", "sample: %d", config.sample);
TSDebug("tcp_info", "log filename: %s", config.log_file);
TSDebug("tcp_info", "log_level: %d", config.log_level);
TSDebug("tcp_info", "hook: %d", config.hook);
config.log_fd = open(config.log_file, O_APPEND | O_CREAT | O_RDWR, 0666);
assert(config.log_fd > 0);
}
static void
log_tcp_info(const char* event_name, const char* client_ip, const char* server_ip, struct tcp_info &info) {
char buffer[256];
// get the current time
struct timeval now;
gettimeofday(&now, NULL);
int bytes = 0;
if (config.log_level == 2) {
#if !defined(freebsd) || defined(__GLIBC__)
bytes = snprintf(buffer, sizeof(buffer), "%s %u %u %s %s %u %u %u %u %u %u %u %u %u %u %u %u\n",
event_name,
(uint32_t)now.tv_sec,
(uint32_t)now.tv_usec,
client_ip,
server_ip,
info.tcpi_last_data_sent,
info.tcpi_last_data_recv,
info.tcpi_snd_cwnd,
info.tcpi_snd_ssthresh,
info.tcpi_rcv_ssthresh,
info.tcpi_rtt,
info.tcpi_rttvar,
info.tcpi_unacked,
info.tcpi_sacked,
info.tcpi_lost,
info.tcpi_retrans,
info.tcpi_fackets
);
#else
bytes = snprintf(buffer, sizeof(buffer), "%s %u %u %s %s %u %u %u %u %u %u %u %u %u %u %u %u\n",
event_name,
(uint32_t)now.tv_sec,
(uint32_t)now.tv_usec,
client_ip,
server_ip,
info.__tcpi_last_data_sent,
info.tcpi_last_data_recv,
info.tcpi_snd_cwnd,
info.tcpi_snd_ssthresh,
info.__tcpi_rcv_ssthresh,
info.tcpi_rtt,
info.tcpi_rttvar,
info.__tcpi_unacked,
info.__tcpi_sacked,
info.__tcpi_lost,
info.__tcpi_retrans,
info.__tcpi_fackets
);
#endif
} else {
bytes = snprintf(buffer, sizeof(buffer), "%s %u %s %s %u\n",
event_name,
(uint32_t)now.tv_sec,
client_ip,
server_ip,
info.tcpi_rtt
);
}
ssize_t wrote = write(config.log_fd, buffer, bytes);
assert(wrote == bytes);
TSDebug("tcp_info", "wrote: %d bytes to file: %s", bytes, config.log_file);
TSDebug("tcp_info", "logging: %s", buffer);
}
static int
tcp_info_hook(TSCont /* contp ATS_UNUSED */, TSEvent event, void *edata)
{
TSHttpSsn ssnp = NULL;
TSHttpTxn txnp = NULL;
const char *event_name;
switch (event) {
case TS_EVENT_HTTP_SSN_START:
ssnp = (TSHttpSsn)edata;
event_name = "ssn_start";
break;
case TS_EVENT_HTTP_TXN_START:
txnp = (TSHttpTxn)edata;
ssnp = TSHttpTxnSsnGet(txnp);
event_name = "txn_start";
break;
case TS_EVENT_HTTP_SEND_RESPONSE_HDR:
txnp = (TSHttpTxn)edata;
ssnp = TSHttpTxnSsnGet(txnp);
event_name = "send_resp_hdr";
break;
case TS_EVENT_HTTP_SSN_CLOSE:
ssnp = (TSHttpSsn)edata;
event_name = "ssn_close";
default:
return 0;
}
TSDebug("tcp_info", "tcp_info_hook called, event: %s", event_name);
struct tcp_info tcp_info;
int tcp_info_len = sizeof(tcp_info);
int fd;
if (TSHttpSsnClientFdGet(ssnp, &fd) != TS_SUCCESS) {
TSDebug("tcp_info", "error getting the client socket fd");
goto done;
}
// get the tcp info structure
if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, (void *)&tcp_info, (socklen_t *)&tcp_info_len) == 0) {
// the structure is the correct size
if (tcp_info_len == sizeof(tcp_info)) {
// no need to run rand if we are always going log (100%)
int random = 0;
if (config.sample < 1000) {
random = rand() % 1000;
TSDebug("tcp_info", "random: %d, config.sample: %d", random, config.sample);
}
if (random < config.sample) {
TSDebug("tcp_info", "got the tcp_info struture and now logging");
// get the client address
const struct sockaddr *client_addr = TSHttpSsnClientAddrGet(ssnp);
const struct sockaddr *server_addr = TSHttpSsnIncomingAddrGet(ssnp);
if (client_addr == NULL || server_addr == NULL)
goto done;
struct sockaddr_in *client_in_addr = (struct sockaddr_in *)client_addr;
struct sockaddr_in *server_in_addr = (struct sockaddr_in *)server_addr;
char client_str[INET_ADDRSTRLEN];
char server_str[INET_ADDRSTRLEN];
// convert ip to string
inet_ntop(client_addr->sa_family, &(client_in_addr->sin_addr), client_str, INET_ADDRSTRLEN);
inet_ntop(server_addr->sa_family, &(server_in_addr->sin_addr), server_str, INET_ADDRSTRLEN);
log_tcp_info(event_name, client_str, server_str, tcp_info);
}
} else {
TSDebug("tcp_info", "tcp_info length is the wrong size");
}
} else {
TSDebug("tcp_info", "error calling getsockopt()");
}
done:
if (txnp != NULL) {
TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
} else if (ssnp != NULL) {
TSHttpSsnReenable(ssnp, TS_EVENT_HTTP_CONTINUE);
}
return 0;
}
void
TSPluginInit(int, const char *[]) // int argc, const char *argv[]
{
TSPluginRegistrationInfo info;
info.plugin_name = (char*)"tcp_info";
info.vendor_name = (char*)"Apache Software Foundation";
info.support_email = (char*)"dev@trafficserver.apache.org";
if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS)
TSError("Plugin registration failed. \n");
// load the configuration file
load_config();
// add a hook to the state machine
// TODO: need another hook before the socket is closed, keeping it in for now because it will be easier to change if or when another hook is added to ATS
if ((config.hook & 1) != 0) {
TSHttpHookAdd(TS_HTTP_SSN_START_HOOK, TSContCreate(tcp_info_hook, NULL));
TSDebug("tcp_info", "added hook to the start of the TCP connection");
}
if ((config.hook & 2) != 0) {
TSHttpHookAdd(TS_HTTP_TXN_START_HOOK, TSContCreate(tcp_info_hook, NULL));
TSDebug("tcp_info", "added hook to the close of the transaction");
}
if ((config.hook & 4) != 0) {
TSHttpHookAdd(TS_HTTP_SEND_RESPONSE_HDR_HOOK, TSContCreate(tcp_info_hook, NULL));
TSDebug("tcp_info", "added hook to the sending of the headers");
}
if ((config.hook & 8) != 0) {
TSHttpHookAdd(TS_HTTP_SSN_CLOSE_HOOK, TSContCreate(tcp_info_hook, NULL));
TSDebug("tcp_info", "added hook to the close of the TCP connection");
}
TSDebug("tcp_info", "tcp info module registered");
}