[MEDIUM] add support for logging via a UNIX socket
The code in haproxy-1.3.13.1 only supports syslogging to an internet
address. The attached patch:
- Adds support for syslogging to a UNIX domain socket (e.g., /dev/log).
If the address field begins with '/' (absolute file path), then
AF_UNIX is used to construct the socket. Otherwise, AF_INET is used.
- Achieves clean single-source build on both Mac OS X and Linux
(sockaddr_in.sin_len and sockaddr_un.sun_len field aren't always present).
For handling sendto() failures in send_log(), it appears that the existing
code is fine (no need to close/recreate socket) for both UDP and UNIX-domain
syslog server. So I left things alone (did not close/recreate socket).
Closing/recreating socket after each failure would also work, but would lead
to increased amount of unnecessary socket creation/destruction if syslog is
temporarily unavailable for some reason (especially for verbose loggers).
Please consider this patch for inclusion into the upstream haproxy codebase.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index c9ba70d..e26fcb6 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -84,10 +84,20 @@
log <address> <facility> [max level]
Adds a global syslog server. Up to two global servers can be defined. They
will receive logs for startups and exits, as well as all logs from proxies
- configured with "log global". <address> is an IPv4 address optionally
- followed by a colon and an UDP port. If no port is specified, 514 is used
- by default (the standard syslog port). <facility> must be one of the 24
- standard syslog facilities :
+ configured with "log global".
+
+ <address> can be one of:
+
+ - An IPv4 address optionally followed by a colon and an UDP port. If
+ no port is specified, 514 is used by default (the standard syslog
+ port).
+
+ - A filesystem path to a UNIX domain socket, keeping in mind
+ considerations for chroot (be sure the path is accessible inside
+ the chroot) and uid/gid (be sure the path is appropriately
+ writeable).
+
+ <facility> must be one of the 24 standard syslog facilities :
kern user mail daemon auth syslog lpr news
uucp cron auth2 ftp ntp audit alert cron2
diff --git a/include/common/standard.h b/include/common/standard.h
index 3f7bed9..248bbe9 100644
--- a/include/common/standard.h
+++ b/include/common/standard.h
@@ -125,6 +125,12 @@
extern const char *invalid_char(const char *name);
/*
+ * converts <str> to a struct sockaddr_un* which is locally allocated.
+ * The format is "/path", where "/path" is a path to a UNIX domain socket.
+ */
+struct sockaddr_un *str2sun(char *str);
+
+/*
* converts <str> to a struct sockaddr_in* which is locally allocated.
* The format is "addr:port", where "addr" can be a dotted IPv4 address,
* a host name, or empty or "*" to indicate INADDR_ANY.
diff --git a/include/types/global.h b/include/types/global.h
index 56c94c4..18a94b2 100644
--- a/include/types/global.h
+++ b/include/types/global.h
@@ -25,6 +25,7 @@
#include <netinet/in.h>
#include <common/config.h>
+#include <types/log.h>
#include <types/protocols.h>
#include <types/task.h>
@@ -59,7 +60,7 @@
char *pidfile;
int logfac1, logfac2;
int loglev1, loglev2;
- struct sockaddr_in logsrv1, logsrv2;
+ struct logsrv logsrv1, logsrv2;
struct {
int maxpollevents; /* max number of poll events at once */
} tune;
diff --git a/include/types/log.h b/include/types/log.h
index 12f8e27..d15b1ab 100644
--- a/include/types/log.h
+++ b/include/types/log.h
@@ -22,6 +22,8 @@
#ifndef _TYPES_LOG_H
#define _TYPES_LOG_H
+#include <sys/un.h>
+#include <netinet/in.h>
#include <common/config.h>
#define MAX_SYSLOG_LEN 1024
@@ -44,6 +46,15 @@
#define LW_REQHDR 1024 /* request header(s) */
#define LW_RSPHDR 2048 /* response header(s) */
+struct logsrv {
+ union {
+ struct sockaddr addr;
+ struct sockaddr_un un; /* AF_UNIX */
+ struct sockaddr_in in; /* AF_INET */
+ } u;
+};
+
+int logsrv_addrlen(const struct logsrv *logsrv);
#endif /* _TYPES_LOG_H */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 240b99b..ef74893 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -38,6 +38,7 @@
#include <types/acl.h>
#include <types/buffers.h>
#include <types/httperr.h>
+#include <types/log.h>
#include <types/protocols.h>
#include <types/session.h>
#include <types/server.h>
@@ -206,7 +207,7 @@
struct sockaddr_in tproxy_addr; /* non-local address we want to bind to for connect() */
#endif
struct proxy *next;
- struct sockaddr_in logsrv1, logsrv2; /* 2 syslog servers */
+ struct logsrv logsrv1, logsrv2; /* 2 syslog servers */
signed char logfac1, logfac2; /* log facility for both servers. -1 = disabled */
int loglev1, loglev2; /* log level for each server, 7 by default */
int to_log; /* things to be logged (LW_*) */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 9473b75..9610cf4 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -425,7 +425,7 @@
global.pidfile = strdup(args[1]);
}
else if (!strcmp(args[0], "log")) { /* syslog server address */
- struct sockaddr_in *sa;
+ struct logsrv logsrv;
int facility, level;
if (*(args[1]) == 0 || *(args[2]) == 0) {
@@ -448,17 +448,23 @@
}
}
- sa = str2sa(args[1]);
- if (!sa->sin_port)
- sa->sin_port = htons(SYSLOG_PORT);
+ if (args[1][0] == '/') {
+ logsrv.u.addr.sa_family = AF_UNIX;
+ logsrv.u.un = *str2sun(args[1]);
+ } else {
+ logsrv.u.addr.sa_family = AF_INET;
+ logsrv.u.in = *str2sa(args[1]);
+ if (!logsrv.u.in.sin_port)
+ logsrv.u.in.sin_port = htons(SYSLOG_PORT);
+ }
if (global.logfac1 == -1) {
- global.logsrv1 = *sa;
+ global.logsrv1 = logsrv;
global.logfac1 = facility;
global.loglev1 = level;
}
else if (global.logfac2 == -1) {
- global.logsrv2 = *sa;
+ global.logsrv2 = logsrv;
global.logfac2 = facility;
global.loglev2 = level;
}
@@ -1639,7 +1645,7 @@
newsrv->prev_state = newsrv->state;
}
else if (!strcmp(args[0], "log")) { /* syslog server address */
- struct sockaddr_in *sa;
+ struct logsrv logsrv;
int facility;
if (*(args[1]) && *(args[2]) == 0 && !strcmp(args[1], "global")) {
@@ -1668,17 +1674,25 @@
}
}
- sa = str2sa(args[1]);
- if (!sa->sin_port)
- sa->sin_port = htons(SYSLOG_PORT);
+ if (args[1][0] == '/') {
+ logsrv.u.addr.sa_family = AF_UNIX;
+ logsrv.u.un = *str2sun(args[1]);
+ } else {
+ logsrv.u.addr.sa_family = AF_INET;
+ logsrv.u.in = *str2sa(args[1]);
+ if (!logsrv.u.in.sin_port) {
+ logsrv.u.in.sin_port =
+ htons(SYSLOG_PORT);
+ }
+ }
if (curproxy->logfac1 == -1) {
- curproxy->logsrv1 = *sa;
+ curproxy->logsrv1 = logsrv;
curproxy->logfac1 = facility;
curproxy->loglev1 = level;
}
else if (curproxy->logfac2 == -1) {
- curproxy->logsrv2 = *sa;
+ curproxy->logsrv2 = logsrv;
curproxy->logfac2 = facility;
curproxy->loglev2 = level;
}
diff --git a/src/log.c b/src/log.c
index 20f9bb4..e4ecc3c 100644
--- a/src/log.c
+++ b/src/log.c
@@ -18,6 +18,7 @@
#include <syslog.h>
#include <time.h>
#include <unistd.h>
+#include <errno.h>
#include <sys/time.h>
@@ -30,6 +31,9 @@
#include <types/log.h>
#include <types/session.h>
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL (0)
+#endif /* !MSG_NOSIGNAL */
const char *log_facilities[NB_LOG_FACILITIES] = {
"kern", "user", "mail", "daemon",
@@ -140,6 +144,32 @@
return facility;
}
+/*
+ * Return the length of the address endpoint, suitable for use with sendto().
+ */
+int logsrv_addrlen(const struct logsrv *logsrv)
+{
+#ifdef __SOCKADDR_COMMON
+ switch (logsrv->u.addr.sa_family) {
+ case AF_UNIX:
+ return sizeof(logsrv->u.un);
+ case AF_INET:
+ return sizeof(logsrv->u.in);
+ default:
+ break;
+ }
+#else /* !__SOCKADDR_COMMON */
+ switch (logsrv->u.addr.sa_family) {
+ case AF_UNIX:
+ return logsrv->u.un.sun_len;
+ case AF_INET:
+ return logsrv->u.in.sin_len;
+ default:
+ break;
+ }
+#endif /* !__SOCKADDR_COMMON */
+ return -1;
+}
/*
* This function sends a syslog message to both log servers of a proxy,
@@ -149,29 +179,20 @@
*/
void send_log(struct proxy *p, int level, const char *message, ...)
{
- static int logfd = -1; /* syslog UDP socket */
+ static int logfdunix = -1; /* syslog to AF_UNIX socket */
+ static int logfdinet = -1; /* syslog to AF_INET socket */
static long tvsec = -1; /* to force the string to be initialized */
va_list argp;
static char logmsg[MAX_SYSLOG_LEN];
static char *dataptr = NULL;
int fac_level;
int hdr_len, data_len;
- struct sockaddr_in *sa[2];
+ struct logsrv *logsrvs[2];
int facilities[2], loglevel[2];
+ int nblogger;
int nbloggers = 0;
char *log_ptr;
- if (logfd < 0) {
- if ((logfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
- return;
- /* we don't want to receive anything on this socket */
- setsockopt(logfd, SOL_SOCKET, SO_RCVBUF, &zero, sizeof(zero));
- /* need for AIX which does not know about MSG_DONTWAIT */
- if (!MSG_DONTWAIT)
- fcntl(logfd, F_SETFL, O_NONBLOCK);
- shutdown(logfd, SHUT_RD); /* does nothing under Linux, maybe needed for others */
- }
-
if (level < 0 || progname == NULL || message == NULL)
return;
@@ -210,35 +231,70 @@
if (p == NULL) {
if (global.logfac1 >= 0) {
- sa[nbloggers] = &global.logsrv1;
+ logsrvs[nbloggers] = &global.logsrv1;
facilities[nbloggers] = global.logfac1;
loglevel[nbloggers] = global.loglev1;
nbloggers++;
}
if (global.logfac2 >= 0) {
- sa[nbloggers] = &global.logsrv2;
+ logsrvs[nbloggers] = &global.logsrv2;
facilities[nbloggers] = global.logfac2;
loglevel[nbloggers] = global.loglev2;
nbloggers++;
}
} else {
if (p->logfac1 >= 0) {
- sa[nbloggers] = &p->logsrv1;
+ logsrvs[nbloggers] = &p->logsrv1;
facilities[nbloggers] = p->logfac1;
loglevel[nbloggers] = p->loglev1;
nbloggers++;
}
if (p->logfac2 >= 0) {
- sa[nbloggers] = &p->logsrv2;
+ logsrvs[nbloggers] = &p->logsrv2;
facilities[nbloggers] = p->logfac2;
loglevel[nbloggers] = p->loglev2;
nbloggers++;
}
+ }
+
+ /* Lazily set up syslog sockets for protocol families of configured
+ * syslog servers. */
+ for (nblogger = 0; nblogger < nbloggers; nblogger++) {
+ const struct logsrv *logsrv = logsrvs[nblogger];
+ int proto, *plogfd;
+ if (logsrv->u.addr.sa_family == AF_UNIX) {
+ proto = 0;
+ plogfd = &logfdunix;
+ } else {
+ /* sa_family == AF_INET */
+ proto = IPPROTO_UDP;
+ plogfd = &logfdinet;
+ }
+ if (*plogfd >= 0) {
+ /* socket already created. */
+ continue;
+ }
+ if ((*plogfd = socket(logsrv->u.addr.sa_family, SOCK_DGRAM,
+ proto)) < 0) {
+ Alert("socket for logger #%d failed: %s (errno=%d)\n",
+ nblogger + 1, strerror(errno), errno);
+ return;
+ }
+ /* we don't want to receive anything on this socket */
+ setsockopt(*plogfd, SOL_SOCKET, SO_RCVBUF, &zero, sizeof(zero));
+ /* does nothing under Linux, maybe needed for others */
+ shutdown(*plogfd, SHUT_RD);
}
- while (nbloggers-- > 0) {
+ /* Send log messages to syslog server. */
+ for (nblogger = 0; nblogger < nbloggers; nblogger++) {
+ const struct logsrv *logsrv = logsrvs[nblogger];
+ int *plogfd = logsrv->u.addr.sa_family == AF_UNIX ?
+ &logfdunix : &logfdinet;
+ int sent;
+
/* we can filter the level of the messages that are sent to each logger */
- if (level > loglevel[nbloggers])
+ if (level > loglevel[nblogger])
continue;
/* For each target, we may have a different facility.
@@ -248,7 +304,7 @@
* time, we only change the facility in the pre-computed header,
* and we change the pointer to the header accordingly.
*/
- fac_level = (facilities[nbloggers] << 3) + level;
+ fac_level = (facilities[nblogger] << 3) + level;
log_ptr = logmsg + 3; /* last digit of the log level */
do {
*log_ptr = '0' + fac_level % 10;
@@ -258,14 +314,12 @@
*log_ptr = '<';
/* the total syslog message now starts at logptr, for dataptr+data_len-logptr */
-
-#ifndef MSG_NOSIGNAL
- sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT,
- (struct sockaddr *)sa[nbloggers], sizeof(**sa));
-#else
- sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT | MSG_NOSIGNAL,
- (struct sockaddr *)sa[nbloggers], sizeof(**sa));
-#endif
+ sent = sendto(*plogfd, log_ptr, dataptr + data_len - log_ptr,
+ MSG_DONTWAIT | MSG_NOSIGNAL, &logsrv->u.addr, logsrv_addrlen(logsrv));
+ if (sent < 0) {
+ Alert("sendto logger #%d failed: %s (errno=%d)\n",
+ nblogger, strerror(errno), errno);
+ }
}
}
diff --git a/src/standard.c b/src/standard.c
index 40ad47e..647a6c8 100644
--- a/src/standard.c
+++ b/src/standard.c
@@ -77,6 +77,37 @@
return (n) ? ultoa_r(n, buffer, size) : (alt ? alt : "");
}
+/*
+ * converts <str> to a struct sockaddr_un* which is locally allocated.
+ * The format is "/path", where "/path" is a path to a UNIX domain socket.
+ */
+struct sockaddr_un *str2sun(char *str)
+{
+ static struct sockaddr_un sun;
+ int strsz; /* length included null */
+
+ memset(&sun, 0, sizeof(sun));
+ str = strdup(str);
+ if (str == NULL)
+ goto out_nofree;
+
+ strsz = strlen(str) + 1;
+ if (strsz > sizeof(sun.sun_path)) {
+ Alert("Socket path '%s' too long (max %d)\n",
+ str, sizeof(sun.sun_path) - 1);
+ goto out_nofree;
+ }
+
+#ifndef __SOCKADDR_COMMON
+ sun.sun_len = sizeof(sun);
+#endif /* !__SOCKADDR_COMMON */
+ sun.sun_family = AF_UNIX;
+ memcpy(sun.sun_path, str, strsz);
+
+ free(str);
+ out_nofree:
+ return &sun;
+}
/*
* Returns non-zero if character <s> is a hex digit (0-9, a-f, A-F), else zero.
@@ -153,6 +184,9 @@
else
sa.sin_addr = *(struct in_addr *) *(he->h_addr_list);
}
+#ifndef __SOCKADDR_COMMON
+ sa.sin_len = sizeof(sa);
+#endif /* !__SOCKADDR_COMMON */
sa.sin_port = htons(port);
sa.sin_family = AF_INET;