From 9d9ea2cd70a369a7f665a322e6c53631e01a2570 Mon Sep 17 00:00:00 2001 From: Andreas Jaggi Date: Wed, 30 May 2018 22:15:36 +0200 Subject: ulogd: json: send messages to a remote host / unix socket Extend the JSON output plugin so that the generated JSON stream can be sent to a remote host via TCP/UDP or to a local unix socket. Signed-off-by: Andreas Jaggi Signed-off-by: Pablo Neira Ayuso --- output/ulogd_output_JSON.c | 291 +++++++++++++++++++++++++++++++++++++++++---- ulogd.conf.in | 11 ++ 2 files changed, 281 insertions(+), 21 deletions(-) --- a/output/ulogd_output_JSON.c +++ b/output/ulogd_output_JSON.c @@ -20,10 +20,15 @@ #include #include +#include #include #include #include #include +#include +#include +#include +#include #include #include #include @@ -36,6 +41,10 @@ #define ULOGD_JSON_DEFAULT_DEVICE "Netfilter" #endif +#define host_ce(x) (x->ces[JSON_CONF_HOST]) +#define port_ce(x) (x->ces[JSON_CONF_PORT]) +#define mode_ce(x) (x->ces[JSON_CONF_MODE]) +#define file_ce(x) (x->ces[JSON_CONF_FILENAME]) #define unlikely(x) __builtin_expect((x),0) struct json_priv { @@ -44,6 +53,15 @@ struct json_priv { int usec_idx; long cached_gmtoff; char cached_tz[6]; /* eg +0200 */ + int mode; + int sock; +}; + +enum json_mode { + JSON_MODE_FILE = 0, + JSON_MODE_TCP, + JSON_MODE_UDP, + JSON_MODE_UNIX }; enum json_conf { @@ -53,6 +71,9 @@ enum json_conf { JSON_CONF_EVENTV1, JSON_CONF_DEVICE, JSON_CONF_BOOLEAN_LABEL, + JSON_CONF_MODE, + JSON_CONF_HOST, + JSON_CONF_PORT, JSON_CONF_MAX }; @@ -95,15 +116,167 @@ static struct config_keyset json_kset = .options = CONFIG_OPT_NONE, .u = { .value = 0 }, }, + [JSON_CONF_MODE] = { + .key = "mode", + .type = CONFIG_TYPE_STRING, + .options = CONFIG_OPT_NONE, + .u = { .string = "file" }, + }, + [JSON_CONF_HOST] = { + .key = "host", + .type = CONFIG_TYPE_STRING, + .options = CONFIG_OPT_NONE, + .u = { .string = "127.0.0.1" }, + }, + [JSON_CONF_PORT] = { + .key = "port", + .type = CONFIG_TYPE_STRING, + .options = CONFIG_OPT_NONE, + .u = { .string = "12345" }, + }, }, }; +static void close_socket(struct json_priv *op) { + if (op->sock != -1) { + close(op->sock); + op->sock = -1; + } +} + +static int _connect_socket_unix(struct ulogd_pluginstance *pi) +{ + struct json_priv *op = (struct json_priv *) &pi->private; + struct sockaddr_un u_addr; + int sfd; + + close_socket(op); + + ulogd_log(ULOGD_DEBUG, "connecting to unix:%s\n", + file_ce(pi->config_kset).u.string); + + sfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sfd == -1) { + return -1; + } + u_addr.sun_family = AF_UNIX; + strncpy(u_addr.sun_path, file_ce(pi->config_kset).u.string, + sizeof(u_addr.sun_path) - 1); + if (connect(sfd, (struct sockaddr *) &u_addr, sizeof(struct sockaddr_un)) == -1) { + close(sfd); + return -1; + } + + op->sock = sfd; + + return 0; +} + +static int _connect_socket_net(struct ulogd_pluginstance *pi) +{ + struct json_priv *op = (struct json_priv *) &pi->private; + struct addrinfo hints; + struct addrinfo *result, *rp; + int sfd, s; + + close_socket(op); + + ulogd_log(ULOGD_DEBUG, "connecting to %s:%s\n", + host_ce(pi->config_kset).u.string, + port_ce(pi->config_kset).u.string); + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = op->mode == JSON_MODE_UDP ? SOCK_DGRAM : SOCK_STREAM; + hints.ai_protocol = 0; + hints.ai_flags = 0; + + s = getaddrinfo(host_ce(pi->config_kset).u.string, + port_ce(pi->config_kset).u.string, &hints, &result); + if (s != 0) { + ulogd_log(ULOGD_ERROR, "getaddrinfo: %s\n", gai_strerror(s)); + return -1; + } + + for (rp = result; rp != NULL; rp = rp->ai_next) { + int on = 1; + + sfd = socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (sfd == -1) + continue; + + setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, + (char *) &on, sizeof(on)); + + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) + break; + + close(sfd); + } + + freeaddrinfo(result); + + if (rp == NULL) { + return -1; + } + + op->sock = sfd; + + return 0; +} + +static int _connect_socket(struct ulogd_pluginstance *pi) +{ + struct json_priv *op = (struct json_priv *) &pi->private; + + if (op->mode == JSON_MODE_UNIX) + return _connect_socket_unix(pi); + else + return _connect_socket_net(pi); +} + +static int json_interp_socket(struct ulogd_pluginstance *upi, char *buf, int buflen) +{ + struct json_priv *opi = (struct json_priv *) &upi->private; + int ret = 0; + + if (opi->sock != -1) + ret = send(opi->sock, buf, buflen, MSG_NOSIGNAL); + free(buf); + if (ret != buflen) { + ulogd_log(ULOGD_ERROR, "Failure sending message: %s\n", + strerror(errno)); + if (ret == -1 || opi->sock == -1) + return _connect_socket(upi); + else + return ULOGD_IRET_ERR; + } + + return ULOGD_IRET_OK; +} + +static int json_interp_file(struct ulogd_pluginstance *upi, char *buf) +{ + struct json_priv *opi = (struct json_priv *) &upi->private; + + fprintf(opi->of, "%s", buf); + free(buf); + + if (upi->config_kset->ces[JSON_CONF_SYNC].u.value != 0) + fflush(opi->of); + + return ULOGD_IRET_OK; +} + #define MAX_LOCAL_TIME_STRING 38 static int json_interp(struct ulogd_pluginstance *upi) { struct json_priv *opi = (struct json_priv *) &upi->private; unsigned int i; + char *buf; + int buflen; json_t *msg; msg = json_object(); @@ -218,34 +391,65 @@ static int json_interp(struct ulogd_plug } } - json_dumpf(msg, opi->of, 0); - fprintf(opi->of, "\n"); + buf = json_dumps(msg, 0); json_decref(msg); + if (buf == NULL) { + ulogd_log(ULOGD_ERROR, "Could not create message\n"); + return ULOGD_IRET_ERR; + } + buflen = strlen(buf); + buf = realloc(buf, sizeof(char)*(buflen+2)); + if (buf == NULL) { + ulogd_log(ULOGD_ERROR, "Could not create message\n"); + return ULOGD_IRET_ERR; + } + strncat(buf, "\n", 1); + buflen++; - if (upi->config_kset->ces[JSON_CONF_SYNC].u.value != 0) - fflush(opi->of); + if (opi->mode == JSON_MODE_FILE) + return json_interp_file(upi, buf); + else + return json_interp_socket(upi, buf, buflen); +} - return ULOGD_IRET_OK; +static void reopen_file(struct ulogd_pluginstance *upi) +{ + struct json_priv *oi = (struct json_priv *) &upi->private; + FILE *old = oi->of; + + ulogd_log(ULOGD_NOTICE, "JSON: reopening logfile\n"); + oi->of = fopen(upi->config_kset->ces[0].u.string, "a"); + if (!oi->of) { + ulogd_log(ULOGD_ERROR, "can't open JSON " + "log file: %s\n", + strerror(errno)); + oi->of = old; + } else { + fclose(old); + } +} + +static void reopen_socket(struct ulogd_pluginstance *upi) +{ + ulogd_log(ULOGD_NOTICE, "JSON: reopening socket\n"); + if (_connect_socket(upi) < 0) { + ulogd_log(ULOGD_ERROR, "can't open JSON " + "socket: %s\n", + strerror(errno)); + } } static void sighup_handler_print(struct ulogd_pluginstance *upi, int signal) { struct json_priv *oi = (struct json_priv *) &upi->private; - FILE *old = oi->of; switch (signal) { case SIGHUP: - ulogd_log(ULOGD_NOTICE, "JSON: reopening logfile\n"); - oi->of = fopen(upi->config_kset->ces[0].u.string, "a"); - if (!oi->of) { - ulogd_log(ULOGD_ERROR, "can't open JSON " - "log file: %s\n", - strerror(errno)); - oi->of = old; - } else { - fclose(old); - } + if (oi->mode == JSON_MODE_FILE) + reopen_file(upi); + else + reopen_socket(upi); break; default: break; @@ -255,6 +459,8 @@ static void sighup_handler_print(struct static int json_configure(struct ulogd_pluginstance *upi, struct ulogd_pluginstance_stack *stack) { + struct json_priv *op = (struct json_priv *) &upi->private; + char *mode_str = mode_ce(upi->config_kset).u.string; int ret; ret = ulogd_wildcard_inputkeys(upi); @@ -265,13 +471,25 @@ static int json_configure(struct ulogd_p if (ret < 0) return ret; + if (!strcasecmp(mode_str, "udp")) { + op->mode = JSON_MODE_UDP; + } else if (!strcasecmp(mode_str, "tcp")) { + op->mode = JSON_MODE_TCP; + } else if (!strcasecmp(mode_str, "unix")) { + op->mode = JSON_MODE_UNIX; + } else if (!strcasecmp(mode_str, "file")) { + op->mode = JSON_MODE_FILE; + } else { + ulogd_log(ULOGD_ERROR, "unknown mode '%s'\n", mode_str); + return -EINVAL; + } + return 0; } -static int json_init(struct ulogd_pluginstance *upi) +static int json_init_file(struct ulogd_pluginstance *upi) { struct json_priv *op = (struct json_priv *) &upi->private; - unsigned int i; op->of = fopen(upi->config_kset->ces[0].u.string, "a"); if (!op->of) { @@ -280,6 +498,27 @@ static int json_init(struct ulogd_plugin return -1; } + return 0; +} + +static int json_init_socket(struct ulogd_pluginstance *upi) +{ + struct json_priv *op = (struct json_priv *) &upi->private; + + if (host_ce(upi->config_kset).u.string == NULL) + return -1; + if (port_ce(upi->config_kset).u.string == NULL) + return -1; + + op->sock = -1; + return _connect_socket(upi); +} + +static int json_init(struct ulogd_pluginstance *upi) +{ + struct json_priv *op = (struct json_priv *) &upi->private; + unsigned int i; + /* search for time */ op->sec_idx = -1; op->usec_idx = -1; @@ -293,15 +532,25 @@ static int json_init(struct ulogd_plugin *op->cached_tz = '\0'; - return 0; + if (op->mode == JSON_MODE_FILE) + return json_init_file(upi); + else + return json_init_socket(upi); +} + +static void close_file(FILE *of) { + if (of != stdout) + fclose(of); } static int json_fini(struct ulogd_pluginstance *pi) { struct json_priv *op = (struct json_priv *) &pi->private; - if (op->of != stdout) - fclose(op->of); + if (op->mode == JSON_MODE_FILE) + close_file(op->of); + else + close_socket(op); return 0; } --- a/ulogd.conf.in +++ b/ulogd.conf.in @@ -213,6 +213,17 @@ sync=1 # Uncomment the following line to use JSON v1 event format that # can provide better compatility with some JSON file reader. #eventv1=1 +# Uncomment the following lines to send the JSON logs to a remote host via UDP +#mode="udp" +#host="192.0.2.10" +#port="10210" +# Uncomment the following lines to send the JSON logs to a remote host via TCP +#mode="tcp" +#host="192.0.2.10" +#port="10210" +# Uncomment the following lines to send the JSON logs to a local unix socket +#mode="unix" +#file="/var/run/ulogd.socket" [pcap1] #default file is /var/log/ulogd.pcap