Hello from DeviceAtlas


We are making available a patch to provide a device detection adaptor for
HAProxy using DeviceAtlas. It will permit HAProxy users to know the nature
of the visiting device, thus enabling different management according to the
type of traffic. The output is an additional header containing information
about the device (e.g. h/w type, OS, OS version, browser etc).



Brief documentation is provided at doc/deviceatlas.txt, which links to
https://deviceatlas.com/deviceatlas-haproxy-module, explaining how to
retrieve the C API source and associated data file, how to compile the code
and then how to configure HAProxy.



Please cc me ( [email protected] ) on any responses.



Best regards



Tomas Trnka

DeviceAtlas R&D Lead

Afilias Technologies

-- 

The information contained in this message may be privileged and
confidential and protected from disclosure. If the reader of this message
is not the intended recipient, or an employee or agent responsible for
delivering this message to the intended recipient, you are hereby notified
that any dissemination, distribution or copying of this communication is
strictly prohibited. If you have received this communication in error,
please notify us immediately by replying to the message and deleting it
from your computer.

dotMobi (Afilias Technologies Limited) is a private company limited by
shares, incorporated and registered in the Republic of Ireland with
registered number 398040 and registered office at First Floor, Fitzwilton
House, Wilton Place, Dublin 2.
From: David Carlier <[email protected]>

This diff is for the DeviceAtlas convertor.
First, the configuration keywords handling (only the log
level configuration part does not end the haproxy process
if it is wrongly set, it fallbacks to the default level).
Furthermore, init, deinit phases and the API lookup phase, 
the da_haproxy function which is fed by the input provided
and set all necessary properties chosen via the configuration
to the output, separated by the separator.
diff --git a/include/import/da.h b/include/import/da.h
new file mode 100644
index 0000000..d990188
--- /dev/null
+++ b/include/import/da.h
@@ -0,0 +1,13 @@
+#ifndef MOBI_DA_DAC_HAPROXY_H
+#define MOBI_DA_DAC_HAPROXY_H
+#ifdef USE_DEVICEATLAS
+
+#include <types/global.h>
+#include <dac.h>
+
+void da_register_cfgkeywords(void);
+int init_deviceatlas(void);
+void deinit_deviceatlas(void);
+int da_haproxy(const struct arg *, struct sample *, void *);
+#endif
+#endif
diff --git a/src/da.c b/src/da.c
new file mode 100644
index 0000000..148fefd
--- /dev/null
+++ b/src/da.c
@@ -0,0 +1,231 @@
+#ifdef USE_DEVICEATLAS
+
+#include <stdio.h>
+
+#include <common/cfgparse.h>
+#include <proto/log.h>
+#include <import/da.h>
+
+static int da_json_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, "deviceatlas json file : expects a json 
path.\n");
+                return -1;
+        }
+        global.deviceatlas.jsonpath = strdup(args[1]);
+        return 0;
+}
+
+static int da_log_level(char **args, int section_type, struct proxy *curpx,
+                        struct proxy *defpx, const char *file, int line,
+                        char **err)
+{
+        int loglevel;
+        if (*(args[1]) == 0) {
+                memprintf(err, "deviceatlas log level : expects an integer 
argument.\n");
+                return -1;
+        }
+
+        loglevel = atol(args[1]);
+        if (loglevel < 0 || loglevel > 3) {
+                memprintf(err, "deviceatlas log level : expects a log level 
between 0 and 3, %s given.\n", args[1]);
+        } else {
+                global.deviceatlas.loglevel = (da_severity_t)loglevel;
+        }
+
+        return 0;
+}
+
+static int da_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, "deviceatlas property separator : expects a 
character argument.\n");
+                return -1;
+        }
+        global.deviceatlas.separator = *args[1];
+        return 0;
+}
+
+static struct cfg_kw_list dacfg_kws = {{ }, {
+        { CFG_GLOBAL, "deviceatlas-json-file",          da_json_file },
+        { CFG_GLOBAL, "deviceatlas-log-level",          da_log_level },
+        { CFG_GLOBAL, "deviceatlas-property-separator", da_property_separator 
},
+        { 0, NULL, NULL },
+}};
+
+static size_t da_haproxy_read(void *ctx, size_t len, char *buf)
+{
+        return fread(buf, 1, len, ctx);
+}
+
+static da_status_t da_haproxy_seek(void *ctx, off_t off)
+{
+        return fseek(ctx, off, SEEK_SET) != -1 ? DA_OK : DA_SYS;
+}
+
+static void da_haproxy_log(da_severity_t severity, da_status_t status,
+        const char *fmt, va_list args)
+{
+        if (severity <= global.deviceatlas.loglevel) {
+                char logbuf[256];
+                vsnprintf(logbuf, sizeof(logbuf), fmt, args);
+                Warning("deviceatlas : %s.\n", logbuf);
+        }
+}
+
+void da_register_cfgkeywords(void)
+{
+        cfg_register_keywords(&dacfg_kws);
+}
+
+int init_deviceatlas(void)
+{
+        da_status_t status = DA_SYS;
+        if (global.deviceatlas.jsonpath != 0) {
+                FILE *jsonp;
+                da_property_decl_t extraprops[] = {{0, 0}};
+                size_t atlasimglen;
+                da_status_t status;
+
+                jsonp = fopen(global.deviceatlas.jsonpath, "r");
+                if (jsonp == 0) {
+                        Alert("deviceatlas : '%s' json file has invalid path 
or is not readable.\n",
+                                global.deviceatlas.jsonpath);
+                        goto out;
+                }
+
+                da_init();
+                da_seterrorfunc(da_haproxy_log);
+                status = da_atlas_compile(jsonp, da_haproxy_read, 
da_haproxy_seek,
+                        &global.deviceatlas.atlasimgptr, &atlasimglen);
+                fclose(jsonp);
+                if (status != DA_OK) {
+                        Alert("deviceatlas : '%s' json file is invalid.\n",
+                                global.deviceatlas.jsonpath);
+                        goto out;
+                }
+
+                status = da_atlas_open(&global.deviceatlas.atlas, extraprops,
+                        global.deviceatlas.atlasimgptr, atlasimglen);
+
+                if (status != DA_OK) {
+                        Alert("deviceatlas : data could not be compiled.\n");
+                        goto out;
+                }
+
+                global.deviceatlas.useragentid = 
da_atlas_header_evidence_id(&global.deviceatlas.atlas,
+                        "user-agent");
+
+                fprintf(stdout, "Deviceatlas module loaded.\n");
+        }
+
+out:
+        return status == DA_OK;
+}
+
+void deinit_deviceatlas(void)
+{
+        if (global.deviceatlas.jsonpath != 0) {
+                free(global.deviceatlas.jsonpath);
+        }
+
+        if (global.deviceatlas.useragentid > 0) {
+                da_atlas_close(&global.deviceatlas.atlas);
+                free(global.deviceatlas.atlasimgptr);
+        }
+
+        da_fini();
+}
+
+int da_haproxy(const struct arg *args, struct sample *smp, void *private)
+{
+        struct chunk *tmp;
+        da_deviceinfo_t devinfo;
+        da_propid_t prop, *pprop;
+        da_type_t proptype;
+        da_status_t status;
+        const char *useragent, *propname;
+        char useragentbuf[1024];
+        int i;
+
+        if (global.deviceatlas.useragentid == 0) {
+                return 1;
+        }
+
+        tmp = get_trash_chunk();
+        chunk_reset(tmp);
+
+        memcpy(useragentbuf, smp->data.str.str, sizeof(useragentbuf));
+        useragentbuf[smp->data.str.len] = 0;
+
+        useragent = (const char *)useragentbuf;
+        propname = (const char *)args[0].data.str.str;
+        i = 0;
+
+        status = da_search(&global.deviceatlas.atlas, &devinfo,
+                global.deviceatlas.useragentid, useragent, 0);
+        if (status != DA_OK) {
+                return 0;
+        }
+
+        for (; propname != 0; i ++, propname = (const char 
*)args[i].data.str.str) {
+                status = da_atlas_getpropid(&global.deviceatlas.atlas,
+                        propname, &prop);
+                if (status != DA_OK) {
+                        chunk_appendf(tmp, "%c", global.deviceatlas.separator);
+                        continue;
+                }
+                pprop = &prop;
+                da_atlas_getproptype(&global.deviceatlas.atlas, *pprop, 
&proptype);
+
+                switch (proptype) {
+                        case DA_TYPE_BOOLEAN: {
+                                bool val;
+                                status = da_getpropboolean(&devinfo, *pprop, 
&val);
+                                if (status == DA_OK) {
+                                        chunk_appendf(tmp, "%d", val);
+                                }
+                                break;
+                        }
+                        case DA_TYPE_INTEGER:
+                        case DA_TYPE_NUMBER: {
+                                long val;
+                                status = da_getpropinteger(&devinfo, *pprop, 
&val);
+                                if (status == DA_OK) {
+                                        chunk_appendf(tmp, "%ld", val);
+                                }
+                                break;
+                        }
+                        case DA_TYPE_STRING: {
+                                const char *val;
+                                status = da_getpropstring(&devinfo, *pprop, 
&val);
+                                if (status == DA_OK) {
+                                        chunk_appendf(tmp, "%s", val);
+                                }
+                                break;
+                        }                         
+                    default:
+                        break;
+                }
+
+                chunk_appendf(tmp, "%c", global.deviceatlas.separator);
+        }
+
+        da_close(&devinfo);
+
+        if (tmp->len) {
+                --tmp->len;
+                tmp->str[tmp->len] = 0;
+        }
+
+        smp->data.str.str = tmp->str;
+        smp->data.str.len = strlen(tmp->str);
+
+        return 1;
+}
+
+#endif
From: David Carlier <[email protected]>

This diff is the raw C struct definition of all DeviceAtlas module
data needed added to the main global struct haproxy configuration.
The three first members are needed for both init and deinit phases
as some dynamic memory allocations are done.
The useragentid serves to hold during the whole lifecycle of the
module the User-Agent HTTP Header identifier from the DeviceAtlas
data during the init process.
diff --git a/include/types/global.h b/include/types/global.h
index 7533bb0..8823128 100644
--- a/include/types/global.h
+++ b/include/types/global.h
@@ -31,6 +31,10 @@
 #include <types/proxy.h>
 #include <types/task.h>
 
+#ifdef USE_DEVICEATLAS
+#include <dac.h>
+#endif
+
 #ifndef UNIX_MAX_PATH
 #define UNIX_MAX_PATH 108
 #endif
@@ -169,6 +173,16 @@ struct global {
        unsigned long cpu_map[LONGBITS];  /* list of CPU masks for the 32/64 
first processes */
 #endif
        struct proxy *stats_fe;     /* the frontend holding the stats settings 
*/
+#ifdef USE_DEVICEATLAS
+        struct {
+                void *atlasimgptr;
+                char *jsonpath;
+                da_atlas_t atlas;
+                da_evidence_id_t useragentid;
+                da_severity_t loglevel;
+                char separator;
+        } deviceatlas;
+#endif
 };
 
 extern struct global global;
From: David Carlier <[email protected]>

This diff declares the deviceatlas module and can accept up to 5 property names
for the API lookup.
diff --git a/src/sample.c b/src/sample.c
index 03540e1..37c5757 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -32,6 +32,10 @@
 #include <proto/sample.h>
 #include <proto/stick_table.h>
 
+#ifdef USE_DEVICEATLAS
+#include <import/da.h>
+#endif
+
 /* sample type names */
 const char *smp_to_type[SMP_TYPES] = {
        [SMP_T_BOOL] = "bool",
@@ -2278,6 +2282,9 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, 
{
        { "div",    sample_conv_arith_div,  ARG1(1,UINT), NULL, SMP_T_UINT, 
SMP_T_UINT },
        { "mod",    sample_conv_arith_mod,  ARG1(1,UINT), NULL, SMP_T_UINT, 
SMP_T_UINT },
        { "neg",    sample_conv_arith_neg,             0, NULL, SMP_T_UINT, 
SMP_T_UINT },
+#ifdef USE_DEVICEATLAS
+       { "da-csv", da_haproxy,             ARG5(1,STR,STR,STR,STR,STR), NULL, 
SMP_T_STR, SMP_T_STR },
+#endif
 
        { NULL, NULL, 0, 0, 0 },
 }};
From: David Carlier <[email protected]>

This diff initialises few DeviceAtlas struct fields member with
their inherent default values.
Furthermore, the specific DeviceAtlas configuration keywords are
registered and the module is initialised and all necessary
resources are freed during the deinit phase.
diff --git a/src/haproxy.c b/src/haproxy.c
index 233c434..2cdcdeb 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -111,6 +111,10 @@
 #include <proto/ssl_sock.h>
 #endif
 
+#ifdef USE_DEVICEATLAS
+#include <import/da.h>
+#endif
+
 /*********************************************************************/
 
 extern const struct comp_algo comp_algos[];
@@ -170,6 +174,14 @@ struct global global = {
        .maxsslconn = DEFAULT_MAXSSLCONN,
 #endif
 #endif
+#ifdef USE_DEVICEATLAS
+       .deviceatlas = {
+            .loglevel = DA_SEV_INFO,
+            .useragentid = 0,
+            .jsonpath = 0,
+            .separator = '|',
+       },
+#endif
        /* others NULL OK */
 };
 
@@ -571,6 +583,10 @@ void init(int argc, char **argv)
 
        /* Initialise lua. */
        hlua_init();
+#if defined(USE_DEVICEATLAS)
+       /* Register deviceatlas config keywords */
+       da_register_cfgkeywords();
+#endif
 
        global.tune.options |= GTUNE_USE_SELECT;  /* select() is always 
available */
 #if defined(ENABLE_POLL)
@@ -782,6 +798,9 @@ void init(int argc, char **argv)
 
        /* now we know the buffer size, we can initialize the channels and 
buffers */
        init_buffer();
+#if defined(USE_DEVICEATLAS)
+       init_deviceatlas();
+#endif
 
        if (have_appsession)
                appsession_init();
@@ -1414,6 +1433,10 @@ void deinit(void)
 
        protocol_unbind_all();
 
+#if defined(USE_DEVICEATLAS)
+       deinit_deviceatlas();
+#endif
+
        free(global.log_send_hostname); global.log_send_hostname = NULL;
        free(global.log_tag); global.log_tag = NULL;
        free(global.chroot);  global.chroot = NULL;
From: David Carlier <[email protected]>

This diff updates the Makefile to compile conditionally via
some new sets of flags, USE_DEVICEATLAS to enable the module
and the couple DEVICEATLAS_INC/DEVICEATLAS_LIB which needs to
point to the API root folder in order to compile the API and
the module.
diff --git a/Makefile b/Makefile
index c0f4cb3..5d583be 100644
--- a/Makefile
+++ b/Makefile
@@ -38,6 +38,7 @@
 #   USE_TFO              : enable TCP fast open. Supported on Linux >= 3.7.
 #   USE_NS               : enable network namespace support. Supported on 
Linux >= 2.6.24.
 #   USE_DL               : enable it if your system requires -ldl. Automatic 
on Linux.
+#   USE_DEVICEATLAS      : enable DeviceAtlas api.
 #
 # Options can be forced by specifying "USE_xxx=1" or can be disabled by using
 # "USE_xxx=" (empty string).
@@ -666,6 +667,17 @@ OPTIONS_CFLAGS += -DCONFIG_HAP_NS
 BUILD_OPTIONS  += $(call ignore_implicit,USE_NS)
 endif
 
+
+ifneq ($(USE_DEVICEATLAS),)
+DEVICEATLAS_INC =
+DEVICEATLAS_LIB =
+OPTIONS_OBJS   += $(DEVICEATLAS_LIB)/json.o
+OPTIONS_OBJS   += $(DEVICEATLAS_LIB)/dac.o
+OPTIONS_OBJS   += src/da.o
+OPTIONS_CFLAGS += -DUSE_DEVICEATLAS $(if 
$(DEVICEATLAS_INC),-I$(DEVICEATLAS_INC))
+BUILD_OPTIONS  += $(call ignore_implicit,USE_DEVICEATLAS)
+endif
+
 #### Global link options
 # These options are added at the end of the "ld" command line. Use LDFLAGS to
 # add options at the beginning of the "ld" command line if needed.
From: David Carlier <[email protected]>

This diff is related to the additional documentation in
order to build the DeviceAtlas module and in addition with
an example of a basic configuration.
diff --git a/doc/deviceatlas.txt b/doc/deviceatlas.txt
new file mode 100644
index 0000000..cb6c618
--- /dev/null
+++ b/doc/deviceatlas.txt
@@ -0,0 +1,22 @@
+In order to add DeviceAtlas Device Detection support, you would need to 
download
+the API source code from https://deviceatlas.com/deviceatlas-haproxy-module 
and once extracted :
+
+$ make TARGET=<target> USE_PCRE=1 USE_DEVICEATLAS=1 DEVICEATLAS_INC=<path to 
the API root folder> DEVICEATLAS_LIB=<path to the API root folder>
+
+These are supported DeviceAtlas directives :
+
+- deviceatlas-json-file <path to the DeviceAtlas JSON data file>.
+- deviceatlas-log-level <number> (0 to 3, level of information returned by the 
API, 0 by default).
+- deviceatlas-property-separator <character> (character used to separate the 
properties produced by the API, | by default).
+
+Sample configuration :
+
+global
+   deviceatlas-json-file <path to json file>
+
+...
+frontend
+   bind *:8881
+   default_backend servers
+   http-request set-header X-DeviceAtlas-Data 
%[req.fhdr(User-Agent),da-csv(primaryHardwareType,osName,osVersion,browserName,browserVersion)]
+
From: David Carlier <[email protected]>

This diff updates the main configuration readme file
with the specific DeviceAtlas module keywords.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 7533bb0..8823128 100644
--- a/doc/configuration.txt     2015-05-29 17:37:38.350265994 +0100
+++ b/doc/configuration.txt     2015-05-29 17:17:50.970293592 +0100
@@ -10646,6 +10646,18 @@
   not be used for security purposes as a 32-bit hash is trivial to break. See
   also "djb2", "sdbm", "wt6" and the "hash-type" directive.
 
+deviceatlas-json-file(<path>)
+  Sets the path of the DeviceAtlas JSON data file to be loaded by the API.
+  The path must be a valid JSON data file and accessible by Haproxy process.
+
+deviceatlas-log-level(<value>)
+  Sets the level of informations returned by the API. This directive is
+  optional and set to 0 by default if not set.
+
+deviceatlas-separator(<char>)
+  Sets the character separator for the API properties results. This directive
+  is optional and set to | by default if not set.
+
 div(<value>)
   Divides the input value of type unsigned integer by <value>, and returns the
   result as an unsigned integer. If <value> is null, the largest unsigned

Reply via email to