From 81ae1953bf7189d2f4aa2b18ff716515920aa6bb Mon Sep 17 00:00:00 2001 From: Robert Tsai Date: Wed, 5 Dec 2007 10:47:29 +0100 Subject: [PATCH] [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. --- doc/configuration.txt | 18 +++++-- include/common/standard.h | 6 +++ include/types/global.h | 3 +- include/types/log.h | 11 ++++ include/types/proxy.h | 3 +- src/cfgparse.c | 38 ++++++++----- src/log.c | 110 ++++++++++++++++++++++++++++---------- src/standard.c | 34 ++++++++++++ 8 files changed, 177 insertions(+), 46 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index c9ba70dcb2..e26fcb6a65 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -84,10 +84,20 @@ group log
[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".
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). must be one of the 24 - standard syslog facilities : + configured with "log global". + +
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). + + 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 3f7bed96ec..248bbe91b9 100644 --- a/include/common/standard.h +++ b/include/common/standard.h @@ -124,6 +124,12 @@ extern int ishex(char s); */ extern const char *invalid_char(const char *name); +/* + * converts 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 to a struct sockaddr_in* which is locally allocated. * The format is "addr:port", where "addr" can be a dotted IPv4 address, diff --git a/include/types/global.h b/include/types/global.h index 56c94c4cd2..18a94b291f 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -59,7 +60,7 @@ struct global { 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 12f8e27675..d15b1ab23d 100644 --- a/include/types/log.h +++ b/include/types/log.h @@ -22,6 +22,8 @@ #ifndef _TYPES_LOG_H #define _TYPES_LOG_H +#include +#include #include #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 240b99b693..ef748936de 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -206,7 +207,7 @@ struct proxy { 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 9473b75ea7..9610cf4c3d 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -425,7 +425,7 @@ int cfg_parse_global(const char *file, int linenum, char **args) 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 @@ int cfg_parse_global(const char *file, int linenum, char **args) } } - 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 @@ int cfg_parse_listen(const char *file, int linenum, char **args) 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 @@ int cfg_parse_listen(const char *file, int linenum, char **args) } } - 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 20f9bb4ff9..e4ecc3c92d 100644 --- a/src/log.c +++ b/src/log.c @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -30,6 +31,9 @@ #include #include +#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 @@ int get_log_facility(const char *fac) 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 @@ int get_log_facility(const char *fac) */ 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 @@ void send_log(struct proxy *p, int level, const char *message, ...) 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++; } } - while (nbloggers-- > 0) { + /* 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); + } + + /* 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 @@ void send_log(struct proxy *p, int level, const char *message, ...) * 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 @@ void send_log(struct proxy *p, int level, const char *message, ...) *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 40ad47e0c2..647a6c8f6b 100644 --- a/src/standard.c +++ b/src/standard.c @@ -77,6 +77,37 @@ const char *limit_r(unsigned long n, char *buffer, int size, const char *alt) return (n) ? ultoa_r(n, buffer, size) : (alt ? alt : ""); } +/* + * converts 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 is a hex digit (0-9, a-f, A-F), else zero. @@ -153,6 +184,9 @@ struct sockaddr_in *str2sa(char *str) 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; -- 2.39.5