From ccfccefb809d9dfeb211c46867733cb8df7a5fb8 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sat, 10 May 2014 01:49:15 +0200 Subject: [PATCH] MEDIUM: unix: implement support for Linux abstract namespace sockets These sockets are the same as Unix sockets except that there's no need for any filesystem access. The address may be whatever string both sides agree upon. This can be really convenient for inter-process communications as well as for chaining backends to frontends. These addresses are forced by prepending their address with "abns@" for "abstract namespace". --- doc/configuration.txt | 3 ++ src/proto_uxst.c | 83 ++++++++++++++++++++++--------------------- src/standard.c | 18 ++++++++-- 3 files changed, 61 insertions(+), 43 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 806ef8b912..1faee6cab3 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -1762,6 +1762,7 @@ bind / [, ...] [param*] - 'ipv4@' -> address is always IPv4 - 'ipv6@' -> address is always IPv6 - 'unix@' -> address is a path to a local unix socket + - 'abns@' -> address is in abstract namespace (Linux only) - 'fd@' -> use file descriptor inherited from the parent. The fd must be bound and may or may not already be listening. @@ -5750,6 +5751,7 @@ server
[:[port]] [param*] - 'ipv4@' -> address is always IPv4 - 'ipv6@' -> address is always IPv6 - 'unix@' -> address is a path to a local unix socket + - 'abns@' -> address is in abstract namespace (Linux only) Any part of the address string may reference any number of environment variables by preceding their name with a dollar sign ('$') and optionally enclosing them with braces ('{}'), @@ -5796,6 +5798,7 @@ source [:] [interface ] - 'ipv4@' -> address is always IPv4 - 'ipv6@' -> address is always IPv6 - 'unix@' -> address is a path to a local unix socket + - 'abns@' -> address is in abstract namespace (Linux only) Any part of the address string may reference any number of environment variables by preceding their name with a dollar sign ('$') and optionally enclosing them with braces ('{}'), diff --git a/src/proto_uxst.c b/src/proto_uxst.c index 3fa383dece..f2621facf9 100644 --- a/src/proto_uxst.c +++ b/src/proto_uxst.c @@ -188,45 +188,46 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle if (ext) goto fd_ready; - /* 1. create socket names */ - if (!path[0]) { - msg = "Invalid empty name for a UNIX socket"; - goto err_return; - } + if (path[0]) { + ret = snprintf(tempname, MAXPATHLEN, "%s.%d.tmp", path, pid); + if (ret < 0 || ret >= MAXPATHLEN) { + msg = "name too long for UNIX socket"; + goto err_return; + } - ret = snprintf(tempname, MAXPATHLEN, "%s.%d.tmp", path, pid); - if (ret < 0 || ret >= MAXPATHLEN) { - msg = "name too long for UNIX socket"; - goto err_return; - } + ret = snprintf(backname, MAXPATHLEN, "%s.%d.bak", path, pid); + if (ret < 0 || ret >= MAXPATHLEN) { + msg = "name too long for UNIX socket"; + goto err_return; + } - ret = snprintf(backname, MAXPATHLEN, "%s.%d.bak", path, pid); - if (ret < 0 || ret >= MAXPATHLEN) { - msg = "name too long for UNIX socket"; - goto err_return; - } + /* 2. clean existing orphaned entries */ + if (unlink(tempname) < 0 && errno != ENOENT) { + msg = "error when trying to unlink previous UNIX socket"; + goto err_return; + } - /* 2. clean existing orphaned entries */ - if (unlink(tempname) < 0 && errno != ENOENT) { - msg = "error when trying to unlink previous UNIX socket"; - goto err_return; - } + if (unlink(backname) < 0 && errno != ENOENT) { + msg = "error when trying to unlink previous UNIX socket"; + goto err_return; + } - if (unlink(backname) < 0 && errno != ENOENT) { - msg = "error when trying to unlink previous UNIX socket"; - goto err_return; - } + /* 3. backup existing socket */ + if (link(path, backname) < 0 && errno != ENOENT) { + msg = "error when trying to preserve previous UNIX socket"; + goto err_return; + } - /* 3. backup existing socket */ - if (link(path, backname) < 0 && errno != ENOENT) { - msg = "error when trying to preserve previous UNIX socket"; - goto err_return; + strncpy(addr.sun_path, tempname, sizeof(addr.sun_path)); + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + } + else { + /* first char is zero, it's an abstract socket whose address + * is defined by all the bytes past this zero. + */ + memcpy(addr.sun_path, path, sizeof(addr.sun_path)); } - - /* 4. prepare new socket */ addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, tempname, sizeof(addr.sun_path)); - addr.sun_path[sizeof(addr.sun_path) - 1] = 0; fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { @@ -254,9 +255,9 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle /* and different of -1 will be used to change the socket owner. * If is not 0, it will be used to restrict access to the socket. * While it is known not to be portable on every OS, it's still useful - * where it works. + * where it works. We also don't change permissions on abstract sockets. */ - if (!ext && + if (!ext && path[0] && (((listener->bind_conf->ux.uid != -1 || listener->bind_conf->ux.gid != -1) && (chown(tempname, listener->bind_conf->ux.uid, listener->bind_conf->ux.gid) == -1)) || (listener->bind_conf->ux.mode != 0 && chmod(tempname, listener->bind_conf->ux.mode) == -1))) { @@ -275,21 +276,21 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle goto err_unlink_temp; } - /* 5. install. - * Point of no return: we are ready, we'll switch the sockets. We don't + /* Point of no return: we are ready, we'll switch the sockets. We don't * fear loosing the socket because we have a copy of it in - * backname. + * backname. Abstract sockets are not renamed. */ - if (!ext && rename(tempname, path) < 0) { + if (!ext && path[0] && rename(tempname, path) < 0) { msg = "cannot switch final and temporary UNIX sockets"; goto err_rename; } - /* 6. cleanup. If we're bound to an fd inherited from the parent, we + /* Cleanup: If we're bound to an fd inherited from the parent, we * want to ensure that destroy_uxst_socket() will never remove the - * path, and for this we simply clear the path to the socket. + * path, and for this we simply clear the path to the socket, which + * under Linux corresponds to an abstract socket. */ - if (!ext) + if (!ext && path[0]) unlink(backname); else ((struct sockaddr_un *)&listener->addr)->sun_path[0] = 0; diff --git a/src/standard.c b/src/standard.c index 305c09e7f4..9cec295f4e 100644 --- a/src/standard.c +++ b/src/standard.c @@ -635,6 +635,11 @@ static struct sockaddr_storage *str2ip(const char *str, struct sockaddr_storage * - "ipv6@" => force address to resolve as IPv6 and fail if not possible. * - "unix@" => force address to be a path to a UNIX socket even if the * path does not start with a '/' + * - 'abns@' -> force address to belong to the abstract namespace (Linux + * only). These sockets are just like Unix sockets but without + * the need for an underlying file system. The address is a + * string. Technically it's like a Unix socket with a zero in + * the first byte of the address. * - "fd@" => an integer must follow, and is a file descriptor number. * * Also note that in order to avoid any ambiguity with IPv6 addresses, the ':' @@ -655,6 +660,7 @@ struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high, char char *back, *str2; char *port1, *port2; int portl, porth, porta; + int abstract = 0; portl = porth = porta = 0; @@ -668,6 +674,12 @@ struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high, char if (strncmp(str2, "unix@", 5) == 0) { str2 += 5; + abstract = 0; + ss.ss_family = AF_UNIX; + } + else if (strncmp(str2, "abns@", 5) == 0) { + str2 += 5; + abstract = 1; ss.ss_family = AF_UNIX; } else if (strncmp(str2, "ipv4@", 5) == 0) { @@ -706,7 +718,7 @@ struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high, char /* complete unix socket path name during startup or soft-restart is * .. */ - prefix_path_len = pfx ? strlen(pfx) : 0; + prefix_path_len = (pfx && !abstract) ? strlen(pfx) : 0; max_path_len = (sizeof(((struct sockaddr_un *)&ss)->sun_path) - 1) - (prefix_path_len ? prefix_path_len + 1 + 5 + 1 + 3 : 0); @@ -716,9 +728,11 @@ struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high, char goto out; } + /* when abstract==1, we skip the first zero and copy all bytes except the trailing zero */ + memset(((struct sockaddr_un *)&ss)->sun_path, 0, sizeof(((struct sockaddr_un *)&ss)->sun_path)); if (prefix_path_len) memcpy(((struct sockaddr_un *)&ss)->sun_path, pfx, prefix_path_len); - memcpy(((struct sockaddr_un *)&ss)->sun_path + prefix_path_len, str2, adr_len + 1); + memcpy(((struct sockaddr_un *)&ss)->sun_path + prefix_path_len + abstract, str2, adr_len + 1 - abstract); } else { /* IPv4 and IPv6 */ port1 = strrchr(str2, ':');