mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-07 07:37:02 +02:00
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
This commit is contained in:
parent
546488559a
commit
54932b4408
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -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)) {
|
||||
|
376
src/log.c
376
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)
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user