3proxy/src/maintcp.c
Vladimir Dubrovin 8cc7d255af connect back: a replaced -r service stops waiting at the reload
A -r service waited up to 60 s for the go-ahead on the connection it
made, without looking at a reload. One replaced by a reload went on
waiting, holding a slot and a thread, and took a client if the far
side's go-ahead came in that time; every reload left another such
connection in the far side's queue. The wait is now a second at a time
(60 s in all, as before): at a reload the service takes a go-ahead
already there, its client going on in the service which replaced it,
and otherwise closes the connection and stops.

The far side (-R) sent its go-ahead to any connection it accepted, and
a closed one cost it a client. A connection the -r side closed, at a
reload or after its wait, is readable before any go-ahead, since that
side sends nothing first: once past the ACL it is passed over, without
counting as one of the tries for as many as the listen queue holds
(1 + maxchild/16), so that connections made to be closed or to send
something cannot keep a client's thread in the loop.

Tests: reattach's far side passes over closed connections as -R does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:28:02 +03:00

232 lines
6.9 KiB
C

/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
/* The main loop of a TCP service: a slot of maxchild, then a connection
accepted, or made back for -r, given a thread.
Part of proxymain.c, which includes it. */
#ifndef NOTCPMAIN
/* -R: the socket the far side of a TCP service connects back to. */
static int cblisten(struct srvstart *s){
struct srvparam *srv = &s->srv;
struct clientparam *defparam = &s->defparam;
PROXYSOCKADDRTYPE cbsa;
parsehost(srv->family, s->cbl_string, (struct sockaddr *)&cbsa);
if((srv->cbsock=nssocket(srv, &srv->so, srv->so.state, NS_OUT, SASOCK(&cbsa), SOCK_STREAM, IPPROTO_TCP))==INVALID_SOCKET) {
dolog(defparam, (unsigned char *)"Failed to allocate connect back socket");
return -6;
}
setopts(srv->cbsock, &srv->cbssockopts, srv->maxseg);
if(srv->so._bind(srv->so.state, srv->cbsock, (struct sockaddr*)&cbsa, SASIZE(&cbsa))==-1) {
dolog(defparam, (unsigned char *)"Failed to bind connect back socket");
return -7;
}
if(srv->so._listen(srv->so.state, srv->cbsock, 1 + (srv->maxchild>>4))==-1) {
dolog(defparam, (unsigned char *)"Failed to listen connect back socket");
return -8;
}
return 0;
}
#endif
/* How long, in seconds, a connection made back (-r) waits for its go-ahead */
#define CBWAIT 60
/* What getting the next client came to. */
enum { NEXT_CLIENT, NEXT_RETRY, NEXT_STOP };
#ifndef NOTCPMAIN
/* The next TCP client: accepted, or with -r connected to and asked to
go ahead. */
static int tcpnext(struct srvstart *s, SOCKET *client, int *nlog){
struct srvparam *srv = &s->srv;
struct clientparam *defparam = &s->defparam;
SOCKET new_sock;
SASIZETYPE size;
unsigned char buf[256];
int error = 0, opt = 1, res, waited;
#ifndef _WIN32
struct linger lg;
#else
unsigned long ul = 1;
#endif
size = sizeof(defparam->sincr);
if(s->iscbc){
new_sock=nssocket(&s->srv, &so, so.state, NS_OUT, SASOCK(&defparam->sincr), SOCK_STREAM, IPPROTO_TCP);
if(new_sock == INVALID_SOCKET){
usleep(SLEEPTIME);
return NEXT_RETRY;
}
setopts(new_sock, &srv->cbcsockopts, srv->maxseg);
parsehost(srv->family, s->cbc_string, (struct sockaddr *)&defparam->sincr);
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
/* as for a listening socket, an interface that cannot be
bound to is not worth retrying */
if(bindtodevice(&so, so.state, new_sock, srv->ibindtodevice, *SAFAMILY(&defparam->sincr))) {
dolog(defparam, (unsigned char *)"failed to bind device");
so._closesocket(so.state, new_sock);
return NEXT_STOP;
}
#endif
if(connectwithpoll(NULL, new_sock,(struct sockaddr *)&defparam->sincr,SASIZE(&defparam->sincr),CONNBACK_TO)) {
so._closesocket(so.state, new_sock);
usleep(SLEEPTIME);
return NEXT_RETRY;
}
/* the far side's go-ahead, waited for a second at a time to notice
a reload: a service paused then takes no more connections and
closes this one, unless the go-ahead came already, and its client
goes on in the service which took its place */
for(waited = 0; !(res = sockrecvfrom(NULL, new_sock, NULL, buf, 1, 1000)); ){
if(srv->config->paused != srv->paused){
res = sockrecvfrom(NULL, new_sock, NULL, buf, 1, 0);
break;
}
if(++waited >= CBWAIT) break;
}
if(res != 1 || *buf!='C') {
so._closesocket(so.state, new_sock);
usleep(SLEEPTIME);
return NEXT_RETRY;
}
}
else {
new_sock = srv->so._accept(srv->so.state, srv->srvsock, (struct sockaddr*)&defparam->sincr, &size);
if(new_sock == INVALID_SOCKET){
#ifdef _WIN32
switch(WSAGetLastError()){
case WSAEMFILE:
case WSAENOBUFS:
case WSAENETDOWN:
usleep(SLEEPTIME * 10);
break;
case WSAEINTR:
error = 1;
break;
default:
break;
}
#else
switch (errno){
#ifdef EMFILE
case EMFILE:
#endif
#ifdef ENFILE
case ENFILE:
#endif
#ifdef ENOBUFS
case ENOBUFS:
#endif
#ifdef ENOMEM
case ENOMEM:
#endif
usleep(SLEEPTIME * 10);
break;
default:
break;
}
#endif
(*nlog)++;
if(!srv->silent && (error || *nlog > 5000)) {
sprintf((char *)buf, "accept(): %s", strerror(errno));
dolog(defparam, buf);
*nlog = 0;
}
return NEXT_RETRY;
}
setopts(new_sock, &srv->clisockopts, srv->maxseg);
}
size = sizeof(defparam->sincl);
if(srv->so._getsockname(srv->so.state, new_sock, (struct sockaddr *)&defparam->sincl, &size)){
sprintf((char *)buf, "getsockname(): %s", strerror(errno));
if(!srv->silent)dolog(defparam, buf);
srv->so._closesocket(srv->so.state, new_sock);
return NEXT_RETRY;
}
#ifdef WITH_UN
if(*SAFAMILY(&defparam->sincl) == AF_UNIX) defparam->sincr = defparam->sincl;
#endif
#ifdef _WIN32
ioctlsocket(new_sock, FIONBIO, &ul);
#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 = srv->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));
*client = new_sock;
return NEXT_CLIENT;
}
#endif
#ifndef NOTCPMAIN
/* Clients until the service is paused: a connection is only taken with a
slot of maxchild in hand, which goes to its child; a slot not handed on is
kept for the next one, in *haveslot, and given back by the caller. */
static int tcploop(struct srvstart *s, int *haveslot){
struct srvparam *srv = &s->srv;
struct clientparam *defparam = &s->defparam;
SOCKET new_sock = INVALID_SOCKET;
int error = 0, next, nfull = 0;
int nlog = 5000;
unsigned char buf[256];
/* -r: a TCP service connects out for each client instead of listening */
int connectback = s->iscbc;
for (;;) {
for(;;){
/* A connection is only taken with a slot of maxchild in hand, which
goes to its child; a slot not handed on is kept for the next one.
The wait is timed to notice a reload. */
while(!(*haveslot) && srv->config->paused == srv->paused){
if(!_3proxy_sem_timedlock(srv->childslots, 1000)){
(*haveslot) = 1;
nfull = 0;
break;
}
if(!srv->silent && !(nfull++ % 5)) {
sprintf((char *)buf, "Warning: too many connected clients (%d/%d)", srv->childcount, srv->maxchild);
dolog(defparam, buf);
}
}
if (connectback) break;
if (srv->config->paused != srv->paused) break;
error = srv->so._poll(srv->so.state, &srv->fds, 1, 1000);
if (error >= 1) break;
if (error == 0) continue;
if (errno != EAGAIN && errno != EINTR) {
sprintf((char *)buf, "poll(): %s/%d", strerror(errno), errno);
if(!srv->silent)dolog(defparam, buf);
break;
}
}
if((srv->config->paused != srv->paused) || (error < 0)) break;
error = 0;
next = tcpnext(s, &new_sock, &nlog);
if(next == NEXT_STOP) break;
if(next == NEXT_RETRY) continue;
if(!srvspawn(s, new_sock)) (*haveslot) = 0;
}
return 0;
}
#endif