Free the HTTP proxy's request buffer while a connection waits

proxychild() allocated its 64 KB buffer for headers once and kept it
for the life of the connection: for a CONNECT tunnel all the time data
was relayed, and for a kept-alive connection between its requests.
With 2000 tunnels that was 128 MB, with 20000 mostly idle connections
over a gigabyte.

The buffer is now freed before a tunnel starts relaying, and when a
kept-alive connection has waited a second for its next request; it is
allocated again once the next request starts coming. logurl() takes a
copy of its own for a request it logs after its buffer was freed.

2000 tunnels: 339 to 213 MB; 20000 connections, 3000 exchanges a
second: 2.65 to 1.63 GB through the HTTP proxy, 2.66 to 1.42 GB through
CONNECT; busy kept-alive connections within 1.5% of before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-13 12:21:23 +03:00
parent 305f23bc49
commit aec167f8b3

View File

@ -140,6 +140,8 @@ char * proxy_stringtable[] = {
#define HDRRESERVE 2048 #define HDRRESERVE 2048
#define BUFSIZE (LINESIZE*2) #define BUFSIZE (LINESIZE*2)
#define FTPBUFSIZE 1536 #define FTPBUFSIZE 1536
/* seconds a connection waits for its next request before its buffer is freed */
#define IDLEBUF 1
#define PST_COUNT (sizeof(proxy_stringtable)/sizeof(proxy_stringtable[0]) - 1) #define PST_COUNT (sizeof(proxy_stringtable)/sizeof(proxy_stringtable[0]) - 1)
static int proxy_stringtable_len[PST_COUNT]; static int proxy_stringtable_len[PST_COUNT];
@ -226,11 +228,16 @@ static void logurl(struct clientparam * param, char * buf, char * req, int ftp){
char *sb; char *sb;
char *se; char *se;
int len; int len;
char *own = NULL;
if(!buf) req = NULL; if(param->res == 555 || param->res == 508) return;
if(req) { if(req) {
len = (int)strlen(req); len = (int)strlen(req);
if(len > (LINESIZE - 1)) len = LINESIZE - 1; if(len > (LINESIZE - 1)) len = LINESIZE - 1;
/* a tunnel gave its buffer back before it was relayed */
if(!buf && !(buf = own = malloc(LINESIZE))) req = NULL;
}
if(req) {
memcpy(buf, req, len + 1); memcpy(buf, req, len + 1);
buf[LINESIZE - 1] = 0; buf[LINESIZE - 1] = 0;
sb = strchr(buf, '\r'); sb = strchr(buf, '\r');
@ -239,7 +246,8 @@ static void logurl(struct clientparam * param, char * buf, char * req, int ftp){
memmove(se, sb, strlen(sb)+1); memmove(se, sb, strlen(sb)+1);
} }
} }
if(param->res != 555 && param->res != 508)dolog(param, (unsigned char *)(req?buf:NULL)); dolog(param, (unsigned char *)(req?buf:NULL));
free(own);
} }
void decodeurl(unsigned char *s, int allowcr){ void decodeurl(unsigned char *s, int allowcr){
@ -335,18 +343,24 @@ void * proxychild(struct clientparam* param) {
if(param->remsock != INVALID_SOCKET) haveconnection = 1; if(param->remsock != INVALID_SOCKET) haveconnection = 1;
if(!(buf = malloc(BUFSIZE))) {RETURN(21);}
bufsize = BUFSIZE;
#ifndef ANONYMOUS #ifndef ANONYMOUS
anonymous = param->srv->anonymous; anonymous = param->srv->anonymous;
#endif #endif
for(;;){ for(;;){
memset(buf, 0, bufsize);
inbuf = 0; inbuf = 0;
chunkedcli = 0; chunkedcli = 0;
clhdrofs_req = -1; clhdrofs_req = -1;
param->nolongdatfilter = 0; param->nolongdatfilter = 0;
/* A connection waiting a while for its next request, or a tunnel, holds
no buffer: it is taken again once a request starts coming. */
if(buf && param->cliinbuf == param->clioffset){
memset(fds, 0, sizeof(fds));
fds[0].fd = param->clisock;
fds[0].events = POLLIN;
if(param->srv->so._poll(param->sostate, fds, 1, IDLEBUF * 1000) == 0) freeptr(&buf);
}
if(buf) memset(buf, 0, bufsize);
if(keepalive && (param->cliinbuf == param->clioffset) && (param->remsock != INVALID_SOCKET)){ if(keepalive && (param->cliinbuf == param->clioffset) && (param->remsock != INVALID_SOCKET)){
memset(fds, 0, sizeof(fds)); memset(fds, 0, sizeof(fds));
@ -372,6 +386,13 @@ for(;;){
} }
} }
if(!buf){
if(sockgetcharcli(param, conf.timeouts[STRING_L], 0) == EOF) RETURN((keepalive)?555:508);
param->clioffset--;
if(!(buf = malloc(BUFSIZE))) {RETURN(21);}
bufsize = BUFSIZE;
memset(buf, 0, bufsize);
}
i = sockgetlinebuf(param, CLIENT, buf, LINESIZE - 1, '\n', conf.timeouts[STRING_L]); i = sockgetlinebuf(param, CLIENT, buf, LINESIZE - 1, '\n', conf.timeouts[STRING_L]);
if(i<=0) { if(i<=0) {
RETURN((keepalive)?555:(i)?507:508); RETURN((keepalive)?555:(i)?507:508);
@ -987,6 +1008,7 @@ for(;;){
freeptr(&req); freeptr(&buf); freeptr(&ftpbase); freeptr(&req); freeptr(&buf); freeptr(&ftpbase);
return (void *)param->redirectfunc; return (void *)param->redirectfunc;
} }
freeptr(&buf);
param->res = mapsocket(param, conf.timeouts[CONNECTION_L]); param->res = mapsocket(param, conf.timeouts[CONNECTION_L]);
RETURN(param->res); RETURN(param->res);
} }
@ -1298,6 +1320,7 @@ for(;;){
} while(param->chunked); } while(param->chunked);
} }
if(isconnect && res == 200 && param->operation){ if(isconnect && res == 200 && param->operation){
freeptr(&buf);
RETURN (mapsocket(param, conf.timeouts[CONNECTION_S])); RETURN (mapsocket(param, conf.timeouts[CONNECTION_S]));
} }
else if(isconnect){ else if(isconnect){