haproxy/src/51d.c
ben@51degrees.com d3842523ff CLEANUP: 51d: Aligned if statements with HAProxy best practices and removed casts from malloc.
Changes to if statements do not affect code operation, just layout of
the code. Type casts from malloc returns have been removed as this cast
happens automatically from the void* type.

This may be backported to 1.6.
2016-01-13 12:10:44 +01:00

650 lines
19 KiB
C

#include <stdio.h>
#include <common/cfgparse.h>
#include <common/chunk.h>
#include <common/buffer.h>
#include <proto/arg.h>
#include <proto/log.h>
#include <proto/proto_http.h>
#include <proto/sample.h>
#include <import/xxhash.h>
#include <import/lru.h>
#include <import/51d.h>
struct _51d_property_names {
struct list list;
char *name;
};
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
static struct lru64_head *_51d_lru_tree = NULL;
static unsigned long long _51d_lru_seed;
#endif
static int _51d_data_file(char **args, int section_type, struct proxy *curpx,
struct proxy *defpx, const char *file, int line,
char **err)
{
if (*(args[1]) == 0) {
memprintf(err,
"'%s' expects a filepath to a 51Degrees trie or pattern data file.",
args[0]);
return -1;
}
if (global._51degrees.data_file_path)
free(global._51degrees.data_file_path);
global._51degrees.data_file_path = strdup(args[1]);
return 0;
}
static int _51d_property_name_list(char **args, int section_type, struct proxy *curpx,
struct proxy *defpx, const char *file, int line,
char **err)
{
int cur_arg = 1;
struct _51d_property_names *name;
if (*(args[cur_arg]) == 0) {
memprintf(err,
"'%s' expects at least one 51Degrees property name.",
args[0]);
return -1;
}
while (*(args[cur_arg])) {
name = calloc(1, sizeof(struct _51d_property_names));
name->name = strdup(args[cur_arg]);
LIST_ADDQ(&global._51degrees.property_names, &name->list);
++cur_arg;
}
return 0;
}
static int _51d_property_separator(char **args, int section_type, struct proxy *curpx,
struct proxy *defpx, const char *file, int line,
char **err)
{
if (*(args[1]) == 0) {
memprintf(err,
"'%s' expects a single character.",
args[0]);
return -1;
}
if (strlen(args[1]) > 1) {
memprintf(err,
"'%s' expects a single character, got '%s'.",
args[0], args[1]);
return -1;
}
global._51degrees.property_separator = *args[1];
return 0;
}
static int _51d_cache_size(char **args, int section_type, struct proxy *curpx,
struct proxy *defpx, const char *file, int line,
char **err)
{
if (*(args[1]) == 0) {
memprintf(err,
"'%s' expects a positive numeric value.",
args[0]);
return -1;
}
global._51degrees.cache_size = atoi(args[1]);
if (global._51degrees.cache_size < 0) {
memprintf(err,
"'%s' expects a positive numeric value, got '%s'.",
args[0], args[1]);
return -1;
}
return 0;
}
static int _51d_fetch_check(struct arg *arg, char **err_msg)
{
if (global._51degrees.data_file_path)
return 1;
memprintf(err_msg, "51Degrees data file is not specified (parameter '51degrees-data-file')");
return 0;
}
static int _51d_conv_check(struct arg *arg, struct sample_conv *conv,
const char *file, int line, char **err_msg)
{
if (global._51degrees.data_file_path)
return 1;
memprintf(err_msg, "51Degrees data file is not specified (parameter '51degrees-data-file')");
return 0;
}
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
static void _51d_lru_free(void *cache_entry)
{
struct chunk *ptr = cache_entry;
if (!ptr)
return;
free(ptr->str);
free(ptr);
}
/* Allocates memory freeing space in the cache if necessary.
*/
static void *_51d_malloc(int size)
{
void *ptr = malloc(size);
if (!ptr) {
/* free the oldest 10 entries from lru to free up some memory
* then try allocating memory again */
lru64_kill_oldest(_51d_lru_tree, 10);
ptr = malloc(size);
}
return ptr;
}
/* Insert the data associated with the sample into the cache as a fresh item.
*/
static void _51d_insert_cache_entry(struct sample *smp, struct lru64 *lru, void* domain)
{
struct chunk *cache_entry = _51d_malloc(sizeof(struct chunk));
if (!cache_entry)
return;
cache_entry->str = _51d_malloc(smp->data.u.str.len + 1);
if (!cache_entry->str) {
free(cache_entry);
return;
}
memcpy(cache_entry->str, smp->data.u.str.str, smp->data.u.str.len);
cache_entry->str[smp->data.u.str.len] = 0;
cache_entry->len = smp->data.u.str.len;
lru64_commit(lru, cache_entry, domain, 0, _51d_lru_free);
}
/* Retrieves the data from the cache and sets the sample data to this string.
*/
static void _51d_retrieve_cache_entry(struct sample *smp, struct lru64 *lru)
{
struct chunk *cache_entry = (struct chunk*)lru->data;
smp->data.u.str.str = cache_entry->str;
smp->data.u.str.len = cache_entry->len;
}
#endif
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
/* Sets the important HTTP headers ahead of the detection
*/
static void _51d_set_headers(struct sample *smp, fiftyoneDegreesWorkset *ws)
{
struct hdr_idx *idx;
struct hdr_ctx ctx;
const struct http_msg *msg;
int i;
idx = &smp->strm->txn->hdr_idx;
msg = &smp->strm->txn->req;
ws->importantHeadersCount = 0;
for (i = 0; i < global._51degrees.header_count; i++) {
ctx.idx = 0;
if (http_find_full_header2(
(global._51degrees.header_names + i)->str,
(global._51degrees.header_names + i)->len,
msg->chn->buf->p, idx, &ctx) == 1) {
ws->importantHeaders[ws->importantHeadersCount].header = ws->dataSet->httpHeaders + i;
ws->importantHeaders[ws->importantHeadersCount].headerValue = ctx.line + ctx.val;
ws->importantHeaders[ws->importantHeadersCount].headerValueLength = ctx.vlen;
ws->importantHeadersCount++;
}
}
}
#endif
#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
static void _51d_set_device_offsets(struct sample *smp)
{
struct hdr_idx *idx;
struct hdr_ctx ctx;
const struct http_msg *msg;
int index;
fiftyoneDegreesDeviceOffsets *offsets = &global._51degrees.device_offsets;
idx = &smp->strm->txn->hdr_idx;
msg = &smp->strm->txn->req;
offsets->size = 0;
for (index = 0; index < global._51degrees.header_count; index++) {
ctx.idx = 0;
if (http_find_full_header2(
(global._51degrees.header_names + index)->str,
(global._51degrees.header_names + index)->len,
msg->chn->buf->p, idx, &ctx) == 1) {
(offsets->firstOffset + offsets->size)->httpHeaderOffset = *(global._51degrees.header_offsets + index);
(offsets->firstOffset + offsets->size)->deviceOffset = fiftyoneDegreesGetDeviceOffset(ctx.line + ctx.val);
offsets->size++;
}
}
}
#endif
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
/* Provides a hash code for the important HTTP headers.
*/
unsigned long long _51d_req_hash(const struct arg *args, fiftyoneDegreesWorkset* ws)
{
unsigned long long seed = _51d_lru_seed ^ (long)args;
unsigned long long hash = 0;
int i;
for(i = 0; i < ws->importantHeadersCount; i++) {
hash ^= ws->importantHeaders[i].header->headerNameOffset;
hash ^= XXH64(ws->importantHeaders[i].headerValue,
ws->importantHeaders[i].headerValueLength,
seed);
}
return hash;
}
#endif
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
static void _51d_process_match(const struct arg *args, struct sample *smp, fiftyoneDegreesWorkset* ws)
{
char *methodName;
#endif
#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
static void _51d_process_match(const struct arg *args, struct sample *smp)
{
char valuesBuffer[1024];
const char **requiredProperties = fiftyoneDegreesGetRequiredPropertiesNames();
int requiredPropertiesCount = fiftyoneDegreesGetRequiredPropertiesCount();
fiftyoneDegreesDeviceOffsets *deviceOffsets = &global._51degrees.device_offsets;
#endif
char no_data[] = "NoData"; /* response when no data could be found */
struct chunk *temp = get_trash_chunk();
int j, i = 0, found;
const char* property_name;
/* Loop through property names passed to the filter and fetch them from the dataset. */
while (args[i].data.str.str) {
/* Try to find request property in dataset. */
found = 0;
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
if (strcmp("Method", args[i].data.str.str) == 0) {
switch(ws->method) {
case EXACT: methodName = "Exact"; break;
case NUMERIC: methodName = "Numeric"; break;
case NEAREST: methodName = "Nearest"; break;
case CLOSEST: methodName = "Closest"; break;
default:
case NONE: methodName = "None"; break;
}
chunk_appendf(temp, "%s", methodName);
found = 1;
}
else if (strcmp("Difference", args[i].data.str.str) == 0) {
chunk_appendf(temp, "%d", ws->difference);
found = 1;
}
else if (strcmp("Rank", args[i].data.str.str) == 0) {
chunk_appendf(temp, "%d", fiftyoneDegreesGetSignatureRank(ws));
found = 1;
}
else {
for (j = 0; j < ws->dataSet->requiredPropertyCount; j++) {
property_name = fiftyoneDegreesGetPropertyName(ws->dataSet, ws->dataSet->requiredProperties[j]);
if (strcmp(property_name, args[i].data.str.str) == 0) {
found = 1;
fiftyoneDegreesSetValues(ws, j);
chunk_appendf(temp, "%s", fiftyoneDegreesGetValueName(ws->dataSet, *ws->values));
break;
}
}
}
#endif
#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
found = 0;
for (j = 0; j < requiredPropertiesCount; j++) {
property_name = requiredProperties[j];
if (strcmp(property_name, args[i].data.str.str) == 0 &&
fiftyoneDegreesGetValueFromOffsets(deviceOffsets, j, valuesBuffer, 1024) > 0) {
found = 1;
chunk_appendf(temp, "%s", valuesBuffer);
break;
}
}
#endif
if (!found)
chunk_appendf(temp, "%s", no_data);
/* Add separator. */
chunk_appendf(temp, "%c", global._51degrees.property_separator);
++i;
}
if (temp->len) {
--temp->len;
temp->str[temp->len] = '\0';
}
smp->data.u.str.str = temp->str;
smp->data.u.str.len = temp->len;
}
static int _51d_fetch(const struct arg *args, struct sample *smp, const char *kw, void *private)
{
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
fiftyoneDegreesWorkset* ws; /* workset for detection */
struct lru64 *lru = NULL;
#endif
/* Needed to ensure that the HTTP message has been fully recieved when
* used with TCP operation. Not required for HTTP operation.
* Data type has to be reset to ensure the string output is processed
* correctly.
*/
CHECK_HTTP_MESSAGE_FIRST();
smp->data.type = SMP_T_STR;
/* Flags the sample to show it uses constant memory*/
smp->flags |= SMP_F_CONST;
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
/* Get only the headers needed for device detection so they can be used
* with the cache to return previous results. Pattern is slower than
* Trie so caching will help improve performance.
*/
/* Get a workset from the pool which will later contain detection results. */
ws = fiftyoneDegreesWorksetPoolGet(global._51degrees.pool);
if (!ws)
return 0;
/* Set the important HTTP headers for this request in the workset. */
_51d_set_headers(smp, ws);
/* Check the cache to see if there's results for these headers already. */
if (_51d_lru_tree) {
lru = lru64_get(_51d_req_hash(args, ws),
_51d_lru_tree, (void*)args, 0);
if (lru && lru->domain) {
fiftyoneDegreesWorksetPoolRelease(global._51degrees.pool, ws);
_51d_retrieve_cache_entry(smp, lru);
return 1;
}
}
fiftyoneDegreesMatchForHttpHeaders(ws);
_51d_process_match(args, smp, ws);
#endif
#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
/* Trie is very fast so all the headers can be passed in and the result
* returned faster than the hashing algorithm process.
*/
_51d_set_device_offsets(smp);
_51d_process_match(args, smp);
#endif
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
fiftyoneDegreesWorksetPoolRelease(global._51degrees.pool, ws);
if (lru)
_51d_insert_cache_entry(smp, lru, (void*)args);
#endif
return 1;
}
static int _51d_conv(const struct arg *args, struct sample *smp, void *private)
{
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
fiftyoneDegreesWorkset* ws; /* workset for detection */
struct lru64 *lru = NULL;
#endif
/* Flags the sample to show it uses constant memory*/
smp->flags |= SMP_F_CONST;
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
/* Look in the list. */
if (_51d_lru_tree) {
unsigned long long seed = _51d_lru_seed ^ (long)args;
lru = lru64_get(XXH64(smp->data.u.str.str, smp->data.u.str.len, seed),
_51d_lru_tree, (void*)args, 0);
if (lru && lru->domain) {
_51d_retrieve_cache_entry(smp, lru);
return 1;
}
}
/* Create workset. This will later contain detection results. */
ws = fiftyoneDegreesWorksetPoolGet(global._51degrees.pool);
if (!ws)
return 0;
#endif
/* Duplicate the data and remove the "const" flag before device detection. */
if (!smp_dup(smp))
return 0;
smp->data.u.str.str[smp->data.u.str.len] = '\0';
/* Perform detection. */
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
fiftyoneDegreesMatch(ws, smp->data.u.str.str);
_51d_process_match(args, smp, ws);
#endif
#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
global._51degrees.device_offsets.firstOffset->deviceOffset = fiftyoneDegreesGetDeviceOffset(smp->data.u.str.str);
global._51degrees.device_offsets.size = 1;
_51d_process_match(args, smp);
#endif
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
fiftyoneDegreesWorksetPoolRelease(global._51degrees.pool, ws);
if (lru)
_51d_insert_cache_entry(smp, lru, (void*)args);
#endif
return 1;
}
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
void _51d_init_http_headers()
{
int index = 0;
const fiftyoneDegreesAsciiString *headerName;
fiftyoneDegreesDataSet *ds = &global._51degrees.data_set;
global._51degrees.header_count = ds->httpHeadersCount;
global._51degrees.header_names = malloc(global._51degrees.header_count * sizeof(struct chunk));
for (index = 0; index < global._51degrees.header_count; index++) {
headerName = fiftyoneDegreesGetString(ds, ds->httpHeaders[index].headerNameOffset);
(global._51degrees.header_names + index)->str = (char*)&headerName->firstByte;
(global._51degrees.header_names + index)->len = headerName->length - 1;
(global._51degrees.header_names + index)->size = (global._51degrees.header_names + index)->len;
}
}
#endif
#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
void _51d_init_http_headers()
{
int index = 0;
global._51degrees.header_count = fiftyoneDegreesGetHttpHeaderCount();
global._51degrees.device_offsets.firstOffset = (fiftyoneDegreesDeviceOffset*)malloc(
global._51degrees.header_count * sizeof(fiftyoneDegreesDeviceOffset));
global._51degrees.header_names = malloc(global._51degrees.header_count * sizeof(struct chunk));
global._51degrees.header_offsets = malloc(global._51degrees.header_count * sizeof(int32_t));
for (index = 0; index < global._51degrees.header_count; index++) {
global._51degrees.header_offsets[index] = fiftyoneDegreesGetHttpHeaderNameOffset(index);
global._51degrees.header_names[index].str = (char*)fiftyoneDegreesGetHttpHeaderNamePointer(index);
global._51degrees.header_names[index].len = strlen(global._51degrees.header_names[index].str);
global._51degrees.header_names[index].size = global._51degrees.header_names[index].len;
}
}
#endif
int init_51degrees(void)
{
int i = 0;
struct chunk *temp;
struct _51d_property_names *name;
char **_51d_property_list = NULL;
fiftyoneDegreesDataSetInitStatus _51d_dataset_status = DATA_SET_INIT_STATUS_NOT_SET;
if (!global._51degrees.data_file_path)
return -1;
if (!LIST_ISEMPTY(&global._51degrees.property_names)) {
i = 0;
list_for_each_entry(name, &global._51degrees.property_names, list)
++i;
_51d_property_list = calloc(i, sizeof(char *));
i = 0;
list_for_each_entry(name, &global._51degrees.property_names, list)
_51d_property_list[i++] = name->name;
}
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
_51d_dataset_status = fiftyoneDegreesInitWithPropertyArray(global._51degrees.data_file_path, &global._51degrees.data_set, (const char**)_51d_property_list, i);
#endif
#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
_51d_dataset_status = fiftyoneDegreesInitWithPropertyArray(global._51degrees.data_file_path, (const char**)_51d_property_list, i);
#endif
temp = get_trash_chunk();
chunk_reset(temp);
switch (_51d_dataset_status) {
case DATA_SET_INIT_STATUS_SUCCESS:
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
/* only 1 workset in the pool because HAProxy is currently single threaded
* this value should be set to the number of threads in future versions.
*/
global._51degrees.pool = fiftyoneDegreesWorksetPoolCreate(&global._51degrees.data_set, NULL, 1);
#endif
_51d_init_http_headers();
break;
case DATA_SET_INIT_STATUS_INSUFFICIENT_MEMORY:
chunk_printf(temp, "Insufficient memory.");
break;
case DATA_SET_INIT_STATUS_CORRUPT_DATA:
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
chunk_printf(temp, "Corrupt data file. Check that the data file provided is uncompressed and Pattern data format.");
#endif
#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
chunk_printf(temp, "Corrupt data file. Check that the data file provided is uncompressed and Trie data format.");
#endif
break;
case DATA_SET_INIT_STATUS_INCORRECT_VERSION:
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
chunk_printf(temp, "Incorrect version. Check that the data file provided is uncompressed and Pattern data format.");
#endif
#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
chunk_printf(temp, "Incorrect version. Check that the data file provided is uncompressed and Trie data format.");
#endif
break;
case DATA_SET_INIT_STATUS_FILE_NOT_FOUND:
chunk_printf(temp, "File not found.");
break;
case DATA_SET_INIT_STATUS_NOT_SET:
chunk_printf(temp, "Data set not initialised.");
break;
}
if (_51d_dataset_status != DATA_SET_INIT_STATUS_SUCCESS) {
if (temp->len)
Alert("51Degrees Setup - Error reading 51Degrees data file. %s\n", temp->str);
else
Alert("51Degrees Setup - Error reading 51Degrees data file.\n");
exit(1);
}
free(_51d_property_list);
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
_51d_lru_seed = random();
if (global._51degrees.cache_size) {
_51d_lru_tree = lru64_new(global._51degrees.cache_size);
}
#endif
return 0;
}
void deinit_51degrees(void)
{
struct _51d_property_names *_51d_prop_name, *_51d_prop_nameb;
free(global._51degrees.header_names);
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
fiftyoneDegreesWorksetPoolFree(global._51degrees.pool);
fiftyoneDegreesDataSetFree(&global._51degrees.data_set);
#endif
#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
free(global._51degrees.device_offsets.firstOffset);
free(global._51degrees.header_offsets);
fiftyoneDegreesDestroy();
#endif
free(global._51degrees.data_file_path); global._51degrees.data_file_path = NULL;
list_for_each_entry_safe(_51d_prop_name, _51d_prop_nameb, &global._51degrees.property_names, list) {
LIST_DEL(&_51d_prop_name->list);
free(_51d_prop_name);
}
#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
while (lru64_destroy(_51d_lru_tree));
#endif
}
static struct cfg_kw_list _51dcfg_kws = {{ }, {
{ CFG_GLOBAL, "51degrees-data-file", _51d_data_file },
{ CFG_GLOBAL, "51degrees-property-name-list", _51d_property_name_list },
{ CFG_GLOBAL, "51degrees-property-separator", _51d_property_separator },
{ CFG_GLOBAL, "51degrees-cache-size", _51d_cache_size },
{ 0, NULL, NULL },
}};
/* Note: must not be declared <const> as its list will be overwritten */
static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
{ "51d.all", _51d_fetch, ARG5(1,STR,STR,STR,STR,STR), _51d_fetch_check, SMP_T_STR, SMP_USE_HRQHV },
{ NULL, NULL, 0, 0, 0 },
}};
/* Note: must not be declared <const> as its list will be overwritten */
static struct sample_conv_kw_list conv_kws = {ILH, {
{ "51d.single", _51d_conv, ARG5(1,STR,STR,STR,STR,STR), _51d_conv_check, SMP_T_STR, SMP_T_STR },
{ NULL, NULL, 0, 0, 0 },
}};
__attribute__((constructor))
static void __51d_init(void)
{
/* register sample fetch and conversion keywords */
sample_register_fetches(&sample_fetch_keywords);
sample_register_convs(&conv_kws);
cfg_register_keywords(&_51dcfg_kws);
}