httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From swi...@shells.gnugeneration.com
Subject [PATCH] UNIX domain socket listener and file descriptor passing
Date Thu, 02 Oct 2008 13:27:15 GMT
Hello,

Please reply directly to me as I am not subscribed.

I created a patch for Apache 1.3.34 from debian etch which adds the
ability to set an absolute path for a Listen address.  With this path a
different bind & listen (make_sock()) routine is used to establish a
UNIX domain socket listener in the filesystem.

After accepting a connection on this new type of listener, a recvmsg() is
entered to receive ancillary data for the purposes of passing a TCP socket
descriptor to Apache from an unrelated process.  Upon receiving such a
message, the UNIX domain socket stream is closed and the passed socket
descriptor is treated as if it were simply accepted through a normal TCP
listener.

The potential implications for this new way of getting TCP sockets into
the Apache process surely are more than I can imagine.

For me the reason for creating this patch was to integrate my module httpx
found here:
http://serverkit.org/modules/httpx/

This module does some wacky things to snoop the HTTP/1.1 Host: request line
before passing the socket to it's final destination httpd process via
a named UNIX domain socket.  The goal here was to allow total freedom in
Apache configuration for HTTP/1.1 name-based vhosts, mod_whatever, user
access to configuration file, runs as their user and group from the start,
no suexec needed, etc.  It's understood that httpx potentially violates the
rfc and is potentially quite rude.

Anyways, I think it's interesting enough to support this ability in Apache
upstream.  You can do all sorts of stuff in external processes that
don't necessarily violate the rfc like httpx can, but could potentially add
plenty value once apps can easily pass socket descriptors to the Apache
process.

I'm sure this can be done in Apache2 as well.  I just had an immediate
need to have it in Apache 1.3 since I'm using it already and intend to
put httpx in front of it with some willing guinea pig web users.

Cheers,
Vito Caputo


--- build-tree-apache.orig/apache_1.3.34/src/include/ap_config.h	2008-10-02 06:51:14.000000000
-0500
+++ build-tree-apache/apache_1.3.34/src/include/ap_config.h	2008-10-02 06:17:54.000000000
-0500
@@ -963,6 +963,7 @@
 #include <sys/types.h>
 #include <sys/msg.h>
 #include <sys/socket.h>
+#include <sys/un.h>
 #define NET_SIZE_T size_t
 #define NEED_HASHBANG_EMUL
 #define NONBLOCK_WHEN_MULTI_LISTEN
@@ -1086,6 +1087,7 @@
 #endif
 #if !defined(WIN32) && !defined(NETWARE)
 #include <sys/socket.h>
+#include <sys/un.h>
 #ifdef HAVE_SYS_SELECT_H
 #include <sys/select.h>
 #endif /* HAVE_SYS_SELECT_H */
--- build-tree-apache.orig/apache_1.3.34/src/include/httpd.h	2008-10-02 06:51:15.000000000
-0500
+++ build-tree-apache/apache_1.3.34/src/include/httpd.h	2008-10-02 06:18:32.000000000 -0500
@@ -974,9 +974,21 @@
 };
 
 /* These are more like real hosts than virtual hosts */
+enum listen_addr_type {
+	LISTEN_ADDR_INET,
+	LISTEN_ADDR_UNIX
+};
+
 struct listen_rec {
     listen_rec *next;
-    struct sockaddr_in local_addr;	/* local IP address and port */
+
+    union {
+    	struct sockaddr_in in; /* INET local IP address and port */
+	struct sockaddr_un un; /* UNIX domain socket path */
+    } local_addr;
+
+    enum listen_addr_type addr_type;
+
     int fd;
     int used;			/* Only used during restart */        
 /* more stuff here, like which protocol is bound to the port */
--- build-tree-apache.orig/apache_1.3.34/src/main/http_config.c	2008-10-02 06:51:15.000000000
-0500
+++ build-tree-apache/apache_1.3.34/src/main/http_config.c	2008-10-02 05:25:48.000000000 -0500
@@ -1636,10 +1636,11 @@
     }
     /* allocate a default listener */
     new = ap_pcalloc(p, sizeof(listen_rec));
-    new->local_addr.sin_family = AF_INET;
-    new->local_addr.sin_addr = ap_bind_address;
+    new->addr_type = LISTEN_ADDR_INET;
+    new->local_addr.in.sin_family = AF_INET;
+    new->local_addr.in.sin_addr = ap_bind_address;
     /* Buck ugly cast to get around terniary op bug in some (MS) compilers */
-    new->local_addr.sin_port = htons((unsigned short)(s->port ? s->port 
+    new->local_addr.in.sin_port = htons((unsigned short)(s->port ? s->port 
                                                         : DEFAULT_HTTP_PORT));
     new->fd = -1;
     new->used = 0;
--- build-tree-apache.orig/apache_1.3.34/src/main/http_core.c	2008-10-02 06:51:15.000000000
-0500
+++ build-tree-apache/apache_1.3.34/src/main/http_core.c	2008-10-02 05:30:58.000000000 -0500
@@ -2697,37 +2697,51 @@
         return err;
     }
 
-    ports = strchr(ips, ':');
-    if (ports != NULL) {
-	if (ports == ips) {
-	    return "Missing IP address";
-	}
-	else if (ports[1] == '\0') {
-	    return "Address must end in :<port-number>";
-	}
-	*(ports++) = '\0';
-    }
-    else {
-	ports = ips;
-    }
+    if(ips[0] == '/') {
+    	/* UNIX domain socket listener */
 
-    new=ap_pcalloc(cmd->pool, sizeof(listen_rec));
-    new->local_addr.sin_family = AF_INET;
-    if (ports == ips) { /* no address */
-	new->local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
-    }
-    else {
-	new->local_addr.sin_addr.s_addr = ap_get_virthost_addr(ips, NULL);
-    }
-    errno = 0; /* clear errno before calling strtol */
-    port = ap_strtol(ports, &endptr, 10);
-    if (errno /* some sort of error */
-       || (endptr && *endptr) /* make sure no trailing characters */
-       || port < 1 || port > 65535) /* underflow/overflow */
-    {
-	return "Missing, invalid, or non-numeric port";
+    	new=ap_pcalloc(cmd->pool, sizeof(listen_rec));
+    	new->addr_type = LISTEN_ADDR_UNIX;
+    	new->local_addr.un.sun_family = AF_LOCAL;
+    	ap_snprintf(new->local_addr.un.sun_path, sizeof new->local_addr.un.sun_path, "%s",
ips);
+
+    } else {
+    	/* TCP listener address */
+
+    	ports = strchr(ips, ':');
+    	if (ports != NULL) {
+	    if (ports == ips) {
+	    	return "Missing IP address";
+	    }
+	    else if (ports[1] == '\0') {
+	    	return "Address must end in :<port-number>";
+	    }
+	    *(ports++) = '\0';
+    	}
+    	else {
+	    ports = ips;
+    	}
+
+    	new=ap_pcalloc(cmd->pool, sizeof(listen_rec));
+    	new->addr_type = LISTEN_ADDR_INET;
+    	new->local_addr.in.sin_family = AF_INET;
+    	if (ports == ips) { /* no address */
+	    new->local_addr.in.sin_addr.s_addr = htonl(INADDR_ANY);
+    	}
+    	else {
+	    new->local_addr.in.sin_addr.s_addr = ap_get_virthost_addr(ips, NULL);
+    	}
+    	errno = 0; /* clear errno before calling strtol */
+    	port = ap_strtol(ports, &endptr, 10);
+    	if (errno /* some sort of error */
+       	    || (endptr && *endptr) /* make sure no trailing characters */
+            || port < 1 || port > 65535) /* underflow/overflow */
+    	{
+	    return "Missing, invalid, or non-numeric port";
+    	}
+    	new->local_addr.in.sin_port = htons((unsigned short)port);
     }
-    new->local_addr.sin_port = htons((unsigned short)port);
+
     new->fd = -1;
     new->used = 0;
     new->next = ap_listeners;
--- build-tree-apache.orig/apache_1.3.34/src/main/http_main.c	2008-10-02 06:51:15.000000000
-0500
+++ build-tree-apache/apache_1.3.34/src/main/http_main.c	2008-10-02 06:47:11.000000000 -0500
@@ -3793,7 +3793,8 @@
 #define sock_disable_nagle(s, c)	/* NOOP */
 #endif
 
-static int make_sock(pool *p, const struct sockaddr_in *server)
+
+static int make_sock_in(pool *p, const struct sockaddr_in *server)
 {
     int s;
     int one = 1;
@@ -3809,7 +3810,7 @@
     ap_block_alarms();
     if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
 	    ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
-		    "make_sock: failed to get a socket for %s", addr);
+		    "make_sock_in: failed to get a socket for %s", addr);
 
 	    ap_unblock_alarms();
 	    exit(1);
@@ -3848,7 +3849,7 @@
     if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(int)) < 0) {
 #ifndef _OSD_POSIX
 	ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
-		    "make_sock: for %s, setsockopt: (SO_REUSEADDR)", addr);
+		    "make_sock_in: for %s, setsockopt: (SO_REUSEADDR)", addr);
 	closesocket(s);
 	ap_unblock_alarms();
 	exit(1);
@@ -3858,7 +3859,7 @@
 #if defined(SO_KEEPALIVE) && !defined(MPE)
     if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *) &one, sizeof(int)) < 0) {
 	ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
-		    "make_sock: for %s, setsockopt: (SO_KEEPALIVE)", addr);
+		    "make_sock_in: for %s, setsockopt: (SO_KEEPALIVE)", addr);
 	closesocket(s);
 
 	ap_unblock_alarms();
@@ -3893,7 +3894,7 @@
 	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
 		(char *) &server_conf->send_buffer_size, sizeof(int)) < 0) {
 	    ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf,
-			"make_sock: failed to set SendBufferSize for %s, "
+			"make_sock_in: failed to set SendBufferSize for %s, "
 			"using default", addr);
 	    /* not a fatal error */
 	}
@@ -3908,7 +3909,7 @@
 
     if (bind(s, (struct sockaddr *) server, sizeof(struct sockaddr_in)) == -1) {
 	ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
-	    "make_sock: could not bind to %s", addr);
+	    "make_sock_in: could not bind to %s", addr);
 #ifdef MPE
 	if (ntohs(server->sin_port) < 1024)
 	    GETUSERMODE();
@@ -3925,7 +3926,7 @@
 
     if (listen(s, ap_listenbacklog) == -1) {
 	ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
-	    "make_sock: unable to listen for connections on %s", addr);
+	    "make_sock_in: unable to listen for connections on %s", addr);
 	closesocket(s);
 	ap_unblock_alarms();
 	exit(1);
@@ -3955,7 +3956,7 @@
 			 "socket option SO_ACCEPTFILTER unkown on this machine. Continuing.");
 	     } else {
 	    	ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_INFO, server_conf,
-			 "make_sock: for %s, setsockopt: (SO_ACCEPTFILTER)", addr);
+			 "make_sock_in: for %s, setsockopt: (SO_ACCEPTFILTER)", addr);
 	     }
 	}
     }
@@ -3972,7 +3973,7 @@
     /* protect various fd_sets */
     if (s >= FD_SETSIZE) {
 	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL,
-	    "make_sock: problem listening on %s, filedescriptor (%u) "
+	    "make_sock_in: problem listening on %s, filedescriptor (%u) "
 	    "larger than FD_SETSIZE (%u) "
 	    "found, you probably need to rebuild Apache with a "
 	    "larger FD_SETSIZE", addr, s, FD_SETSIZE);
@@ -3985,6 +3986,99 @@
 }
 
 
+/* based loosely on make_sock_in() above which was originally make_sock() */
+/* note that we don't try to do all the socket tuning that make_sock_in()
+ * does, because that has to be done by the real listener (httpx) */
+static int make_sock_un(pool *p, const struct sockaddr_un *server)
+{
+    int s;
+    char addr[512];
+
+    ap_snprintf(addr, sizeof(addr), "UNIX path: %s", server->sun_path);
+
+    ap_block_alarms();
+    if ((s = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) {
+	    ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
+		    "make_sock_un: failed to get a socket for %s", addr);
+
+	    ap_unblock_alarms();
+	    exit(1);
+    }
+
+    /* Solaris (probably versions 2.4, 2.5, and 2.5.1 with various levels
+     * of tcp patches) has some really weird bugs where if you dup the
+     * socket now it breaks things across SIGHUP restarts.  It'll either
+     * be unable to bind, or it won't respond.
+     */
+#if defined (SOLARIS2) && SOLARIS2 < 260
+#define WORKAROUND_SOLARIS_BUG
+#endif
+
+    /* PR#1282 Unixware 1.x appears to have the same problem as solaris */
+#if defined (UW) && UW < 200
+#define WORKAROUND_SOLARIS_BUG
+#endif
+
+    /* PR#1973 NCR SVR4 systems appear to have the same problem */
+#if defined (MPRAS)
+#define WORKAROUND_SOLARIS_BUG
+#endif
+
+#ifndef WORKAROUND_SOLARIS_BUG
+#ifndef BEOS /* this won't work for BeOS sockets!! */
+    s = ap_slack(s, AP_SLACK_HIGH);
+#endif
+
+    ap_note_cleanups_for_socket_ex(p, s, 1);	/* arrange to close on exec or restart */
+#ifdef TPF
+    os_note_additional_cleanups(p, s);
+#endif /* TPF */
+#endif
+
+    unlink(server->sun_path);
+
+    if (bind(s, (struct sockaddr *) server, sizeof(struct sockaddr_un)) == -1) {
+	ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
+	    "make_sock_un: could not bind to %s", addr);
+
+	closesocket(s);
+	ap_unblock_alarms();
+	exit(1);
+    }
+
+    if (listen(s, ap_listenbacklog) == -1) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
+	    "make_sock_in: unable to listen for connections on %s", addr);
+	closesocket(s);
+	ap_unblock_alarms();
+	exit(1);
+    }
+
+#ifdef WORKAROUND_SOLARIS_BUG
+    s = ap_slack(s, AP_SLACK_HIGH);
+
+    ap_note_cleanups_for_socket_ex(p, s, 1);	/* arrange to close on exec or restart */
+#endif
+    ap_unblock_alarms();
+
+#ifdef CHECK_FD_SETSIZE
+    /* protect various fd_sets */
+    if (s >= FD_SETSIZE) {
+	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL,
+	    "make_sock_in: problem listening on %s, filedescriptor (%u) "
+	    "larger than FD_SETSIZE (%u) "
+	    "found, you probably need to rebuild Apache with a "
+	    "larger FD_SETSIZE", addr, s, FD_SETSIZE);
+	closesocket(s);
+	exit(1);
+    }
+#endif
+
+    return s;
+}
+
+
+
 /*
  * During a restart we keep track of the old listeners here, so that we
  * can re-use the sockets.  We have to do this because we won't be able
@@ -4134,7 +4228,19 @@
     for (;;) {
 	fd = find_listener(lr);
 	if (fd < 0) {
-	    fd = make_sock(p, &lr->local_addr);
+	    switch(lr->addr_type) {
+	    	case LISTEN_ADDR_INET:
+	    		fd = make_sock_in(p, &lr->local_addr.in);
+			break;
+
+		case LISTEN_ADDR_UNIX:
+	    		fd = make_sock_un(p, &lr->local_addr.un);
+			break;
+
+		default:
+			/* impossible */
+			break;
+	    }
 	}
 	else {
 	    ap_note_cleanups_for_socket_ex(p, fd, 1);
@@ -4494,7 +4600,7 @@
     NET_SIZE_T clen;
     struct sockaddr sa_server;
     struct sockaddr sa_client;
-    listen_rec *lr;
+    listen_rec *lr = ap_listeners;
 
     /* All of initialization is a critical section, we don't care if we're
      * told to HUP or USR1 before we're done initializing.  For example,
@@ -4691,8 +4797,62 @@
 		}
 	    }
 
-	    if (csd >= 0)
+	    if (csd >= 0) {
+	    	if(lr->addr_type == LISTEN_ADDR_UNIX) {
+		    /* if this listener is a unix domain socket, we need to
+		     * receive the passed socket descriptor. */
+			struct msghdr	msg;
+			struct iovec	iov[1];
+			ssize_t		n;
+			char		b;
+
+			union {
+				struct cmsghdr	cm;
+				char		control[CMSG_SPACE(sizeof(int))];
+			} control_un;
+			struct cmsghdr	*cmptr;
+
+			msg.msg_control = control_un.control;
+			msg.msg_controllen = sizeof(control_un.control);
+			msg.msg_name = NULL;
+			msg.msg_namelen = 0;
+			iov[0].iov_base = &b;
+			iov[0].iov_len = 1;
+			msg.msg_iov = iov;
+			msg.msg_iovlen = 1;
+
+			n = recvmsg(csd, &msg, 0);
+			if(n <= 0) {
+			    ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
+				"accept UNIX: giving up.");
+			    clean_child_exit(APEXIT_CHILDFATAL);
+			}
+
+			if((cmptr = CMSG_FIRSTHDR(&msg)) != NULL &&
+			   cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
+			   	if(cmptr->cmsg_level != SOL_SOCKET) {
+				    ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
+					"accept UNIX: giving up.");
+				    clean_child_exit(APEXIT_CHILDFATAL);
+				}
+				if(cmptr->cmsg_type != SCM_RIGHTS) {
+				    ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
+					"accept UNIX: giving up.");
+				    clean_child_exit(APEXIT_CHILDFATAL);
+				}
+				close(csd);
+				csd = *((int *)CMSG_DATA(cmptr));
+				clen = sizeof(sa_client);
+				getpeername(csd, &sa_client, &clen);
+			} else {
+			    ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
+				"accept UNIX: giving up.");
+			    clean_child_exit(APEXIT_CHILDFATAL);
+			}
+		}
+
 		break;		/* We have a socket ready for reading */
+	    }
 	    else {
 
 		/* Our old behaviour here was to continue after accept()
@@ -6620,8 +6780,20 @@
     printf("   Listening on port(s):");
     lr = ap_listeners;
     do {
-       printf(" %d", ntohs(lr->local_addr.sin_port));
-       lr = lr->next;
+    	switch(lr->addr_type) {
+	    case LISTEN_ADDR_INET:
+		printf(" %d", ntohs(lr->local_addr.in.sin_port));
+		break;
+
+	    case LISTEN_ADDR_INET:
+		printf(" %s", lr->local_addr.un.sun_path);
+		break;
+
+	    default:
+		/* impossible */
+		break;
+	}
+    	lr = lr->next;
     } while(lr && lr != ap_listeners);
     
     /* Display dynamic modules loaded */

Mime
View raw message