diff --git a/src/common.c b/src/common.c index 6dacf65..10bf296 100644 --- a/src/common.c +++ b/src/common.c @@ -387,7 +387,6 @@ int return shutdown(s, how); } int WINAPI def_closesocket(void* state, SOCKET s){ - usleep(SLEEPTIME); return closesocket(s); } #else @@ -715,7 +714,9 @@ int doconnect(struct clientparam * param){ if(param->srv->so._getpeername(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsr, &size)==-1) {return (14);} } else { +#ifndef _WIN32 struct linger lg = {1,conf.timeouts[LINGER_TO]}; +#endif int tries = RANGETRIES; if(SAISNULL(¶m->sinsr)){ @@ -747,7 +748,10 @@ int doconnect(struct clientparam * param){ *SAPORT(¶m->sinsl) = 0; 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)); +#endif if(extbindtodevice(param, param->remsock)) return 12; #ifdef WITH_UN if(*SAFAMILY(¶m->sinsl) != AF_UNIX) diff --git a/src/ftppr.c b/src/ftppr.c index f7d4ce6..e6f8d42 100644 --- a/src/ftppr.c +++ b/src/ftppr.c @@ -23,14 +23,18 @@ void * ftpprchild(struct clientparam* param) { SOCKET sc = INVALID_SOCKET, ss = INVALID_SOCKET, clidatasock = INVALID_SOCKET; SASIZETYPE sasize; char * req = NULL; +#ifndef _WIN32 struct linger lg; +#endif struct pollfd fds; if(!(buf = malloc(BUFSIZE))) RETURN(876); param->ctrlsock = param->clisock; param->operation = CONNECT; +#ifndef _WIN32 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);} for(;;){ i = sockgetlinebuf(param, CLIENT, buf, BUFSIZE - 10, '\n', conf.timeouts[CONNECTION_S]); @@ -262,8 +266,11 @@ void * ftpprchild(struct clientparam* param) { } sc = param->remsock; 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, clidatasock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg)); +#endif param->clisock = clidatasock; res = mapsocket(param, conf.timeouts[CONNECTION_S]); if(param->remsock != INVALID_SOCKET) { diff --git a/src/httpsrv.c b/src/httpsrv.c index 6a25589..ab16f17 100644 --- a/src/httpsrv.c +++ b/src/httpsrv.c @@ -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 request line and the headers, exactly as they came, since the proxy code reads them again from the beginning. */ @@ -1451,6 +1482,12 @@ static int httpsrv_request(struct clientparam *param, struct httpreq *r) } } 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 should let the client offer credentials again. */ if(i >= 4 && i <= 6) op_authrequired(r); @@ -1573,7 +1610,12 @@ void * httpsrvchild(struct clientparam *param) 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; } /* A child named by an access rule takes the connection over, which the diff --git a/src/proxymain.c b/src/proxymain.c index be3d6dd..6a531bf 100644 --- a/src/proxymain.c +++ b/src/proxymain.c @@ -306,7 +306,9 @@ int MODULEMAINFUNC (int argc, char** argv){ unsigned char *cbc_string = NULL, *cbl_string = NULL; PROXYSOCKADDRTYPE cbsa; FILE *fp = NULL; +#ifndef _WIN32 struct linger lg; +#endif int nlog = 5000; int haveslot = 0, nfull = 0; #ifdef __linux__ @@ -1117,10 +1119,14 @@ int MODULEMAINFUNC (int argc, char** argv){ #else fcntl(new_sock,F_SETFL,O_NONBLOCK | fcntl(new_sock,F_GETFL)); #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_linger = conf.timeouts[STRING_L]; - 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)); } #ifndef NOUDPMAIN @@ -1413,21 +1419,34 @@ void freeparam(struct clientparam * param) { } if(param->clibuf) free(param->clibuf); 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->ctrlsocksrv != INVALID_SOCKET && param->ctrlsocksrv != param->remsock) { +#ifndef _WIN32 param->srv->so._shutdown(param->sostate, param->ctrlsocksrv, SHUT_RDWR); +#endif param->srv->so._closesocket(param->sostate, param->ctrlsocksrv); } if(param->ctrlsock != INVALID_SOCKET && param->ctrlsock != param->clisock) { +#ifndef _WIN32 param->srv->so._shutdown(param->sostate, param->ctrlsock, SHUT_RDWR); +#endif param->srv->so._closesocket(param->sostate, param->ctrlsock); } if(param->remsock != INVALID_SOCKET) { +#ifndef _WIN32 param->srv->so._shutdown(param->sostate, param->remsock, SHUT_RDWR); +#endif param->srv->so._closesocket(param->sostate, param->remsock); } if(param->clisock != INVALID_SOCKET) { +#ifndef _WIN32 param->srv->so._shutdown(param->sostate, param->clisock, SHUT_RDWR); +#endif param->srv->so._closesocket(param->sostate, param->clisock); } } diff --git a/src/sockmap.c b/src/sockmap.c index df7ad16..0e993bb 100644 --- a/src/sockmap.c +++ b/src/sockmap.c @@ -819,9 +819,6 @@ log("timeout"); else if(inclientpipe || inserverpipe) res = 94; #endif - if((param->nwrites > 0 && !SERVERTERMWRITE) || (param->nreads > 0 && !CLIENTTERMWRITE)) - usleep(SLEEPTIME * 10); - CLEANRET: #ifdef WITHSPLICE