From: Emeric Brun Date: Tue, 7 Jul 2020 07:43:24 +0000 (+0200) Subject: MINOR: log: adds syslog udp message handler and parsing. X-Git-Tag: v2.3-dev1~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=54932b4408652e3fa0879350a3aca1dda805c7dc;p=thirdparty%2Fhaproxy.git MINOR: log: adds syslog udp message handler and parsing. 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 --- diff --git a/include/haproxy/log.h b/include/haproxy/log.h index 7f7813797d..683c135a06 100644 --- a/include/haproxy/log.h +++ b/include/haproxy/log.h @@ -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(); diff --git a/include/haproxy/proxy-t.h b/include/haproxy/proxy-t.h index c7e499930c..6216524d48 100644 --- a/include/haproxy/proxy-t.h +++ b/include/haproxy/proxy-t.h @@ -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 { diff --git a/src/cfgparse.c b/src/cfgparse.c index e46a4f6fde..f408d65535 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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)) { diff --git a/src/log.c b/src/log.c index fec610284e..b6d5c45fdd 100644 --- 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 , of size + * it fills , and depending of the detected + * header format and message will point on remaining payload of + * + * 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 + * and 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) diff --git a/src/proto_udp.c b/src/proto_udp.c index d006c4557c..6defe7e342 100644 --- a/src/proto_udp.c +++ b/src/proto_udp.c @@ -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) {