3proxy/src/sockgetchar.c
Vladimir Dubrovin ef4969da19
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
Unix sockets: no addresses on streams, abstract names, tcppm port 0
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>
2026-10-18 12:25:13 +03:00

223 lines
7.5 KiB
C

/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
int socksend(struct clientparam *param, SOCKET sock, unsigned char * buf, int bufsize, int to){
int sent = 0;
int res;
struct pollfd fds;
fds.fd = sock;
fds.events = POLLOUT;
do {
if(conf.timetoexit) return 0;
/* try to send first; poll only when the socket would block */
res = param?param->srv->so._send(param->sostate, sock, (char *)buf + sent, bufsize - sent, 0) : so._send(so.state, sock, (char *)buf + sent, bufsize - sent, 0);
if(res > 0) {
sent += res;
continue;
}
if(res < 0 && errno != EAGAIN && errno != EINTR) break;
res = param?param->srv->so._poll(param->sostate, &fds, 1, to*1000):so._poll(so.state, &fds, 1, to*1000);
if(res < 0 && (errno == EAGAIN || errno == EINTR)) continue;
if(res < 1) break;
} while (sent < bufsize);
return sent;
}
int socksendto(struct clientparam *param, SOCKET sock, struct sockaddr * sin, unsigned char * buf, int bufsize, int to){
int sent = 0;
int res;
struct pollfd fds;
fds.fd = sock;
do {
if(conf.timetoexit) return 0;
fds.events = POLLOUT;
res = param?param->srv->so._poll(param->sostate, &fds, 1, to):so._poll(so.state, &fds, 1, to);
if(res < 0 && (errno == EAGAIN || errno == EINTR)) continue;
if(res < 1) break;
res = param?param->srv->so._sendto(param->sostate, sock, (char *)buf + sent, bufsize - sent, 0, sin, SASIZE(sin)):so._sendto(so.state, sock, (char *)buf + sent, bufsize - sent, 0, sin, SASIZE(sin));
if(res < 0) {
if(errno != EAGAIN && errno != EINTR) break;
continue;
}
sent += res;
} while (sent < bufsize);
return sent;
}
int sockrecvfrom(struct clientparam *param, SOCKET sock, struct sockaddr * sin, unsigned char * buf, int bufsize, int to){
struct pollfd fds;
SASIZETYPE sasize;
int res;
fds.fd = sock;
fds.events = POLLIN;
if(conf.timetoexit) return EOF;
res = param?param->srv->so._poll(param->sostate, &fds, 1, to):so._poll(so.state, &fds, 1, to);
if (res<1) return 0;
sasize = sizeof(PROXYSOCKADDRTYPE);
/* sin NULL for a stream socket: nothing to learn the address into */
do {
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));
return res;
}
int sockgetcharcli(struct clientparam * param, int timeosec, int timeousec){
int len;
if(!param->clibuf){
if(!(param->clibuf = malloc(SRVBUFSIZE))) return 0;
param->clibufsize = SRVBUFSIZE;
param->clioffset = param->cliinbuf = 0;
}
if(param->cliinbuf && param->clioffset < param->cliinbuf){
return (int)param->clibuf[param->clioffset++];
}
param->clioffset = param->cliinbuf = 0;
if ((len = sockrecvfrom(param, param->clisock, (struct sockaddr *)&param->sincr, param->clibuf, param->clibufsize, timeosec*1000 + timeousec))<=0) return EOF;
param->cliinbuf = len;
param->clioffset = 1;
return (int)*param->clibuf;
}
/* Put bytes back in front of whatever the client has not been read yet, so a
service which has already taken a request off the socket can hand it to
another one, which reads it the way it reads anything else. */
int pushbackcli(struct clientparam * param, const unsigned char * data, int len){
unsigned left = 0;
unsigned need;
if(len <= 0) return 0;
if(param->clibuf) left = param->cliinbuf - param->clioffset;
need = (unsigned)len + left;
if(!param->clibuf){
if(!(param->clibuf = malloc(need > SRVBUFSIZE? need : SRVBUFSIZE))) return 1;
param->clibufsize = need > SRVBUFSIZE? need : SRVBUFSIZE;
}
else if(param->clibufsize < need){
unsigned char *nb = realloc(param->clibuf, need);
if(!nb) return 1;
param->clibuf = nb;
param->clibufsize = need;
}
if(left) memmove(param->clibuf + len, param->clibuf + param->clioffset, left);
memcpy(param->clibuf, data, (size_t)len);
param->clioffset = 0;
param->cliinbuf = need;
return 0;
}
unsigned long sockfillbuffcli(struct clientparam * param, unsigned long size, int timeosec){
int len;
if(!param->clibuf) return 0;
if(param->cliinbuf == param->clioffset){
param->cliinbuf = param->clioffset = 0;
}
else if(param->clioffset){
memmove(param->clibuf, param->clibuf + param->clioffset, param->cliinbuf - param->clioffset);
param->cliinbuf -= param->clioffset;
param->clioffset = 0;
}
if(size <= param->cliinbuf) return size;
size -= param->cliinbuf;
if((len = sockrecvfrom(param, param->clisock, (struct sockaddr *)&param->sincr, param->clibuf + param->cliinbuf, (param->clibufsize - param->cliinbuf) < size? param->clibufsize - param->cliinbuf:size, timeosec*1000)) > 0){
param->cliinbuf += len;
}
return param->cliinbuf;
}
unsigned long sockfillbuffsrv(struct clientparam * param, unsigned long size, int timeosec){
int len;
if(!param->srvbuf) return 0;
if(param->srvinbuf == param->srvoffset){
param->srvinbuf = param->srvoffset = 0;
}
else if(param->srvoffset){
memmove(param->srvbuf, param->srvbuf + param->srvoffset, param->srvinbuf - param->srvoffset);
param->srvinbuf -= param->srvoffset;
param->srvoffset = 0;
}
if(size <= param->srvinbuf) return size;
size -= param->srvinbuf;
if((len = sockrecvfrom(param, param->remsock, (struct sockaddr *)&param->sinsr, param->srvbuf + param->srvinbuf, (param->srvbufsize - param->srvinbuf) < size? param->srvbufsize - param->srvinbuf:size, timeosec*1000)) > 0){
param->srvinbuf += len;
param->nreads++;
param->statssrv64 += len;
}
return param->srvinbuf;
}
int sockgetcharsrv(struct clientparam * param, int timeosec, int timeousec){
int len;
int bufsize;
if(!param->srvbuf){
bufsize = SRVBUFSIZE;
if(param->ndatfilterssrv > 0 && bufsize < 32768) bufsize = 32768;
if(!(param->srvbuf = malloc(bufsize))) return 0;
param->srvbufsize = bufsize;
param->srvoffset = param->srvinbuf = 0;
}
if(param->srvinbuf && param->srvoffset < param->srvinbuf){
return (int)param->srvbuf[param->srvoffset++];
}
param->srvoffset = param->srvinbuf = 0;
if ((len = sockrecvfrom(param, param->remsock, (struct sockaddr *)&param->sinsr, param->srvbuf, param->srvbufsize, timeosec*1000 + timeousec))<=0) return EOF;
param->srvinbuf = len;
param->srvoffset = 1;
param->nreads++;
param->statssrv64 += len;
return (int)*param->srvbuf;
}
int sockgetlinebuf(struct clientparam * param, DIRECTION which, unsigned char * buf, int bufsize, int delim, int to){
int c;
int i=0;
for(;;){
unsigned char *base = which? param->srvbuf : param->clibuf;
unsigned *offp = which? &param->srvoffset : &param->clioffset;
unsigned *inp = which? &param->srvinbuf : &param->cliinbuf;
/* drain already-buffered bytes in bulk instead of one call per byte */
if(base && *offp < *inp && i < bufsize){
int avail = (int)(*inp - *offp);
int n = (bufsize - i < avail)? bufsize - i : avail;
if(delim != EOF){
unsigned char *d = (unsigned char *)memchr(base + *offp, delim, n);
if(d) n = (int)(d - (base + *offp)) + 1;
/* caller may use param->srvbuf / param->clibuf as buf */
memmove(buf + i, base + *offp, n);
i += n; *offp += n;
if(d || i >= bufsize) return i;
}
else {
memmove(buf + i, base + *offp, n);
i += n; *offp += n;
if(i >= bufsize) return i;
}
}
if(i >= bufsize) return i;
/* buffer empty: one call forces a refill (with poll + accounting), then bulk-drain the rest */
if((c = which? sockgetcharsrv(param, to, 0) : sockgetcharcli(param, to, 0)) == EOF) return i;
buf[i++] = c;
if((delim != EOF && c == delim) || i >= bufsize) return i;
}
}