From: Sean Young Date: Sun, 31 Jan 2016 11:31:00 +0000 (+0000) Subject: http: support unix domain sockets X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f446229b2206691eae7ec892e68b2a0d6ef61306;p=libevent http: support unix domain sockets 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 --- diff --git a/http-internal.h b/http-internal.h index dfd5b01b..a5844e1d 100644 --- a/http-internal.h +++ b/http-internal.h @@ -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 28b59b88..0a7efb42 100644 --- 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 #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) { diff --git a/include/event2/http.h b/include/event2/http.h index 22e6122e..89175fb7 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -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, * */ #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); diff --git a/test/regress_http.c b/test/regress_http.c index df9eee3a..a9c990b2 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -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 + +/* 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),