Split proxymain.c into its parts

proxymain.c stays the one unit built for 3proxy and included by each
standalone service, and now includes its parts:

  mainconf.c	services from their configuration lines: options,
		srvprepare(), mainprepare(), srvinit(), srvfree(), and
		copying and freeing access lists and authentication
  mainlisten.c	listening sockets, shared and taken over on a reload,
		and reattach()
  mainclient.c	threads for the clients of TCP services, their pool,
		srvspawn(), releaseparam(), and the filters' hooks
  maintcp.c	the main loop of a TCP service, tcploop(), taken out of
		srvrun(), with tcpnext() and cblisten()
  mainudp.c	the main loop of a UDP service

proxymain.c keeps srvstart, srvrun() and main(), and declares the
functions of file scope of the parts so that they can be in any order.
The code is moved, not changed, but for tcploop(). The parts are
dependencies of the objects that include proxymain.c in Makefile.inc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-18 16:11:46 +03:00
parent 58424770aa
commit 5cbc72d289
7 changed files with 2962 additions and 2812 deletions

View File

@ -1,3 +1,6 @@
# proxymain.c and the parts it includes
PROXYMAIN = proxymain.c mainconf.c mainlisten.c mainclient.c maintcp.c mainudp.c
#
# 3 proxy common Makefile
#
@ -28,32 +31,32 @@ ftp$(OBJSUFFICS): ftp.c proxy.h structures.h
sockgetchar$(OBJSUFFICS): sockgetchar.c proxy.h structures.h
$(CC) $(CFLAGS) sockgetchar.c
proxy$(OBJSUFFICS): proxy.c proxy.h structures.h proxymain.c
proxy$(OBJSUFFICS): proxy.c proxy.h structures.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)ANONYMOUS $(DEFINEOPTION)NOUDPMAIN proxy.c
pop3p$(OBJSUFFICS): pop3p.c proxy.h structures.h proxymain.c
pop3p$(OBJSUFFICS): pop3p.c proxy.h structures.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)NOUDPMAIN pop3p.c
imapp$(OBJSUFFICS): imapp.c proxy.h structures.h proxymain.c
imapp$(OBJSUFFICS): imapp.c proxy.h structures.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)NOUDPMAIN imapp.c
smtpp$(OBJSUFFICS): smtpp.c proxy.h structures.h proxymain.c
smtpp$(OBJSUFFICS): smtpp.c proxy.h structures.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)NOUDPMAIN smtpp.c
ftppr$(OBJSUFFICS): ftppr.c proxy.h structures.h proxymain.c
ftppr$(OBJSUFFICS): ftppr.c proxy.h structures.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)NOUDPMAIN ftppr.c
tcppm$(OBJSUFFICS): tcppm.c proxy.h structures.h proxymain.c
tcppm$(OBJSUFFICS): tcppm.c proxy.h structures.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOUDPMAIN tcppm.c
udppm$(OBJSUFFICS): udppm.c proxy.h structures.h proxymain.c
udppm$(OBJSUFFICS): udppm.c proxy.h structures.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOTCPMAIN udppm.c
tlspr$(OBJSUFFICS): tlspr.c proxy.h structures.h proxymain.c
tlspr$(OBJSUFFICS): tlspr.c proxy.h structures.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)PORTMAP $(DEFINEOPTION)NOUDPMAIN tlspr.c
socks$(OBJSUFFICS): socks.c proxy.h structures.h proxymain.c
socks$(OBJSUFFICS): socks.c proxy.h structures.h $(PROXYMAIN)
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)NOUDPMAIN socks.c
3proxy$(OBJSUFFICS): 3proxy.c proxy.h structures.h
@ -86,7 +89,7 @@ $(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFF
$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS): sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(LIBS)
mainfunc$(OBJSUFFICS): proxy.h structures.h proxymain.c
mainfunc$(OBJSUFFICS): proxy.h structures.h $(PROXYMAIN)
$(CC) $(COUT)mainfunc$(OBJSUFFICS) $(CFLAGS) $(DEFINEOPTION)MODULEMAINFUNC=mainfunc proxymain.c
srvproxy$(OBJSUFFICS): proxy.c proxy.h structures.h

602
src/mainclient.c Normal file
View File

@ -0,0 +1,602 @@
/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
/* Clients of TCP services: a thread for each, from a pool, from the filters
to the end of the child serving it, and the filters' hooks.
Part of proxymain.c, which includes it. */
/* Child functions do not call each other, a child requesting redirection to
another child returns it instead of calling it, to keep the stack flat.
The child which completed the request returns NULL. param is logged by
the child and released here, but not freed: its thread may keep it.
*/
void * childfunc(struct clientparam * param){
PROXYFUNC pf = param->srv->pf;
int i;
for(i = 0; pf && i < MAXCHILDREDIRECTS; i++) pf = (PROXYFUNC)(*pf)(param);
if(pf){
param->res = 101;
dolog(param, (unsigned char *)"Redirection loop");
}
releaseparam(param);
return NULL;
}
/* One connection, from the filters to the end of the child which answers
it. The structure is released, not freed, when it returns. */
static void serveparam(struct clientparam *param){
int i = -1;
#ifdef MODULEMAINFUNC
if(makefilters(param->srv, param) > CONTINUE){
releaseparam(param);
return;
}
#endif
if(param->srv->cbsock != INVALID_SOCKET){
SASIZETYPE size = sizeof(param->sinsr);
struct pollfd fds;
fds.fd = param->srv->cbsock;
fds.events = POLLIN;
fds.revents = 0;
for(i=5+(param->srv->maxchild>>10); i; i--){
if(param->srv->so._poll(param->sostate, &fds, 1, 1000*CONNBACK_TO)!=1){
dolog(param, (unsigned char *)"Connect back not received, check connback client");
i = 0;
break;
}
param->remsock = param->srv->so._accept(param->sostate, param->srv->cbsock, (struct sockaddr*)&param->sinsr, &size);
if(param->remsock == INVALID_SOCKET) {
dolog(param, (unsigned char *)"Connect back accept() failed");
continue;
}
{
#ifdef _WIN32
unsigned long ul=1;
ioctlsocket(param->remsock, FIONBIO, &ul);
#else
fcntl(param->remsock,F_SETFL,O_NONBLOCK | fcntl(param->remsock,F_GETFL));
#endif
}
#ifndef WITHMAIN
param->req = param->sinsr;
if(param->srv->acl) param->res = checkACL(param);
if(param->res){
dolog(param, (unsigned char *)"Connect back ACL failed");
param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = INVALID_SOCKET;
continue;
}
#endif
if(socksend(param, param->remsock, (unsigned char *)"C", 1, CONNBACK_TO) != 1){
dolog(param, (unsigned char *)"Connect back sending command failed");
param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = INVALID_SOCKET;
continue;
}
break;
}
}
if(!i){
param->res = 13;
releaseparam(param);
}
else {
#ifndef WITHMAIN
#ifndef _WIN32
sigset_t mask;
sigfillset(&mask);
pthread_sigmask(SIG_SETMASK, &mask, NULL);
#endif
#endif
if(param->srv->haproxy){
char buf[128];
int i;
i = sockgetlinebuf(param, CLIENT, (unsigned char *)buf, sizeof(buf)-1, '\n', param->srv->timeouts[STRING_S]);
if(i > 12 && !strncasecmp(buf, "PROXY TCP", 9)){
char *token, *token2=NULL;
unsigned short u1=0, u2=0;
buf[i] = 0;
token = strchr(buf, ' ');
if(token) token = strchr(token+1, ' ');
if(token) token++;
if(token) token2 = strchr(token+1, ' ');
if(token2) {
*token2 = 0;
getip46(46, (unsigned char*) token, (struct sockaddr *)&param->sincr);
token = token2+1;
token2 = strchr(token, ' ');
}
if(token2) {
*token2 = 0;
getip46(46, (unsigned char *) token, (struct sockaddr *)&param->sincl);
token = token2+1;
token2 = strchr(token, ' ');
}
if(token){
sscanf(token,"%hu%hu", &u1, &u2);
if(u1) *SAPORT(&param->sincr) = htons(u1);
if(u2) *SAPORT(&param->sincl) = htons(u2);
}
}
}
childfunc(param);
}
}
/* Frees a worker's structure, once its thread is done with it. */
static void freeworker(struct clientparam *param){
releaseparam(param);
_3proxy_sem_destroy(param->wake);
free(param);
}
/* The automatic cap: 32 idle workers for each running service, but never
more than all of them could have connections. */
static unsigned poollimit(void){
unsigned limit;
if(runconf->poolmax >= 0) limit = (unsigned)runconf->poolmax;
else {
limit = 32 * tpool.services;
if(limit > tpool.maxchild) limit = tpool.maxchild;
}
tpool.limit = limit;
return limit;
}
/* Parks a worker whose connection has ended and waits for the next one.
Returns 0 when the thread is to end instead: on exit, when the pool is
full, when the thread was left in another network namespace, or when it
waited longer than the idle timeout. */
static int poolpark(struct clientparam *param){
int timeout;
_3proxy_mutex_lock(&tpool.mutex);
if(timetoexit || nsbroken() || tpool.nidle >= poollimit()){
_3proxy_mutex_unlock(&tpool.mutex);
return 0;
}
param->poolnext = tpool.idle;
tpool.idle = param;
tpool.nidle++;
param->pooled = 1;
timeout = runconf->pooltimeout;
_3proxy_mutex_unlock(&tpool.mutex);
if(timeout <= 0) _3proxy_sem_lock(param->wake);
else if(_3proxy_sem_timedlock(param->wake, (unsigned)timeout * 1000)){
struct clientparam **pp;
_3proxy_mutex_lock(&tpool.mutex);
if(param->pooled){
for(pp = &tpool.idle; *pp != param; pp = &(*pp)->poolnext);
*pp = param->poolnext;
tpool.nidle--;
param->pooled = 0;
_3proxy_mutex_unlock(&tpool.mutex);
return 0;
}
_3proxy_mutex_unlock(&tpool.mutex);
/* taken from the pool as the wait ran out: the connection follows */
_3proxy_sem_lock(param->wake);
}
return !param->poolexit;
}
/* Wakes every idle worker to end, when the process exits. */
void pooldrain(void){
struct clientparam *param;
_3proxy_mutex_lock(&tpool.mutex);
while((param = tpool.idle)){
tpool.idle = param->poolnext;
tpool.nidle--;
param->pooled = 0;
param->poolexit = 1;
_3proxy_sem_unlock(param->wake);
}
_3proxy_mutex_unlock(&tpool.mutex);
}
#ifdef _WIN32
DWORD WINAPI threadfunc(LPVOID p) {
#else
void * threadfunc (void *p) {
#endif
struct clientparam *param = (struct clientparam *)p;
do serveparam(param);
while(poolpark(param));
freeworker(param);
#ifdef _WIN32
return 0;
#else
return NULL;
#endif
}
/* A thread for the client, client_sock for TCP. 0 once it runs, the slot
in hand going with it. */
static int srvspawn(struct srvstart *s, SOCKET client_sock){
struct srvparam *srv = &s->srv;
struct clientparam *defparam = &s->defparam;
struct clientparam * newparam = NULL, **pp;
pthread_t thread;
unsigned char buf[256];
int error = 0, reused;
size_t need;
#ifdef _WIN32
HANDLE h;
need = 16384 + srv->stacksize;
#else
need = threadstacksize(srv->stacksize);
#endif
/* an idle worker if one has the stack this service needs */
_3proxy_mutex_lock(&tpool.mutex);
for(pp = &tpool.idle; *pp; pp = &(*pp)->poolnext){
if((*pp)->stacksize >= need){
newparam = *pp;
*pp = newparam->poolnext;
newparam->pooled = 0;
tpool.nidle--;
tpool.reused++;
break;
}
}
_3proxy_mutex_unlock(&tpool.mutex);
reused = newparam != NULL;
if(!reused && (!(newparam = malloc (sizeof(*defparam))) || _3proxy_sem_init_timed(newparam->wake, 0, 1))){
if(newparam) free(newparam);
srv->so._closesocket(srv->so.state, client_sock);
defparam->res = 21;
if(!srv->silent)dolog(defparam, (unsigned char *)"Memory Allocation Failed");
usleep(SLEEPTIME);
return 1;
};
if(!reused){
newparam->stacksize = need;
newparam->pooled = newparam->poolexit = 0;
newparam->poolnext = NULL;
}
memcpy(newparam, defparam, offsetof(struct clientparam, filters));
newparam->clibuf = newparam->clibufdata;
newparam->srvbuf = newparam->srvbufdata;
newparam->clibufsize = newparam->srvbufsize = SRVBUFSIZE(srv);
clearstat(newparam);
if(!s->isudp) newparam->clisock = client_sock;
newparam->prev = newparam->next = NULL;
_3proxy_mutex_lock(&srv->counter_mutex);
if(!srv->child){
srv->child = newparam;
}
else {
newparam->next = srv->child;
srv->child = srv->child->prev = newparam;
}
/* a worker from the pool is woken below, once it is one of the children */
if(!reused){
#ifdef _WIN32
#ifndef _WINCE
h = (HANDLE)_beginthreadex((LPSECURITY_ATTRIBUTES )NULL, (unsigned)(16384 + srv->stacksize), (void *)threadfunc, (void *) newparam, 0, &thread);
#else
h = (HANDLE)CreateThread((LPSECURITY_ATTRIBUTES )NULL, (unsigned)(16384 + srv->stacksize), (void *)threadfunc, (void *) newparam, 0, &thread);
#endif
if (h) {
CloseHandle(h);
}
else {
sprintf((char *)buf, "_beginthreadex(): %s", _strerror(NULL));
error = 1;
}
#else
if ((error = pthread_create(&thread, &s->pa, threadfunc, (void *)newparam))){
sprintf((char *)buf, "pthread_create(): %s", strerror(error));
}
#endif
}
if(error){
if(!srv->silent)dolog(defparam, buf);
if(newparam->prev) newparam->prev->next = newparam->next;
else srv->child = newparam->next;
if(newparam->next) newparam->next->prev = newparam->prev;
if(newparam->clisock != INVALID_SOCKET){
srv->so._shutdown(srv->so.state, newparam->clisock, SHUT_RDWR);
srv->so._closesocket(srv->so.state, newparam->clisock);
newparam->clisock = INVALID_SOCKET;
}
newparam->srv = NULL;
freeworker(newparam);
}
else {
srv->childcount++;
#ifndef STDMAIN
listenercount(srv, 1);
#endif
if(!reused){
newparam->workerid = (uint64_t)thread;
_3proxy_mutex_lock(&tpool.mutex);
tpool.created++;
_3proxy_mutex_unlock(&tpool.mutex);
}
newparam->threadid = newparam->workerid;
}
_3proxy_mutex_unlock(&srv->counter_mutex);
if(reused) _3proxy_sem_unlock(newparam->wake);
memset(&defparam->sincl, 0, sizeof(defparam->sincl));
memset(&defparam->sincr, 0, sizeof(defparam->sincr));
return error;
}
/* Ends a connection: everything it holds is released and it leaves its
service, but the structure itself stays, with nothing in it pointing to
what was freed. After this the service may be gone at any moment, so
param->srv is cleared and nothing here touches it again. */
void releaseparam(struct clientparam * param) {
if(param->srv){
if(param->srv->so.freefunc) param->srv->so.freefunc(param->sostate);
}
if(param->clibuf != param->clibufdata) free(param->clibuf);
if(param->srvbuf != param->srvbufdata) free(param->srvbuf);
param->clibuf = param->clibufdata;
param->srvbuf = param->srvbufdata;
#ifdef WITHSPLICE
if(param->pipecli[0] >= 0) close(param->pipecli[0]);
if(param->pipecli[1] >= 0) close(param->pipecli[1]);
if(param->pipesrv[0] >= 0) close(param->pipesrv[0]);
if(param->pipesrv[1] >= 0) close(param->pipesrv[1]);
param->pipecli[0] = param->pipecli[1] = param->pipesrv[0] = param->pipesrv[1] = -1;
#endif
/* 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);
}
param->ctrlsocksrv = param->ctrlsock = param->remsock = param->clisock = INVALID_SOCKET;
}
#ifndef STDMAIN
while(param->nfilters > 0){
param->nfilters--;
if(param->filters[param->nfilters].filter->filter_clear)
(*param->filters[param->nfilters].filter->filter_clear)(param->filters[param->nfilters].data);
}
if(param->connlim) stopconnlims(param);
/* a slot held in the newest service on the listener after a reload */
if(param->newsrv){
slotrelease(param->newsrv);
param->newsrv = NULL;
}
#endif
/* the sockets were closed and the filters cleared with it */
param->sostate = NULL;
/* Leaving the service comes last. Once its last child is gone, srvfree()
frees the service along with its socket functions, filters and mutex,
and a close above may have lingered for seconds before getting here. */
if(param->srv){
_3proxy_mutex_lock(&param->srv->counter_mutex);
if(param->prev || param->next || param->srv->child == param){
if(param->prev){
param->prev->next = param->next;
}
else
param->srv->child = param->next;
if(param->next){
param->next->prev = param->prev;
}
(param->srv->childcount)--;
#ifndef STDMAIN
listenercount(param->srv, -1);
#endif
/* under the mutex too: srvfree() may destroy the semaphore as
soon as the mutex shows the last child gone */
_3proxy_sem_unlock(param->srv->childslots);
}
_3proxy_mutex_unlock(&param->srv->counter_mutex);
param->prev = param->next = NULL;
param->srv = NULL;
}
}
void freeparam(struct clientparam * param) {
releaseparam(param);
free(param);
}
FILTER_ACTION handleconnectflt(struct clientparam *cparam){
#ifndef STDMAIN
FILTER_ACTION action;
int i;
for(i=0; i<cparam->nconnectfilters ;i++){
action = (*cparam->connectfilters[i]->filter->filter_connect)(cparam->connectfilters[i]->data, cparam);
if(action!=CONTINUE) return action;
}
#endif
return PASS;
}
/* A buffer filter given a buffer too small for what it makes of it hands
back one of its own, allocated with malloc, and leaves the one it was given
alone: nothing refers to that one any more, so it is freed here, unless it
is one of the connection's own buffers. The caller owns whatever *buf_p
points to on return. */
static void filterswap(struct clientparam *param, unsigned char **cur, unsigned char *now){
if(now != *cur){
if(*cur != param->clibufdata && *cur != param->srvbufdata && *cur != param->tmpbuf) free(*cur);
*cur = now;
}
}
#ifndef STDMAIN
FILTER_ACTION makefilters (struct srvparam *srv, struct clientparam *param){
FILTER_ACTION res=PASS;
FILTER_ACTION action;
int i;
if(!srv->nfilters) return PASS;
for(i=0; i<srv->nfilters; i++){
if(!srv->filter[i].filter_client)continue;
/* the slot may hold what the connection before left in it */
param->filters[param->nfilters].data = NULL;
action = (*srv->filter[i].filter_client)(srv->filter[i].data, param, &param->filters[param->nfilters].data);
if(action == PASS) continue;
if(action > CONTINUE) {
/* a filter refusing the connection is not kept, but it may
have made a state for it */
if(srv->filter[i].filter_clear && param->filters[param->nfilters].data)
(*srv->filter[i].filter_clear)(param->filters[param->nfilters].data);
return action;
}
param->filters[param->nfilters].filter = srv->filter + i;
if(srv->filter[i].filter_request)param->reqfilters[param->nreqfilters++] = param->filters + param->nfilters;
if(srv->filter[i].filter_connect)param->connectfilters[param->nconnectfilters++] = param->filters + param->nfilters;
if(srv->filter[i].filter_afterauth)param->afterauthfilters[param->nafterauthfilters++] = param->filters + param->nfilters;
if(srv->filter[i].filter_header_cli)param->hdrfilterscli[param->nhdrfilterscli++] = param->filters + param->nfilters;
if(srv->filter[i].filter_header_srv)param->hdrfilterssrv[param->nhdrfilterssrv++] = param->filters + param->nfilters;
if(srv->filter[i].filter_predata)param->predatfilters[param->npredatfilters++] = param->filters + param->nfilters;
if(srv->filter[i].filter_data_cli)param->datfilterscli[param->ndatfilterscli++] = param->filters + param->nfilters;
if(srv->filter[i].filter_data_srv)param->datfilterssrv[param->ndatfilterssrv++] = param->filters + param->nfilters;
param->nfilters++;
}
return res;
}
#endif
#ifndef STDMAIN
FILTER_ACTION handleafterauthflt(struct clientparam *cparam){
#ifndef STDMAIN
FILTER_ACTION action;
int i;
for(i=0; i<cparam->nafterauthfilters ;i++){
action = (*cparam->afterauthfilters[i]->filter->filter_afterauth)(cparam->afterauthfilters[i]->data, cparam);
if(action!=CONTINUE) return action;
}
#endif
return PASS;
}
FILTER_ACTION handlereqfilters(struct clientparam *param, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){
FILTER_ACTION action;
int i;
unsigned char *cur = *buf_p;
for(i=0; i<param->nreqfilters; i++){
action = (*param->reqfilters[i]->filter->filter_request)(param->reqfilters[i]->data, param, buf_p, bufsize_p, offset, length_p);
filterswap(param, &cur, *buf_p);
if(action!=CONTINUE) return action;
}
return PASS;
}
FILTER_ACTION handlehdrfilterssrv(struct clientparam *param, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){
FILTER_ACTION action;
int i;
unsigned char *cur = *buf_p;
for(i=0; i<param->nhdrfilterssrv; i++){
action = (*param->hdrfilterssrv[i]->filter->filter_header_srv)(param->hdrfilterssrv[i]->data, param, buf_p, bufsize_p, offset, length_p);
filterswap(param, &cur, *buf_p);
if(action!=CONTINUE) return action;
}
return PASS;
}
FILTER_ACTION handlehdrfilterscli(struct clientparam *param, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){
FILTER_ACTION action;
int i;
unsigned char *cur = *buf_p;
for(i = 0; i < param->nhdrfilterscli; i++){
action = (*param->hdrfilterscli[i]->filter->filter_header_cli)(param->hdrfilterscli[i]->data, param, buf_p, bufsize_p, offset, length_p);
filterswap(param, &cur, *buf_p);
if(action!=CONTINUE) return action;
}
return PASS;
}
#endif
FILTER_ACTION handlepredatflt(struct clientparam *cparam){
#ifndef STDMAIN
FILTER_ACTION action;
int i;
if(cparam->predatdone) return PASS;
cparam->predatdone = 1;
for(i=0; i<cparam->npredatfilters ;i++){
action = (*cparam->predatfilters[i]->filter->filter_predata)(cparam->predatfilters[i]->data, cparam);
if(action!=CONTINUE) return action;
}
#endif
return PASS;
}
FILTER_ACTION handledatfltcli(struct clientparam *cparam, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){
#ifndef STDMAIN
FILTER_ACTION action;
int i;
unsigned char *cur = *buf_p;
for(i=0; i<cparam->ndatfilterscli ;i++){
action = (*cparam->datfilterscli[i]->filter->filter_data_cli)(cparam->datfilterscli[i]->data, cparam, buf_p, bufsize_p, offset, length_p);
filterswap(cparam, &cur, *buf_p);
if(action!=CONTINUE) return action;
}
#endif
return PASS;
}
FILTER_ACTION handledatfltsrv(struct clientparam *cparam, unsigned char ** buf_p, int * bufsize_p, int offset, int * length_p){
FILTER_ACTION action;
int i;
unsigned char *cur = *buf_p;
for(i=0; i<cparam->ndatfilterssrv; i++){
action = (*cparam->datfilterssrv[i]->filter->filter_data_srv)(cparam->datfilterssrv[i]->data, cparam, buf_p, bufsize_p, offset, length_p);
filterswap(cparam, &cur, *buf_p);
if(action!=CONTINUE) return action;
}
return PASS;
}

1125
src/mainconf.c Normal file

File diff suppressed because it is too large Load Diff

552
src/mainlisten.c Normal file
View File

@ -0,0 +1,552 @@
/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
/* Listening sockets, shared by the services of a configuration on one
address and taken over by those of the next one on a reload, and the
clients of the old services going by the new rules.
Part of proxymain.c, which includes it. */
/* The listening socket. A UDP one also receives the datagrams of new
clients, so it is their client socket too. 0, or the code to exit with. */
#ifndef STDMAIN
/* A listening socket, shared by the services of a configuration on the
same address, port, network namespace and device, all accepting on it.
It outlives the services that opened it when, on a reload, services of
the new configuration on the same address with the same bind-time
options take it over. The old services stop accepting once their
configuration is no longer in force; whoever lets go of the socket last
closes it. A UDP service's socket is the way its sessions answer too:
the old sessions go on answering through it, the new services reading
what comes, the sessions found by client address whichever service made
them. */
struct listener {
struct listener *next;
SOCKET sock;
int isudp;
/* a service connecting back (-r) has no socket to share, but its
clients find the newest service through a listener all the same:
one without a socket, for the place it connects to */
char *cbdest;
PROXYSOCKADDRTYPE sa;
char *netns, *device;
struct sockoptset opts;
int maxseg;
uint64_t undev, unino; /* its file, for a Unix socket on a path */
int optversion; /* the configuration whose options are set */
/* the services accepting on it, linked by lisnext */
struct srvparam *owners;
int users; /* the services holding it: those accepting,
and those which did until their last client
is gone, whose clients look up the newest
service through it after a reload */
/* held while the first of its services opens the socket, binding
which may take long: the others then use what it opened */
_3proxy_mutex_t open_mutex;
/* its clients, whichever of its services, old or new, they came to:
those connected now, and those done with */
uint64_t clients, processed;
/* the traffic of its clients as logged: from the server (%I) and
from the client (%O) */
uint64_t trafin, trafout;
_3proxy_mutex_t count_mutex;
/* a UDP service's sessions on a reload, for the newest service on the
listener to adopt */
struct udpsession *udphand;
};
/* A client of srv comes (1) or goes (-1). */
static void listenercount(struct srvparam *srv, int delta){
struct listener *l = srv->listener;
if(!l) return;
_3proxy_mutex_lock(&l->count_mutex);
if(delta > 0) l->clients++;
else {
l->clients--;
l->processed++;
}
_3proxy_mutex_unlock(&l->count_mutex);
}
/* The traffic of a client, about to be cleared once logged, counted for
its listener. */
void listenertraffic(struct clientparam *param){
struct listener *l = param->srv->listener;
if(!l) return;
_3proxy_mutex_lock(&l->count_mutex);
l->trafin += param->statssrv64;
l->trafout += param->statscli64;
_3proxy_mutex_unlock(&l->count_mutex);
}
static uint64_t nocount = 0;
/* The counters of a running service's listener, for the admin pages; the
listener lasts as long as the service. */
uint64_t *listenerclients(struct srvparam *srv){
return srv->listener? &srv->listener->clients : &nocount;
}
uint64_t *listenerprocessed(struct srvparam *srv){
return srv->listener? &srv->listener->processed : &nocount;
}
uint64_t *listenertrafin(struct srvparam *srv){
return srv->listener? &srv->listener->trafin : &nocount;
}
uint64_t *listenertrafout(struct srvparam *srv){
return srv->listener? &srv->listener->trafout : &nocount;
}
static struct listener *listeners = NULL;
_3proxy_mutex_t listener_mutex;
static void listenerstop(struct srvparam *srv);
#ifndef NOUDPMAIN
static void udpendall(struct udpsession *list, int res);
#endif
static void takeopts(struct listener *l, struct srvparam *srv);
static void listenerrelease(struct srvparam *srv);
#endif
#ifdef WITH_UN
/* A Unix socket bound to a path: its file is noted, as a service of a
newer configuration may bind the same path anew before this socket is
closed, when the file is the other's, which closing must not remove. */
static void unbound(PROXYSOCKADDRTYPE *sa, uint64_t *dev, uint64_t *ino){
struct stat st;
if(*SAFAMILY(sa) == AF_UNIX && *SAADDR(sa) && !stat((char *)SAADDR(sa), &st)){
*dev = (uint64_t)st.st_dev;
*ino = (uint64_t)st.st_ino;
}
}
static void ununlink(PROXYSOCKADDRTYPE *sa, uint64_t dev, uint64_t ino){
struct stat st;
if(*SAFAMILY(sa) == AF_UNIX && *SAADDR(sa) && !stat((char *)SAADDR(sa), &st) &&
(uint64_t)st.st_dev == dev && (uint64_t)st.st_ino == ino)
unlink((char *)SAADDR(sa));
}
#endif
/* The listening socket of the service: its listener's, opened by the first
of the listener's services to get here. */
static int srvlisten(struct srvstart *s){
#ifndef STDMAIN
struct srvparam *srv = &s->srv;
struct clientparam *defparam = &s->defparam;
struct listener *l = srv->listener;
SOCKET sock;
unsigned char buf[256];
int res, taken;
if(l){
_3proxy_mutex_lock(&l->open_mutex);
_3proxy_mutex_lock(&listener_mutex);
sock = l->sock;
_3proxy_mutex_unlock(&listener_mutex);
if(sock == INVALID_SOCKET){
if(!(res = srvopen(s))){
_3proxy_mutex_lock(&listener_mutex);
l->sock = srv->srvsock;
l->optversion = srv->version;
l->undev = srv->undev;
l->unino = srv->unino;
_3proxy_mutex_unlock(&listener_mutex);
}
else if(srv->srvsock != INVALID_SOCKET){
so._closesocket(so.state, srv->srvsock);
srv->srvsock = INVALID_SOCKET;
}
_3proxy_mutex_unlock(&l->open_mutex);
return res;
}
/* listening already: taken over from a service of the configuration
before, its options to be set, or opened by another service of
this one; only the backlog may be new */
taken = l->optversion != srv->version;
if(taken) takeopts(l, srv);
_3proxy_mutex_unlock(&l->open_mutex);
srv->srvsock = sock;
#ifndef NOUDPMAIN
if(s->isudp) defparam->clisock = sock;
else
#endif
if(srv->so._listen (srv->so.state, sock, srv->backlog?srv->backlog : 1+(srv->maxchild>>3))==-1) {
srv->srvsock = INVALID_SOCKET;
sprintf((char *)buf, "listen(): %s", strerror(errno));
if(!srv->silent)dolog(defparam, buf);
return -4;
}
if(!srv->silent){
sprintf((char *)buf, "Accepting connections, %s [%"PRIu64"/%"PRIu64"]", taken? "taken over" : "shared",
(uint64_t)getpid(), (uint64_t)pthread_self());
dolog(defparam, buf);
}
srv->fds.fd = sock;
srv->fds.events = POLLIN;
return 0;
}
#endif
return srvopen(s);
}
/* Opens, binds and listens on the service's socket. */
static int srvopen(struct srvstart *s){
struct srvparam *srv = &s->srv;
struct clientparam *defparam = &s->defparam;
SOCKET sock = INVALID_SOCKET;
unsigned sleeptime;
unsigned char buf[256];
#ifdef _WIN32
unsigned long ul = 1;
#endif
#ifndef NOTCPMAIN
if(!s->isudp){
sock=nssocket(srv, &srv->so, srv->so.state, NS_IN, SASOCK(&srv->intsa), SOCK_STREAM,
#ifdef WITH_UN
*SAFAMILY(&srv->intsa) == AF_UNIX? 0 :
#endif
IPPROTO_TCP
);
}
#endif
#ifndef NOUDPMAIN
if(s->isudp){
sock=nssocket(srv, &srv->so, srv->so.state, NS_IN, SASOCK(&srv->intsa), SOCK_DGRAM, IPPROTO_UDP);
}
#endif
if( sock == INVALID_SOCKET) {
perror("socket()");
return -2;
}
setopts(sock, &srv->lissockopts, srv->maxseg);
#ifdef _WIN32
ioctlsocket(sock, FIONBIO, &ul);
#else
fcntl(sock,F_SETFL,O_NONBLOCK | fcntl(sock,F_GETFL));
#endif
srv->srvsock = sock;
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
if(bindtodevice(&srv->so, srv->so.state, sock, srv->ibindtodevice, *SAFAMILY(&srv->intsa))) {
dolog(defparam, (unsigned char *)"failed to bind device");
return -12;
}
#endif
#ifdef WITH_UN
if(*SAFAMILY(&srv->intsa) == AF_UNIX){
struct sockaddr_un *sun = (struct sockaddr_un *)&srv->intsa;
if(*sun->sun_path)unlink(sun->sun_path);
}
#endif
for(sleeptime = SLEEPTIME * 100; srv->so._bind(srv->so.state, sock, (struct sockaddr*)&srv->intsa, SASIZE(&srv->intsa))==-1; usleep(sleeptime)) {
sprintf((char *)buf, "bind(): %s", strerror(errno));
if(!srv->silent)dolog(defparam, buf);
sleeptime = (sleeptime<<1);
if(!sleeptime) return -3;
}
#ifdef WITH_UN
unbound(&srv->intsa, &srv->undev, &srv->unino);
#endif
#ifndef NOTCPMAIN
if(!s->isudp){
if(srv->so._listen (srv->so.state, sock, srv->backlog?srv->backlog : 1+(srv->maxchild>>3))==-1) {
sprintf((char *)buf, "listen(): %s", strerror(errno));
if(!srv->silent)dolog(defparam, buf);
return -4;
}
}
#endif
#ifndef NOUDPMAIN
if(s->isudp) defparam->clisock = sock;
#endif
srv->fds.fd = sock;
srv->fds.events = POLLIN;
if(!srv->silent){
sprintf((char *)buf, "Accepting connections [%"PRIu64"/%"PRIu64"]", (uint64_t)getpid(), (uint64_t)pthread_self());
dolog(defparam, buf);
}
return 0;
}
#ifndef STDMAIN
/* -ol options that act only when set before bind(): a listening socket
differing in them is another socket. The rest are set on a socket taken
over, those no longer asked for turned off. */
static int bindtimeopt(const char *name){
return !strncmp(name, "SO_REUSE", 8) || !strcmp(name, "SO_EXCLUSIVEADDRUSE") ||
!strcmp(name, "SO_PORT_SCALABILITY") || !strcmp(name, "IP_TRANSPARENT") ||
!strcmp(name, "TCP_FASTOPEN");
}
/* Whether a socket with the options of l can be made one with those of srv:
the same bind-time ones, with the same values; TCP_MAXSEG, whose default
is not 0, cannot be turned off again. */
static int sameopts(struct listener *l, struct srvparam *srv){
const struct sockoptset *a = &l->opts, *b = &srv->lissockopts;
int i;
for(i = 0; sockopts[i].optname && i < MAXSOCKOPTS; i++){
unsigned bit = 1u << i;
if(bindtimeopt(sockopts[i].optname)){
if((a->set & bit) != (b->set & bit)) return 0;
if((a->set & bit) && a->value[i] != b->value[i]) return 0;
}
#ifdef TCP_MAXSEG
else if(sockopts[i].opt == TCP_MAXSEG && *sockopts[i].optname == 'T' &&
(a->set & bit) && !(b->set & bit)) return 0;
#endif
}
return 1;
}
/* Sets the options of srv on the socket of l taken over: those given, and
off those which l had and srv has not. */
static void takeopts(struct listener *l, struct srvparam *srv){
struct sockoptset off;
int i;
memset(&off, 0, sizeof(off));
for(i = 0; sockopts[i].optname && i < MAXSOCKOPTS; i++){
unsigned bit = 1u << i;
if((l->opts.set & bit) && !(srv->lissockopts.set & bit) && !bindtimeopt(sockopts[i].optname))
off.set |= bit;
}
if(off.set) setopts(l->sock, &off, 0);
setopts(l->sock, &srv->lissockopts, srv->maxseg);
_3proxy_mutex_lock(&listener_mutex);
l->opts = srv->lissockopts;
l->maxseg = srv->maxseg;
_3proxy_mutex_unlock(&listener_mutex);
}
static int samelistener(struct listener *l, struct srvparam *srv, int isudp){
if(l->isudp != isudp || l->cbdest) return 0;
#ifdef __linux__
if(!samestr(l->netns, srv->inetns)) return 0;
#endif
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
if(!samestr(l->device, srv->ibindtodevice)) return 0;
#endif
return SASIZE(&l->sa) == SASIZE(&srv->intsa) &&
!memcmp(&l->sa, &srv->intsa, SASIZE(&srv->intsa)) &&
sameopts(l, srv);
}
/* Whether s goes on listener l: one connecting back (-r) on the
pseudo-listener of the place it connects to, any other on the socket for
its address. */
static int listenerfor(struct listener *l, struct srvstart *s){
if(!s->isudp && s->iscbc) return l->cbdest && s->cbc_string && !strcmp(l->cbdest, (char *)s->cbc_string);
return !l->cbdest && samelistener(l, &s->srv, s->isudp);
}
static int listenerhas(struct listener *l, struct extparam *config){
struct srvparam *o;
for(o = l->owners; o; o = o->lisnext) if(o->config == config) return 1;
return 0;
}
/* A prepared service, before it starts and before, on a reload, the old
services are told to stop: it joins the listener another service of its
configuration has for the same address, or takes over one an older
configuration's services accept on, one of a service of its type first.
Otherwise it gets a listener of its own, its socket opened when it
starts. A service connecting back has a pseudo-listener without a socket
for the place it connects to, which several of its services may share
too. Without memory for a listener the service keeps its socket to
itself. */
void mainclaim(void *service){
struct srvstart *s = (struct srvstart *)service;
struct srvparam *srv = &s->srv;
struct listener *l, *found = NULL, *newl;
/* made beforehand, as the mutex is held from finding a listener to
joining it, lest its last service let go of it meanwhile */
if((newl = malloc(sizeof(*newl)))){
memset(newl, 0, sizeof(*newl));
newl->sock = INVALID_SOCKET;
newl->isudp = s->isudp;
newl->maxseg = srv->maxseg;
newl->sa = srv->intsa;
newl->opts = srv->lissockopts;
newl->optversion = srv->version;
if((!s->isudp && s->iscbc && s->cbc_string && !(newl->cbdest = strdup((char *)s->cbc_string)))
#ifdef __linux__
|| (srv->inetns && !(newl->netns = strdup(srv->inetns)))
#endif
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
|| (srv->ibindtodevice && !(newl->device = strdup(srv->ibindtodevice)))
#endif
){
free(newl->cbdest);
free(newl->netns);
free(newl->device);
free(newl);
newl = NULL;
}
else {
_3proxy_mutex_init(&newl->open_mutex);
_3proxy_mutex_init(&newl->count_mutex);
}
}
_3proxy_mutex_lock(&listener_mutex);
for(l = listeners; l; l = l->next){
if(!l->owners || !listenerfor(l, s)) continue;
if(listenerhas(l, srv->config)){
found = l;
break;
}
if(!found || (l->owners->service == srv->service && found->owners->service != srv->service))
found = l;
}
if(!found && (found = newl)){
newl = NULL;
found->next = listeners;
listeners = found;
}
if(found){
srv->lisnext = found->owners;
found->owners = srv;
found->users++;
srv->listener = found;
}
_3proxy_mutex_unlock(&listener_mutex);
if(newl){
_3proxy_mutex_destroy(&newl->open_mutex);
_3proxy_mutex_destroy(&newl->count_mutex);
free(newl->cbdest);
free(newl->netns);
free(newl->device);
free(newl);
}
}
/* A service stops accepting: once the last of the listener's services has,
the socket is closed, so that connections do not queue up on it. */
static void listenerstop(struct srvparam *srv){
struct listener *l = srv->listener;
struct srvparam **op;
SOCKET sock = INVALID_SOCKET;
struct udpsession *hand = NULL;
_3proxy_mutex_lock(&listener_mutex);
for(op = &l->owners; *op; op = &(*op)->lisnext) if(*op == srv){
*op = srv->lisnext;
srv->lisnext = NULL;
break;
}
if(!l->owners){
sock = l->sock;
l->sock = INVALID_SOCKET;
/* sessions handed over with no service left to adopt them */
hand = l->udphand;
l->udphand = NULL;
}
_3proxy_mutex_unlock(&listener_mutex);
#ifndef NOUDPMAIN
udpendall(hand, 26);
#endif
if(sock == INVALID_SOCKET) return;
so._closesocket(so.state, sock);
#ifdef WITH_UN
ununlink(&l->sa, l->undev, l->unino);
#endif
}
/* A service lets go of its listener once its last client is gone, having
stopped accepting before; the last to let go frees it. */
static void listenerrelease(struct srvparam *srv){
struct listener *l = srv->listener, **lp;
int last;
srv->listener = NULL;
_3proxy_mutex_lock(&listener_mutex);
last = !--l->users;
if(last) for(lp = &listeners; *lp; lp = &(*lp)->next) if(*lp == l) {
*lp = l->next;
break;
}
_3proxy_mutex_unlock(&listener_mutex);
if(!last) return;
_3proxy_mutex_destroy(&l->open_mutex);
_3proxy_mutex_destroy(&l->count_mutex);
if(l->netns) free(l->netns);
if(l->device) free(l->device);
if(l->cbdest) free(l->cbdest);
free(l);
}
/* Gives back a client's slot in a service it was not a child of. */
static void slotrelease(struct srvparam *srv){
_3proxy_mutex_lock(&srv->counter_mutex);
_3proxy_sem_unlock(srv->childslots);
_3proxy_mutex_unlock(&srv->counter_mutex);
}
/* After a reload, unless noforce: a client goes by the rules of a newest
service of its type on its listener, now accepting there, re-authenticated
as it if auth is set. It takes one of that service's maxconn slots,
which counts it there and keeps the service, and its configuration,
until the client is done; a slot it held in a service before is given
back. 0 to go on, or the code to end the connection with: 26 if no
service of its type is there now, 27 if none of those has a slot free,
or what the authentication says. */
int reattach(struct clientparam *param, int auth){
struct srvparam *cur = PSRV(param), *newest, *orig, *o;
int version = runconf->version, res, newv = 0;
/* without a listener (connecting back) nothing replaces it */
if(!cur->listener){
param->version = version;
return 0;
}
_3proxy_mutex_lock(&listener_mutex);
for(o = cur->listener->owners; o; o = o->lisnext) if(o->version > newv) newv = o->version;
if(newv <= cur->version){
/* the old services have not let go of the socket yet */
_3proxy_mutex_unlock(&listener_mutex);
return 0;
}
/* one of the newest services of the client's type with a slot free;
param->service: the old service is a zombie by now. While it accepts
such a service is not freed, nor after the client has its slot. */
res = 26;
newest = NULL;
for(o = cur->listener->owners; o; o = o->lisnext){
if(o->version != newv || o->service != param->service) continue;
if(!_3proxy_sem_timedlock(o->childslots, 0)){
newest = o;
break;
}
res = 27;
}
_3proxy_mutex_unlock(&listener_mutex);
if(!newest) return res;
if(param->newsrv) slotrelease(param->newsrv);
param->newsrv = newest;
param->version = version;
if(!auth) return 0;
/* authenticated as the newest service's client: its access lists, users,
limits and counters */
orig = param->srv;
param->srv = newest;
res = (*newest->authfunc)(param);
param->srv = orig;
return res;
}
#endif

217
src/maintcp.c Normal file
View File

@ -0,0 +1,217 @@
/*
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
/* 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;
#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;
}
if(sockrecvfrom(NULL, new_sock, NULL, buf, 1, 60*1000) != 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

391
src/mainudp.c Normal file
View File

@ -0,0 +1,391 @@
/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
/* The main loop of a UDP service: no thread for a client.
Part of proxymain.c, which includes it. */
#ifndef NOUDPMAIN
/* A UDP service's loop, in the service's thread: the datagrams of its
clients come on the service's socket, those of their destinations on the
sockets of their sessions, and every answer to a client leaves from the
service's socket. No thread for a client. */
struct udpstate {
struct udploop u;
const struct udpservice *svc;
struct udpsession *head;
int count;
struct hashtable keys; /* a session by its client's address */
struct pollfd *fds;
struct udpsession **of; /* the session of each of fds, NULL: the service */
int nalloc;
int nfull;
};
static void udpkey2hash(const struct hashtable *ht, void *index, uint8_t *hash){
PROXYSOCKADDRTYPE *sa = (PROXYSOCKADDRTYPE *)index;
uint32_t m1, m2;
m1 = murmurhash3(SAADDR(sa), SAADDRLEN(sa), 0x3a3a3a3a);
m2 = murmurhash3(SAPORT(sa), 2, m1);
memcpy(hash, &m1, 4);
memcpy(hash+4, &m2, 4);
}
int udpanswer(struct udploop *u, struct clientparam *param, unsigned char *buf, int len){
return param->srv->so._sendto(param->sostate, param->srv->srvsock, (char *)buf, len, 0,
(struct sockaddr *)&param->sincr, SASIZE(&param->sincr));
}
/* A session among its service's children, counted there and, if it is new,
on the listener. */
static void udpjoin(struct srvparam *srv, struct clientparam *param, int new){
param->prev = param->next = NULL;
_3proxy_mutex_lock(&srv->counter_mutex);
if(srv->child){
param->next = srv->child;
srv->child->prev = param;
}
srv->child = param;
srv->childcount++;
#ifndef STDMAIN
if(new) listenercount(srv, 1);
#endif
_3proxy_mutex_unlock(&srv->counter_mutex);
}
/* A new client: a slot of maxchild and a param, as a thread for it would
have; NULL without a slot or memory. */
static struct udpsession *udpnew(struct udpstate *st){
struct srvparam *srv = st->u.srv;
struct udpsession *ss;
struct clientparam *param = NULL;
if(_3proxy_sem_timedlock(srv->childslots, 0)){
if(!srv->silent && !(st->nfull++ % 100)){
char buf[128];
sprintf(buf, "Warning: too many connected clients (%d/%d), datagram dropped", srv->childcount, srv->maxchild);
dolog(st->u.defparam, (unsigned char *)buf);
}
return NULL;
}
st->nfull = 0;
if(!(ss = calloc(1, sizeof(*ss))) || !(param = malloc(sizeof(*param)))){
free(ss);
_3proxy_sem_unlock(srv->childslots);
return NULL;
}
memset(param, 0, sizeof(*param));
memcpy(param, st->u.defparam, offsetof(struct clientparam, filters));
param->clibuf = param->clibufdata;
param->srvbuf = param->srvbufdata;
param->clibufsize = param->srvbufsize = SRVBUFSIZE(srv);
clearstat(param);
param->clisock = param->remsock = param->ctrlsock = param->ctrlsocksrv = INVALID_SOCKET;
udpjoin(srv, param, 1);
ss->param = param;
ss->svc = st->svc;
#ifdef MODULEMAINFUNC
if(makefilters(srv, param) > CONTINUE){
freeparam(param);
free(ss);
return NULL;
}
#endif
return ss;
}
/* A session ends: logged by its service, its sockets closed, its slot
given back. Its own service may be gone after. */
static void udpend(struct udpsession *ss){
struct clientparam *param = ss->param;
if(ss->svc->end) (*ss->svc->end)(NULL, ss);
param->clisock = INVALID_SOCKET;
freeparam(param);
free(ss);
}
static void udpendall(struct udpsession *list, int res){
struct udpsession *ss;
while((ss = list)){
list = ss->next;
if(!ss->param->res) ss->param->res = res;
udpend(ss);
}
}
static void udpadd(struct udpstate *st, struct udpsession *ss){
ss->prev = NULL;
ss->next = st->head;
if(st->head) st->head->prev = ss;
st->head = ss;
st->count++;
ss->keyed = st->svc->keyed;
if(ss->keyed) hashadd(&st->keys, &ss->param->sincr, &ss, MAX_COUNTER_TIME);
}
static void udpremove(struct udpstate *st, struct udpsession *ss){
if(ss->prev) ss->prev->next = ss->next;
else st->head = ss->next;
if(ss->next) ss->next->prev = ss->prev;
ss->next = ss->prev = NULL;
st->count--;
if(ss->keyed) hashdelete(&st->keys, &ss->param->sincr);
}
static void udprefresh(struct udpsession *ss, time_t now){
if(ss->idle) ss->deadline = now + ss->idle;
}
#ifndef STDMAIN
/* On a reload: the sessions go to the listener, for the newest service on
it to adopt; if it has none, the last service to stop on it ends them. */
static void udphandover(struct udpstate *st){
struct listener *l = st->u.srv->listener;
struct udpsession *ss, *last = NULL;
for(ss = st->head; ss; ss = ss->next){
if(ss->keyed) hashdelete(&st->keys, &ss->param->sincr);
ss->keyed = 0;
last = ss;
}
if(!last) return;
if(!l){
udpendall(st->head, 26);
st->head = NULL;
return;
}
_3proxy_mutex_lock(&listener_mutex);
last->next = l->udphand;
if(l->udphand) l->udphand->prev = last;
l->udphand = st->head;
_3proxy_mutex_unlock(&listener_mutex);
st->head = NULL;
st->count = 0;
}
/* Sessions of an older service on the listener become this one's: a slot
here, the old one given back, its rules and limits, re-authenticated; 26
if it is another kind of service, 27 without a slot. */
static void udpadopt(struct udpstate *st){
struct srvparam *srv = st->u.srv, *old;
struct listener *l = srv->listener;
struct udpsession *list, *ss;
struct clientparam *param;
int res;
if(!l || !l->udphand) return;
_3proxy_mutex_lock(&listener_mutex);
list = l->udphand;
l->udphand = NULL;
_3proxy_mutex_unlock(&listener_mutex);
while((ss = list)){
list = ss->next;
ss->next = ss->prev = NULL;
param = ss->param;
old = param->srv;
if(old == srv) { udpadd(st, ss); continue; }
if(param->service != srv->service){
param->res = 26;
udpend(ss);
continue;
}
if(_3proxy_sem_timedlock(srv->childslots, 0)){
param->res = 27;
udpend(ss);
continue;
}
/* what belongs to the old service and configuration let go */
while(param->nfilters > 0){
param->nfilters--;
if(param->filters[param->nfilters].filter->filter_clear)
(*param->filters[param->nfilters].filter->filter_clear)(param->filters[param->nfilters].data);
}
if(param->connlim) stopconnlims(param);
param->bandlimfunc = NULL;
param->bandlims[0] = param->bandlimsout[0] = NULL;
param->trafcountfunc = NULL;
param->maxtrafin64 = param->maxtrafout64 = 0;
_3proxy_mutex_lock(&old->counter_mutex);
if(param->prev) param->prev->next = param->next;
else old->child = param->next;
if(param->next) param->next->prev = param->prev;
old->childcount--;
_3proxy_sem_unlock(old->childslots);
_3proxy_mutex_unlock(&old->counter_mutex);
param->srv = srv;
param->sostate = srv->so.state;
param->version = srv->version;
udpjoin(srv, param, 0);
ss->svc = st->svc;
#ifdef MODULEMAINFUNC
if(makefilters(srv, param) > CONTINUE){
param->res = 26;
udpend(ss);
continue;
}
#endif
if((res = (*srv->authfunc)(param))){
param->res = res;
udpend(ss);
continue;
}
udpadd(st, ss);
}
}
#endif
#ifndef STDMAIN
/* After a reload, while an older service of this kind still accepts on the
listener, it goes on serving the service's socket alone, until it hands
its sessions over: a client is not given a session in both. */
static int udpolder(struct srvparam *srv){
struct srvparam *o;
int older = 0;
if(!srv->listener) return 0;
_3proxy_mutex_lock(&listener_mutex);
for(o = srv->listener->owners; o; o = o->lisnext)
if(o != srv && o->version < srv->version && o->service == srv->service) older = 1;
_3proxy_mutex_unlock(&listener_mutex);
return older;
}
#endif
/* The datagrams waiting on the service's socket: to a session, or a new
one. Some at a time, so that the sessions get their turn. */
static void udpclients(struct udpstate *st, time_t now){
struct srvparam *srv = st->u.srv;
struct clientparam *defparam = st->u.defparam;
struct udpsession *ss;
SASIZETYPE size;
int len, n;
for(n = 0; n < 64; n++){
size = sizeof(defparam->sincr);
len = srv->so._recvfrom(srv->so.state, srv->srvsock, (char *)srv->udpbuf, UDPBUFSIZE, 0,
(struct sockaddr *)&defparam->sincr, &size);
if(len < 0){
#ifdef _WIN32
/* an answer to a client was refused (ICMP): reported here */
if(WSAGetLastError() == WSAECONNRESET) continue;
#endif
break;
}
if(st->svc->keyed && hashresolv(&st->keys, &defparam->sincr, &ss, NULL)){
if(ss->done) continue;
udprefresh(ss, now);
if(st->svc->client && (*st->svc->client)(&st->u, ss, srv->udpbuf, len)) ss->done = 1;
continue;
}
if(!(ss = udpnew(st))) continue;
ss->param->sincr = defparam->sincr;
udpadd(st, ss);
if((*st->svc->first)(&st->u, ss, srv->udpbuf, len)) ss->done = 1;
else if(!ss->deadline || ss->idle) udprefresh(ss, now);
}
}
static int udploop(struct srvstart *s){
struct udpstate st;
struct srvparam *srv = &s->srv;
struct udpsession *ss, *next;
time_t now;
int i, n, nfds, timeo, res = 0, wait = 0;
memset(&st, 0, sizeof(st));
st.u.srv = srv;
st.u.defparam = &s->defparam;
st.u.buf = srv->udpbuf2;
st.svc = s->udp;
st.keys.index2hash_add = udpkey2hash;
st.keys.index2hash_search = udpkey2hash;
st.keys.recsize = sizeof(struct udpsession *);
st.keys.hash_size = 8;
if(st.svc->keyed && inithashtable(&st.keys, 64, 256, 1048576)) return 21;
for(;;){
if(srv->config->paused != srv->paused) break;
#ifndef STDMAIN
udpadopt(&st);
#endif
if(st.nalloc < 2 * st.count + 1){
int want = 2 * st.count + 64;
struct pollfd *f = realloc(st.fds, want * sizeof(*f));
if(f) st.fds = f;
if(f && (st.of = realloc(st.of, want * sizeof(*st.of)))) st.nalloc = want;
else { res = 21; break; }
}
now = time(NULL);
timeo = 1000;
#ifndef STDMAIN
/* waiting for an older service's sessions: soon looked for again */
if((wait = udpolder(srv))) timeo = 20;
#endif
nfds = 0;
st.fds[nfds].fd = srv->srvsock;
st.fds[nfds].events = wait? 0 : POLLIN;
st.fds[nfds].revents = 0;
st.of[nfds++] = NULL;
for(ss = st.head; ss; ss = ss->next){
if(ss->deadline && (ss->deadline - now) * 1000 < timeo)
timeo = ss->deadline > now? (int)(ss->deadline - now) * 1000 : 0;
if(ss->param->remsock != INVALID_SOCKET){
st.fds[nfds].fd = ss->param->remsock;
st.fds[nfds].events = POLLIN;
st.fds[nfds].revents = 0;
st.of[nfds++] = ss;
}
/* a parent's control connection: its end ends the session */
if(ss->param->ctrlsocksrv != INVALID_SOCKET && ss->param->ctrlsocksrv != ss->param->remsock){
st.fds[nfds].fd = ss->param->ctrlsocksrv;
st.fds[nfds].events = POLLIN;
st.fds[nfds].revents = 0;
st.of[nfds++] = ss;
}
}
n = srv->so._poll(srv->so.state, st.fds, nfds, timeo);
if(n < 0 && errno != EINTR && errno != EAGAIN){
res = 91;
break;
}
now = time(NULL);
for(i = 1; n > 0 && i < nfds; i++){
ss = st.of[i];
if(!st.fds[i].revents || ss->done) continue;
if(st.fds[i].fd == ss->param->remsock){
udprefresh(ss, now);
if((*st.svc->server)(&st.u, ss)) ss->done = 1;
}
else ss->done = 1;
}
if(n > 0 && !wait && st.fds[0].revents) udpclients(&st, now);
for(ss = st.head; ss; ss = next){
next = ss->next;
if(!ss->done && ss->deadline && ss->deadline <= now){
ss->done = 1;
ss->param->res = st.svc->timeoutres;
}
if(ss->done){
udpremove(&st, ss);
udpend(ss);
}
}
}
#ifndef STDMAIN
if(srv->config->paused != srv->paused) udphandover(&st);
#endif
udpendall(st.head, 26);
if(st.svc->keyed) destroyhashtable(&st.keys);
free(st.fds);
free(st.of);
return res;
}
#endif

File diff suppressed because it is too large Load Diff