Fix socket draining / lingering

This commit is contained in:
Vladimir Dubrovin 2026-10-11 19:04:53 +03:00
parent baea44477e
commit f128fd2aa7
5 changed files with 76 additions and 7 deletions

View File

@ -387,7 +387,6 @@ int
return shutdown(s, how); return shutdown(s, how);
} }
int WINAPI def_closesocket(void* state, SOCKET s){ int WINAPI def_closesocket(void* state, SOCKET s){
usleep(SLEEPTIME);
return closesocket(s); return closesocket(s);
} }
#else #else
@ -715,7 +714,9 @@ int doconnect(struct clientparam * param){
if(param->srv->so._getpeername(param->sostate, param->remsock, (struct sockaddr *)&param->sinsr, &size)==-1) {return (14);} if(param->srv->so._getpeername(param->sostate, param->remsock, (struct sockaddr *)&param->sinsr, &size)==-1) {return (14);}
} }
else { else {
#ifndef _WIN32
struct linger lg = {1,conf.timeouts[LINGER_TO]}; struct linger lg = {1,conf.timeouts[LINGER_TO]};
#endif
int tries = RANGETRIES; int tries = RANGETRIES;
if(SAISNULL(&param->sinsr)){ if(SAISNULL(&param->sinsr)){
@ -747,7 +748,10 @@ int doconnect(struct clientparam * param){
*SAPORT(&param->sinsl) = 0; *SAPORT(&param->sinsl) = 0;
setopts(param->remsock, param->srv->srvsockopts); setopts(param->remsock, param->srv->srvsockopts);
#ifndef _WIN32
/* not on Windows, see the accepting side in proxymain.c */
param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg)); param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
#endif
if(extbindtodevice(param, param->remsock)) return 12; if(extbindtodevice(param, param->remsock)) return 12;
#ifdef WITH_UN #ifdef WITH_UN
if(*SAFAMILY(&param->sinsl) != AF_UNIX) if(*SAFAMILY(&param->sinsl) != AF_UNIX)

View File

@ -23,14 +23,18 @@ void * ftpprchild(struct clientparam* param) {
SOCKET sc = INVALID_SOCKET, ss = INVALID_SOCKET, clidatasock = INVALID_SOCKET; SOCKET sc = INVALID_SOCKET, ss = INVALID_SOCKET, clidatasock = INVALID_SOCKET;
SASIZETYPE sasize; SASIZETYPE sasize;
char * req = NULL; char * req = NULL;
#ifndef _WIN32
struct linger lg; struct linger lg;
#endif
struct pollfd fds; struct pollfd fds;
if(!(buf = malloc(BUFSIZE))) RETURN(876); if(!(buf = malloc(BUFSIZE))) RETURN(876);
param->ctrlsock = param->clisock; param->ctrlsock = param->clisock;
param->operation = CONNECT; param->operation = CONNECT;
#ifndef _WIN32
lg.l_onoff = 1; lg.l_onoff = 1;
lg.l_linger = conf.timeouts[STRING_L];; lg.l_linger = conf.timeouts[STRING_L];
#endif
if(socksend(param, param->ctrlsock, (unsigned char *)"220 Ready\r\n", 11, conf.timeouts[STRING_S])!=11) {RETURN (801);} if(socksend(param, param->ctrlsock, (unsigned char *)"220 Ready\r\n", 11, conf.timeouts[STRING_S])!=11) {RETURN (801);}
for(;;){ for(;;){
i = sockgetlinebuf(param, CLIENT, buf, BUFSIZE - 10, '\n', conf.timeouts[CONNECTION_S]); i = sockgetlinebuf(param, CLIENT, buf, BUFSIZE - 10, '\n', conf.timeouts[CONNECTION_S]);
@ -262,8 +266,11 @@ void * ftpprchild(struct clientparam* param) {
} }
sc = param->remsock; sc = param->remsock;
param->remsock = ss; param->remsock = ss;
#ifndef _WIN32
/* not on Windows, see the accepting side in proxymain.c */
param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg)); param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
param->srv->so._setsockopt(param->sostate, clidatasock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg)); param->srv->so._setsockopt(param->sostate, clidatasock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
#endif
param->clisock = clidatasock; param->clisock = clidatasock;
res = mapsocket(param, conf.timeouts[CONNECTION_S]); res = mapsocket(param, conf.timeouts[CONNECTION_S]);
if(param->remsock != INVALID_SOCKET) { if(param->remsock != INVALID_SOCKET) {

View File

@ -1154,6 +1154,37 @@ static void httpsrv_drain(struct clientparam *param, uint64_t len)
} }
} }
/* Ends a connection this server is closing while the client may still be
sending: a body which was not read, or a request sent behind one answered
with a close. Closing with that unread resets the connection, and the
client loses the answer. The sending side is shut down instead and what
arrives is thrown away until the client closes too, for a bounded time
and amount, which is what lets the answer be read. */
static void httpsrv_linger(struct clientparam *param)
{
char buf[HTTPSRV_BLOCK];
struct pollfd fd;
uint64_t left = HTTPSRV_MAXBODY;
time_t deadline = time(NULL) + conf.timeouts[LINGER_TO];
if(param->clisock == INVALID_SOCKET) return;
if(param->srv->so._shutdown(param->sostate, param->clisock, SHUT_WR)) return;
while(left){
int timeo = (int)(deadline - time(NULL));
int got;
if(timeo <= 0) break;
memset(&fd, 0, sizeof(fd));
fd.fd = param->clisock;
fd.events = POLLIN;
if(param->srv->so._poll(param->sostate, &fd, 1, timeo * 1000) <= 0) break;
got = (int)param->srv->so._recv(param->sostate, param->clisock, buf,
(left > sizeof(buf))? (int)sizeof(buf) : (int)left, 0);
if(got <= 0) break;
left -= (uint64_t)got;
}
}
/* Keeps the request as the client wrote it. Only what a handoff needs: the /* Keeps the request as the client wrote it. Only what a handoff needs: the
request line and the headers, exactly as they came, since the proxy code request line and the headers, exactly as they came, since the proxy code
reads them again from the beginning. */ reads them again from the beginning. */
@ -1451,6 +1482,12 @@ static int httpsrv_request(struct clientparam *param, struct httpreq *r)
} }
} }
if(i && i != 10){ if(i && i != 10){
/* The body goes before the answer, as for any answer this server
gives: left in the stream it would be read as the next request. */
if(!r->drained){
if(r->contentlen) httpsrv_drain(param, r->contentlen);
r->drained = 1;
}
/* 4 no credentials, 5 unknown user, 6 wrong password: all of them /* 4 no credentials, 5 unknown user, 6 wrong password: all of them
should let the client offer credentials again. */ should let the client offer credentials again. */
if(i >= 4 && i <= 6) op_authrequired(r); if(i >= 4 && i <= 6) op_authrequired(r);
@ -1573,7 +1610,12 @@ void * httpsrvchild(struct clientparam *param)
dolog(param, (unsigned char *)logbuf); dolog(param, (unsigned char *)logbuf);
} }
if(handoff || !r.keepalive) break; if(handoff) break;
if(!r.keepalive){
/* the client may still be sending; let it read the answer */
httpsrv_linger(param);
break;
}
first = 0; first = 0;
} }
/* A child named by an access rule takes the connection over, which the /* A child named by an access rule takes the connection over, which the

View File

@ -306,7 +306,9 @@ int MODULEMAINFUNC (int argc, char** argv){
unsigned char *cbc_string = NULL, *cbl_string = NULL; unsigned char *cbc_string = NULL, *cbl_string = NULL;
PROXYSOCKADDRTYPE cbsa; PROXYSOCKADDRTYPE cbsa;
FILE *fp = NULL; FILE *fp = NULL;
#ifndef _WIN32
struct linger lg; struct linger lg;
#endif
int nlog = 5000; int nlog = 5000;
int haveslot = 0, nfull = 0; int haveslot = 0, nfull = 0;
#ifdef __linux__ #ifdef __linux__
@ -1117,10 +1119,14 @@ int MODULEMAINFUNC (int argc, char** argv){
#else #else
fcntl(new_sock,F_SETFL,O_NONBLOCK | fcntl(new_sock,F_GETFL)); fcntl(new_sock,F_SETFL,O_NONBLOCK | fcntl(new_sock,F_GETFL));
#endif #endif
#ifndef _WIN32
/* Not on Windows: there closesocket() on a non-blocking socket with a linger
timeout fails with WSAEWOULDBLOCK while data is queued, leaving the socket
open. Without it the close completes in the background, data first. */
lg.l_onoff = 1; lg.l_onoff = 1;
lg.l_linger = conf.timeouts[STRING_L]; lg.l_linger = conf.timeouts[STRING_L];
srv.so._setsockopt(srv.so.state, new_sock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg)); srv.so._setsockopt(srv.so.state, new_sock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
#endif
srv.so._setsockopt(srv.so.state, new_sock, SOL_SOCKET, SO_OOBINLINE, (char *)&opt, sizeof(int)); srv.so._setsockopt(srv.so.state, new_sock, SOL_SOCKET, SO_OOBINLINE, (char *)&opt, sizeof(int));
} }
#ifndef NOUDPMAIN #ifndef NOUDPMAIN
@ -1413,21 +1419,34 @@ void freeparam(struct clientparam * param) {
} }
if(param->clibuf) free(param->clibuf); if(param->clibuf) free(param->clibuf);
if(param->srvbuf) free(param->srvbuf); if(param->srvbuf) free(param->srvbuf);
/* Not shut down on Windows: there shutting down receiving resets the
connection as soon as the peer sends anything more, and close()
alone sends whatever is queued before its FIN. Elsewhere it stays:
on the BSDs it discards what is left unread, so the close that
follows is not turned into a reset. */
if(param->srv) { if(param->srv) {
if(param->ctrlsocksrv != INVALID_SOCKET && param->ctrlsocksrv != param->remsock) { if(param->ctrlsocksrv != INVALID_SOCKET && param->ctrlsocksrv != param->remsock) {
#ifndef _WIN32
param->srv->so._shutdown(param->sostate, param->ctrlsocksrv, SHUT_RDWR); param->srv->so._shutdown(param->sostate, param->ctrlsocksrv, SHUT_RDWR);
#endif
param->srv->so._closesocket(param->sostate, param->ctrlsocksrv); param->srv->so._closesocket(param->sostate, param->ctrlsocksrv);
} }
if(param->ctrlsock != INVALID_SOCKET && param->ctrlsock != param->clisock) { if(param->ctrlsock != INVALID_SOCKET && param->ctrlsock != param->clisock) {
#ifndef _WIN32
param->srv->so._shutdown(param->sostate, param->ctrlsock, SHUT_RDWR); param->srv->so._shutdown(param->sostate, param->ctrlsock, SHUT_RDWR);
#endif
param->srv->so._closesocket(param->sostate, param->ctrlsock); param->srv->so._closesocket(param->sostate, param->ctrlsock);
} }
if(param->remsock != INVALID_SOCKET) { if(param->remsock != INVALID_SOCKET) {
#ifndef _WIN32
param->srv->so._shutdown(param->sostate, param->remsock, SHUT_RDWR); param->srv->so._shutdown(param->sostate, param->remsock, SHUT_RDWR);
#endif
param->srv->so._closesocket(param->sostate, param->remsock); param->srv->so._closesocket(param->sostate, param->remsock);
} }
if(param->clisock != INVALID_SOCKET) { if(param->clisock != INVALID_SOCKET) {
#ifndef _WIN32
param->srv->so._shutdown(param->sostate, param->clisock, SHUT_RDWR); param->srv->so._shutdown(param->sostate, param->clisock, SHUT_RDWR);
#endif
param->srv->so._closesocket(param->sostate, param->clisock); param->srv->so._closesocket(param->sostate, param->clisock);
} }
} }

View File

@ -819,9 +819,6 @@ log("timeout");
else if(inclientpipe || inserverpipe) res = 94; else if(inclientpipe || inserverpipe) res = 94;
#endif #endif
if((param->nwrites > 0 && !SERVERTERMWRITE) || (param->nreads > 0 && !CLIENTTERMWRITE))
usleep(SLEEPTIME * 10);
CLEANRET: CLEANRET:
#ifdef WITHSPLICE #ifdef WITHSPLICE