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 the kernel does TLS for it both ways (\fBssl_ktls\fR). Available in builds with
WITHSPLICE, the default on Linux. WITHSPLICE, the default on Linux.
.br .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 .B -C
(for TCP services) a connection closed by any of the sides terminates the (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 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); srv->family = atoi(argv[i]+1);
break; break;
case 'b': case 'b':
/* -b[SIZE][,PIPESIZE] */
srv->bufsize = atoi(argv[i]+2); 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; break;
#ifdef STDMAIN #ifdef STDMAIN
#ifndef _WIN32 #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 /* what was buffered goes first: the pipe is read once it is
through */ 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))){ (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; w->splice = 0;
return 1; 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); w->left - w->inpipe), SPLICE_F_NONBLOCK | SPLICE_F_MOVE);
if(n > 0){ if(n > 0){
w->inpipe += n; w->inpipe += n;

View File

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

View File

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