]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: log: adds syslog udp message handler and parsing.
authorEmeric Brun <ebrun@haproxy.com>
Tue, 7 Jul 2020 07:43:24 +0000 (09:43 +0200)
committerWilly Tarreau <w@1wt.eu>
Wed, 15 Jul 2020 15:50:12 +0000 (17:50 +0200)
This patch introduce a new fd handler used to parse syslog
message on udp.

The parsing function returns level, facility and metadata that
can be immediatly reused to forward message to a log server.

This handler is enabled on udp listeners if proxy is internally set
to mode PR_MODE_SYSLOG

include/haproxy/log.h
include/haproxy/proxy-t.h
src/cfgparse.c
src/log.c
src/proto_udp.c

index 7f7813797d414554258a8fcb2137255bf1725a33..683c135a0630978629e4552db969a6c2548f8c8b 100644 (file)
@@ -46,6 +46,8 @@ extern unsigned int dropped_logs;
 extern THREAD_LOCAL char *logline;
 extern THREAD_LOCAL char *logline_rfc5424;
 
+/* syslog UDP message handler */
+void syslog_fd_handler(int fd);
 
 /* Initialize/Deinitialize log buffers used for syslog messages */
 int init_log_buffers();
index c7e499930cae8df95c849c01c7862e02f499222e..6216524d482aaa9a0ff63c08a0df35f3a0c3c68a 100644 (file)
@@ -56,6 +56,8 @@ enum pr_mode {
        PR_MODE_HTTP,
        PR_MODE_HEALTH,
        PR_MODE_CLI,
+       PR_MODE_SYSLOG,
+       PR_MODES
 } __attribute__((packed));
 
 enum PR_SRV_STATE_FILE {
index e46a4f6fde92155a2286564bfb74c0c70bc8da1a..f408d655357b9bdb67baa138bab8613841e71c7f 100644 (file)
@@ -2377,6 +2377,13 @@ int check_config_validity()
                case PR_MODE_CLI:
                        cfgerr += proxy_cfg_ensure_no_http(curproxy);
                        break;
+               case PR_MODE_SYSLOG:
+               case PR_MODES:
+                       /* should not happen, bug gcc warn missing switch statement */
+                       ha_alert("config : %s '%s' cannot use syslog mode for this proxy.\n",
+                                proxy_type_str(curproxy), curproxy->id);
+                       cfgerr++;
+                       break;
                }
 
                if (curproxy != global.stats_fe && (curproxy->cap & PR_CAP_FE) && LIST_ISEMPTY(&curproxy->conf.listeners)) {
index fec610284eae908b7d7b0a4c23e902299750f9dd..b6d5c45fdd4267138c3866d87ae86b68c99e235a 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -3166,6 +3166,382 @@ void app_log(struct list *logsrvs, struct buffer *tag, int level, const char *fo
 
        __send_log(logsrvs, tag, level, logline, data_len, default_rfc5424_sd_log_format, 2);
 }
+/*
+ * This function parse a received log message <buf>, of size <buflen>
+ * it fills <level>, <facility> and <metadata> depending of the detected
+ * header format and message will point on remaining payload of <size>
+ *
+ * <metadata> must point on a preallocated array of LOG_META_FIELDS*sizeof(struct ist)
+ * struct ist len will be set to 0 if field is not found
+ * <level> and <facility> will be set to -1 if not found.
+ */
+void parse_log_message(char *buf, size_t buflen, int *level, int *facility,
+                       struct ist *metadata, char **message, size_t *size)
+{
+
+       char *p;
+       int fac_level = 0;
+
+       *level = *facility = -1;
+
+       *message = buf;
+       *size = buflen;
+
+       memset(metadata, 0, LOG_META_FIELDS*sizeof(struct ist));
+
+       p = buf;
+       if (*size < 2 || *p != '<')
+               return;
+
+       p++;
+       while (*p != '>') {
+               if (*p > '9' || *p < '0')
+                       return;
+               fac_level = 10*fac_level + (*p - '0');
+               p++;
+               if ((p - buf) > buflen)
+                       return;
+       }
+
+       *facility = fac_level >> 3;
+       *level = fac_level & 0x7;
+       p++;
+
+       metadata[LOG_META_PRIO] = ist2(buf, p - buf);
+
+       buflen -= p - buf;
+       buf = p;
+
+       *size = buflen;
+       *message = buf;
+
+       /* for rfc5424, prio is always followed by '1' and ' ' */
+       if ((*size > 2) && (p[0] == '1') && (p[1] == ' ')) {
+               /* format is always '1 TIMESTAMP HOSTNAME TAG PID MSGID STDATA '
+                * followed by message.
+                * Each header field can present NILVALUE: '-'
+                */
+
+               p += 2;
+               /* timestamp is NILVALUE '-' */
+               if (*size > 2 && (p[0] == '-') && p[1] == ' ') {
+                       metadata[LOG_META_TIME] = ist2(p, 1);
+                       p++;
+               }
+               else if (*size > LOG_ISOTIME_MINLEN) {
+                       metadata[LOG_META_TIME].ptr = p;
+
+                       /* check if optionnal secfrac is present
+                        * in timestamp.
+                        * possible format are:
+                        * ex: '1970-01-01T00:00:00.000000Z'
+                        *     '1970-01-01T00:00:00.000000+00:00'
+                        *     '1970-01-01T00:00:00.000000-00:00'
+                        *     '1970-01-01T00:00:00Z'
+                        *     '1970-01-01T00:00:00+00:00'
+                        *     '1970-01-01T00:00:00-00:00'
+                        */
+                       p += 19;
+                       if (*p == '.') {
+                               p++;
+                               if ((p - buf) >= buflen)
+                                       goto bad_format;
+                               while (*p != 'Z' && *p != '+' && *p != '-') {
+                                       if ((unsigned char)(*p - '0') > 9)
+                                               goto bad_format;
+
+                                       p++;
+                                       if ((p - buf) >= buflen)
+                                               goto bad_format;
+                               }
+                       }
+
+                       if (*p == 'Z')
+                               p++;
+                       else
+                               p += 6; /* case of '+00:00 or '-00:00' */
+
+                       if ((p - buf) >= buflen || *p != ' ')
+                               goto bad_format;
+                       metadata[LOG_META_TIME].len = p - metadata[LOG_META_TIME].ptr;
+               }
+               else
+                       goto bad_format;
+
+
+               p++;
+               if ((p - buf) >= buflen || *p == ' ')
+                       goto bad_format;
+
+               metadata[LOG_META_HOST].ptr = p;
+               while (*p != ' ') {
+                       p++;
+                       if ((p - buf) >= buflen)
+                               goto bad_format;
+               }
+               metadata[LOG_META_HOST].len = p - metadata[LOG_META_HOST].ptr;
+               if (metadata[LOG_META_HOST].len == 1 && metadata[LOG_META_HOST].ptr[0] == '-')
+                       metadata[LOG_META_HOST].len = 0;
+
+               p++;
+               if ((p - buf) >= buflen || *p == ' ')
+                       goto bad_format;
+
+               metadata[LOG_META_TAG].ptr = p;
+               while (*p != ' ') {
+                       p++;
+                       if ((p - buf) >= buflen)
+                               goto bad_format;
+               }
+               metadata[LOG_META_TAG].len = p - metadata[LOG_META_TAG].ptr;
+               if (metadata[LOG_META_TAG].len == 1 && metadata[LOG_META_TAG].ptr[0] == '-')
+                       metadata[LOG_META_TAG].len = 0;
+
+               p++;
+               if ((p - buf) >= buflen || *p == ' ')
+                       goto bad_format;
+
+               metadata[LOG_META_PID].ptr = p;
+               while (*p != ' ') {
+                       p++;
+                       if ((p - buf) >= buflen)
+                               goto bad_format;
+               }
+               metadata[LOG_META_PID].len = p - metadata[LOG_META_PID].ptr;
+               if (metadata[LOG_META_PID].len == 1 && metadata[LOG_META_PID].ptr[0] == '-')
+                       metadata[LOG_META_PID].len = 0;
+
+               p++;
+               if ((p - buf) >= buflen || *p == ' ')
+                       goto bad_format;
+
+               metadata[LOG_META_MSGID].ptr = p;
+               while (*p != ' ') {
+                       p++;
+                       if ((p - buf) >= buflen)
+                               goto bad_format;
+               }
+               metadata[LOG_META_MSGID].len = p - metadata[LOG_META_MSGID].ptr;
+               if (metadata[LOG_META_MSGID].len == 1 && metadata[LOG_META_MSGID].ptr[0] == '-')
+                       metadata[LOG_META_MSGID].len = 0;
+
+               p++;
+               if ((p - buf) >= buflen || *p == ' ')
+                       goto bad_format;
+
+               /* structured data format is:
+                * ex:
+                *    '[key1=value1 key2=value2][key3=value3]'
+                *
+                * space is invalid outside [] because
+                * considered as the end of structured data field
+                */
+               metadata[LOG_META_STDATA].ptr = p;
+               if (*p == '[') {
+                       int elem = 0;
+
+                       while (1) {
+                               if (elem) {
+                                       /* according to rfc this char is escaped in param values */
+                                       if (*p == ']' && *(p-1) != '\\')
+                                               elem = 0;
+                               }
+                               else {
+                                       if (*p == '[')
+                                               elem = 1;
+                                       else if (*p == ' ')
+                                               break;
+                                       else
+                                               goto bad_format;
+                               }
+                               p++;
+                               if ((p - buf) >= buflen)
+                                       goto bad_format;
+                       }
+               }
+               else if (*p == '-') {
+                       /* case of NILVALUE */
+                       p++;
+                       if ((p - buf) >= buflen || *p != ' ')
+                               goto bad_format;
+               }
+               else
+                       goto bad_format;
+
+               metadata[LOG_META_STDATA].len = p - metadata[LOG_META_STDATA].ptr;
+               if (metadata[LOG_META_STDATA].len == 1 && metadata[LOG_META_STDATA].ptr[0] == '-')
+                       metadata[LOG_META_STDATA].len = 0;
+
+               p++;
+
+               buflen -= p - buf;
+               buf = p;
+
+               *size = buflen;
+               *message = p;
+       }
+       else if (*size > LOG_LEGACYTIME_LEN) {
+               int m;
+
+               /* supported header format according to rfc3164.
+                * ex:
+                *  'Jan  1 00:00:00 HOSTNAME TAG[PID]: '
+                *  or 'Jan  1 00:00:00 HOSTNAME TAG: '
+                *  or 'Jan  1 00:00:00 HOSTNAME '
+                * Note: HOSTNAME is mandatory, and day
+                * of month uses a single space prefix if
+                * less than 10 to ensure hour offset is
+                * always the same.
+                */
+
+               /* Check month to see if it correspond to a rfc3164
+                * header ex 'Jan  1 00:00:00' */
+               for (m = 0; m < 12; m++)
+                       if (!memcmp(monthname[m], p, 3))
+                               break;
+               /* Month not found */
+               if (m == 12)
+                       goto bad_format;
+
+               metadata[LOG_META_TIME] = ist2(p, LOG_LEGACYTIME_LEN);
+
+               p += LOG_LEGACYTIME_LEN;
+               if ((p - buf) >= buflen || *p != ' ')
+                       goto bad_format;
+
+               p++;
+               if ((p - buf) >= buflen || *p == ' ')
+                       goto bad_format;
+
+               metadata[LOG_META_HOST].ptr = p;
+               while (*p != ' ') {
+                       p++;
+                       if ((p - buf) >= buflen)
+                               goto bad_format;
+               }
+               metadata[LOG_META_HOST].len = p - metadata[LOG_META_HOST].ptr;
+
+               /* TAG seems to no be mandatory */
+               p++;
+
+               buflen -= p - buf;
+               buf = p;
+
+               *size = buflen;
+               *message = buf;
+
+               if (!buflen)
+                       return;
+
+               while (((p  - buf) < buflen) && *p != ' ' && *p != ':')
+                       p++;
+
+               /* a tag must present a trailing ':' */
+               if (((p - buf) >= buflen) || *p != ':')
+                       return;
+               p++;
+               /* followed by a space */
+               if (((p - buf) >= buflen) || *p != ' ')
+                       return;
+
+               /* rewind to parse tag and pid */
+               p = buf;
+               metadata[LOG_META_TAG].ptr = p;
+               /* we have the guarantee that ':' will be reach before size limit */
+               while (*p != ':') {
+                       if (*p == '[') {
+                               metadata[LOG_META_TAG].len = p - metadata[LOG_META_TAG].ptr;
+                               metadata[LOG_META_PID].ptr = p + 1;
+                       }
+                       else if (*p == ']' && metadata[LOG_META_PID].ptr) {
+                               if (p[1] != ':')
+                                       return;
+                               metadata[LOG_META_PID].len = p - metadata[LOG_META_PID].ptr;
+                       }
+                       p++;
+               }
+               if (!metadata[LOG_META_TAG].len)
+                       metadata[LOG_META_TAG].len = p - metadata[LOG_META_TAG].ptr;
+
+               /* let pass ':' and ' ', we still have warranty size is large enought */
+               p += 2;
+
+               buflen -= p - buf;
+               buf = p;
+
+               *size = buflen;
+               *message = buf;
+       }
+
+       return;
+
+bad_format:
+       /* bad syslog format, we reset all parsed syslog fields
+        * but priority is kept because we are able to re-build
+        * this message using LOF_FORMAT_PRIO.
+        */
+       metadata[LOG_META_TIME].len = 0;
+       metadata[LOG_META_HOST].len = 0;
+       metadata[LOG_META_TAG].len = 0;
+       metadata[LOG_META_PID].len = 0;
+       metadata[LOG_META_MSGID].len = 0;
+       metadata[LOG_META_STDATA].len = 0;
+
+       return;
+}
+
+/*
+ * UDP syslog fd handler
+ */
+void syslog_fd_handler(int fd)
+{
+       static THREAD_LOCAL struct ist metadata[LOG_META_FIELDS];
+       ssize_t ret = 0;
+       struct buffer *buf = get_trash_chunk();
+       size_t size;
+       char *message;
+       int level;
+       int facility;
+       struct listener *l = objt_listener(fdtab[fd].owner);
+       int max_accept;
+
+       if(!l)
+               ABORT_NOW();
+
+       if (fdtab[fd].ev & FD_POLL_IN) {
+
+               if (!fd_recv_ready(fd))
+                       return;
+
+               max_accept = l->maxaccept ? l->maxaccept : 1;
+
+               do {
+                       /* Source address */
+                       struct sockaddr_storage saddr = {0};
+                       socklen_t saddrlen;
+
+                       saddrlen = sizeof(saddr);
+
+                       ret = recvfrom(fd, buf->area, buf->size, 0, (struct sockaddr *)&saddr, &saddrlen);
+                       if (ret < 0) {
+                               if (errno == EINTR)
+                                       continue;
+                               if (errno == EAGAIN)
+                                       fd_cant_recv(fd);
+                               goto out;
+                       }
+                       buf->data = ret;
+
+                       parse_log_message(buf->area, buf->data, &level, &facility, metadata, &message, &size);
+
+                       process_send_log(&l->bind_conf->frontend->logsrvs, level, facility, metadata, message, size);
+
+               } while (--max_accept);
+       }
+
+out:
+       return;
+}
 
 /* parse the "show startup-logs" command, returns 1 if a message is returned, otherwise zero */
 static int cli_parse_show_startup_logs(char **args, char *payload, struct appctx *appctx, void *private)
index d006c4557c8cfe9136e4a13cfa665a2b58de1a81..6defe7e342e99efb2c9aff88e86fc93d8fc3cd88 100644 (file)
@@ -303,9 +303,14 @@ int udp_bind_listener(struct listener *listener, char *errmsg, int errlen)
        listener->fd = fd;
        listener->state = LI_LISTEN;
 
-       err |= ERR_FATAL | ERR_ALERT;
-       msg = "UDP is not yet supported on this proxy mode";
-       goto udp_close_return;
+       if (listener->bind_conf->frontend->mode == PR_MODE_SYSLOG)
+               fd_insert(fd, listener, syslog_fd_handler,
+                         thread_mask(listener->bind_conf->bind_thread) & all_threads_mask);
+       else {
+               err |= ERR_FATAL | ERR_ALERT;
+               msg = "UDP is not yet supported on this proxy mode";
+               goto udp_close_return;
+       }
 
  udp_return:
        if (msg && errlen) {