Unix sockets: remove on closing only the file this socket bound

When a reload cannot take a listening Unix socket over (a bind-time
-ol option changed), the new service binds the path anew, and the old
listener, closing after it, removed the file, the new socket's by then,
leaving nothing to connect to. The socket's file (device and inode) is
now noted on binding and the path removed on closing only if it is
still that file.

structures.h includes <stddef.h> for offsetof() in SASIZE(), which
needs it wherever the header is used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-18 12:38:11 +03:00
parent 9612bbdd22
commit 5c19e6368f
3 changed files with 45 additions and 2 deletions

View File

@ -898,6 +898,7 @@ struct listener {
char *netns, *device; char *netns, *device;
struct sockoptset opts; struct sockoptset opts;
int maxseg; int maxseg;
uint64_t undev, unino; /* its file, for a Unix socket on a path */
int optversion; /* the configuration whose options are set */ int optversion; /* the configuration whose options are set */
/* the services accepting on it, linked by lisnext */ /* the services accepting on it, linked by lisnext */
struct srvparam *owners; struct srvparam *owners;
@ -972,6 +973,28 @@ static void listenerrelease(struct srvparam *srv);
static int srvopen(struct srvstart *s); static int srvopen(struct srvstart *s);
#ifdef WITH_UN
/* A Unix socket bound to a path: its file is noted, as a service of a
newer configuration may bind the same path anew before this socket is
closed, when the file is the other's, which closing must not remove. */
static void unbound(PROXYSOCKADDRTYPE *sa, uint64_t *dev, uint64_t *ino){
struct stat st;
if(*SAFAMILY(sa) == AF_UNIX && *SAADDR(sa) && !stat((char *)SAADDR(sa), &st)){
*dev = (uint64_t)st.st_dev;
*ino = (uint64_t)st.st_ino;
}
}
static void ununlink(PROXYSOCKADDRTYPE *sa, uint64_t dev, uint64_t ino){
struct stat st;
if(*SAFAMILY(sa) == AF_UNIX && *SAADDR(sa) && !stat((char *)SAADDR(sa), &st) &&
(uint64_t)st.st_dev == dev && (uint64_t)st.st_ino == ino)
unlink((char *)SAADDR(sa));
}
#endif
/* The listening socket of the service: its listener's, opened by the first /* The listening socket of the service: its listener's, opened by the first
of the listener's services to get here. */ of the listener's services to get here. */
static int srvlisten(struct srvstart *s){ static int srvlisten(struct srvstart *s){
@ -993,6 +1016,8 @@ static int srvlisten(struct srvstart *s){
_3proxy_mutex_lock(&listener_mutex); _3proxy_mutex_lock(&listener_mutex);
l->sock = srv->srvsock; l->sock = srv->srvsock;
l->optversion = srv->version; l->optversion = srv->version;
l->undev = srv->undev;
l->unino = srv->unino;
_3proxy_mutex_unlock(&listener_mutex); _3proxy_mutex_unlock(&listener_mutex);
} }
else if(srv->srvsock != INVALID_SOCKET){ else if(srv->srvsock != INVALID_SOCKET){
@ -1087,6 +1112,9 @@ static int srvopen(struct srvstart *s){
sleeptime = (sleeptime<<1); sleeptime = (sleeptime<<1);
if(!sleeptime) return -3; if(!sleeptime) return -3;
} }
#ifdef WITH_UN
unbound(&srv->intsa, &srv->undev, &srv->unino);
#endif
#ifndef NOTCPMAIN #ifndef NOTCPMAIN
if(!s->isudp){ if(!s->isudp){
if(srv->so._listen (srv->so.state, sock, srv->backlog?srv->backlog : 1+(srv->maxchild>>3))==-1) { if(srv->so._listen (srv->so.state, sock, srv->backlog?srv->backlog : 1+(srv->maxchild>>3))==-1) {
@ -1838,7 +1866,7 @@ static void listenerstop(struct srvparam *srv){
if(sock == INVALID_SOCKET) return; if(sock == INVALID_SOCKET) return;
so._closesocket(so.state, sock); so._closesocket(so.state, sock);
#ifdef WITH_UN #ifdef WITH_UN
if(*SAFAMILY(&l->sa) == AF_UNIX && *SAADDR(&l->sa))unlink((char *)SAADDR(&l->sa)); ununlink(&l->sa, l->undev, l->unino);
#endif #endif
} }
@ -2047,7 +2075,7 @@ void srvfree(struct srvparam * srv){
if(srv->srvsock != INVALID_SOCKET) { if(srv->srvsock != INVALID_SOCKET) {
so._closesocket(srv->so.state, srv->srvsock); so._closesocket(srv->so.state, srv->srvsock);
#ifdef WITH_UN #ifdef WITH_UN
if(*SAFAMILY(&srv->intsa) == AF_UNIX && *SAADDR(&srv->intsa))unlink((char *)SAADDR(&srv->intsa)); ununlink(&srv->intsa, srv->undev, srv->unino);
#endif #endif
} }
srv->srvsock = INVALID_SOCKET; srv->srvsock = INVALID_SOCKET;

View File

@ -113,6 +113,7 @@ int
#ifdef WITH_UN #ifdef WITH_UN
#include <sys/un.h> #include <sys/un.h>
#include <stddef.h>
#endif #endif
#define ALLOW 0 #define ALLOW 0
@ -631,6 +632,8 @@ struct srvparam {
AUTHFUNC authfunc; AUTHFUNC authfunc;
PROXYFUNC pf; PROXYFUNC pf;
SOCKET srvsock, cbsock; SOCKET srvsock, cbsock;
/* the file of a Unix socket bound to a path, to remove only it */
uint64_t undev, unino;
int childcount; int childcount;
int maxchild; int maxchild;
int backlog; int backlog;

View File

@ -157,6 +157,18 @@ def run(t):
t.fail("its file is still there", path, "missing") t.fail("its file is still there", path, "missing")
t.eq((200, 1000), unix_get(path, url), "and the proxy answers on it") t.eq((200, 1000), unix_get(path, url), "and the proxy answers on it")
# --- a bind-time option changed: a new socket on the same path, and
# the old one, closing after, must not remove the new one's file -------
t.write_config("unixsock", config().replace(f"proxy -iunix:{path}",
f"proxy -olSO_REUSEADDR=0 -iunix:{path}"))
t.http(f"http://127.0.0.1:{adm}/R")
# the old services stop within a second of the reload
time.sleep(2)
t.ok("a new socket on the same path keeps its file when the old one closes") \
if os.path.exists(path) else \
t.fail("a new socket on the same path keeps its file when the old one closes", path, "missing")
t.eq((200, 1000), unix_get(path, url), "and the proxy answers on it")
# --- no service listens there any more: the file goes ------------------- # --- no service listens there any more: the file goes -------------------
t.write_config("unixsock", config(unix=False)) t.write_config("unixsock", config(unix=False))
t.http(f"http://127.0.0.1:{adm}/R") t.http(f"http://127.0.0.1:{adm}/R")