]> granicus.if.org Git - libevent/commitdiff
http: support unix domain sockets
authorSean Young <sean@mess.org>
Sun, 31 Jan 2016 11:31:00 +0000 (11:31 +0000)
committerAzat Khuzhin <azat@libevent.org>
Tue, 10 Aug 2021 20:22:10 +0000 (23:22 +0300)
There are no standard for encoding a unix socket in an url. nginx uses:

    http://unix:/path/to/unix/socket:/httppath

The second colon is needed to delimit where the unix path ends and where
the rest of the url continues.

Signed-off-by: Sean Young <sean@mess.org>
http-internal.h
http.c
include/event2/http.h
test/regress_http.c

index dfd5b01bf658478d21873e9dba0a61a8345a797b..a5844e1de1c32c8515e4d755569176be2944f725 100644 (file)
@@ -61,6 +61,9 @@ struct evhttp_connection {
        char *address;                  /* address to connect to */
        ev_uint16_t port;
 
+#ifndef _WIN32
+       char *unixsocket;
+#endif
        size_t max_headers_size;
        ev_uint64_t max_body_size;
 
diff --git a/http.c b/http.c
index 28b59b8821b3aefb7d09e3df52626be350f9fef0..0a7efb420bf7587727fc8229384a4e5ca7fc8153 100644 (file)
--- a/http.c
+++ b/http.c
@@ -28,6 +28,8 @@
 #include "event2/event-config.h"
 #include "evconfig-private.h"
 
+#define member_size(type, member) sizeof(((type *)0)->member)
+
 #ifdef EVENT__HAVE_SYS_PARAM_H
 #include <sys/param.h>
 #endif
@@ -1354,6 +1356,9 @@ evhttp_connection_free(struct evhttp_connection *evcon)
        if (evcon->address != NULL)
                mm_free(evcon->address);
 
+       if (evcon->unixsocket != NULL)
+               mm_free(evcon->unixsocket);
+
        mm_free(evcon);
 }
 
@@ -2513,21 +2518,16 @@ evhttp_connection_new(const char *address, ev_uint16_t port)
        return (evhttp_connection_base_new(NULL, NULL, address, port));
 }
 
-struct evhttp_connection *
-evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev,
-    const char *address, ev_uint16_t port)
+static struct evhttp_connection *
+evhttp_connection_new_(struct event_base *base, struct bufferevent* bev)
 {
-       struct evhttp_connection *evcon = NULL;
-
-       event_debug(("Attempting connection to %s:%d\n", address, port));
+       struct evhttp_connection *evcon;
 
        if ((evcon = mm_calloc(1, sizeof(struct evhttp_connection))) == NULL) {
                event_warn("%s: calloc failed", __func__);
                goto error;
        }
 
-       evcon->port = port;
-
        evcon->max_headers_size = EV_SIZE_MAX;
        evcon->max_body_size = EV_SIZE_MAX;
 
@@ -2538,11 +2538,6 @@ evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_bas
 
        evcon->retry_cnt = evcon->retry_max = 0;
 
-       if ((evcon->address = mm_strdup(address)) == NULL) {
-               event_warn("%s: strdup failed", __func__);
-               goto error;
-       }
-
        if (bev == NULL) {
                if (!(bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE))) {
                        event_warn("%s: bufferevent_socket_new failed", __func__);
@@ -2567,7 +2562,6 @@ evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_bas
            bufferevent_get_priority(bev),
            evhttp_deferred_read_cb, evcon);
 
-       evcon->dns_base = dnsbase;
        evcon->ai_family = AF_UNSPEC;
 
        return (evcon);
@@ -2578,6 +2572,63 @@ evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_bas
        return (NULL);
 }
 
+#ifndef _WIN32
+struct evhttp_connection *
+evhttp_connection_base_bufferevent_unix_new(struct event_base *base, struct bufferevent* bev, const char *unixsocket)
+{
+       struct evhttp_connection *evcon;
+
+       if (strlen(unixsocket) >= member_size(struct sockaddr_un, sun_path)) {
+               event_warn("%s: unix socket too long", __func__);
+               return NULL;
+       }
+
+       evcon = evhttp_connection_new_(base, bev);
+       if (evcon == NULL)
+               goto error;
+
+       if ((evcon->unixsocket = mm_strdup(unixsocket)) == NULL) {
+               event_warn("%s: strdup failed", __func__);
+               goto error;
+       }
+
+       evcon->ai_family = AF_UNIX;
+
+       return (evcon);
+ error:
+       if (evcon != NULL)
+               evhttp_connection_free(evcon);
+       return (NULL);
+}
+#endif
+
+struct evhttp_connection *
+evhttp_connection_base_bufferevent_new(struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev,
+    const char *address, unsigned short port)
+{
+       struct evhttp_connection *evcon;
+
+       event_debug(("Attempting connection to %s:%d\n", address, port));
+
+       evcon = evhttp_connection_new_(base, bev);
+       if (evcon == NULL)
+               goto error;
+
+       if ((evcon->address = mm_strdup(address)) == NULL) {
+               event_warn("%s: strdup failed", __func__);
+               goto error;
+       }
+       evcon->port = port;
+       evcon->dns_base = dnsbase;
+
+       return (evcon);
+error:
+       if (evcon != NULL)
+               evhttp_connection_free(evcon);
+       return (NULL);
+}
+
+
 struct bufferevent* evhttp_connection_get_bufferevent(struct evhttp_connection *evcon)
 {
        return evcon->bufev;
@@ -2792,7 +2843,16 @@ evhttp_connection_connect_(struct evhttp_connection *evcon)
                        socklen = sizeof(struct sockaddr_in6);
                }
                ret = bufferevent_socket_connect(evcon->bufev, sa, socklen);
-       } else {
+       }
+#ifndef _WIN32
+       else if (evcon->unixsocket) {
+               struct sockaddr_un sockaddr;
+               sockaddr.sun_family = AF_UNIX;
+               strcpy(sockaddr.sun_path, evcon->unixsocket);
+               ret = bufferevent_socket_connect(evcon->bufev, (const struct sockaddr*)&sockaddr, sizeof(sockaddr));
+       }
+#endif
+       else {
                ret = bufferevent_socket_connect_hostname(evcon->bufev,
                                evcon->dns_base, evcon->ai_family, address, evcon->port);
        }
@@ -4456,7 +4516,6 @@ evhttp_get_request_connection(
        evutil_socket_t fd, struct sockaddr *sa, ev_socklen_t salen)
 {
        struct evhttp_connection *evcon;
-       char *hostname = NULL, *portname = NULL;
        struct bufferevent* bev = NULL;
 
 #ifdef EVENT__HAVE_STRUCT_SOCKADDR_UN
@@ -4466,24 +4525,45 @@ evhttp_get_request_connection(
        }
 #endif
 
-       name_from_addr(sa, salen, &hostname, &portname);
-       if (hostname == NULL || portname == NULL) {
-               if (hostname) mm_free(hostname);
-               if (portname) mm_free(portname);
-               return (NULL);
+#ifndef _WIN32
+       if (sa->sa_family == AF_UNIX) {
+               struct sockaddr_un *sockaddr = (struct sockaddr_un *)sa;
+
+               event_debug(("%s: new request from unix socket on "
+                       EV_SOCK_FMT"\n", __func__, EV_SOCK_ARG(fd)));
+
+               /* we need a connection object to put the http request on */
+               if (http->bevcb != NULL) {
+                       bev = (*http->bevcb)(http->base, http->bevcbarg);
+               }
+
+               evcon = evhttp_connection_base_bufferevent_unix_new(http->base,
+                       bev, sockaddr->sun_path);
        }
+       else
+#endif
+       {
+               char *hostname = NULL, *portname = NULL;
+
+               name_from_addr(sa, salen, &hostname, &portname);
+               if (hostname == NULL || portname == NULL) {
+                       if (hostname) mm_free(hostname);
+                       if (portname) mm_free(portname);
+                       return (NULL);
+               }
 
-       event_debug(("%s: new request from %s:%s on "EV_SOCK_FMT"\n",
-               __func__, hostname, portname, EV_SOCK_ARG(fd)));
+               event_debug(("%s: new request from %s:%s on "EV_SOCK_FMT"\n",
+                       __func__, hostname, portname, EV_SOCK_ARG(fd)));
 
-       /* we need a connection object to put the http request on */
-       if (http->bevcb != NULL) {
-               bev = (*http->bevcb)(http->base, http->bevcbarg);
+               /* we need a connection object to put the http request on */
+               if (http->bevcb != NULL) {
+                       bev = (*http->bevcb)(http->base, http->bevcbarg);
+               }
+               evcon = evhttp_connection_base_bufferevent_new(
+                       http->base, NULL, bev, hostname, atoi(portname));
+               mm_free(hostname);
+               mm_free(portname);
        }
-       evcon = evhttp_connection_base_bufferevent_new(
-               http->base, NULL, bev, hostname, atoi(portname));
-       mm_free(hostname);
-       mm_free(portname);
        if (evcon == NULL)
                return (NULL);
 
@@ -4518,10 +4598,12 @@ evhttp_associate_new_request_with_connection(struct evhttp_connection *evcon)
        if ((req = evhttp_request_new(evhttp_handle_request, http)) == NULL)
                return (-1);
 
-       if ((req->remote_host = mm_strdup(evcon->address)) == NULL) {
-               event_warn("%s: strdup", __func__);
-               evhttp_request_free(req);
-               return (-1);
+       if (evcon->address != NULL) {
+               if ((req->remote_host = mm_strdup(evcon->address)) == NULL) {
+                       event_warn("%s: strdup", __func__);
+                       evhttp_request_free(req);
+                       return (-1);
+               }
        }
        req->remote_port = evcon->port;
 
@@ -4739,6 +4821,9 @@ struct evhttp_uri {
        char *userinfo; /* userinfo (typically username:pass), or NULL */
        char *host; /* hostname, IP address, or NULL */
        int port; /* port, or zero */
+#ifndef _WIN32
+       char *unixsocket; /* unix domain socket or NULL */
+#endif
        char *path; /* path, or "". */
        char *query; /* query, or NULL */
        char *fragment; /* fragment or NULL */
@@ -4910,6 +4995,20 @@ parse_authority(struct evhttp_uri *uri, char *s, char *eos, unsigned *flags)
        } else {
                cp = s;
        }
+
+#ifndef _WIN32
+       if (*flags & EVHTTP_URI_UNIX_SOCKET && !strncmp(cp, "unix:", 5)) {
+               char *e = strchr(cp + 5, ':');
+               if (e) {
+                       *e = '\0';
+                       uri->unixsocket = mm_strdup(cp + 5);
+                       return 0;
+               } else {
+                       return -1;
+               }
+       }
+#endif
+
        /* Optionally, we end with ":port" */
        for (port=eos-1; port >= cp && EVUTIL_ISDIGIT_(*port); --port)
                ;
@@ -5203,6 +5302,9 @@ evhttp_uri_free(struct evhttp_uri *uri)
        URI_FREE_STR_(scheme);
        URI_FREE_STR_(userinfo);
        URI_FREE_STR_(host);
+#ifndef _WIN32
+       URI_FREE_STR_(unixsocket);
+#endif
        URI_FREE_STR_(path);
        URI_FREE_STR_(query);
        URI_FREE_STR_(fragment);
@@ -5231,6 +5333,15 @@ evhttp_uri_join(struct evhttp_uri *uri, char *buf, size_t limit)
                URI_ADD_(scheme);
                evbuffer_add(tmp, ":", 1);
        }
+#ifndef _WIN32
+       if (uri->unixsocket) {
+               evbuffer_add(tmp, "//", 2);
+               if (uri->userinfo)
+                       evbuffer_add_printf(tmp, "%s@", uri->userinfo);
+               evbuffer_add_printf(tmp, "unix:%s:", uri->unixsocket);
+       }
+       else
+#endif
        if (uri->host) {
                evbuffer_add(tmp, "//", 2);
                if (uri->userinfo)
@@ -5296,6 +5407,13 @@ evhttp_uri_get_host(const struct evhttp_uri *uri)
 {
        return uri->host;
 }
+#ifndef _WIN32
+const char *
+evhttp_uri_get_unixsocket(const struct evhttp_uri *uri)
+{
+       return uri->unixsocket;
+}
+#endif
 int
 evhttp_uri_get_port(const struct evhttp_uri *uri)
 {
@@ -5385,6 +5503,14 @@ evhttp_uri_set_host(struct evhttp_uri *uri, const char *host)
 
        return 0;
 }
+#ifndef _WIN32
+int
+evhttp_uri_set_unixsocket(struct evhttp_uri *uri, const char *unixsocket)
+{
+       URI_SET_STR_(unixsocket);
+       return 0;
+}
+#endif
 int
 evhttp_uri_set_port(struct evhttp_uri *uri, int port)
 {
index 22e6122e6b90a82791b3f961427a6e3f0249627b..89175fb79e64b25a073671ab76561c1a0b03a8d2 100644 (file)
@@ -652,7 +652,7 @@ enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE };
  * @param dnsbase the dns_base to use for resolving host names; if not
  *     specified host name resolution will block.
  * @param bev a bufferevent to use for connecting to the server; if NULL, a
- *     socket-based bufferevent will be created.  This buffrevent will be freed
+ *     socket-based bufferevent will be created.  This bufferevent will be freed
  *     when the connection closes.  It must have no fd set on it.
  * @param address the address to which to connect
  * @param port the port to connect to
@@ -663,6 +663,21 @@ EVENT2_EXPORT_SYMBOL
 struct evhttp_connection *evhttp_connection_base_bufferevent_new(
        struct event_base *base, struct evdns_base *dnsbase, struct bufferevent* bev, const char *address, ev_uint16_t port);
 
+/**
+ * Create and return a connection object that can be used to for making HTTP
+ * requests over an unix domain socket.
+ *
+ * @param base the event_base to use for handling the connection
+ * @param bev a bufferevent to use for connecting to the server; if NULL, a
+ *     socket-based bufferevent will be created.  This bufferevent will be freed
+ *     when the connection closes.  It must have no fd set on it.
+ * @param path path of unix domain socket
+ * @return an evhttp_connection object that can be used for making requests
+ */
+EVENT2_EXPORT_SYMBOL
+struct evhttp_connection *evhttp_connection_base_bufferevent_unix_new(
+       struct event_base *base, struct bufferevent* bev, const char *path);
+
 /**
  * Return the bufferevent that an evhttp_connection is using.
  */
@@ -1285,6 +1300,10 @@ const char *evhttp_uri_get_userinfo(const struct evhttp_uri *uri);
  */
 EVENT2_EXPORT_SYMBOL
 const char *evhttp_uri_get_host(const struct evhttp_uri *uri);
+/** Return the unix socket part of an evhttp_uri, or NULL if there is no unix
+ * socket set */
+EVENT2_EXPORT_SYMBOL
+const char *evhttp_uri_get_unixsocket(const struct evhttp_uri *uri);
 /** Return the port part of an evhttp_uri, or -1 if there is no port set. */
 EVENT2_EXPORT_SYMBOL
 int evhttp_uri_get_port(const struct evhttp_uri *uri);
@@ -1312,6 +1331,11 @@ int evhttp_uri_set_userinfo(struct evhttp_uri *uri, const char *userinfo);
  * Returns 0 on success, -1 if host is not well-formed. */
 EVENT2_EXPORT_SYMBOL
 int evhttp_uri_set_host(struct evhttp_uri *uri, const char *host);
+/** Set the unix socket of an evhttp_uri, or clear the unix socket if unixsocket==NULL.
+ * Returns 0 on success, -1 if unixsocket is not well-formed */
+EVENT2_EXPORT_SYMBOL
+int evhttp_uri_set_unixsocket(struct evhttp_uri *uri, const char *unixsocket);
+
 /** Set the port of an evhttp_uri, or clear the port if port==-1.
  * Returns 0 on success, -1 if port is not well-formed. */
 EVENT2_EXPORT_SYMBOL
@@ -1382,6 +1406,7 @@ struct evhttp_uri *evhttp_uri_parse_with_flags(const char *source_uri,
  * </ul>
  */
 #define EVHTTP_URI_NONCONFORMANT 0x01
+
 /**
  * Strip brackets from the IPv6 address and only for evhttp_uri_get_host(),
  * evhttp_uri_join() returns the host with brackets.
@@ -1392,6 +1417,13 @@ struct evhttp_uri *evhttp_uri_parse_with_flags(const char *source_uri,
  */
 #define EVHTTP_URI_HOST_STRIP_BRACKETS 0x04
 
+/**
+ * Parse unix domain socket URIs, for example:
+ *
+ * http://unix:/run/control.sock:/controller
+ */
+#define EVHTTP_URI_UNIX_SOCKET 0x08
+
 /** Alias for evhttp_uri_parse_with_flags(source_uri, 0) */
 EVENT2_EXPORT_SYMBOL
 struct evhttp_uri *evhttp_uri_parse(const char *source_uri);
index df9eee3a0b83b4c2b40bbf3d407c2739b7f9bcf3..a9c990b284bc1c6d8cc7eb02de1d5c382640e922 100644 (file)
@@ -2083,7 +2083,6 @@ http_virtual_host_test(void *arg)
                evhttp_free(http);
 }
 
-
 /* test date header and content length */
 
 static void
@@ -2220,6 +2219,97 @@ http_dispatcher_test(void *arg)
                evhttp_free(http);
 }
 
+#ifndef _WIN32
+/* test unix socket */
+#include <sys/un.h>
+
+/* Should this be part of the libevent library itself? */
+static int evhttp_bind_unixsocket(struct evhttp *httpd, const char *path)
+{
+       struct sockaddr_un local;
+       struct stat st;
+       int fd;
+
+       local.sun_family = AF_UNIX;
+       strcpy(local.sun_path, path);
+
+       /* if the file exists and it is a socket, remove it. Someone
+          could create a symlink and get us to remove random files */
+       if (stat(path, &st) == 0 && S_ISSOCK(st.st_mode))
+               unlink(path);
+
+       fd = socket(AF_UNIX, SOCK_CLOEXEC | SOCK_NONBLOCK | SOCK_STREAM, 0);
+       if (fd == -1)
+               return -1;
+
+       if (bind(fd, (struct sockaddr*)&local, sizeof(local))) {
+               close(fd);
+               return -1;
+       }
+
+       /* fchmod(fd, 0777) does nothing */
+       if (chmod(path, 0777)) {
+               close(fd);
+               return -1;
+       }
+
+       if (listen(fd, 128)) {
+               close(fd);
+               return -1;
+       }
+
+       if (evhttp_accept_socket(httpd, fd)) {
+               close(fd);
+               return -1;
+       }
+
+       return 0;
+}
+
+static void http_unix_socket_test(void *arg)
+{
+       struct basic_test_data *data = arg;
+       struct evhttp_uri *uri;
+       struct evhttp_connection *evcon = NULL;
+       struct evhttp_request *req;
+
+       struct evhttp *myhttp = evhttp_new(data->base);
+
+       tt_assert(!evhttp_bind_unixsocket(myhttp, "foo"));
+
+       evhttp_set_cb(myhttp, "/", http_dispatcher_cb, data->base);
+
+       uri = evhttp_uri_parse_with_flags("http://unix:./foo:/?arg=val", EVHTTP_URI_UNIX_SOCKET);
+       tt_assert(uri);
+
+       evcon = evhttp_connection_base_bufferevent_unix_new(data->base, NULL, evhttp_uri_get_unixsocket(uri));
+
+       /*
+        * At this point, we want to schedule an HTTP GET request
+        * server using our make request method.
+        */
+       req = evhttp_request_new(http_dispatcher_test_done, data->base);
+       tt_assert(req);
+
+       /* Add the information that we care about */
+       evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost");
+
+       if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/?arg=val") == -1) {
+               tt_abort_msg("Couldn't make request");
+       }
+
+       event_base_dispatch(data->base);
+
+ end:
+       if (evcon)
+               evhttp_connection_free(evcon);
+       if (myhttp)
+               evhttp_free(myhttp);
+       if (uri)
+               evhttp_uri_free(uri);
+}
+#endif
+
 /*
  * HTTP POST test.
  */
@@ -2844,9 +2934,8 @@ end:
 static void
 http_parse_uri_test(void *ptr)
 {
-       const int nonconform = (ptr != NULL);
-       const unsigned parse_flags =
-           nonconform ? EVHTTP_URI_NONCONFORMANT : 0;
+       int nonconform = 0, unixsock = 0;
+       int parse_flags = 0;
        struct evhttp_uri *uri = NULL;
        char url_tmp[4096];
 #define URI_PARSE_FLAGS(uri, flags) \
@@ -2862,6 +2951,17 @@ http_parse_uri_test(void *ptr)
                TT_FAIL(("\"%s\" != \"%s\"",ret,want));                 \
        } while(0)
 
+       if (ptr) {
+               if (strstr(ptr, "nc") != NULL) {
+                       nonconform = 1;
+                       parse_flags |= EVHTTP_URI_NONCONFORMANT;
+               }
+               if (strstr(ptr, "un") != NULL) {
+                       unixsock = 1;
+                       parse_flags |= EVHTTP_URI_UNIX_SOCKET;
+               }
+       }
+
        tt_want(evhttp_uri_join(NULL, 0, 0) == NULL);
        tt_want(evhttp_uri_join(NULL, url_tmp, 0) == NULL);
        tt_want(evhttp_uri_join(NULL, url_tmp, sizeof(url_tmp)) == NULL);
@@ -2886,6 +2986,18 @@ http_parse_uri_test(void *ptr)
                        evhttp_uri_free(uri);                           \
                }                                                       \
        } while(0)
+#define UNI(s) do {                                                    \
+               uri = URI_PARSE(s);                                     \
+               if (uri == NULL && unixsock) {                  \
+                       TT_FAIL(("Couldn't parse unix socket URI \"%s\"", \
+                               s));                                    \
+               }                                                       \
+               if (uri) {                                              \
+                       tt_want(evhttp_uri_join(uri, url_tmp,           \
+                               sizeof(url_tmp)));                      \
+                       evhttp_uri_free(uri);                           \
+               }                                                       \
+       } while(0)
 
        NCF("http://www.test.com/ why hello");
        NCF("http://www.test.com/why-hello\x01");
@@ -2922,6 +3034,10 @@ http_parse_uri_test(void *ptr)
        BAD("http://www.example.com:hihi/");
        BAD("://www.example.com/");
 
+       UNI("http://unix:/tmp/foobar/:/foo");
+       UNI("http://user:pass@unix:/tmp/foobar/:/foo");
+       UNI("http://unix:a:");
+
        /* bad URIs: joining */
        uri = evhttp_uri_new();
        tt_want(0==evhttp_uri_set_host(uri, "www.example.com"));
@@ -5654,6 +5770,8 @@ struct testcase_t http_testcases[] = {
        { "parse_query_str_flags", http_parse_query_str_flags_test, 0, NULL, NULL },
        { "parse_uri", http_parse_uri_test, 0, NULL, NULL },
        { "parse_uri_nc", http_parse_uri_test, 0, &basic_setup, (void*)"nc" },
+       { "parse_uri_un", http_parse_uri_test, 0, &basic_setup, (void*)"un" },
+       { "parse_uri_un_nc", http_parse_uri_test, 0, &basic_setup, (void*)"un_nc" },
        { "uriencode", http_uriencode_test, 0, NULL, NULL },
        HTTP(basic),
        HTTP(basic_trailing_space),
@@ -5674,6 +5792,9 @@ struct testcase_t http_testcases[] = {
        HTTP_RET_N(cancel_by_host_ns_timeout_inactive_server, cancel, TT_NO_LOGS, BY_HOST | NO_NS | NS_TIMEOUT | INACTIVE_SERVER),
 
        HTTP(virtual_host),
+#ifndef _WIN32
+       HTTP(unix_socket),
+#endif
        HTTP(post),
        HTTP(put),
        HTTP(delete),