Relays: the size of a splice() pipe given to a service, -b,SIZE

-b, which made a service read less at once into a connection's buffer,
takes the size of its pipes after a comma: -b,262144, -b8192,32768.
From 4096 to 64M (MAXPIPESIZE); a pipe the system will not make so
large (pipe-max-size for an unprivileged process) is not used, and the
data is copied, as with -DPIPESIZE, which stays the default for a
service given none. sockmap() and the callbacks' relay fill a pipe up
to the service's size (SRVPIPESIZE()), and only a pipe of other than
64K costs an fcntl() as it is made.

The man page tells of -b, which it did not before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-28 13:20:42 +03:00
parent add40b1d85
commit a6402010a4
6 changed files with 44 additions and 21 deletions

View File

@ -229,6 +229,17 @@ the filters see all of the data, nor does one ending or beginning TLS, unless
the kernel does TLS for it both ways (\fBssl_ktls\fR). Available in builds with
WITHSPLICE, the default on Linux.
.br
.B -b
\fISIZE\fR[\fB,\fIPIPESIZE\fR] (for TCP services) read at most \fISIZE\fR bytes at
once into a connection's buffer: less than the 16K it has, never more.
\fIPIPESIZE\fR (Linux, with \fB-s\fR) makes the pipes of splice() hold that many
bytes instead of 64K, from 4096 to 67108864, e.g. \fB-b,262144\fR: the
system rounds it up to a power of 2 pages, and gives an unprivileged process
no more than /proc/sys/fs/pipe-max-size (1M by default). A pipe that cannot
be made so large is not used, and the data is copied. Larger pipes move more
at once and take more kernel memory for each connection; 64K was as fast as
larger ones in our tests.
.br
.B -C
(for TCP services) a connection closed by any of the sides terminates the
session, buffered data is delivered before the sockets are closed. By default

View File

@ -400,7 +400,16 @@ static int srvprepare(struct srvstart *s, int argc, char **argv){
srv->family = atoi(argv[i]+1);
break;
case 'b':
/* -b[SIZE][,PIPESIZE] */
srv->bufsize = atoi(argv[i]+2);
#ifdef WITHSPLICE
if(strchr(argv[i]+2, ',')){
long n = atol(strchr(argv[i]+2, ',') + 1);
if(n < 4096 || n > MAXPIPESIZE) error = 1;
else srv->pipesize = (unsigned)n;
}
#endif
break;
#ifdef STDMAIN
#ifndef _WIN32

View File

@ -620,13 +620,13 @@ static int cbway(struct cbmap *m, struct cbway *w){
/* what was buffered goes first: the pipe is read once it is
through */
if(!inb && !w->wantbuf && w->left > w->inpipe && !m->term_r[w->src] && !end && w->inpipe < PIPESIZE &&
if(!inb && !w->wantbuf && w->left > w->inpipe && !m->term_r[w->src] && !end && w->inpipe < SRVPIPESIZE(param->srv) &&
(rs->ready & (POLLIN | POLLHUP | POLLERR))){
if(pipe[0] < 0 && pipenew(pipe) < 0){
if(pipe[0] < 0 && pipenew(pipe, SRVPIPESIZE(param->srv)) < 0){
w->splice = 0;
return 1;
}
n = splice(from, NULL, pipe[1], NULL, (size_t)CBMIN((uint64_t)PIPESIZE - w->inpipe,
n = splice(from, NULL, pipe[1], NULL, (size_t)CBMIN((uint64_t)SRVPIPESIZE(param->srv) - w->inpipe,
w->left - w->inpipe), SPLICE_F_NONBLOCK | SPLICE_F_MOVE);
if(n > 0){
w->inpipe += n;

View File

@ -614,11 +614,14 @@ extern struct commands commandhandlers[];
#define SPLICE_TLSPENDING 2
extern int (*splicetls)(struct clientparam *param, SOCKET s);
/* what a pipe of splice() is given at most: 64K, what a pipe holds;
built with -DPIPESIZE=<bytes>, pipes are made that large */
built with -DPIPESIZE=<bytes>, or for a service given -b,<bytes>, pipes
are made that large, up to MAXPIPESIZE */
#ifndef PIPESIZE
#define PIPESIZE 65536
#endif
int pipenew(int p[2]);
#define MAXPIPESIZE (64 * 1024 * 1024)
#define SRVPIPESIZE(srv) ((srv)->pipesize? (srv)->pipesize : (unsigned)PIPESIZE)
int pipenew(int p[2], unsigned size);
#define mapsocket(a,b) ((a->srv->usesplice && !a->nosplice && !a->ndatfilterssrv && !a->ndatfilterscli)?sockmap(a,b,a->srv->usesplice):sockmap(a,b,0))
#else
#define mapsocket(a,b) sockmap(a,b, 0)

View File

@ -35,18 +35,16 @@ ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t le
and EBADMSG on some kernels */
#define SPLICEREFUSED(e) ((e) == EINVAL || (e) == EIO || (e) == EBADMSG)
/* A pipe for splice(), nonblocking, of PIPESIZE: -1 if there is none to be
had, or not of that size. */
int pipenew(int p[2]){
/* A pipe for splice(), nonblocking, holding size: -1 if there is none to
be had, or not so large. A pipe holds 64K as it is made. */
int pipenew(int p[2], unsigned size){
if(pipe2(p, O_NONBLOCK) < 0) return -1;
#if PIPESIZE != 65536
if(fcntl(p[0], F_SETPIPE_SZ, PIPESIZE) < 0){
if(size != 65536 && fcntl(p[0], F_SETPIPE_SZ, (int)size) < 0){
close(p[0]);
close(p[1]);
p[0] = p[1] = -1;
return -1;
}
#endif
return 0;
}
@ -140,6 +138,7 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
#ifdef WITHSPLICE
uint64_t inclientpipe = 0, inserverpipe = 0;
unsigned pipesize = SRVPIPESIZE(param->srv);
int TOCLIENTPIPE = 0, FROMCLIENTPIPE = 0, TOSERVERPIPE = 0, FROMSERVERPIPE = 0;
/* The client's data goes through pipecli, the server's through pipesrv.
They belong to the connection: made when first needed, kept for the
@ -361,7 +360,7 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
/* each direction goes through its pipe if spliced, through its
buffer otherwise; what was in the buffer before is sent first */
if(splicecli && inclientpipe && !inclientbuf && FROMCLIENTPIPE && TOSERVER){
res = splice(pipecli[0], NULL, param->remsock, NULL, MIN(PIPESIZE, inclientpipe), SPLICE_F_NONBLOCK|SPLICE_F_MOVE);
res = splice(pipecli[0], NULL, param->remsock, NULL, MIN(pipesize, inclientpipe), SPLICE_F_NONBLOCK|SPLICE_F_MOVE);
/* from a pipe holding data: the socket is full */
if(res < 0 && errno == EAGAIN && fiberblockfunc) fiberblockfunc(param->remsock, POLLOUT);
if(res >0) {
@ -388,7 +387,7 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
}
}
if(splicesrv && inserverpipe && !inserverbuf && FROMSERVERPIPE && TOCLIENT){
res = splice(pipesrv[0], NULL, param->clisock, NULL, MIN(PIPESIZE, inserverpipe), SPLICE_F_NONBLOCK|SPLICE_F_MOVE);
res = splice(pipesrv[0], NULL, param->clisock, NULL, MIN(pipesize, inserverpipe), SPLICE_F_NONBLOCK|SPLICE_F_MOVE);
/* from a pipe holding data: the socket is full */
if(res < 0 && errno == EAGAIN && fiberblockfunc) fiberblockfunc(param->clisock, POLLOUT);
if(res > 0) {
@ -408,7 +407,7 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
}
}
if(splicecli && !wantbufcli && fromclient>inclientpipe && FROMCLIENT && TOCLIENTPIPE && !SESSIONEND){
if(pipecli[0] < 0 && pipenew(pipecli) < 0){
if(pipecli[0] < 0 && pipenew(pipecli, pipesize) < 0){
/* no pipe to be had: copy instead */
allowcli = splicecli = TOCLIENTPIPE = FROMCLIENTPIPE = 0;
if(param->cliinbuf < CLIROOM) TOCLIENTBUF = 1;
@ -425,7 +424,7 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
continue;
}
errno = 0;
res = splice(param->clisock, NULL, pipecli[1], NULL, (int)MIN((uint64_t)PIPESIZE - inclientpipe, (uint64_t)fromclient-inclientpipe), SPLICE_F_NONBLOCK|SPLICE_F_MOVE);
res = splice(param->clisock, NULL, pipecli[1], NULL, (int)MIN((uint64_t)pipesize - inclientpipe, (uint64_t)fromclient-inclientpipe), SPLICE_F_NONBLOCK|SPLICE_F_MOVE);
if(res <= 0) {
FROMCLIENT = TOCLIENTPIPE = 0;
if(res == 0 && !errno) {
@ -455,13 +454,13 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
else if(++streakcli > splicestreak) wantbufcli = 1;
}
inclientpipe += res;
if(inclientpipe >= PIPESIZE) TOCLIENTPIPE = 0;
if(inclientpipe >= pipesize) TOCLIENTPIPE = 0;
needaction = 0;
continue;
}
}
if(splicesrv && !wantbufsrv && fromserver > inserverpipe && FROMSERVER && TOSERVERPIPE && !SESSIONEND){
if(pipesrv[0] < 0 && pipenew(pipesrv) < 0){
if(pipesrv[0] < 0 && pipenew(pipesrv, pipesize) < 0){
allowsrv = splicesrv = TOSERVERPIPE = FROMSERVERPIPE = 0;
if(param->srvinbuf < SRVROOM) TOSERVERBUF = 1;
continue;
@ -475,7 +474,7 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
continue;
}
errno = 0;
res = splice(param->remsock, NULL, pipesrv[1], NULL, MIN(PIPESIZE - inserverpipe, fromserver - inserverpipe), SPLICE_F_NONBLOCK|SPLICE_F_MOVE);
res = splice(param->remsock, NULL, pipesrv[1], NULL, MIN(pipesize - inserverpipe, fromserver - inserverpipe), SPLICE_F_NONBLOCK|SPLICE_F_MOVE);
if(res <= 0) {
FROMSERVER = TOSERVERPIPE = 0;
if(res == 0 && !errno) {
@ -505,7 +504,7 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
param->nreads++;
param->statssrv64 += res;
inserverpipe += res;
if(inserverpipe >= PIPESIZE) TOSERVERPIPE = 0;
if(inserverpipe >= pipesize) TOSERVERPIPE = 0;
if(param->bandlimfunc) {
int sl1;
sl1 = (*param->bandlimfunc)(param, res, 0);
@ -753,7 +752,7 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
// }
#ifdef WITHSPLICE
{
if(splicecli && fromclient>inclientpipe && !TOCLIENTPIPE && inclientpipe < PIPESIZE){
if(splicecli && fromclient>inclientpipe && !TOCLIENTPIPE && inclientpipe < pipesize){
if(!after){
fds[fdsc].fd = pipecli[1];
fds[fdsc].events |= POLLOUT;
@ -781,7 +780,7 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
}
fdsc++;
}
if(splicesrv && fromserver>inserverpipe && !TOSERVERPIPE && inserverpipe < PIPESIZE){
if(splicesrv && fromserver>inserverpipe && !TOSERVERPIPE && inserverpipe < pipesize){
if(!after){
fds[fdsc].fd = pipesrv[1];
fds[fdsc].events |= POLLOUT;

View File

@ -723,6 +723,7 @@ struct srvparam {
int usesplice;
/* the automatic mode's heuristics, -s[N]:MIN,STREAK,SMALL */
int splicemin, splicestreak, splicesmall;
unsigned pipesize; /* -b,SIZE: what a pipe is made to hold, 0 PIPESIZE */
#endif
int halfclose;
int udpauth;