mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-24 12:05:47 +08:00
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
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 (result 95): 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. A test case covers a proxy on a Unix socket and on an abstract one on Linux, tcppm to a Unix socket, a parent reached through one, and a listening Unix socket across reloads: its file kept and the proxy answering, and the file removed once no service listens on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
36ac6e673b
commit
ef4969da19
@ -84,7 +84,7 @@ void * threadfunc (void *p) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if(socksendto(param, param->remsock, (struct sockaddr*)¶m->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;
|
||||||
@ -705,7 +705,12 @@ static int srvprepare(struct srvstart *s, 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
|
||||||
|
)) {
|
||||||
fprintf(stderr, "%s of %s\n"
|
fprintf(stderr, "%s of %s\n"
|
||||||
"Usage: %s options"
|
"Usage: %s options"
|
||||||
" [-e<external_ip>] <port_to_bind>"
|
" [-e<external_ip>] <port_to_bind>"
|
||||||
@ -761,6 +766,11 @@ static int srvprepare(struct srvstart *s, int argc, char **argv){
|
|||||||
|
|
||||||
/* The listening socket. A UDP one also receives the datagrams of new
|
/* The listening socket. A UDP one also receives the datagrams of new
|
||||||
clients, so it is their client socket too. 0, or the code to exit with. */
|
clients, so it is their client socket too. 0, or the code to exit with. */
|
||||||
|
#ifdef WITH_UN
|
||||||
|
static void unbound(struct srvparam *srv);
|
||||||
|
static void ununlink(struct srvparam *srv);
|
||||||
|
#endif
|
||||||
|
|
||||||
static int srvlisten(struct srvstart *s){
|
static int srvlisten(struct srvstart *s){
|
||||||
struct srvparam *srv = &s->srv;
|
struct srvparam *srv = &s->srv;
|
||||||
struct clientparam *defparam = &s->defparam;
|
struct clientparam *defparam = &s->defparam;
|
||||||
@ -815,6 +825,9 @@ static int srvlisten(struct srvstart *s){
|
|||||||
sleeptime = (sleeptime<<1);
|
sleeptime = (sleeptime<<1);
|
||||||
if(!sleeptime) return -3;
|
if(!sleeptime) return -3;
|
||||||
}
|
}
|
||||||
|
#ifdef WITH_UN
|
||||||
|
unbound(srv);
|
||||||
|
#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) {
|
||||||
@ -909,7 +922,7 @@ static int tcpnext(struct srvstart *s, SOCKET *client, int *nlog){
|
|||||||
return NEXT_RETRY;
|
return NEXT_RETRY;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
usleep(SLEEPTIME);
|
usleep(SLEEPTIME);
|
||||||
return NEXT_RETRY;
|
return NEXT_RETRY;
|
||||||
@ -1478,13 +1491,37 @@ void srvinit2(struct srvparam * srv, struct clientparam *param){
|
|||||||
param->sinsr = srv->extsa;
|
param->sinsr = srv->extsa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
void srvfree(struct srvparam * srv){
|
void srvfree(struct srvparam * srv){
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,7 +107,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;
|
||||||
@ -275,8 +274,9 @@ 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(¶m->sinsr);
|
/* a connected stream: no address, which a Unix socket on Linux
|
||||||
res = param->srv->so._sendto(param->sostate, param->remsock, (char *)param->clibuf + param->clioffset, (int)MIN(inclientbuf, fromclient), 0, (struct sockaddr*)¶m->sinsr, sasize);
|
would refuse */
|
||||||
|
res = param->srv->so._sendto(param->sostate, param->remsock, (char *)param->clibuf + param->clioffset, (int)MIN(inclientbuf, fromclient), 0, NULL, 0);
|
||||||
if(res <= 0) {
|
if(res <= 0) {
|
||||||
TOSERVER = 0;
|
TOSERVER = 0;
|
||||||
if(errno && errno != EAGAIN && errno != EINTR){
|
if(errno && errno != EAGAIN && errno != EINTR){
|
||||||
@ -322,8 +322,7 @@ log("send to client from buf");
|
|||||||
if(fromserver) TOSERVERBUF = 1;
|
if(fromserver) TOSERVERBUF = 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sasize = SASIZE(¶m->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*)¶m->sincr, sasize);
|
|
||||||
if(res <= 0) {
|
if(res <= 0) {
|
||||||
TOCLIENT = 0;
|
TOCLIENT = 0;
|
||||||
if(errno && errno != EAGAIN && errno != EINTR){
|
if(errno && errno != EAGAIN && errno != EINTR){
|
||||||
@ -543,8 +542,9 @@ 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);
|
/* no address: it would overwrite the client's with what a
|
||||||
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 *)¶m->sincr, &sasize);
|
stream socket gives, an empty one for a Unix socket */
|
||||||
|
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);
|
||||||
if(res <= 0) {
|
if(res <= 0) {
|
||||||
FROMCLIENT = 0;
|
FROMCLIENT = 0;
|
||||||
if(res == 0) {
|
if(res == 0) {
|
||||||
@ -587,8 +587,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 *)¶m->sinsr, &sasize);
|
|
||||||
if(res <= 0) {
|
if(res <= 0) {
|
||||||
FROMSERVER = 0;
|
FROMSERVER = 0;
|
||||||
if(res == 0) {
|
if(res == 0) {
|
||||||
|
|||||||
@ -230,7 +230,7 @@ fprintf(stderr, "%hu bound to communicate with server\n", *SAPORT(¶m->sinsl)
|
|||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
sasize = SASIZE(¶m->sinsl);
|
sasize = sizeof(param->sinsl);
|
||||||
param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsl, &sasize);
|
param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsl, &sasize);
|
||||||
}
|
}
|
||||||
if(command == 3) {
|
if(command == 3) {
|
||||||
@ -250,7 +250,7 @@ fflush(stderr);
|
|||||||
*SAPORT(&sin) = 0;
|
*SAPORT(&sin) = 0;
|
||||||
/* the port the client is told to send its datagrams to */
|
/* the port the client is told to send its datagrams to */
|
||||||
if(bindwithrange(param, param->clisock, &sin, param->intport)) {RETURN (12);}
|
if(bindwithrange(param, param->clisock, &sin, param->intport)) {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",
|
||||||
|
|||||||
@ -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
|
||||||
@ -160,7 +161,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)
|
||||||
@ -608,6 +611,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;
|
||||||
|
|||||||
@ -140,7 +140,7 @@ int udpbind(struct clientparam *param)
|
|||||||
return 12;
|
return 12;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sasize = SASIZE(¶m->sinsl);
|
sasize = sizeof(param->sinsl);
|
||||||
param->srv->so._getsockname(param->sostate, param->remsock,
|
param->srv->so._getsockname(param->sostate, param->remsock,
|
||||||
(struct sockaddr *)¶m->sinsl, &sasize);
|
(struct sockaddr *)¶m->sinsl, &sasize);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
139
tests/cases/unixsock.py
Normal file
139
tests/cases/unixsock.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
"""Unix domain sockets: a service listening on one (-iunix:PATH, and on
|
||||||
|
Linux an abstract -iunix:@NAME), tcppm forwarding to one (DSTADDR
|
||||||
|
unix:PATH), a parent reached through one (parent ... unix:PATH), and a
|
||||||
|
listening one across reloads: each reload's service binding it anew, its
|
||||||
|
file kept while a service listens on it and removed once none does."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def unix_get(path, url, timeout=10):
|
||||||
|
"""A GET for url sent to the proxy at the Unix socket path (a leading @
|
||||||
|
for an abstract one): (status, body length), or (None, error)."""
|
||||||
|
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(timeout)
|
||||||
|
try:
|
||||||
|
s.connect("\0" + path[1:] if path.startswith("@") else path)
|
||||||
|
s.sendall(f"GET {url} HTTP/1.0\r\nHost: unix\r\n\r\n".encode())
|
||||||
|
got = b""
|
||||||
|
while True:
|
||||||
|
b = s.recv(65536)
|
||||||
|
if not b:
|
||||||
|
break
|
||||||
|
got += b
|
||||||
|
except OSError as e:
|
||||||
|
return None, str(e)
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
head, _, body = got.partition(b"\r\n\r\n")
|
||||||
|
try:
|
||||||
|
return int(head.split()[1]), len(body)
|
||||||
|
except (IndexError, ValueError):
|
||||||
|
return None, head[:80]
|
||||||
|
|
||||||
|
|
||||||
|
def run(t):
|
||||||
|
if not hasattr(socket, "AF_UNIX") or sys.platform.startswith("win"):
|
||||||
|
t.skip("Unix domain sockets (not on this platform)")
|
||||||
|
return
|
||||||
|
|
||||||
|
origin = t.free_port()
|
||||||
|
mapped = t.free_port()
|
||||||
|
chained = t.free_port()
|
||||||
|
adm = t.free_port()
|
||||||
|
path = os.path.join(t.tmpdir, "px.sock")
|
||||||
|
abstract = f"@3proxy-test-{os.getpid()}" if sys.platform.startswith("linux") else None
|
||||||
|
url = f"http://127.0.0.1:{origin}/data?size=1000"
|
||||||
|
|
||||||
|
def config(unix=True):
|
||||||
|
services = f"""
|
||||||
|
flush
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
proxy -iunix:{path}
|
||||||
|
""" if unix else ""
|
||||||
|
if unix and abstract:
|
||||||
|
services += f"""
|
||||||
|
proxy -iunix:{abstract}
|
||||||
|
"""
|
||||||
|
if unix:
|
||||||
|
services += f"""
|
||||||
|
flush
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
tcppm {mapped} unix:{path} 0
|
||||||
|
|
||||||
|
flush
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
parent 1000 http unix:{path} 0
|
||||||
|
proxy -p{chained}
|
||||||
|
"""
|
||||||
|
return f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http data * /data
|
||||||
|
httpsrv -p{origin}
|
||||||
|
{services}
|
||||||
|
flush
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
admin -p{adm}
|
||||||
|
"""
|
||||||
|
|
||||||
|
server = t.start("unixsock", config(), ports=[origin, mapped, chained, adm])
|
||||||
|
# a build without Unix sockets (NO_UN) never makes the file
|
||||||
|
deadline = time.time() + 5
|
||||||
|
while time.time() < deadline and not os.path.exists(path):
|
||||||
|
time.sleep(0.05)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
t.skip("Unix domain sockets (not supported by this build)")
|
||||||
|
return
|
||||||
|
|
||||||
|
# --- a proxy listening on a Unix socket ---------------------------------
|
||||||
|
t.eq((200, 1000), unix_get(path, url), "a proxy listening on a Unix socket answers")
|
||||||
|
if abstract:
|
||||||
|
t.eq((200, 1000), unix_get(abstract, url),
|
||||||
|
"a proxy listening on an abstract Unix socket answers")
|
||||||
|
|
||||||
|
# --- tcppm forwarding to a Unix socket: the proxy behind it answers -----
|
||||||
|
r = t.http(url, proxy=f"127.0.0.1:{mapped}")
|
||||||
|
t.eq(200, r.status, "tcppm forwards a TCP port to a Unix socket")
|
||||||
|
t.eq(1000, r.length, "and the whole answer comes back through it")
|
||||||
|
|
||||||
|
# --- a parent reached through a Unix socket ------------------------------
|
||||||
|
r = t.http(url, proxy=f"127.0.0.1:{chained}")
|
||||||
|
t.eq(200, r.status, "a proxy chains to a parent on a Unix socket")
|
||||||
|
t.eq(1000, r.length, "and the whole answer comes back through the chain")
|
||||||
|
|
||||||
|
# --- reloads: each new service binds the path anew; the old one, closing
|
||||||
|
# after, must not remove the new one's file ----------------------------
|
||||||
|
for i in range(3):
|
||||||
|
before = server.output().count("Accepting connections")
|
||||||
|
t.write_config("unixsock", config())
|
||||||
|
t.http(f"http://127.0.0.1:{adm}/R")
|
||||||
|
deadline = time.time() + 10
|
||||||
|
while time.time() < deadline and server.output().count("Accepting connections") < before + 5:
|
||||||
|
time.sleep(0.05)
|
||||||
|
# the old services stop within a second of each reload
|
||||||
|
time.sleep(1.5)
|
||||||
|
t.ok("after three reloads the socket file is still there") if os.path.exists(path) else \
|
||||||
|
t.fail("after three reloads the socket file is still there", 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 -------------------
|
||||||
|
t.write_config("unixsock", config(unix=False))
|
||||||
|
t.http(f"http://127.0.0.1:{adm}/R")
|
||||||
|
deadline = time.time() + 5
|
||||||
|
while time.time() < deadline and os.path.exists(path):
|
||||||
|
time.sleep(0.1)
|
||||||
|
t.ok("once no service listens on it the socket file is removed") \
|
||||||
|
if not os.path.exists(path) else \
|
||||||
|
t.fail("once no service listens on it the socket file is removed", "no file", path)
|
||||||
|
got = unix_get(path, url, timeout=2)
|
||||||
|
t.ok("and nothing answers there") if got[0] is None else \
|
||||||
|
t.fail("and nothing answers there", "no connection", got)
|
||||||
Loading…
Reference in New Issue
Block a user