Unix sockets: no addresses on streams, abstract names, tcppm port 0
Some checks failed
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Has been cancelled
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-latest) (push) Has been cancelled
C/C++ CI MacOS / ${{ matrix.target }} (macos-15) (push) Has been cancelled
C/C++ CI Windows / ${{ matrix.target }} (windows-2022) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (macos-15) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-latest) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (windows-2022) (push) Has been cancelled
C/C++ CI cmake / ubuntu-latest (wolfSSL) (push) Has been cancelled

On Linux a proxy listening on a Unix socket relayed only the first
bytes of an answer: sockmap() read with recvfrom() into the client's
address, which for a Unix stream socket Linux fills with an empty
AF_UNIX one, and then sent with sendto() to that address, which a
connected Unix stream socket refuses. A TCP connection has no use for
addresses: sockmap() now sends and receives with NULL ones, and
connect-back sends its command with socksend() and reads the answer
with sockrecvfrom() without an address, which a NULL sin now allows.

An abstract socket name (-iunix:@NAME) was bound with the whole size of
struct sockaddr_un, padded with zeros, so a client naming it by its
bytes, as most do, reached another name. SASIZE() now gives an abstract
address its exact size; getsockname() calls that took SASIZE() of the
old contents as the buffer size take the buffer's size instead.

tcppm refused port 0 for a unix: target, though the manual gives
"tcppm 8080 unix:/var/run/app.sock 0" as the example: the port of a
Unix socket target is ignored, so 0 is allowed.

On a reload the new service bound the path anew and the old one,
closing after it, removed the file, the new service's by then, leaving
nothing to connect to. A service now notes the file it bound (device
and inode) and removes the path on closing only if it is still that
file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-18 12:42:01 +03:00
parent 20302a67ff
commit ae4011604f
6 changed files with 52 additions and 18 deletions

View File

@ -7,6 +7,31 @@
*/ */
#include "proxy.h" #include "proxy.h"
#ifdef WITH_UN
/* A service bound to a Unix socket path notes its file, as a service of a
newer configuration may bind the same path before this one closes: the
file is then the other's, which closing must not remove. */
static void unbound(struct srvparam *srv){
struct stat st;
if(*SAFAMILY(&srv->intsa) == AF_UNIX && *SAADDR(&srv->intsa) &&
!stat((char *)SAADDR(&srv->intsa), &st)){
srv->undev = (uint64_t)st.st_dev;
srv->unino = (uint64_t)st.st_ino;
}
}
static void ununlink(struct srvparam *srv){
struct stat st;
if(*SAFAMILY(&srv->intsa) == AF_UNIX && *SAADDR(&srv->intsa) &&
!stat((char *)SAADDR(&srv->intsa), &st) &&
(uint64_t)st.st_dev == srv->undev && (uint64_t)st.st_ino == srv->unino)
unlink((char *)SAADDR(&srv->intsa));
}
#endif
#ifdef __linux__ #ifdef __linux__
#include <sched.h> #include <sched.h>
#endif #endif
@ -84,7 +109,7 @@ void * threadfunc (void *p) {
continue; continue;
} }
#endif #endif
if(socksendto(param, param->remsock, (struct sockaddr*)&param->sinsr, (unsigned char *)"C", 1, CONNBACK_TO*1000) != 1){ if(socksend(param, param->remsock, (unsigned char *)"C", 1, CONNBACK_TO) != 1){
dolog(param, (unsigned char *)"Connect back sending command failed"); dolog(param, (unsigned char *)"Connect back sending command failed");
param->srv->so._closesocket(param->sostate, param->remsock); param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = INVALID_SOCKET; param->remsock = INVALID_SOCKET;
@ -687,7 +712,12 @@ int MODULEMAINFUNC (int argc, char** argv){
else { else {
#endif #endif
#ifndef NOPORTMAP #ifndef NOPORTMAP
if (error || argc != i+3 || *argv[i]=='-'|| (*SAPORT(&srv.intsa) = htons((uint16_t)atoi(argv[i])))==0 || (srv.targetport = htons((uint16_t)atoi(argv[i+2])))==0) { /* the port of a Unix socket target is ignored, 0 as the manual has it */
if (error || argc != i+3 || *argv[i]=='-'|| (*SAPORT(&srv.intsa) = htons((uint16_t)atoi(argv[i])))==0 || ((srv.targetport = htons((uint16_t)atoi(argv[i+2])))==0
#ifdef WITH_UN
&& strncmp(argv[i+1], "unix:", 5)
#endif
)) {
#ifndef STDMAIN #ifndef STDMAIN
haveerror = 1; haveerror = 1;
#endif #endif
@ -870,6 +900,9 @@ int MODULEMAINFUNC (int argc, char** argv){
return -3; return -3;
} }
} }
#ifdef WITH_UN
unbound(&srv);
#endif
if(!isudp){ if(!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) {
sprintf((char *)buf, "listen(): %s", strerror(errno)); sprintf((char *)buf, "listen(): %s", strerror(errno));
@ -1020,7 +1053,7 @@ int MODULEMAINFUNC (int argc, char** argv){
continue; continue;
} }
if(sockrecvfrom(NULL, new_sock,(struct sockaddr*)&defparam.sincr,buf,1,60*1000) != 1 || *buf!='C') { if(sockrecvfrom(NULL, new_sock, NULL, buf, 1, 60*1000) != 1 || *buf!='C') {
so._closesocket(so.state, new_sock); so._closesocket(so.state, new_sock);
new_sock = INVALID_SOCKET; new_sock = INVALID_SOCKET;
usleep(SLEEPTIME); usleep(SLEEPTIME);
@ -1320,7 +1353,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);
#endif #endif
} }
srv->srvsock = INVALID_SOCKET; srv->srvsock = INVALID_SOCKET;

View File

@ -64,8 +64,9 @@ int sockrecvfrom(struct clientparam *param, SOCKET sock, struct sockaddr * sin,
res = param?param->srv->so._poll(param->sostate, &fds, 1, to):so._poll(so.state, &fds, 1, to); res = param?param->srv->so._poll(param->sostate, &fds, 1, to):so._poll(so.state, &fds, 1, to);
if (res<1) return 0; if (res<1) return 0;
sasize = sizeof(PROXYSOCKADDRTYPE); sasize = sizeof(PROXYSOCKADDRTYPE);
/* sin NULL for a stream socket: nothing to learn the address into */
do { do {
res = param?param->srv->so._recvfrom(param->sostate, sock, (char *)buf, bufsize, 0, (struct sockaddr *)sin, &sasize):so._recvfrom(so.state, sock, (char *)buf, bufsize, 0, (struct sockaddr *)sin, &sasize); res = param?param->srv->so._recvfrom(param->sostate, sock, (char *)buf, bufsize, 0, (struct sockaddr *)sin, sin? &sasize : NULL):so._recvfrom(so.state, sock, (char *)buf, bufsize, 0, (struct sockaddr *)sin, sin? &sasize : NULL);
} while (res < 0 && (errno == EAGAIN || errno == EINTR)); } while (res < 0 && (errno == EAGAIN || errno == EINTR));
return res; return res;
} }

View File

@ -92,7 +92,6 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
int sleeptime = 0; int sleeptime = 0;
FILTER_ACTION action; FILTER_ACTION action;
int res = 0; int res = 0;
SASIZETYPE sasize;
int needaction = 0; int needaction = 0;
int graceclinum=0, gracesrvnum=0, graceclitraf=0, gracesrvtraf=0; int graceclinum=0, gracesrvnum=0, graceclitraf=0, gracesrvtraf=0;
time_t gracetime = 0; time_t gracetime = 0;
@ -246,8 +245,7 @@ log("send to server from buf");
param->clioffset = param->cliinbuf = 0; param->clioffset = param->cliinbuf = 0;
if(fromclient) TOCLIENTBUF = 1; if(fromclient) TOCLIENTBUF = 1;
} }
sasize = SASIZE(&param->sinsr); res = param->srv->so._sendto(param->sostate, param->remsock, (char *)param->clibuf + param->clioffset, (int)MIN(inclientbuf, fromclient), 0, NULL, 0);
res = param->srv->so._sendto(param->sostate, param->remsock, (char *)param->clibuf + param->clioffset, (int)MIN(inclientbuf, fromclient), 0, (struct sockaddr*)&param->sinsr, sasize);
if(res <= 0) { if(res <= 0) {
TOSERVER = 0; TOSERVER = 0;
if(errno && errno != EAGAIN && errno != EINTR){ if(errno && errno != EAGAIN && errno != EINTR){
@ -293,8 +291,7 @@ log("send to client from buf");
if(fromserver) TOSERVERBUF = 1; if(fromserver) TOSERVERBUF = 1;
continue; continue;
} }
sasize = SASIZE(&param->sincr); res = param->srv->so._sendto(param->sostate, param->clisock, (char *)param->srvbuf + param->srvoffset, (int)MIN(inserverbuf,fromserver), 0, NULL, 0);
res = param->srv->so._sendto(param->sostate, param->clisock, (char *)param->srvbuf + param->srvoffset, (int)MIN(inserverbuf,fromserver), 0, (struct sockaddr*)&param->sincr, sasize);
if(res <= 0) { if(res <= 0) {
TOCLIENT = 0; TOCLIENT = 0;
if(errno && errno != EAGAIN && errno != EINTR){ if(errno && errno != EAGAIN && errno != EINTR){
@ -485,8 +482,7 @@ log("done read from server to pipe\n");
#ifdef WITHLOG #ifdef WITHLOG
log("read from client to buf"); log("read from client to buf");
#endif #endif
sasize = sizeof(param->sincr); res = param->srv->so._recvfrom(param->sostate, param->clisock, (char *)param->clibuf + param->cliinbuf, (int)MIN((uint64_t)param->clibufsize - param->cliinbuf, fromclient-inclientbuf), 0, NULL, NULL);
res = param->srv->so._recvfrom(param->sostate, param->clisock, (char *)param->clibuf + param->cliinbuf, (int)MIN((uint64_t)param->clibufsize - param->cliinbuf, fromclient-inclientbuf), 0, (struct sockaddr *)&param->sincr, &sasize);
if(res <= 0) { if(res <= 0) {
FROMCLIENT = 0; FROMCLIENT = 0;
if(res == 0) { if(res == 0) {
@ -518,8 +514,7 @@ log("done read from client to buf");
#ifdef WITHLOG #ifdef WITHLOG
log("read from server to buf"); log("read from server to buf");
#endif #endif
sasize = sizeof(param->sinsr); res = param->srv->so._recvfrom(param->sostate, param->remsock, (char *)param->srvbuf + param->srvinbuf, (int)MIN((uint64_t)param->srvbufsize - param->srvinbuf, fromserver-inserverbuf), 0, NULL, NULL);
res = param->srv->so._recvfrom(param->sostate, param->remsock, (char *)param->srvbuf + param->srvinbuf, (int)MIN((uint64_t)param->srvbufsize - param->srvinbuf, fromserver-inserverbuf), 0, (struct sockaddr *)&param->sinsr, &sasize);
if(res <= 0) { if(res <= 0) {
FROMSERVER = 0; FROMSERVER = 0;
if(res == 0) { if(res == 0) {

View File

@ -227,7 +227,7 @@ fprintf(stderr, "%hu bound to communicate with server\n", *SAPORT(&param->sinsl)
fflush(stderr); fflush(stderr);
#endif #endif
} }
sasize = SASIZE(&param->sinsl); sasize = sizeof(param->sinsl);
param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)&param->sinsl, &sasize); param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)&param->sinsl, &sasize);
} }
if(command == 3) { if(command == 3) {
@ -246,7 +246,7 @@ fflush(stderr);
sin = param->sincl; sin = param->sincl;
*SAPORT(&sin) = 0; *SAPORT(&sin) = 0;
if(param->srv->so._bind(param->sostate, param->clisock,(struct sockaddr *)&sin,SASIZE(&sin))) {RETURN (12);} if(param->srv->so._bind(param->sostate, param->clisock,(struct sockaddr *)&sin,SASIZE(&sin))) {RETURN (12);}
sasize = SASIZE(&sin); sasize = sizeof(sin);
param->srv->so._getsockname(param->sostate, param->clisock, (struct sockaddr *)&sin, &sasize); param->srv->so._getsockname(param->sostate, param->clisock, (struct sockaddr *)&sin, &sasize);
#if SOCKSTRACE > 0 #if SOCKSTRACE > 0
fprintf(stderr, "%hu binded to communicate with client\n", fprintf(stderr, "%hu binded to communicate with client\n",

View File

@ -105,6 +105,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
@ -152,7 +153,9 @@ int
#define UN_SAADDR(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (unsigned char *)((struct sockaddr_un *)sa)->sun_path : #define UN_SAADDR(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (unsigned char *)((struct sockaddr_un *)sa)->sun_path :
#define UN_SAADDRLEN(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (int)sizeof(((struct sockaddr_un *)sa)->sun_path) : #define UN_SAADDRLEN(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (int)sizeof(((struct sockaddr_un *)sa)->sun_path) :
#define UN_SASOCK(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? PF_UNIX : #define UN_SASOCK(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? PF_UNIX :
#define UN_SASIZE(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? sizeof(struct sockaddr_un) : /* an abstract name (Linux) is its bytes after the leading 0, no more:
padded with zeros it would be another name */
#define UN_SASIZE(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (((struct sockaddr_un *)sa)->sun_path[0] || !((struct sockaddr_un *)sa)->sun_path[1]? sizeof(struct sockaddr_un) : offsetof(struct sockaddr_un, sun_path) + 1 + strlen(((struct sockaddr_un *)sa)->sun_path + 1)) :
#define UN_SAISNULL(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (((struct sockaddr_un *)sa)->sun_path[0] == 0 && ((struct sockaddr_un *)sa)->sun_path[1] == 0) : #define UN_SAISNULL(sa) (((struct sockaddr_un *)sa)->sun_family == AF_UNIX)? (((struct sockaddr_un *)sa)->sun_path[0] == 0 && ((struct sockaddr_un *)sa)->sun_path[1] == 0) :
#else #else
#define UN_SAPORT(sa) #define UN_SAPORT(sa)
@ -527,6 +530,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

@ -136,7 +136,7 @@ int udpbind(struct clientparam *param)
return 12; return 12;
} }
} }
sasize = SASIZE(&param->sinsl); sasize = sizeof(param->sinsl);
param->srv->so._getsockname(param->sostate, param->remsock, param->srv->so._getsockname(param->sostate, param->remsock,
(struct sockaddr *)&param->sinsl, &sasize); (struct sockaddr *)&param->sinsl, &sasize);
return 0; return 0;