mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-10 17:17:06 +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;
|
||||||
extern THREAD_LOCAL char *logline_rfc5424;
|
extern THREAD_LOCAL char *logline_rfc5424;
|
||||||
|
|
||||||
|
/* syslog UDP message handler */
|
||||||
|
void syslog_fd_handler(int fd);
|
||||||
|
|
||||||
/* Initialize/Deinitialize log buffers used for syslog messages */
|
/* Initialize/Deinitialize log buffers used for syslog messages */
|
||||||
int init_log_buffers();
|
int init_log_buffers();
|
||||||
|
@ -56,6 +56,8 @@ enum pr_mode {
|
|||||||
PR_MODE_HTTP,
|
PR_MODE_HTTP,
|
||||||
PR_MODE_HEALTH,
|
PR_MODE_HEALTH,
|
||||||
PR_MODE_CLI,
|
PR_MODE_CLI,
|
||||||
|
PR_MODE_SYSLOG,
|
||||||
|
PR_MODES
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
enum PR_SRV_STATE_FILE {
|
enum PR_SRV_STATE_FILE {
|
||||||
|
@ -2377,6 +2377,13 @@ int check_config_validity()
|
|||||||
case PR_MODE_CLI:
|
case PR_MODE_CLI:
|
||||||
cfgerr += proxy_cfg_ensure_no_http(curproxy);
|
cfgerr += proxy_cfg_ensure_no_http(curproxy);
|
||||||
break;
|
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)) {
|
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);
|
__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 */
|
/* 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)
|
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->fd = fd;
|
||||||
listener->state = LI_LISTEN;
|
listener->state = LI_LISTEN;
|
||||||
|
|
||||||
|
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;
|
err |= ERR_FATAL | ERR_ALERT;
|
||||||
msg = "UDP is not yet supported on this proxy mode";
|
msg = "UDP is not yet supported on this proxy mode";
|
||||||
goto udp_close_return;
|
goto udp_close_return;
|
||||||
|
}
|
||||||
|
|
||||||
udp_return:
|
udp_return:
|
||||||
if (msg && errlen) {
|
if (msg && errlen) {
|
||||||
|
Loading…
Reference in New Issue
Block a user