]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] add support for logging via a UNIX socket
authorRobert Tsai <rob@xoopit.com>
Wed, 5 Dec 2007 09:47:29 +0000 (10:47 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 5 Dec 2007 09:47:29 +0000 (10:47 +0100)
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
include/common/standard.h
include/types/global.h
include/types/log.h
include/types/proxy.h
src/cfgparse.c
src/log.c
src/standard.c

index c9ba70dcb2053c6e78e77641e1d619e3046f37fa..e26fcb6a652bcce4c5068600462506533a929ab1 100644 (file)
@@ -84,10 +84,20 @@ group <group name>
 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
index 3f7bed96ecd32029fdb5158f089414d426ee056e..248bbe91b99c17e4c72717ae2bdfaf2671301a1e 100644 (file)
@@ -124,6 +124,12 @@ extern int ishex(char s);
  */
 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,
index 56c94c4cd2f7f0a5deb7a0252884aecb2ce967f1..18a94b291f4cf2ce60f620ff4197758767af801c 100644 (file)
@@ -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 @@ 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;
index 12f8e27675220f59177d8a33ae2ec6d142363ad7..d15b1ab23d079657962328fcbbfdfed76754ce82 100644 (file)
@@ -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
 #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 */
 
index 240b99b6931bd4b9b0914f07d944650d3af25ad0..ef748936de7c683c2eb5608ffc45c73552e65e68 100644 (file)
@@ -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 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_*) */
index 9473b75ea792b63d1fc05090c1b241b73b4ec5a5..9610cf4c3d352339dfa83ee673301126d73121ea 100644 (file)
@@ -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;
                        }
index 20f9bb4ff9ac6770c158ce895ae6712a107cd114..e4ecc3c92dcfeab15271dbc7f56dd9c8625aabcd 100644 (file)
--- 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 @@ 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);
+               }
        }
 }
 
index 40ad47e0c23370de0f3871f4023f77d02698a2b5..647a6c8f6b563a644dd85c0ef8af7c7287ba1d51 100644 (file)
@@ -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 <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 @@ 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;