UDP services in one thread: udppm and dnspr without client threads

A UDP service now runs in its own thread alone (udploop()): it reads the
service's socket and the sockets of its sessions towards their
destinations, keeps their deadlines, and answers every client from the
service's socket. A session is a struct clientparam as a client thread's
would be, with a slot of maxchild, among the service's children and
counted on its listener, but without a thread. The services supply
handlers (struct udpservice, childdef.udp): udppm keeps a session per
client address, forwarding its datagrams, behind the headers of a
parent chain, from a socket of its own and answering what comes back,
until STRING_L of silence or, with -s, the first answer; dnspr makes a
session of each query, answered at once from the fake address, the
name cache or the resolver, or sent on to the first name server over
UDP, whose answer is waited for in the loop up to DNS_TO (a name server
over TCP is still waited for in place). Authentication stays in the
loop for now; datagrams over a bandwidth limit, or of a client without
a free slot, are dropped.

On a reload the old service goes on serving the socket until it sees
its configuration replaced, then hands its sessions to the listener;
the newest service of its kind there, which until then does not read
the socket so that no client gets two sessions, adopts them: a slot of
its own, the old one given back, its filters and limits, and
re-authentication. A session is ended with 26 if the newest service is
of another kind, 27 without a slot, or with what the authentication
says; sessions nobody is left to adopt are ended by the last service to
stop on the listener.

On Windows a send on a socket waited while another thread polled it,
which delayed udppm's answers by up to a second, and old UDP services
did not exit after a reload; with one thread per socket neither
happens, and the second socket bound to the service's address
(NO_SHARE_UDP_SOCKET) is gone from the code and the build files.

udpinit, the global udp_table, udpnext(), udppmchild() and
dnsprchild() are gone; udpsockmap() serves SOCKS5 UDP ASSOCIATE only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-18 14:57:35 +03:00
parent 7efc23cdf2
commit 7cdfd87aa6
12 changed files with 746 additions and 342 deletions

View File

@ -120,9 +120,6 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(WIN32) if(WIN32)
# Windows-specific configuration # Windows-specific configuration
add_compile_definitions( add_compile_definitions(
# Windows does not take a recv and a send on one socket from two
# threads, so a UDP service answers from a socket of its own
NO_SHARE_UDP_SOCKET
WIN32 WIN32
_WIN32 _WIN32
_MBCS _MBCS

View File

@ -18,7 +18,7 @@ SSL_LIBS = wolfssl.lib
SSL_DEFS = /D "WITH_SSL" SSL_DEFS = /D "WITH_SSL"
SSL_LIBS = libcrypto.lib libssl.lib SSL_LIBS = libcrypto.lib libssl.lib
!ENDIF !ENDIF
CFLAGS = /D "WITH_HTTPSRV" /nologo /MT /W3 /Ox /GS /EHs- /GA /GF /D "MSVC" /D "WITH_WSAPOLL" /D "NDEBUG" /D "WIN32" $(SSL_DEFS) /D "WITH_PCRE" /D "WITH_ODBC" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /D "NO_SHARE_UDP_SOCKET" /Fp"proxy.pch" /FD /c $(BUILDDATE) $(VERSION) CFLAGS = /D "WITH_HTTPSRV" /nologo /MT /W3 /Ox /GS /EHs- /GA /GF /D "MSVC" /D "WITH_WSAPOLL" /D "NDEBUG" /D "WIN32" $(SSL_DEFS) /D "WITH_PCRE" /D "WITH_ODBC" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /Fp"proxy.pch" /FD /c $(BUILDDATE) $(VERSION)
COUT = /Fo COUT = /Fo
LN = link LN = link
LDFLAGS = /nologo /subsystem:console /incremental:no LDFLAGS = /nologo /subsystem:console /incremental:no

View File

@ -8,7 +8,7 @@ BUILDDIR = ../bin/
PREFIX = 3proxy_ PREFIX = 3proxy_
CRYPT_PREFIX = 3proxy_ CRYPT_PREFIX = 3proxy_
CC = cl CC = cl
CFLAGS = /D "WITH_HTTPSRV" /nologo /Ox /MT /D "NOIPV6" /D "NO_UN" /D "NODEBUG" /D "NORADIUS" /D"WATCOM" /D "NO_SHARE_UDP_SOCKET" /D "MSVC" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /D "PRId64=\"I64d\"" /D "PRIu64=\"I64u\"" /D "SCNu64=\"I64u\"" /D "SCNx64=\"I64x\"" /D "SCNd64=\"I64d\"" /D "PRIx64=\"I64x\"" /c $(VERSION) $(BUILDDATE) CFLAGS = /D "WITH_HTTPSRV" /nologo /Ox /MT /D "NOIPV6" /D "NO_UN" /D "NODEBUG" /D "NORADIUS" /D"WATCOM" /D "MSVC" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /D "PRId64=\"I64d\"" /D "PRIu64=\"I64u\"" /D "SCNu64=\"I64u\"" /D "SCNx64=\"I64x\"" /D "SCNd64=\"I64d\"" /D "PRIx64=\"I64x\"" /c $(VERSION) $(BUILDDATE)
COUT = /Fo COUT = /Fo
LN = link LN = link
LDFLAGS = /nologo /subsystem:console /incremental:no LDFLAGS = /nologo /subsystem:console /incremental:no

View File

@ -11,9 +11,6 @@ CRYPT_PREFIX ?= $(PREFIX)
CC ?= gcc CC ?= gcc
CFLAGS ?= -O3 -flto CFLAGS ?= -O3 -flto
CFLAGS += -fno-strict-aliasing -c -mthreads -DWITH_WSAPOLL -DWITH_ODBC CFLAGS += -fno-strict-aliasing -c -mthreads -DWITH_WSAPOLL -DWITH_ODBC
# Windows does not take a recv and a send on one socket from two threads,
# so a UDP service answers from a socket of its own
CFLAGS += -DNO_SHARE_UDP_SOCKET
COUT = -o COUT = -o
LN ?= $(CC) LN ?= $(CC)
LDFLAGS ?= -O3 -flto LDFLAGS ?= -O3 -flto

View File

@ -256,6 +256,8 @@ static int h_proxy(int argc, unsigned char ** argv){
ch.argc = argc; ch.argc = argc;
ch.argv = argv; ch.argv = argv;
/* set below for a UDP service only */
childdef.udp = NULL;
if(!strcmp((char *)argv[0], "proxy")) { if(!strcmp((char *)argv[0], "proxy")) {
childdef.pf = proxychild; childdef.pf = proxychild;
childdef.port = 3128; childdef.port = 3128;
@ -331,7 +333,8 @@ static int h_proxy(int argc, unsigned char ** argv){
childdef.helpmessage = ""; childdef.helpmessage = "";
} }
else if(!strcmp((char *)argv[0], "udppm")) { else if(!strcmp((char *)argv[0], "udppm")) {
childdef.pf = udppmchild; childdef.pf = NULL;
childdef.udp = &udppmservice;
childdef.port = 0; childdef.port = 0;
childdef.isudp = 1; childdef.isudp = 1;
childdef.service = S_UDPPM; childdef.service = S_UDPPM;
@ -364,7 +367,8 @@ static int h_proxy(int argc, unsigned char ** argv){
} }
#endif #endif
else if(!strcmp((char *)argv[0], "dnspr")) { else if(!strcmp((char *)argv[0], "dnspr")) {
childdef.pf = dnsprchild; childdef.pf = NULL;
childdef.udp = &dnsprservice;
childdef.port = 53; childdef.port = 53;
childdef.isudp = 1; childdef.isudp = 1;
childdef.service = S_DNSPR; childdef.service = S_DNSPR;
@ -883,7 +887,7 @@ struct redirdesc redirs[] = {
{R_INTPORT, "intport", NULL}, {R_INTPORT, "intport", NULL},
{R_TLS, "tls", tlsprchild}, {R_TLS, "tls", tlsprchild},
{R_HA, "ha", NULL}, {R_HA, "ha", NULL},
{R_DNS, "dns", dnsprchild}, {R_DNS, "dns", NULL},
{0, NULL, NULL} {0, NULL, NULL}
}; };

View File

@ -11,100 +11,30 @@
#ifndef UDP #ifndef UDP
#define UDP #define UDP
#endif #endif
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
#define BUFSIZE 16384 #define BUFSIZE 16384
/* dnspr: a query is a session of its own in the service's thread (see
udploop() in proxymain.c). It is answered there and then, from the name
cache, the resolver or a fake address, or sent on to the first name
server, whose answer, or its timeout, ends it. A name server over TCP is
still waited for in the loop. */
void * dnsprchild(struct clientparam* param) { struct dnsquery {
uint32_t ip = 0; unsigned type;
unsigned char *bbuf; uint32_t ip, ttl;
unsigned char *buf, *s1, *s2;
char * host = NULL;
unsigned char c;
SASIZETYPE size;
int res, i;
int len;
int semlocked = 1;
unsigned type=0;
uint32_t ttl;
unsigned char addr[16]; unsigned char addr[16];
#ifdef _WIN32 char *host;
unsigned long ul = 1; unsigned char *query; /* as the client sent it, for a name server */
#endif int len;
};
/* The answer to the client, the query in buf made one: ip set, an address
for it; a PTR for the service's own address; else the name not found. */
static int dnsanswer(struct udploop *u, struct clientparam *param, struct dnsquery *q, unsigned char *buf, int len){
uint32_t ttl;
if(!(bbuf = malloc(BUFSIZE+2))){ if(q->ip){
RETURN (21);
}
buf = bbuf+2;
size = sizeof(param->sinsl);
getsockname(param->srv->srvsock, (struct sockaddr *)&param->sincl, &size);
i = param->srv->udplen;
if(i > BUFSIZE) i = BUFSIZE;
memcpy(buf, param->srv->udpbuf, i);
_3proxy_sem_unlock(udpinit);
semlocked = 0;
#if defined(_WIN32) && !defined(SHARE_UDP_SOCKET)
if((param->clisock=nssocket(param->srv, &param->srv->so, param->sostate, NS_IN, AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
RETURN(818);
}
ioctlsocket(param->clisock, FIONBIO, &ul);
if(param->srv->so._setsockopt(param->sostate, param->clisock, SOL_SOCKET, SO_REUSEADDR, (char *)&ul, sizeof(int))) {RETURN(820);};
if(param->srv->so._bind(param->sostate, param->clisock,(struct sockaddr *)&param->sincl,SASIZE(&param->sincl))) {
RETURN(822);
}
#else
/* The reply has to come from the address the query was sent to, which
is the listening socket. */
param->clisock = param->srv->srvsock;
#endif
if(i < 0) {
RETURN(813);
}
buf[BUFSIZE - 1] = 0;
if(i<=13 || i>1000){
RETURN (814);
}
param->operation = DNSRESOLVE;
if((res = (*param->srv->authfunc)(param))) {RETURN(res);}
if(buf[4]!=0 || buf[5]!=1) RETURN(816);
for(len = 12; len<i; len+=(c+1)){
c = buf[len];
if(!c)break;
buf[len] = '.';
}
if(len > (i-4)) {RETURN(817);}
host = strdup((char *)buf+13);
if(!host) {RETURN(21);}
for(s2 = buf + 12; (s1 = (unsigned char *)strchr((char *)s2 + 1, '.')); s2 = s1)*s2 = (unsigned char)((s1 - s2) - 1);
*s2 = (len - (int)(s2 - buf)) - 1;
type = ((unsigned)buf[len+1])*256 + (unsigned)buf[len+2];
if(type==0x01 && param->srv->fakeip){
ip = 1;
ttl = 3600;
*(uint32_t *)addr = param->srv->fakeip;
}
#ifndef NOIPV6
else if(type==0x1c && *(uint32_t *)param->srv->fakeip6){
ip = 1;
ttl = 3600;
memcpy(addr, param->srv->fakeip6, 16);
}
#endif
else if((type==0x01 || type==0x1c) && !param->srv->s_option){
ip = udpresolve((type==0x1c)?AF_INET6:AF_INET, (unsigned char *)host, addr, &ttl, param, 0);
}
len+=5;
if(ip){
buf[2] = 0x85; buf[2] = 0x85;
buf[3] = 0x80; buf[3] = 0x80;
buf[6] = 0; buf[6] = 0;
@ -113,19 +43,83 @@ void * dnsprchild(struct clientparam* param) {
memset(buf+len, 0, 16); memset(buf+len, 0, 16);
buf[len] = 0xc0; buf[len] = 0xc0;
buf[len+1] = 0x0c; buf[len+1] = 0x0c;
buf[len+3] = type; buf[len+3] = q->type;
buf[len+5] = 1; buf[len+5] = 1;
ttl = htonl(ttl); ttl = htonl(q->ttl);
memcpy(buf + len + 6, &ttl, 4); memcpy(buf + len + 6, &ttl, 4);
buf[len+11] = type==1? 4:16; buf[len+11] = q->type==1? 4:16;
memcpy(buf+len+12,(void *)&addr,type==1? 4:16); memcpy(buf+len+12, q->addr, q->type==1? 4:16);
len+=(type==1?16:28); len += (q->type==1? 16:28);
} }
else if(type == 0x0c) { else {
unsigned a = 0, b = 0, c = 0, d = 0; buf[2] = 0x85;
if(sscanf(host, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) a = b = c = d = 0; buf[3] = 0x83;
ip = htonl((d<<24) ^ (c<<16) ^ (b<<8) ^ a); }
if(*SAFAMILY(&param->sincl) == AF_INET && ip == *(uint32_t *)SAADDR(&param->sincl)){ if(udpanswer(u, param, buf, len) != len) return 819;
return q->ip? 0 : 888;
}
static int dnsprfirst(struct udploop *u, struct udpsession *ss, unsigned char *query, int i){
struct clientparam *param = ss->param;
struct dnsquery *q;
unsigned char *buf, *s1, *s2;
unsigned char c;
int res, len;
uint32_t ttl, ip = 0;
#ifdef _WIN32
unsigned long ul = 1;
#endif
if(!(q = calloc(1, sizeof(*q))) || !(q->query = malloc(BUFSIZE + 2))){
free(q);
return (param->res = 21);
}
ss->data = q;
if(i > BUFSIZE) i = BUFSIZE;
memcpy(q->query + 2, query, i);
q->len = i;
buf = q->query + 2;
param->sincl = param->srv->intsa;
if(i<=13 || i>1000) return (param->res = 814);
buf[BUFSIZE - 1] = 0;
param->operation = DNSRESOLVE;
if((res = (*param->srv->authfunc)(param))) return (param->res = res);
if(buf[4]!=0 || buf[5]!=1) return (param->res = 816);
for(len = 12; len<i; len+=(c+1)){
c = buf[len];
if(!c)break;
buf[len] = '.';
}
if(len > (i-4)) return (param->res = 817);
if(!(q->host = strdup((char *)buf+13))) return (param->res = 21);
for(s2 = buf + 12; (s1 = (unsigned char *)strchr((char *)s2 + 1, '.')); s2 = s1)*s2 = (unsigned char)((s1 - s2) - 1);
*s2 = (len - (int)(s2 - buf)) - 1;
q->type = ((unsigned)buf[len+1])*256 + (unsigned)buf[len+2];
q->ttl = 3600;
if(q->type==0x01 && param->srv->fakeip){
ip = 1;
*(uint32_t *)q->addr = param->srv->fakeip;
}
#ifndef NOIPV6
else if(q->type==0x1c && *(uint32_t *)param->srv->fakeip6){
ip = 1;
memcpy(q->addr, param->srv->fakeip6, 16);
}
#endif
else if((q->type==0x01 || q->type==0x1c) && !param->srv->s_option){
ip = udpresolve((q->type==0x1c)?AF_INET6:AF_INET, (unsigned char *)q->host, q->addr, &q->ttl, param, 0);
}
len += 5;
if(!ip && q->type == 0x0c) {
unsigned a = 0, b = 0, cc = 0, d = 0;
uint32_t ptr;
if(sscanf(q->host, "%u.%u.%u.%u", &a, &b, &cc, &d) != 4) a = b = cc = d = 0;
ptr = htonl((d<<24) ^ (cc<<16) ^ (b<<8) ^ a);
if(*SAFAMILY(&param->sincl) == AF_INET && ptr == *(uint32_t *)SAADDR(&param->sincl)){
buf[2] = 0x85; buf[2] = 0x85;
buf[3] = 0x80; buf[3] = 0x80;
buf[6] = 0; buf[6] = 0;
@ -141,88 +135,114 @@ void * dnsprchild(struct clientparam* param) {
buf[len+11] = 7; buf[len+11] = 7;
buf[len+12] = 6; buf[len+12] = 6;
memcpy(buf+len+13,(void *)"3proxy",6); memcpy(buf+len+13,(void *)"3proxy",6);
len+=20; len += 20;
q->ip = 1;
if(udpanswer(u, param, buf, len) != len) return (param->res = 819);
return 1;
} }
else ip = 0;
} }
if(!ip && param->srv->config->numservers){ q->ip = ip;
if((param->remsock=param->srv->so._socket(param->sostate, SASOCK(&param->srv->config->nservers[0].addr), param->srv->config->nservers[0].usetcp? SOCK_STREAM:SOCK_DGRAM, param->srv->config->nservers[0].usetcp?IPPROTO_TCP:IPPROTO_UDP)) == INVALID_SOCKET) { if(ip || !param->srv->config->numservers){
RETURN(818); param->res = dnsanswer(u, param, q, buf, len);
return 1;
} }
/* on to the first name server */
if((param->remsock=param->srv->so._socket(param->sostate, SASOCK(&param->srv->config->nservers[0].addr), param->srv->config->nservers[0].usetcp? SOCK_STREAM:SOCK_DGRAM, param->srv->config->nservers[0].usetcp?IPPROTO_TCP:IPPROTO_UDP)) == INVALID_SOCKET)
return (param->res = 818);
memset(&param->sinsl, 0, sizeof(param->sinsl)); memset(&param->sinsl, 0, sizeof(param->sinsl));
*SAFAMILY(&param->sinsl) = *SAFAMILY(&param->srv->config->nservers[0].addr); *SAFAMILY(&param->sinsl) = *SAFAMILY(&param->srv->config->nservers[0].addr);
if(bindwithrange(param, param->remsock, &param->sinsl, param->extport)) { if(bindwithrange(param, param->remsock, &param->sinsl, param->extport)) return (param->res = 819);
RETURN(819);
}
param->sinsr = param->srv->config->nservers[0].addr; param->sinsr = param->srv->config->nservers[0].addr;
if(param->srv->config->nservers[0].usetcp) { if(param->srv->config->nservers[0].usetcp) {
if(connectwithpoll(param, param->remsock,(struct sockaddr *)&param->sinsr,SASIZE(&param->sinsr),param->srv->timeouts[CONNECT_TO])) RETURN(830); unsigned short us;
buf-=2;
if(connectwithpoll(param, param->remsock,(struct sockaddr *)&param->sinsr,SASIZE(&param->sinsr),param->srv->timeouts[CONNECT_TO])) return (param->res = 830);
buf -= 2;
*(unsigned short*)buf = htons(i); *(unsigned short*)buf = htons(i);
i+=2; i += 2;
if(socksend(param, param->remsock, buf, i, param->srv->timeouts[SINGLEBYTE_L]) != i) return (param->res = 820);
param->statscli64 += i;
param->nwrites++;
len = sockrecvfrom(param, param->remsock, NULL, buf, BUFSIZE, param->srv->timeouts[DNS_TO]*1000);
if(len <= 13) return (param->res = 821);
param->statssrv64 += len;
param->nreads++;
us = ntohs(*(unsigned short *)buf);
if(us > 4096) return (param->res = 833);
buf += 2;
len -= 2;
if(len < us) len += sockgetlinebuf(param, SERVER, buf+len, us - len, 0, param->srv->timeouts[SINGLEBYTE_L]);
if(len != us) return (param->res = 832);
if(buf[6] || buf[7]){
if(udpanswer(u, param, buf, len) != len) return (param->res = 822);
return 1;
}
param->res = dnsanswer(u, param, q, buf, len);
return 1;
} }
else {
#ifdef _WIN32 #ifdef _WIN32
ioctlsocket(param->remsock, FIONBIO, &ul); ioctlsocket(param->remsock, FIONBIO, &ul);
#else #else
fcntl(param->remsock, F_SETFL, O_NONBLOCK | fcntl(param->remsock, F_GETFL)); fcntl(param->remsock, F_SETFL, O_NONBLOCK | fcntl(param->remsock, F_GETFL));
#endif #endif
} if(param->srv->so._sendto(param->sostate, param->remsock, (char *)buf, i, 0, (struct sockaddr *)&param->sinsr, SASIZE(&param->sinsr)) != i)
return (param->res = 820);
if(socksendto(param, param->remsock, (struct sockaddr *)&param->sinsr, buf, i, param->srv->timeouts[SINGLEBYTE_L]*1000) != i){
RETURN(820);
}
param->statscli64 += i; param->statscli64 += i;
param->nwrites++; param->nwrites++;
len = sockrecvfrom(param, param->remsock, (struct sockaddr *)&param->sinsr, buf, BUFSIZE, param->srv->timeouts[DNS_TO]*1000); /* its answer is waited for in the loop, as long as DNS_TO */
if(len <= 13) { ss->idle = 0;
RETURN(821); ss->deadline = time(NULL) + param->srv->timeouts[DNS_TO];
} return 0;
param->statssrv64 += len;
param->nreads++;
if(param->srv->config->nservers[0].usetcp) {
unsigned short us;
us = ntohs(*(unsigned short *)buf);
if(us > 4096) RETURN(833);
buf += 2;
len -= 2;
if(len < us) len += sockgetlinebuf(param, SERVER, buf+len, us - len, 0, param->srv->timeouts[SINGLEBYTE_L]);
if(len != us) RETURN(832);
}
if(buf[6] || buf[7]){
if(socksendto(param, param->clisock, (struct sockaddr *)&param->sincr, buf, len, param->srv->timeouts[SINGLEBYTE_L]*1000) != len){
RETURN(822);
}
RETURN(0);
}
}
if(!ip) {
buf[2] = 0x85;
buf[3] = 0x83;
}
res = socksendto(param, param->clisock, (struct sockaddr *)&param->sincr, buf, len, param->srv->timeouts[SINGLEBYTE_L]*1000);
if(res != len){RETURN(819);}
if(!ip) {RETURN(888);}
CLEANRET:
if(semlocked) _3proxy_sem_unlock(udpinit);
if(param->res!=813){
sprintf((char *)buf, "%04x/%s/",
(unsigned)type,
host?host:"");
if((ip && type == 0x01) || type == 0x1c){
myinet_ntop(type == 0x01? AF_INET:AF_INET6, addr, (char *)buf+strlen((char *)buf), 64);
}
dolog(param, buf);
}
if(bbuf)free(bbuf);
if(host)free(host);
#if !defined(_WIN32) || defined(SHARE_UDP_SOCKET)
/* The socket belongs to the service, so the caller must not close it. */
param->clisock = INVALID_SOCKET;
#endif
return (NULL);
} }
/* The name server's answer: to the client as it is if it has answers,
else as the name not found. */
static int dnsprserver(struct udploop *u, struct udpsession *ss){
struct clientparam *param = ss->param;
struct dnsquery *q = ss->data;
int len;
len = param->srv->so._recvfrom(param->sostate, param->remsock, (char *)u->buf, UDPBUFSIZE, 0, NULL, NULL);
if(len < 0 && (errno == EAGAIN || errno == EINTR)) return 0;
if(len <= 13) return (param->res = 821);
param->statssrv64 += len;
param->nreads++;
if(u->buf[6] || u->buf[7]){
if(udpanswer(u, param, u->buf, len) != len) return (param->res = 822);
param->res = 0;
return 1;
}
q->ip = 0;
param->res = dnsanswer(u, param, q, u->buf, len);
return 1;
}
static void dnsprend(struct udploop *u, struct udpsession *ss){
struct clientparam *param = ss->param;
struct dnsquery *q = ss->data;
char line[256];
if(param->res != 813){
sprintf(line, "%04x/%.128s/", q? q->type : 0, q && q->host? q->host : "");
if(q && ((q->ip && q->type == 0x01) || q->type == 0x1c)){
myinet_ntop(q->type == 0x01? AF_INET:AF_INET6, q->addr, line+strlen(line), 64);
}
dolog(param, (unsigned char *)line);
}
if(q){
free(q->host);
free(q->query);
free(q);
}
ss->data = NULL;
}
const struct udpservice dnsprservice = {
0,
dnsprfirst,
NULL,
dnsprserver,
dnsprend,
821
};

View File

@ -55,12 +55,12 @@ struct symbol symbols[] = {
{symbols+28, "de64", (void *) de64}, {symbols+28, "de64", (void *) de64},
{symbols+29, "tohex", (void *) tohex}, {symbols+29, "tohex", (void *) tohex},
{symbols+30, "fromhex", (void *) fromhex}, {symbols+30, "fromhex", (void *) fromhex},
{symbols+31, "dnspr", (void *) dnsprchild}, {symbols+31, "dnspr", NULL},
{symbols+32, "pop3p", (void *) pop3pchild}, {symbols+32, "pop3p", (void *) pop3pchild},
{symbols+33, "proxy", (void *) proxychild}, {symbols+33, "proxy", (void *) proxychild},
{symbols+34, "socks", (void *) sockschild}, {symbols+34, "socks", (void *) sockschild},
{symbols+35, "tcppm", (void *) tcppmchild}, {symbols+35, "tcppm", (void *) tcppmchild},
{symbols+36, "udppm", (void *) udppmchild}, {symbols+36, "udppm", NULL},
{symbols+38, "ftppr", (void *) ftpprchild}, {symbols+38, "ftppr", (void *) ftpprchild},
{symbols+39, "smtpp", (void *) smtppchild}, {symbols+39, "smtpp", (void *) smtppchild},
{symbols+40, "auto", (void *) smtppchild}, {symbols+40, "auto", (void *) smtppchild},

View File

@ -146,20 +146,6 @@ void daemonize(void);
#endif #endif
#endif #endif
/* A UDP service answers from the address the client sent to, so the reply has
to leave the listening socket. Sharing that socket with the request handler
is the simple way, and what every Unix build does.
Older Windows cannot have two operations in flight on one socket, so a
build for it binds a second socket to the same address instead, which needs
SO_REUSEADDR on the listening socket as well. A build for those versions
asks for that with NO_SHARE_UDP_SOCKET, as Makefile.watcom does.
*/
#ifndef SHARE_UDP_SOCKET
#ifndef NO_SHARE_UDP_SOCKET
#define SHARE_UDP_SOCKET
#endif
#endif
#ifndef _WIN32 #ifndef _WIN32
size_t threadstacksize(int extra); size_t threadstacksize(int extra);
@ -250,6 +236,50 @@ extern struct extparam confboot;
int sockmap(struct clientparam * param, int timeo, int usesplice); int sockmap(struct clientparam * param, int timeo, int usesplice);
int udpsockmap(struct clientparam * param, int timeo); int udpsockmap(struct clientparam * param, int timeo);
int udpbind(struct clientparam * param); int udpbind(struct clientparam * param);
int socks5_udp_skip_hdr(unsigned char *buf, int len);
/* A UDP service runs in one thread, the service's: it reads the service's
socket, from which every answer to a client leaves too, and the sockets
of its sessions towards their destinations, and keeps the time. A
session is a client of the service, a struct clientparam, without a
thread. */
struct udploop;
struct udpsession {
struct udpsession *next, *prev;
struct clientparam *param;
const struct udpservice *svc;
time_t deadline; /* when it ends unless refreshed */
int idle; /* seconds of silence it lasts, 0: deadline fixed */
int keyed; /* found by its client's address */
int done; /* to be ended, param->res saying how */
void *data; /* the service's own, freed by it in end() */
};
struct udpservice {
/* datagrams from a client are found its session by the client's
address (udppm), or each starts one of its own (dnspr) */
int keyed;
/* a session's first datagram, in buf, from param->sincr: 0 to keep the
session, nonzero to end it, param->res saying how */
int (*first)(struct udploop *u, struct udpsession *ss, unsigned char *buf, int len);
/* a later datagram of its client (keyed services) */
int (*client)(struct udploop *u, struct udpsession *ss, unsigned char *buf, int len);
/* its socket towards the destination is readable */
int (*server)(struct udploop *u, struct udpsession *ss);
/* it ends: logged, its own data freed */
void (*end)(struct udploop *u, struct udpsession *ss);
/* param->res of a session whose deadline passed */
int timeoutres;
};
struct udploop {
struct srvparam *srv;
struct clientparam *defparam;
/* scratch of UDPBUFSIZE for a handler */
unsigned char *buf;
};
/* an answer to the session's client, from the service's socket */
int udpanswer(struct udploop *u, struct clientparam *param, unsigned char *buf, int len);
extern const struct udpservice udppmservice, dnsprservice;
int socks5_setaddr(int family, int atyp, const unsigned char *addr, PROXYSOCKADDRTYPE *sa); int socks5_setaddr(int family, int atyp, const unsigned char *addr, PROXYSOCKADDRTYPE *sa);
/* which of a service's network namespaces a socket is made in */ /* which of a service's network namespaces a socket is made in */
#define NS_IN 0 /* -ni: what faces the clients */ #define NS_IN 0 /* -ni: what faces the clients */
@ -337,7 +367,6 @@ unsigned char* en64 (const unsigned char *in, unsigned char *out, int inlen, int
void tohex(unsigned char *in, unsigned char *out, int len); void tohex(unsigned char *in, unsigned char *out, int len);
void fromhex(unsigned char *in, unsigned char *out, int len); void fromhex(unsigned char *in, unsigned char *out, int len);
extern _3proxy_sem_t udpinit;
#ifdef _WIN32 #ifdef _WIN32
#define _3proxy_sem_init(x, count, maxcount) (((x) = CreateSemaphore(NULL, (count), (maxcount), NULL))? 0 : 1) #define _3proxy_sem_init(x, count, maxcount) (((x) = CreateSemaphore(NULL, (count), (maxcount), NULL))? 0 : 1)
#define _3proxy_sem_lock(x) WaitForSingleObject(x, INFINITE) #define _3proxy_sem_lock(x) WaitForSingleObject(x, INFINITE)
@ -460,7 +489,6 @@ extern char *copyright;
#define SERVICES 5 #define SERVICES 5
void * dnsprchild(struct clientparam * param);
void * pop3pchild(struct clientparam * param); void * pop3pchild(struct clientparam * param);
void * imappchild(struct clientparam * param); void * imappchild(struct clientparam * param);
void * smtppchild(struct clientparam * param); void * smtppchild(struct clientparam * param);
@ -468,7 +496,6 @@ void * proxychild(struct clientparam * param);
void * sockschild(struct clientparam * param); void * sockschild(struct clientparam * param);
void * tcppmchild(struct clientparam * param); void * tcppmchild(struct clientparam * param);
void * autochild(struct clientparam * param); void * autochild(struct clientparam * param);
void * udppmchild(struct clientparam * param);
#ifdef WITH_HTTPSRV #ifdef WITH_HTTPSRV
int op_admin(struct httpreq *r, const unsigned char *params); int op_admin(struct httpreq *r, const unsigned char *params);
int op_admin_counters(struct httpreq *r, const unsigned char *params); int op_admin_counters(struct httpreq *r, const unsigned char *params);

View File

@ -35,9 +35,6 @@ static void serveparam(struct clientparam *param){
int i = -1; int i = -1;
#ifdef MODULEMAINFUNC #ifdef MODULEMAINFUNC
if(makefilters(param->srv, param) > CONTINUE){ if(makefilters(param->srv, param) > CONTINUE){
#ifndef NOUDPMAIN
if(param->srv->service == S_UDPPM) _3proxy_sem_unlock(udpinit);
#endif
releaseparam(param); releaseparam(param);
return; return;
} }
@ -97,7 +94,7 @@ static void serveparam(struct clientparam *param){
#ifndef _WIN32 #ifndef _WIN32
sigset_t mask; sigset_t mask;
sigfillset(&mask); sigfillset(&mask);
if(param->srv->service != S_UDPPM)pthread_sigmask(SIG_SETMASK, &mask, NULL); pthread_sigmask(SIG_SETMASK, &mask, NULL);
#endif #endif
#endif #endif
@ -378,6 +375,7 @@ struct srvstart {
unsigned char *cbc_string, *cbl_string; unsigned char *cbc_string, *cbl_string;
FILE *fp; FILE *fp;
int isudp, iscbc, iscbl; int isudp, iscbc, iscbl;
const struct udpservice *udp;
#ifndef _WIN32 #ifndef _WIN32
pthread_attr_t pa; pthread_attr_t pa;
#endif #endif
@ -484,6 +482,7 @@ static int srvprepare(struct srvstart *s, int argc, char **argv){
#endif #endif
srv->pf = childdef.pf; srv->pf = childdef.pf;
s->isudp = childdef.isudp; s->isudp = childdef.isudp;
s->udp = childdef.udp;
/* Default socket options, which -oc, -os, -ol and -oR change by naming /* Default socket options, which -oc, -os, -ol and -oR change by naming
them, NAME=0 to turn one off. them, NAME=0 to turn one off.
@ -496,17 +495,13 @@ static int srvprepare(struct srvstart *s, int argc, char **argv){
listening port there, and it only allows another local process to bind listening port there, and it only allows another local process to bind
the same address and port, with undefined behaviour as to which socket the same address and port, with undefined behaviour as to which socket
receives the connections. Use -olSO_EXCLUSIVEADDRUSE to prevent that receives the connections. Use -olSO_EXCLUSIVEADDRUSE to prevent that
instead. A Windows build which does not share the listening socket is the instead.
exception: its UDP services bind a second socket to the same address to
answer from, and Windows only allows that when both sockets ask for it.
*/ */
(void)getopts("TCP_NODELAY", &srv->clisockopts); (void)getopts("TCP_NODELAY", &srv->clisockopts);
(void)getopts("TCP_NODELAY", &srv->srvsockopts); (void)getopts("TCP_NODELAY", &srv->srvsockopts);
#ifndef _WIN32 #ifndef _WIN32
(void)getopts("SO_REUSEADDR", &srv->lissockopts); (void)getopts("SO_REUSEADDR", &srv->lissockopts);
(void)getopts("SO_REUSEADDR", &srv->cbssockopts); (void)getopts("SO_REUSEADDR", &srv->cbssockopts);
#elif !defined SHARE_UDP_SOCKET
if(s->isudp) (void)getopts("SO_REUSEADDR", &srv->lissockopts);
#endif #endif
#ifdef SO_REUSEPORT #ifdef SO_REUSEPORT
(void)getopts("SO_REUSEPORT", &srv->lissockopts); (void)getopts("SO_REUSEPORT", &srv->lissockopts);
@ -514,7 +509,6 @@ static int srvprepare(struct srvstart *s, int argc, char **argv){
#endif #endif
#ifndef NOUDPMAIN #ifndef NOUDPMAIN
if(s->isudp) { if(s->isudp) {
if(!udp_table.ihashtable)inithashtable(&udp_table, 64, 256, 65536);
srv->udpbuf = malloc(UDPBUFSIZE); srv->udpbuf = malloc(UDPBUFSIZE);
srv->udpbuf2 = malloc(UDPBUFSIZE); srv->udpbuf2 = malloc(UDPBUFSIZE);
if(!srv->udpbuf || !srv->udpbuf2) return 2; if(!srv->udpbuf || !srv->udpbuf2) return 2;
@ -916,6 +910,9 @@ struct listener {
from the client (%O) */ from the client (%O) */
uint64_t trafin, trafout; uint64_t trafin, trafout;
_3proxy_mutex_t count_mutex; _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). */ /* A client of srv comes (1) or goes (-1). */
@ -967,6 +964,9 @@ static struct listener *listeners = NULL;
_3proxy_mutex_t listener_mutex; _3proxy_mutex_t listener_mutex;
static void listenerstop(struct srvparam *srv); 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 takeopts(struct listener *l, struct srvparam *srv);
static void listenerrelease(struct srvparam *srv); static void listenerrelease(struct srvparam *srv);
#endif #endif
@ -1292,40 +1292,383 @@ static int tcpnext(struct srvstart *s, SOCKET *client, int *nlog){
#endif #endif
#ifndef NOUDPMAIN #ifndef NOUDPMAIN
/* The next UDP client: a datagram from a peer with no association yet. One /* A UDP service's loop, in the service's thread: the datagrams of its
from a known peer is passed on to it here. A new client is returned with clients come on the service's socket, those of their destinations on the
udpinit held, for its child to read the datagram and release it. */ sockets of their sessions, and every answer to a client leaves from the
static int udpnext(struct srvstart *s){ 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 srvparam *srv = &s->srv;
struct clientparam *defparam = &s->defparam; struct udpsession *ss, *next;
struct clientparam *toparam; time_t now;
int i, n, nfds, timeo, res = 0, wait = 0;
_3proxy_sem_lock(udpinit); memset(&st, 0, sizeof(st));
srv->udplen = sockrecvfrom(NULL, srv->srvsock, (struct sockaddr *)&defparam->sincr, srv->udpbuf, UDPBUFSIZE, 0); st.u.srv = srv;
if(srv->udplen <= 0) { st.u.defparam = &s->defparam;
_3proxy_sem_unlock(udpinit); st.u.buf = srv->udpbuf2;
return NEXT_RETRY; st.svc = s->udp;
} st.keys.index2hash_add = udpkey2hash;
if(hashresolv(&udp_table, defparam, &toparam, NULL)) { st.keys.index2hash_search = udpkey2hash;
int i, len=0; 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(!toparam->bandlimfunc || !(*toparam->bandlimfunc)(toparam, 0, srv->udplen)){ if(f) st.fds = f;
if(toparam->udp_nhops){ if(f && (st.of = realloc(st.of, want * sizeof(*st.of)))) st.nalloc = want;
for(i=1; i < toparam->udp_nhops; i++){ else { res = 21; break; }
len+=socks5_udp_build_hdr(srv->udpbuf2+len, &toparam->udp_relay[i-1]);
} }
len += socks5_udp_build_hdr(srv->udpbuf2+len, &toparam->req); 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;
} }
memcpy(srv->udpbuf2+len, srv->udpbuf, srv->udplen > UDPBUFSIZE - len?UDPBUFSIZE - len : srv->udplen); /* a parent's control connection: its end ends the session */
len += srv->udplen > UDPBUFSIZE - len?UDPBUFSIZE - len : srv->udplen; if(ss->param->ctrlsocksrv != INVALID_SOCKET && ss->param->ctrlsocksrv != ss->param->remsock){
srv->so._sendto(toparam->sostate, toparam->remsock, (char *)srv->udpbuf2, len, 0, (struct sockaddr *)&toparam->sinsr, SASIZE(&toparam->sinsr)); st.fds[nfds].fd = ss->param->ctrlsocksrv;
toparam->statscli64 += srv->udplen; st.fds[nfds].events = POLLIN;
toparam->nwrites++; st.fds[nfds].revents = 0;
st.of[nfds++] = ss;
} }
_3proxy_sem_unlock(udpinit);
return NEXT_RETRY;
} }
return NEXT_CLIENT; 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 #endif
@ -1363,12 +1706,7 @@ static int srvspawn(struct srvstart *s, SOCKET client_sock){
reused = newparam != NULL; reused = newparam != NULL;
if(!reused && (!(newparam = malloc (sizeof(*defparam))) || _3proxy_sem_init_timed(newparam->wake, 0, 1))){ if(!reused && (!(newparam = malloc (sizeof(*defparam))) || _3proxy_sem_init_timed(newparam->wake, 0, 1))){
if(newparam) free(newparam); if(newparam) free(newparam);
if(!s->isudp) srv->so._closesocket(srv->so.state, client_sock); srv->so._closesocket(srv->so.state, client_sock);
#ifndef NOUDPMAIN
else {
_3proxy_sem_unlock(udpinit);
}
#endif
defparam->res = 21; defparam->res = 21;
if(!srv->silent)dolog(defparam, (unsigned char *)"Memory Allocation Failed"); if(!srv->silent)dolog(defparam, (unsigned char *)"Memory Allocation Failed");
usleep(SLEEPTIME); usleep(SLEEPTIME);
@ -1427,11 +1765,6 @@ static int srvspawn(struct srvstart *s, SOCKET client_sock){
newparam->clisock = INVALID_SOCKET; newparam->clisock = INVALID_SOCKET;
} }
newparam->srv = NULL; newparam->srv = NULL;
#ifndef NOUDPMAIN
if(s->isudp){
_3proxy_sem_unlock(udpinit);
}
#endif
freeworker(newparam); freeworker(newparam);
} }
else { else {
@ -1489,6 +1822,10 @@ static int srvrun(struct srvstart *s){
tpool.services++; tpool.services++;
tpool.maxchild += srv->maxchild; tpool.maxchild += srv->maxchild;
_3proxy_mutex_unlock(&tpool.mutex); _3proxy_mutex_unlock(&tpool.mutex);
#ifndef NOUDPMAIN
if(s->isudp) res = udploop(s);
else
#endif
for (;;) { for (;;) {
for(;;){ for(;;){
/* A connection is only taken with a slot of maxchild in hand, which /* A connection is only taken with a slot of maxchild in hand, which
@ -1518,12 +1855,10 @@ static int srvrun(struct srvstart *s){
} }
if((srv->config->paused != srv->paused) || (error < 0)) break; if((srv->config->paused != srv->paused) || (error < 0)) break;
error = 0; error = 0;
#if defined NOTCPMAIN #ifndef NOTCPMAIN
next = udpnext(s);
#elif defined NOUDPMAIN
next = tcpnext(s, &new_sock, &nlog); next = tcpnext(s, &new_sock, &nlog);
#else #else
next = s->isudp? udpnext(s) : tcpnext(s, &new_sock, &nlog); next = NEXT_STOP;
#endif #endif
if(next == NEXT_STOP) break; if(next == NEXT_STOP) break;
if(next == NEXT_RETRY) continue; if(next == NEXT_RETRY) continue;
@ -1851,6 +2186,7 @@ static void listenerstop(struct srvparam *srv){
struct listener *l = srv->listener; struct listener *l = srv->listener;
struct srvparam **op; struct srvparam **op;
SOCKET sock = INVALID_SOCKET; SOCKET sock = INVALID_SOCKET;
struct udpsession *hand = NULL;
_3proxy_mutex_lock(&listener_mutex); _3proxy_mutex_lock(&listener_mutex);
for(op = &l->owners; *op; op = &(*op)->lisnext) if(*op == srv){ for(op = &l->owners; *op; op = &(*op)->lisnext) if(*op == srv){
@ -1861,8 +2197,14 @@ static void listenerstop(struct srvparam *srv){
if(!l->owners){ if(!l->owners){
sock = l->sock; sock = l->sock;
l->sock = INVALID_SOCKET; 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); _3proxy_mutex_unlock(&listener_mutex);
#ifndef NOUDPMAIN
udpendall(hand, 26);
#endif
if(sock == INVALID_SOCKET) return; if(sock == INVALID_SOCKET) return;
so._closesocket(so.state, sock); so._closesocket(so.state, sock);
#ifdef WITH_UN #ifdef WITH_UN
@ -1963,10 +2305,6 @@ void maindrop(void *service){
#endif #endif
#ifndef NOUDPMAIN
int udpinited = 0;
_3proxy_sem_t udpinit;
#endif
void srvinit(struct srvparam * srv, struct clientparam *param){ void srvinit(struct srvparam * srv, struct clientparam *param){
@ -2017,12 +2355,6 @@ void srvinit(struct srvparam * srv, struct clientparam *param){
*SAFAMILY(&param->req) = *SAFAMILY(&param->sinsl) = *SAFAMILY(&param->sinsr) = *SAFAMILY(&param->sincr) = *SAFAMILY(&param->sincl) = AF_INET; *SAFAMILY(&param->req) = *SAFAMILY(&param->sinsl) = *SAFAMILY(&param->sinsr) = *SAFAMILY(&param->sincr) = *SAFAMILY(&param->sincl) = AF_INET;
_3proxy_mutex_init(&srv->counter_mutex); _3proxy_mutex_init(&srv->counter_mutex);
(void)_3proxy_sem_init_timed(srv->childslots, srv->maxchild, srv->maxchild); (void)_3proxy_sem_init_timed(srv->childslots, srv->maxchild, srv->maxchild);
#ifndef NOUDPMAIN
if(!udpinited){
(void)_3proxy_sem_init(udpinit, 1, 1);
}
udpinited = 1;
#endif
srv->intsa = conf->intsa; srv->intsa = conf->intsa;
srv->extsa = conf->extsa; srv->extsa = conf->extsa;
#ifndef NOIPV6 #ifndef NOIPV6
@ -2147,13 +2479,6 @@ void srvfree(struct srvparam * srv){
void releaseparam(struct clientparam * param) { void releaseparam(struct clientparam * param) {
if(param->srv){ if(param->srv){
if(param->srv->so.freefunc) param->srv->so.freefunc(param->sostate); if(param->srv->so.freefunc) param->srv->so.freefunc(param->sostate);
#ifndef STDMAIN
if(param->srv->service == S_UDPPM){
_3proxy_mutex_lock(&param->srv->counter_mutex);
hashdelete(&udp_table, param);
_3proxy_mutex_unlock(&param->srv->counter_mutex);
}
#endif
} }
if(param->clibuf != param->clibufdata) free(param->clibuf); if(param->clibuf != param->clibufdata) free(param->clibuf);
if(param->srvbuf != param->srvbufdata) free(param->srvbuf); if(param->srvbuf != param->srvbufdata) free(param->srvbuf);

View File

@ -976,12 +976,17 @@ struct symbol {
void * value; void * value;
}; };
struct udpservice;
struct proxydef { struct proxydef {
PROXYFUNC pf; PROXYFUNC pf;
uint16_t port; uint16_t port;
int isudp; int isudp;
int service; int service;
char * helpmessage; char * helpmessage;
/* a UDP service: its datagrams are handled in the service's own thread
by these, no thread for a client */
const struct udpservice *udp;
}; };
extern struct proxydef childdef; extern struct proxydef childdef;
@ -996,7 +1001,6 @@ extern struct hashtable dns_table;
extern struct hashtable dns6_table; extern struct hashtable dns6_table;
extern struct hashtable auth_table; extern struct hashtable auth_table;
extern const struct hashtable pwl_template; extern const struct hashtable pwl_template;
extern struct hashtable udp_table;
struct authcache { struct authcache {
unsigned char username[64]; unsigned char username[64];

View File

@ -14,30 +14,36 @@
#ifndef UDP #ifndef UDP
#define UDP #define UDP
#endif #endif
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
/* udppm: a session per client address, in the service's thread (see
udploop() in proxymain.c). Its datagrams go on to the target from a socket
of the session's own, behind the headers of a parent chain if the ACL
gives one; what comes back is answered from the service's socket. */
static void udpparam2hash(const struct hashtable *ht, void *index, uint8_t *hash){ /* A datagram of the client to the target, or to the first relay of a chain
struct clientparam *param = (struct clientparam *)index; with the headers of the hops after it and of the target. */
uint32_t m1, m2; static void udppmforward(struct udploop *u, struct clientparam *param, unsigned char *data, int len){
m1 = murmurhash3(SAADDR(&param->srv->intsa), SAADDRLEN(&param->srv->intsa), 0x3a3a3a3a); int i, off = 0;
m1 = murmurhash3(SAPORT(&param->sincr), 2, m1);
m2 = murmurhash3(SAADDR(&param->sincr), SAADDRLEN(&param->sincr), m1); if(param->bandlimfunc && (*param->bandlimfunc)(param, 0, len)) return;
m2 = murmurhash3(SAPORT(&param->srv->intsa), 2, m2); if(param->udp_nhops){
memcpy(hash, &m1, 4); for(i = 1; i < param->udp_nhops; i++)
memcpy(hash+4, &m2, 4); off += socks5_udp_build_hdr(u->buf + off, &param->udp_relay[i-1]);
off += socks5_udp_build_hdr(u->buf + off, &param->req);
}
if(len > UDPBUFSIZE - off) len = UDPBUFSIZE - off;
memcpy(u->buf + off, data, len);
param->srv->so._sendto(param->sostate, param->remsock, (char *)u->buf, off + len, 0,
(struct sockaddr *)&param->sinsr, SASIZE(&param->sinsr));
param->statscli64 += len;
param->nwrites++;
} }
struct hashtable udp_table = {udpparam2hash, udpparam2hash, sizeof(struct clientparam *), 8}; static int udppmfirst(struct udploop *u, struct udpsession *ss, unsigned char *buf, int len){
int socks5_udp_build_hdr(unsigned char *buf, PROXYSOCKADDRTYPE *addr); struct clientparam *param = ss->param;
int res;
void * udppmchild(struct clientparam* param) {
int authres;
int i;
int len = 0;
if(parsehostname((char *)param->srv->target, param, ntohs(param->srv->targetport))) { RETURN(100) }
if(parsehostname((char *)param->srv->target, param, ntohs(param->srv->targetport))) return (param->res = 100);
#ifndef NOIPV6 #ifndef NOIPV6
param->sinsl = *SAFAMILY(&param->req) == AF_INET6? param->srv->extsa6 : param->srv->extsa; param->sinsl = *SAFAMILY(&param->req) == AF_INET6? param->srv->extsa6 : param->srv->extsa;
#else #else
@ -46,46 +52,74 @@ void * udppmchild(struct clientparam* param) {
*SAPORT(&param->sinsl) = 0; *SAPORT(&param->sinsl) = 0;
param->sinsr = param->req; param->sinsr = param->req;
param->operation = UDPASSOC; param->operation = UDPASSOC;
authres = (*param->srv->authfunc)(param); if((res = (*param->srv->authfunc)(param))) return (param->res = res);
if(authres) { RETURN(authres); } if((res = udpbind(param))) return (param->res = res);
if((authres = udpbind(param))) { RETURN(authres); } ss->idle = param->srv->timeouts[STRING_L];
if(!param->srv->s_option)hashadd(&udp_table, param, &param, MAX_COUNTER_TIME); udppmforward(u, param, buf, len);
if(!param->bandlimfunc || !(*param->bandlimfunc)(param, 0, param->srv->udplen)){ return 0;
if(param->udp_nhops){
for(i=1; i < param->udp_nhops; i++){
len+=socks5_udp_build_hdr(param->srvbuf+len, &param->udp_relay[i-1]);
}
len += socks5_udp_build_hdr(param->srvbuf+len, &param->req);
}
memcpy(param->srvbuf+len, param->srv->udpbuf, param->srv->udplen > UDPBUFSIZE - len?UDPBUFSIZE - len : param->srv->udplen);
len += param->srv->udplen > UDPBUFSIZE - len?UDPBUFSIZE - len : param->srv->udplen;
param->srv->so._sendto(param->sostate, param->remsock, (char *)param->srvbuf, len, 0, (struct sockaddr *)&param->sinsr, SASIZE(&param->sinsr));
param->statscli64 += param->srvinbuf;
param->nwrites++;
}
_3proxy_sem_unlock(udpinit);
param->srvinbuf = 0;
param->clisock = param->srv->srvsock;
param->waitserver64 = 0x7fffffffffffffff;
param->res = udpsockmap(param, param->srv->timeouts[STRING_L]);
_3proxy_sem_lock(udpinit);
if(!param->srv->s_option)hashdelete(&udp_table, param);
CLEANRET:
_3proxy_sem_unlock(udpinit);
dolog(param, NULL);
param->clisock = INVALID_SOCKET;
return (NULL);
} }
static int udppmclient(struct udploop *u, struct udpsession *ss, unsigned char *buf, int len){
udppmforward(u, ss->param, buf, len);
return 0;
}
/* What the target, or the relay of a chain, sends back: to the client, the
headers of the chain taken off. */
static int udppmserver(struct udploop *u, struct udpsession *ss){
struct clientparam *param = ss->param;
PROXYSOCKADDRTYPE from;
SASIZETYPE size = sizeof(from);
int len, off = 0, k, next;
len = param->srv->so._recvfrom(param->sostate, param->remsock, (char *)u->buf, UDPBUFSIZE, 0,
(struct sockaddr *)&from, &size);
if(len < 0 && (errno == EAGAIN || errno == EINTR)) return 0;
#ifdef _WIN32
/* an earlier datagram was refused (ICMP): reported here, not fatal */
if(len < 0 && WSAGetLastError() == WSAECONNRESET) return 0;
#endif
if(len <= 0) return (param->res = 486);
if(!SAISNULL(&param->sinsr) && *SAPORT(&param->sinsr) &&
(SAADDRLEN(&from) != SAADDRLEN(&param->sinsr) ||
memcmp(SAADDR(&from), SAADDR(&param->sinsr), SAADDRLEN(&from)) ||
memcmp(SAPORT(&from), SAPORT(&param->sinsr), 2)))
return 0;
for(k = 0; k < param->udp_nhops; k++){
if((next = socks5_udp_skip_hdr(u->buf + off, len - off)) < 0) return 0;
off += next;
}
len -= off;
param->statssrv64 += len;
param->nreads++;
if(param->bandlimfunc && (*param->bandlimfunc)(param, len, 0)) return 0;
udpanswer(u, param, u->buf + off, len);
/* -s: one answer to a request */
if(param->srv->s_option) return 1;
return 0;
}
static void udppmend(struct udploop *u, struct udpsession *ss){
dolog(ss->param, NULL);
}
const struct udpservice udppmservice = {
1,
udppmfirst,
udppmclient,
udppmserver,
udppmend,
92
};
#ifdef WITHMAIN #ifdef WITHMAIN
struct proxydef childdef = { struct proxydef childdef = {
udppmchild, NULL,
0, 0,
1, 1,
S_UDPPM, S_UDPPM,
" -s single packet UDP service for request/reply (DNS-like) services\n" " -s single packet UDP service for request/reply (DNS-like) services\n",
&udppmservice
}; };
#include "proxymain.c" #include "proxymain.c"
#endif #endif

View File

@ -21,7 +21,7 @@ int socks5_udp_build_hdr(unsigned char *buf, PROXYSOCKADDRTYPE *addr)
return 4 + SAADDRLEN(addr) + 2; return 4 + SAADDRLEN(addr) + 2;
} }
static int socks5_udp_skip_hdr(unsigned char *buf, int len) int socks5_udp_skip_hdr(unsigned char *buf, int len)
{ {
int addr_len; int addr_len;
int off; int off;
@ -49,7 +49,6 @@ static int socks5_udp_skip_hdr(unsigned char *buf, int len)
* 3 three parent proxies (prepend 2 headers / strip 2 headers) * 3 three parent proxies (prepend 2 headers / strip 2 headers)
* *
* param->waitserver64 non-zero: skip client socket polling (server→client only) * param->waitserver64 non-zero: skip client socket polling (server→client only)
* param->srv->s_option non-zero: return after first datagram sent to client
* param->ctrlsock TCP control socket from the client; INVALID_SOCKET if none. * param->ctrlsock TCP control socket from the client; INVALID_SOCKET if none.
*/ */
@ -207,7 +206,6 @@ int udpsockmap(struct clientparam *param, int timeo)
sin = cliaddr; sin = cliaddr;
nhops = param->udp_nhops; nhops = param->udp_nhops;
if(param->srv->service == S_UDPPM) nhops++;
udpfds(param, &p); udpfds(param, &p);
for (;;) { for (;;) {
@ -330,7 +328,6 @@ int udpsockmap(struct clientparam *param, int timeo)
ares = udpreconnect(param); ares = udpreconnect(param);
if (param->remsock == INVALID_SOCKET) return ares; if (param->remsock == INVALID_SOCKET) return ares;
nhops = param->udp_nhops; nhops = param->udp_nhops;
if(param->srv->service == S_UDPPM) nhops++;
udpfds(param, &p); udpfds(param, &p);
} }
lastres = ares; lastres = ares;
@ -413,7 +410,6 @@ int udpsockmap(struct clientparam *param, int timeo)
param->srv->so._sendto(param->sostate, param->clisock, param->srv->so._sendto(param->sostate, param->clisock,
(char *)param->srvbuf + sendoff, sendlen, 0, (char *)param->srvbuf + sendoff, sendlen, 0,
(struct sockaddr *)&sin, SASIZE(&sin)); (struct sockaddr *)&sin, SASIZE(&sin));
if (param->srv->s_option && param->srv->service == S_UDPPM) return 0;
} }
if ((p.ctrl >= 0 && p.fds[p.ctrl].revents) || if ((p.ctrl >= 0 && p.fds[p.ctrl].revents) ||