blob: a538344be8883f334117c55aaa6f27398e8bde20 [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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>
#include <ts/ts.h>
#include <string.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <time.h>
#include <inttypes.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef enum {
JSON_OUTPUT,
CSV_OUTPUT
} output_format;
typedef struct {
unsigned int recordTypes;
char *stats_path;
int stats_path_len;
char *allowIps;
int ipCount;
char *allowIps6;
int ip6Count;
} config_t;
typedef struct {
char *config_path;
volatile time_t last_load;
config_t* config;
} config_holder_t;
#define FREE_TMOUT 300000
static int free_handler(TSCont cont, TSEvent event, void *edata);
static int config_handler(TSCont cont, TSEvent event, void *edata);
static config_t* get_config(TSCont cont);
static config_holder_t* new_config_holder(const char* path);
#define STR_BUFFER_SIZE 65536
#define SYSTEM_RECORD_TYPE (0x100)
#define DEFAULT_RECORD_TYPES (SYSTEM_RECORD_TYPE | TS_RECORDTYPE_PROCESS | TS_RECORDTYPE_PLUGIN)
typedef struct stats_state_t {
TSVConn net_vc;
TSVIO read_vio;
TSVIO write_vio;
TSIOBuffer req_buffer;
TSIOBuffer resp_buffer;
TSIOBufferReader resp_reader;
int output_bytes;
int body_written;
int globals_cnt;
char **globals;
char *interfaceName;
char *query;
unsigned int recordTypes;
output_format output;
} stats_state;
int configReloadRequests = 0;
int configReloads = 0;
time_t lastReloadRequest = 0;
time_t lastReload = 0;
time_t astatsLoad = 0;
#define PLUGIN_TAG "astats_over_http"
#define DEFAULT_CONFIG_NAME "astats.config"
#define DEFAULT_IP "127.0.0.1"
#define DEFAULT_IP6 "::1"
static bool is_ip_allowed(const config_t* config, const struct sockaddr* addr);
static char * nstr(const char *s) {
char *mys = (char *)TSmalloc(strlen(s)+1);
strcpy(mys, s);
return mys;
}
static char * nstrl(const char *s, int len) {
char *mys = (char *)TSmalloc(len + 1);
memcpy(mys, s, len);
mys[len] = 0;
return mys;
}
static char ** parseGlobals(char *str, int *globals_cnt) {
char *tok = 0;
char **globals = 0;
char **old = 0;
int globals_size = 0, cnt = 0, i;
while (1) {
tok = strtok_r(str, ";", &str);
if (!tok)
break;
if (cnt >= globals_size) {
old = globals;
globals = (char **) TSmalloc(sizeof(char *) * (globals_size + 20));
if (old) {
memcpy(globals, old, sizeof(char *) * (globals_size));
TSfree(old);
old = NULL;
}
globals_size += 20;
}
globals[cnt] = tok;
cnt++;
}
*globals_cnt = cnt;
for (i = 0; i < cnt; i++)
TSDebug(PLUGIN_TAG, "globals[%d]: '%s'", i, globals[i]);
return globals;
}
static void stats_fillState(stats_state *my_state, char *query, int query_len) {
char* arg = 0;
while (1) {
arg = strtok_r(query, "&", &query);
if (!arg)
break;
if (strstr(arg, "application=")) {
arg = arg + strlen("application=");
my_state->globals = parseGlobals(arg, &my_state->globals_cnt);
} else if (strstr(arg, "inf.name=")) {
my_state->interfaceName = arg + strlen("inf.name=");
} else if(strstr(arg, "record.types=")) {
my_state->recordTypes = strtol(arg + strlen("record.types="), NULL, 16);
}
}
}
static void stats_cleanup(TSCont contp, stats_state *my_state) {
if (my_state->req_buffer) {
TSIOBufferDestroy(my_state->req_buffer);
my_state->req_buffer = NULL;
}
if (my_state->resp_buffer) {
TSIOBufferDestroy(my_state->resp_buffer);
my_state->resp_buffer = NULL;
}
TSVConnClose(my_state->net_vc);
TSfree(my_state);
my_state = NULL;
TSContDestroy(contp);
}
static void
stats_process_accept(TSCont contp, stats_state *my_state) {
my_state->req_buffer = TSIOBufferCreate();
my_state->resp_buffer = TSIOBufferCreate();
my_state->resp_reader = TSIOBufferReaderAlloc(my_state->resp_buffer);
my_state->read_vio = TSVConnRead(my_state->net_vc, contp, my_state->req_buffer, INT64_MAX);
}
static int
stats_add_data_to_resp_buffer(const char *s, stats_state *my_state) {
int s_len = strlen(s);
TSIOBufferWrite(my_state->resp_buffer, s, s_len);
return s_len;
}
static const char RESP_HEADER_JSON[] = "HTTP/1.0 200 Ok\r\nContent-Type: text/json\r\nCache-Control: no-cache\r\n\r\n";
static const char RESP_HEADER_CSV[] = "HTTP/1.0 200 Ok\r\nContent-Type: text/csv\r\nCache-Control: no-cache\r\n\r\n";
static void
stats_process_read(TSCont contp, TSEvent event, stats_state *my_state) {
TSDebug(PLUGIN_TAG, "stats_process_read(%d)", event);
if (event == TS_EVENT_VCONN_READ_READY) {
switch (my_state->output) {
case JSON_OUTPUT:
my_state->output_bytes = stats_add_data_to_resp_buffer(RESP_HEADER_JSON, my_state);
break;
case CSV_OUTPUT:
my_state->output_bytes = stats_add_data_to_resp_buffer(RESP_HEADER_CSV, my_state);
break;
default:
TSError("stats_process_read: Unknown output format\n");
break;
}
TSVConnShutdown(my_state->net_vc, 1, 0);
my_state->write_vio = TSVConnWrite(my_state->net_vc, contp, my_state->resp_reader, INT64_MAX);
}
else if (event == TS_EVENT_ERROR)
TSError("stats_process_read: Received TS_EVENT_ERROR\n");
else if (event == TS_EVENT_VCONN_EOS)
/* client may end the connection, simply return */
return;
else if (event == TS_EVENT_NET_ACCEPT_FAILED)
TSError("stats_process_read: Received TS_EVENT_NET_ACCEPT_FAILED\n");
else {
printf("Unexpected Event %d\n", event);
TSReleaseAssert(!"Unexpected Event");
}
}
#define APPEND(a) my_state->output_bytes += stats_add_data_to_resp_buffer(a, my_state)
#define APPEND_STAT_JSON(a, fmt, v) do { \
char b[3048]; \
int nbytes = snprintf(b, sizeof(b), " \"%s\": " fmt ",\n", a, v); \
if (0 < nbytes && nbytes < (int)sizeof(b)) \
APPEND(b); \
} while(0)
#define APPEND_STAT_CSV(a, fmt, v) do { \
char b[3048]; \
int nbytes = snprintf(b, sizeof(b), "%s," fmt "\n", a, v); \
if (0 < nbytes && nbytes < (int)sizeof(b)) \
APPEND(b); \
} while(0)
static void
json_out_stat(TSRecordType rec_type, void *edata, int registered, const char *name, TSRecordDataType data_type, TSRecordData *datum) {
stats_state *my_state = edata;
int found = 0;
int i;
if (my_state->globals_cnt) {
for (i = 0; i < my_state->globals_cnt; i++) {
if (strstr(name, my_state->globals[i])) {
found = 1;
break;
}
}
if (!found)
return; // skip
}
switch(data_type) {
case TS_RECORDDATATYPE_COUNTER:
APPEND_STAT_JSON(name, "%" PRIu64, datum->rec_counter); break;
case TS_RECORDDATATYPE_INT:
APPEND_STAT_JSON(name, "%" PRIu64, datum->rec_int); break;
case TS_RECORDDATATYPE_FLOAT:
APPEND_STAT_JSON(name, "%f", datum->rec_float); break;
case TS_RECORDDATATYPE_STRING:
APPEND_STAT_JSON(name, "\"%s\"", datum->rec_string); break;
default:
TSDebug(PLUGIN_TAG, "unkown type for %s: %d", name, data_type);
break;
}
}
static void
csv_out_stat(TSRecordType rec_type, void *edata, int registered, const char *name, TSRecordDataType data_type, TSRecordData *datum) {
stats_state *my_state = edata;
int found = 0;
int i;
if (my_state->globals_cnt) {
for (i = 0; i < my_state->globals_cnt; i++) {
if (strstr(name, my_state->globals[i])) {
found = 1;
break;
}
}
if (!found)
return; // skip
}
switch(data_type) {
case TS_RECORDDATATYPE_COUNTER:
APPEND_STAT_CSV(name, "%" PRIu64, datum->rec_counter); break;
case TS_RECORDDATATYPE_INT:
APPEND_STAT_CSV(name, "%" PRIu64, datum->rec_int); break;
case TS_RECORDDATATYPE_FLOAT:
APPEND_STAT_CSV(name, "%f", datum->rec_float); break;
case TS_RECORDDATATYPE_STRING:
APPEND_STAT_CSV(name, "%s", datum->rec_string); break;
default:
TSDebug(PLUGIN_TAG, "unkown type for %s: %d", name, data_type);
break;
}
}
static char * getFile(char *filename, char *buffer, int bufferSize) {
TSFile f= 0;
size_t s = 0;
f = TSfopen(filename, "r");
if (!f)
{
buffer[0] = 0;
return buffer;
}
s = TSfread(f, buffer, bufferSize);
if (s > 0)
buffer[s] = 0;
else
buffer[0] = 0;
TSfclose(f);
return buffer;
}
static int getSpeed(char *inf, char *buffer, int bufferSize) {
char* str;
char b[256];
int speed = 0;
snprintf(b, sizeof(b), "/sys/class/net/%s/operstate", inf);
str = getFile(b, buffer, bufferSize);
if (str && strstr(str, "up"))
{
snprintf(b, sizeof(b), "/sys/class/net/%s/speed", inf);
str = getFile(b, buffer, bufferSize);
speed = strtol(str, 0, 10);
}
return speed;
}
static void appendSystemStateJson(stats_state *my_state) {
char *interface = my_state->interfaceName;
char buffer[16384];
char *str;
char *end;
int speed = 0;
APPEND_STAT_JSON("inf.name", "\"%s\"", interface);
speed = getSpeed(interface, buffer, sizeof(buffer));
APPEND_STAT_JSON("inf.speed", "%d", speed);
str = getFile("/proc/net/dev", buffer, sizeof(buffer));
if (str && interface) {
str = strstr(str, interface);
if (str) {
end = strstr(str, "\n");
if (end)
*end = 0;
APPEND_STAT_JSON("proc.net.dev", "\"%s\"", str);
}
}
str = getFile("/proc/loadavg", buffer, sizeof(buffer));
if (str) {
end = strstr(str, "\n");
if (end)
*end = 0;
APPEND_STAT_JSON("proc.loadavg", "\"%s\"", str);
}
}
static void appendSystemStateCsv(stats_state *my_state) {
char *interface = my_state->interfaceName;
char buffer[16384];
char *str;
char *end;
int speed = 0;
APPEND_STAT_CSV("inf.name", "%s", interface);
speed = getSpeed(interface, buffer, sizeof(buffer));
APPEND_STAT_CSV("inf.speed", "%d", speed);
str = getFile("/proc/net/dev", buffer, sizeof(buffer));
if (str && interface) {
str = strstr(str, interface);
if (str) {
end = strstr(str, "\n");
if (end)
*end = 0;
APPEND_STAT_CSV("proc.net.dev", "%s", str);
}
}
str = getFile("/proc/loadavg", buffer, sizeof(buffer));
if (str) {
end = strstr(str, "\n");
if (end)
*end = 0;
APPEND_STAT_CSV("proc.loadavg", "%s", str);
}
}
static void json_out_stats(stats_state *my_state) {
const char *version;
TSDebug(PLUGIN_TAG, "recordTypes: '0x%x'", my_state->recordTypes);
APPEND("{ \"ats\": {\n");
TSRecordDump(my_state->recordTypes, json_out_stat, my_state);
version = TSTrafficServerVersionGet();
APPEND(" \"server\": \"");
APPEND(version);
APPEND("\"\n");
APPEND(" }");
if (my_state->recordTypes & SYSTEM_RECORD_TYPE) {
APPEND(",\n \"system\": {\n");
appendSystemStateJson(my_state);
APPEND_STAT_JSON("configReloadRequests", "%d", configReloadRequests);
APPEND_STAT_JSON("lastReloadRequest", "%" PRIu64, lastReloadRequest);
APPEND_STAT_JSON("configReloads", "%d", configReloads);
APPEND_STAT_JSON("lastReload", "%" PRIu64, lastReload);
APPEND_STAT_JSON("astatsLoad", "%" PRIu64, astatsLoad);
APPEND("\"something\": \"here\"");
APPEND("\n }");
}
APPEND("\n}\n");
}
static void csv_out_stats(stats_state *my_state) {
const char *version;
TSDebug(PLUGIN_TAG, "recordTypes: '0x%x'", my_state->recordTypes);
TSRecordDump(my_state->recordTypes, csv_out_stat, my_state);
version = TSTrafficServerVersionGet();
//APPEND("version","%s",version);
APPEND_STAT_CSV("version","%s", version);
if (my_state->recordTypes & SYSTEM_RECORD_TYPE) {
//APPEND(",\n \"system\": {\n");
appendSystemStateCsv(my_state);
APPEND_STAT_CSV("configReloadRequests", "%d", configReloadRequests);
APPEND_STAT_CSV("lastReloadRequest", "%" PRIu64, lastReloadRequest);
APPEND_STAT_CSV("configReloads", "%d", configReloads);
APPEND_STAT_CSV("lastReload", "%" PRIu64, lastReload);
APPEND_STAT_CSV("astatsLoad", "%" PRIu64, astatsLoad);
APPEND("something,here\n");
}
}
static void stats_process_write(TSCont contp, TSEvent event, stats_state *my_state) {
if (event == TS_EVENT_VCONN_WRITE_READY) {
if (my_state->body_written == 0) {
TSDebug(PLUGIN_TAG, "plugin adding response body");
my_state->body_written = 1;
switch (my_state->output) {
case JSON_OUTPUT:
json_out_stats(my_state);
break;
case CSV_OUTPUT:
csv_out_stats(my_state);
break;
default:
TSError("stats_process_write: Unknown output type\n");
break;
}
TSVIONBytesSet(my_state->write_vio, my_state->output_bytes);
}
TSVIOReenable(my_state->write_vio);
TSfree(my_state->globals);
my_state->globals = NULL;
TSfree(my_state->query);
my_state->query = NULL;
} else if (TS_EVENT_VCONN_WRITE_COMPLETE)
stats_cleanup(contp, my_state);
else if (event == TS_EVENT_ERROR)
TSError("stats_process_write: Received TS_EVENT_ERROR\n");
else
TSReleaseAssert(!"Unexpected Event");
}
static int stats_dostuff(TSCont contp, TSEvent event, void *edata) {
stats_state *my_state = TSContDataGet(contp);
if (event == TS_EVENT_NET_ACCEPT) {
my_state->net_vc = (TSVConn) edata;
stats_process_accept(contp, my_state);
} else if (edata == my_state->read_vio)
stats_process_read(contp, event, my_state);
else if (edata == my_state->write_vio)
stats_process_write(contp, event, my_state);
else
TSReleaseAssert(!"Unexpected Event");
return 0;
}
static int astats_origin(TSCont cont, TSEvent event, void *edata) {
TSCont icontp;
stats_state *my_state;
config_t* config;
TSHttpTxn txnp = (TSHttpTxn) edata;
TSMBuffer reqp;
TSMLoc hdr_loc = NULL, url_loc = NULL, accept_field = NULL;
TSEvent reenable = TS_EVENT_HTTP_CONTINUE;
config = get_config(cont);
TSDebug(PLUGIN_TAG, "in the read stuff");
if (TSHttpTxnClientReqGet(txnp, &reqp, &hdr_loc) != TS_SUCCESS)
goto cleanup;
if (TSHttpHdrUrlGet(reqp, hdr_loc, &url_loc) != TS_SUCCESS)
goto cleanup;
int path_len = 0;
const char* path = TSUrlPathGet(reqp,url_loc,&path_len);
TSDebug(PLUGIN_TAG,"Path: %.*s",path_len,path);
TSDebug(PLUGIN_TAG,"Path: %.*s",path_len,path);
if (!(path_len == config->stats_path_len && !memcmp(path, config->stats_path, config->stats_path_len))) {
goto notforme;
}
const struct sockaddr *addr = TSHttpTxnClientAddrGet(txnp);
if(!is_ip_allowed(config, addr)) {
TSDebug(PLUGIN_TAG, "not right ip");
TSHttpTxnStatusSet(txnp, TS_HTTP_STATUS_FORBIDDEN);
reenable = TS_EVENT_HTTP_ERROR;
goto notforme;
}
int query_len;
char *query = (char*)TSUrlHttpQueryGet(reqp,url_loc,&query_len);
TSDebug(PLUGIN_TAG,"query: %.*s",query_len,query);
TSSkipRemappingSet(txnp,1); //not strictly necessary, but speed is everything these days
/* This is us -- register our intercept */
TSDebug(PLUGIN_TAG, "Intercepting request");
icontp = TSContCreate(stats_dostuff, TSMutexCreate());
my_state = (stats_state *) TSmalloc(sizeof(*my_state));
memset(my_state, 0, sizeof(*my_state));
accept_field = TSMimeHdrFieldFind(reqp, hdr_loc, TS_MIME_FIELD_ACCEPT, TS_MIME_LEN_ACCEPT);
my_state->output = JSON_OUTPUT; // default to json output
// accept header exists, use it to determine response type
if (accept_field != TS_NULL_MLOC) {
int len = -1;
const char* str = TSMimeHdrFieldValueStringGet(reqp, hdr_loc, accept_field, -1, &len);
// Parse the Accept header, default to JSON output unless its another supported format
if (!strncasecmp(str, "text/csv", len)) {
my_state->output = CSV_OUTPUT;
} else {
my_state->output = JSON_OUTPUT;
}
}
my_state->recordTypes = config->recordTypes;
if (query_len) {
my_state->query = nstrl(query, query_len);
TSDebug(PLUGIN_TAG,"new query: %s", my_state->query);
stats_fillState(my_state, my_state->query, query_len);
}
TSContDataSet(icontp, my_state);
TSHttpTxnIntercept(icontp, txnp);
goto cleanup;
notforme:
cleanup:
#if (TS_VERSION_NUMBER < 2001005)
if (path)
TSHandleStringRelease(reqp, url_loc, path);
#endif
if (url_loc)
TSHandleMLocRelease(reqp, hdr_loc, url_loc);
if (hdr_loc)
TSHandleMLocRelease(reqp, TS_NULL_MLOC, hdr_loc);
if (accept_field)
TSHandleMLocRelease(reqp, TS_NULL_MLOC, accept_field);
TSHttpTxnReenable(txnp, reenable);
return 0;
}
void TSPluginInit(int argc, const char *argv[]) {
TSPluginRegistrationInfo info;
TSCont main_cont, config_cont;
config_holder_t *config_holder;
info.plugin_name = PLUGIN_TAG;
info.vendor_name = "Comcast";
info.support_email = "justin@fp-x.com";
astatsLoad = time(NULL);
#if (TS_VERSION_NUMBER < 3000000)
if (TSPluginRegister(TS_SDK_VERSION_2_0, &info) != TS_SUCCESS) {
#elif (TS_VERSION_NUMBER < 6000000)
if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
#else
if (TSPluginRegister(&info) != TS_SUCCESS) {
#endif
TSError("Plugin registration failed. \n");
}
config_holder = new_config_holder(argc > 1 ? argv[1] : NULL);
main_cont = TSContCreate(astats_origin, NULL);
TSContDataSet(main_cont, (void *) config_holder);
TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, main_cont);
config_cont = TSContCreate(config_handler, TSMutexCreate());
TSContDataSet(config_cont, (void *) config_holder);
TSMgmtUpdateRegister(config_cont, PLUGIN_TAG);
/* Create a continuation with a mutex as there is a shared global structure
containing the headers to add */
TSDebug(PLUGIN_TAG, "astats module registered, path: '%s'", config_holder->config->stats_path);
}
static bool is_ip_match(const char *ip, char *ipmask, char mask) {
unsigned int j, i,k;
char cm;
// to be able to set mask to 128
unsigned int umask = 0xff & mask;
for(j=0, i=0; ((i+1)*8) <= umask; i++) {
if(ip[i] != ipmask[i]) {
return false;
}
j+=8;
}
cm = 0;
for(k=0; j<umask;j++,k++) {
cm |= 1<<(7-k);
}
if((ip[i]&cm) != (ipmask[i]&cm)) {
return false;
}
return true;
}
static bool is_ip_allowed(const config_t* config, const struct sockaddr* addr) {
char ip_port_text_buffer[INET6_ADDRSTRLEN];
int i;
char *ipmask;
if(!addr) {
return true;
}
if (addr->sa_family == AF_INET && config->allowIps) {
const struct sockaddr_in* addr_in = (struct sockaddr_in*) addr;
const char *ip = (char*) &addr_in->sin_addr;
for(i=0; i < config->ipCount; i++) {
ipmask = config->allowIps + (i*(sizeof(struct in_addr) + 1));
if(is_ip_match(ip, ipmask, ipmask[4])) {
TSDebug(PLUGIN_TAG, "clientip is %s--> ALLOW", inet_ntop(AF_INET,ip,ip_port_text_buffer,INET6_ADDRSTRLEN));
return true;
}
}
TSDebug(PLUGIN_TAG, "clientip is %s--> DENY", inet_ntop(AF_INET,ip,ip_port_text_buffer,INET6_ADDRSTRLEN));
return false;
} else if (addr->sa_family == AF_INET6 && config->allowIps6) {
const struct sockaddr_in6* addr_in6 = (struct sockaddr_in6*) addr;
const char *ip = (char*) &addr_in6->sin6_addr;
for(i=0; i < config->ip6Count; i++) {
ipmask = config->allowIps6 + (i*(sizeof(struct in6_addr) + 1));
if(is_ip_match(ip, ipmask, ipmask[sizeof(struct in6_addr)])) {
TSDebug(PLUGIN_TAG, "clientip6 is %s--> ALLOW", inet_ntop( AF_INET6,ip,ip_port_text_buffer,INET6_ADDRSTRLEN));
return true;
}
}
TSDebug(PLUGIN_TAG, "clientip6 is %s--> DENY", inet_ntop( AF_INET6,ip,ip_port_text_buffer,INET6_ADDRSTRLEN));
return false;
}
return true;
}
static void parseIps(config_t* config, char* ipStr) {
char buffer[STR_BUFFER_SIZE];
char *p, *tok1, *tok2, *ip;
int i, mask;
char ip_port_text_buffer[INET_ADDRSTRLEN];
if(!ipStr) {
config->ipCount = 1;
ip = config->allowIps = TSmalloc(sizeof(struct in_addr) + 1);
inet_pton(AF_INET, DEFAULT_IP, ip);
ip[4] = 32;
return;
}
strcpy(buffer, ipStr);
p = buffer;
while(strtok_r(p, ", \n", &p)) {
config->ipCount++;
}
if(!config->ipCount) {
return;
}
config->allowIps = TSmalloc(5*config->ipCount); // 4 bytes for ip + 1 for bit mask
strcpy(buffer, ipStr);
p = buffer;
i = 0;
while((tok1 = strtok_r(p, ", \n", &p))) {
TSDebug(PLUGIN_TAG, "%d) parsing: %s", i+1,tok1);
tok2 = strtok_r(tok1, "/", &tok1);
ip = config->allowIps+((sizeof(struct in_addr) + 1)*i);
if(!inet_pton(AF_INET, tok2, ip)) {
TSDebug(PLUGIN_TAG, "%d) skipping: %s", i+1,tok1);
continue;
}
if (tok1 != NULL) {
tok2 = strtok_r(tok1, "/", &tok1);
}
if(!tok2) {
mask = 32;
} else {
mask = atoi(tok2);
}
ip[4] = mask;
TSDebug(PLUGIN_TAG, "%d) adding netmask: %s/%d", i+1,
inet_ntop(AF_INET,ip,ip_port_text_buffer,INET_ADDRSTRLEN),ip[4]);
i++;
}
}
static void parseIps6(config_t* config, char* ipStr) {
char buffer[STR_BUFFER_SIZE];
char *p, *tok1, *tok2, *ip;
int i, mask;
char ip_port_text_buffer[INET6_ADDRSTRLEN];
if(!ipStr) {
config->ip6Count = 1;
ip = config->allowIps6 = TSmalloc(sizeof(struct in6_addr) + 1);
inet_pton(AF_INET6, DEFAULT_IP6, ip);
ip[sizeof(struct in6_addr)] = 128;
return;
}
strcpy(buffer, ipStr);
p = buffer;
while(strtok_r(p, ", \n", &p)) {
config->ip6Count++;
}
if(!config->ip6Count) {
return;
}
config->allowIps6 = TSmalloc((sizeof(struct in6_addr) + 1)*config->ip6Count); // 16 bytes for ip + 1 for bit mask
strcpy(buffer, ipStr);
p = buffer;
i = 0;
while((tok1 = strtok_r(p, ", \n", &p))) {
TSDebug(PLUGIN_TAG, "%d) parsing: %s", i+1,tok1);
tok2 = strtok_r(tok1, "/", &tok1);
ip = config->allowIps6+((sizeof(struct in6_addr)+1)*i);
if(!inet_pton(AF_INET6, tok2, ip)) {
TSDebug(PLUGIN_TAG, "%d) skipping: %s", i+1,tok1);
continue;
}
if (tok1 != NULL) {
tok2 = strtok_r(tok1, "/", &tok1);
}
if(!tok2) {
mask = 128;
} else {
mask = atoi(tok2);
}
ip[sizeof(struct in6_addr)] = mask;
TSDebug(PLUGIN_TAG, "%d) adding netmask: %s/%d", i+1,
inet_ntop(AF_INET6,ip,ip_port_text_buffer,INET6_ADDRSTRLEN),ip[sizeof(struct in6_addr)]);
i++;
}
}
static config_t* new_config(TSFile fh) {
char buffer[STR_BUFFER_SIZE];
config_t* config = NULL;
config = (config_t*)TSmalloc(sizeof(config_t));
config->stats_path = 0;
config->stats_path_len = 0;
config->allowIps = 0;
config->ipCount = 0;
config->allowIps6 = 0;
config->ip6Count = 0;
config->recordTypes = DEFAULT_RECORD_TYPES;
if(!fh) {
config->stats_path = nstr("_astats");
config->stats_path_len = strlen(config->stats_path);
TSDebug(PLUGIN_TAG, "No config, using defaults");
return config;
}
while (TSfgets(fh, buffer, STR_BUFFER_SIZE - 1)) {
if (*buffer == '#') {
continue; /* # Comments, only at line beginning */
}
char* p = 0;
if((p = strstr(buffer, "path="))) {
p+=strlen("path=");
config->stats_path = nstr(strtok_r(p, " \n", &p));
config->stats_path_len = strlen(config->stats_path);
} else if((p = strstr(buffer, "record_types="))) {
p+=strlen("record_types=");
config->recordTypes = strtol(strtok_r(p, " \n", &p), NULL, 16);
} else if((p = strstr(buffer, "allow_ip="))) {
p+=strlen("allow_ip=");
parseIps(config, p);
} else if((p = strstr(buffer, "allow_ip6="))) {
p+=strlen("allow_ip6=");
parseIps6(config, p);
}
}
if(!config->ipCount) {
parseIps(config, NULL);
}
if(!config->ip6Count) {
parseIps6(config, NULL);
}
TSDebug(PLUGIN_TAG, "config path=%s", config->stats_path);
return config;
}
static void delete_config(config_t* config) {
TSDebug(PLUGIN_TAG, "Freeing config");
TSfree(config->allowIps);
TSfree(config->allowIps6);
TSfree(config->stats_path);
TSfree(config);
}
// standard api below...
static config_t* get_config(TSCont cont) {
config_holder_t* configh = (config_holder_t *) TSContDataGet(cont);
if(!configh) {
return 0;
}
return configh->config;
}
static void load_config_file(config_holder_t *config_holder) {
TSFile fh;
struct stat s;
config_t *newconfig, *oldconfig;
TSCont free_cont;
configReloadRequests++;
lastReloadRequest = time(NULL);
// check date
if (stat(config_holder->config_path, &s) < 0) {
TSDebug(PLUGIN_TAG, "Could not stat %s", config_holder->config_path);
if(config_holder->config) {
return;
}
} else {
TSDebug(PLUGIN_TAG, "s.st_mtime=%lu, last_load=%lu", s.st_mtime, config_holder->last_load);
if (s.st_mtime < config_holder->last_load) {
return;
}
}
TSDebug(PLUGIN_TAG, "Opening config file: %s", config_holder->config_path);
fh = TSfopen(config_holder->config_path, "r");
if (!fh) {
TSError("[%s] Unable to open config: %s.\n",
PLUGIN_TAG, config_holder->config_path);
if(config_holder->config) {
return;
}
}
newconfig = 0;
newconfig = new_config(fh);
if(newconfig) {
configReloads++;
lastReload = lastReloadRequest;
config_holder->last_load = lastReloadRequest;
config_t ** confp = &(config_holder->config);
oldconfig = __sync_lock_test_and_set(confp, newconfig);
if (oldconfig) {
TSDebug(PLUGIN_TAG, "scheduling free: %p (%p)", oldconfig, newconfig);
free_cont = TSContCreate(free_handler, TSMutexCreate());
TSContDataSet(free_cont, (void *) oldconfig);
#if TS_VERSION_MAJOR < 9
TSContSchedule(free_cont, FREE_TMOUT, TS_THREAD_POOL_TASK);
#else
TSContScheduleOnPool(free_cont, FREE_TMOUT, TS_THREAD_POOL_TASK);
#endif
}
}
if(fh)
TSfclose(fh);
return;
}
static config_holder_t* new_config_holder(const char* path) {
char default_config_file[1024];
config_holder_t* config_holder = TSmalloc(sizeof(config_holder_t));
config_holder->config_path = 0;
config_holder->config = 0;
config_holder->last_load = 0;
// TSmalloc(32);
//
if(path) {
config_holder->config_path = nstr(path);
} else {
/* Default config file of plugins/cacheurl.config */
// sprintf(default_config_file, "%s/astats.config", TSPluginDirGet());
sprintf(default_config_file, "%s/"DEFAULT_CONFIG_NAME, TSConfigDirGet());
config_holder->config_path = nstr(default_config_file);
}
load_config_file(config_holder);
return config_holder;
}
static int free_handler(TSCont cont, TSEvent event, void *edata) {
config_t *config;
TSDebug(PLUGIN_TAG, "Freeing old config");
config = (config_t *) TSContDataGet(cont);
delete_config(config);
TSContDestroy(cont);
return 0;
}
static int config_handler(TSCont cont, TSEvent event, void *edata) {
config_holder_t *config_holder;
TSDebug(PLUGIN_TAG, "In config Handler");
config_holder = (config_holder_t *) TSContDataGet(cont);
load_config_file(config_holder);
return 0;
}