mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-29 06:15:48 +08:00
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>
232 lines
6.9 KiB
C
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
|