3proxy/src/udpsockmap.c
Vladimir Dubrovin fa3807c6f1 Builds: no Open Watcom, no build without IPv6, no select()
The Win32 "lite" build with Open Watcom (Makefile.watcom, its release
workflow, the CMake branch for it) was the only one built without IPv6
(NOIPV6), without fibers and event loops, with a select() of its own
for poll() (mypoll()), and with the calling conventions of Watcom's
pragmas in the plugins. They are gone with it: every build has IPv6,
and polls with poll() on Unix and WSAPoll() on Windows, which were
what every other build asked for (WITH_POLL, WITH_WSAPOLL, the
3PROXY_USE_POLL and 3PROXY_USE_WSAPOLL options of CMake, FD_SETSIZE
for select()). getip(), the IPv4-only resolver of NOIPV6, goes too.

Plugin binary compatibility: plugins built with Open Watcom, and their
calling convention, are no longer supported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:54:51 +03:00

414 lines
13 KiB
C

/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
/* space reserved in front of the received datagram to prepend the headers
of the second and the third hop of a SOCKS5 UDP chain */
#define UDPHDROFF 48
int socks5_udp_build_hdr(unsigned char *buf, PROXYSOCKADDRTYPE *addr)
{
buf[0] = buf[1] = buf[2] = 0;
buf[3] = (*SAFAMILY(addr) == AF_INET) ? 1 : 4;
memcpy(buf + 4, SAADDR(addr), SAADDRLEN(addr));
memcpy(buf + 4 + SAADDRLEN(addr), SAPORT(addr), 2);
return 4 + SAADDRLEN(addr) + 2;
}
int socks5_udp_skip_hdr(unsigned char *buf, int len)
{
int addr_len;
int off;
if (len < 4) return -1;
switch (buf[3]) {
case 1: addr_len = 4; break;
case 4: addr_len = 16; break;
case 3:
if (len < 5) return -1;
addr_len = 1 + (unsigned char)buf[4];
break;
default: return -1;
}
off = 4 + addr_len + 2;
return (off <= len) ? off : -1;
}
/*
* udpsockmap: bidirectional UDP relay.
*
* param->udp_nhops selects the relay mode:
* 0 direct SOCKS5 relay (strip/add headers)
* 1 one parent SOCKS5 proxy (pass datagrams unchanged)
* 2 two parent proxies (prepend 1 header / strip 1 header)
* 3 three parent proxies (prepend 2 headers / strip 2 headers)
*
* param->waitserver64 non-zero: skip client socket polling (server→client only)
* param->ctrlsock TCP control socket from the client; INVALID_SOCKET if none.
*/
struct udppoll {
struct pollfd fds[4];
int nfds;
int cli, ctrl, ctrlsrv;
};
static void udpfds(struct clientparam *param, struct udppoll *p)
{
memset(p->fds, 0, sizeof(p->fds));
p->nfds = 0;
p->cli = p->ctrl = p->ctrlsrv = -1;
p->fds[p->nfds].fd = param->remsock; /* always index 0 */
p->fds[p->nfds].events = POLLIN;
p->nfds++;
if (!param->waitserver64) {
p->fds[p->nfds].fd = param->clisock;
p->fds[p->nfds].events = POLLIN;
p->cli = p->nfds++;
}
if (param->ctrlsock != INVALID_SOCKET) {
p->fds[p->nfds].fd = param->ctrlsock;
p->fds[p->nfds].events = POLLIN;
p->ctrl = p->nfds++;
}
if (param->ctrlsocksrv != INVALID_SOCKET) {
p->fds[p->nfds].fd = param->ctrlsocksrv;
p->fds[p->nfds].events = POLLIN;
p->ctrlsrv = p->nfds++;
}
}
static void udplog(struct clientparam *param)
{
unsigned char buf[400];
int len;
if(!param->srv->logfunc) return;
len = sprintf((char *)buf, "UDPMAP ");
if(*param->hostname) len += sprintf((char *)buf + len, "%.256s", param->hostname);
else len += myinet_ntop(*SAFAMILY(&param->req), SAADDR(&param->req), (char *)buf + len, 64);
sprintf((char *)buf + len, ":%hu", ntohs(*SAPORT(&param->req)));
param->srv->logfunc(param, buf);
}
int udpbind(struct clientparam *param)
{
SOCKET s;
SASIZETYPE sasize;
s = nssocket(param->srv, &param->srv->so, param->sostate, NS_OUT, SASOCK(&param->sinsl), SOCK_DGRAM, IPPROTO_UDP);
if (s == INVALID_SOCKET) return 11;
#ifdef _WIN32
{ unsigned long ul = 1; ioctlsocket(s, FIONBIO, &ul); }
#else
fcntl(s, F_SETFL, O_NONBLOCK | fcntl(s, F_GETFL));
#endif
param->remsock = s;
if (extbindtodevice(param, param->remsock)) {
param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = INVALID_SOCKET;
return 12;
}
if (bindwithrange(param, param->remsock, &param->sinsl, param->extport)) {
if (param->extport) {
param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = INVALID_SOCKET;
return 12;
}
*SAPORT(&param->sinsl) = 0;
if (param->srv->so._bind(param->sostate, param->remsock,
(struct sockaddr *)&param->sinsl, SASIZE(&param->sinsl))) {
param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = INVALID_SOCKET;
return 12;
}
}
sasize = sizeof(param->sinsl);
param->srv->so._getsockname(param->sostate, param->remsock,
(struct sockaddr *)&param->sinsl, &sasize);
return 0;
}
static void udpreset(struct clientparam *param)
{
param->redirected = 0;
param->udp_nhops = 0;
memset(param->udp_relay, 0, sizeof(param->udp_relay));
param->sinsl = *SAFAMILY(&param->req) == AF_INET6? param->srv->extsa6 : param->srv->extsa;
param->sinsr = param->req;
}
/*
* Authorize param->req and (re)build the server side of the association:
* a parent proxy chain and/or an external address may be selected by the ACL.
* Returns the authorization result. param->remsock is INVALID_SOCKET if the
* socket can not be created, in this case the association can not continue.
*/
static int udpreconnect(struct clientparam *param)
{
int res;
if (param->ctrlsocksrv != INVALID_SOCKET) {
param->srv->so._closesocket(param->sostate, param->ctrlsocksrv);
param->ctrlsocksrv = INVALID_SOCKET;
}
if (param->remsock != INVALID_SOCKET) {
param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = INVALID_SOCKET;
}
udpreset(param);
res = (*param->srv->authfunc)(param);
if (res) {
if (param->ctrlsocksrv != INVALID_SOCKET) {
param->srv->so._closesocket(param->sostate, param->ctrlsocksrv);
param->ctrlsocksrv = INVALID_SOCKET;
}
if (param->remsock != INVALID_SOCKET) {
param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = INVALID_SOCKET;
}
udpreset(param);
}
if (udpbind(param)) return res? res : 11;
return res;
}
int udpsockmap(struct clientparam *param, int timeo)
{
PROXYSOCKADDRTYPE sin;
PROXYSOCKADDRTYPE cliaddr;
PROXYSOCKADDRTYPE from;
PROXYSOCKADDRTYPE lastdst;
struct udppoll p;
SASIZETYPE sasize;
int len, res, nhops;
int firstpacket = 1;
int havedst = 0, lastres = 0;
char lastname[256] = "";
struct ace *lastace = NULL;
cliaddr = param->sincr;
if(param->ctrlsock != INVALID_SOCKET){
sasize = sizeof(cliaddr);
param->srv->so._getpeername(param->sostate, param->ctrlsock, (struct sockaddr *)&cliaddr, &sasize);
}
sin = cliaddr;
nhops = param->udp_nhops;
udpfds(param, &p);
for (;;) {
res = sockpoll(&param->srv->so, param->sostate, p.fds, p.nfds, timeo * 1000);
if (res < 0) return 481;
if (res == 0) return 92;
/* a reload: the session goes by the newest configuration's rules,
as a TCP connection does in sockmap() */
if (param->version < runconf->version) {
if (param->srv->noforce || !reattachfunc) param->version = runconf->version;
else if ((len = (*reattachfunc)(param, 1)) && len != 2) return len;
}
/* datagram from client */
if (p.cli >= 0 && p.fds[p.cli].revents) {
unsigned char *base = param->srvbuf + UDPHDROFF;
PROXYSOCKADDRTYPE dst;
char dstnamebuf[256];
char *dstname = NULL;
int i, k, w, off;
sasize = sizeof(sin);
len = param->srv->so._recvfrom(param->sostate, param->clisock,
(char *)base, UDPBUFSIZE - UDPHDROFF,
0, (struct sockaddr *)&sin, &sasize);
if (len < 0 && (errno == EAGAIN || errno == EINTR)) continue;
if (len <= 0) return 482;
if (SAADDRLEN(&sin) != SAADDRLEN(&cliaddr) ||
memcmp(SAADDR(&sin), SAADDR(&cliaddr), SAADDRLEN(&sin)))
continue;
if (firstpacket) {
if (!SAISNULL(&param->req) && *SAPORT(&param->req) &&
SAADDRLEN(&param->req) == SAADDRLEN(&sin) &&
!memcmp(SAADDR(&param->req), SAADDR(&sin), SAADDRLEN(&param->req)) &&
memcmp(SAPORT(&param->req), SAPORT(&sin), 2))
continue;
cliaddr = sin;
firstpacket = 0;
} else if (memcmp(SAPORT(&sin), SAPORT(&cliaddr), 2)) {
continue;
}
if(param->bandlimfunc && (*param->bandlimfunc)(param, 0, len)) continue;
if (len < 10 || base[0] || base[1] || base[2]) return 483;
memset(&dst, 0, sizeof(dst));
switch (base[3]) {
case 1:
if (socks5_setaddr(param->srv->family, 1, base + 4, &dst)) continue;
i = 8;
break;
case 4:
if (len < 22) return 484;
if (socks5_setaddr(param->srv->family, 4, base + 4, &dst)) continue;
i = 20;
break;
case 3: {
int sz = base[4];
if (len < 7 + sz) return 485;
memcpy(dstnamebuf, base + 5, sz);
dstnamebuf[sz] = 0;
dstname = dstnamebuf;
i = 5 + sz;
if (!getip46m(param->srv->resolvmode, param->srv->family, (unsigned char *)dstnamebuf,
(struct sockaddr *)&dst)) {
if (!nhops) return 100;
memset(&dst, 0, sizeof(dst));
*SAFAMILY(&dst) = AF_INET;
}
break;
}
default: return 997;
}
/* As for TCP requests, a literal address is the host name,
otherwise an ACL with host names only matches any address */
if (!dstname) {
myinet_ntop(*SAFAMILY(&dst), SAADDR(&dst), dstnamebuf, sizeof(dstnamebuf));
dstname = dstnamebuf;
}
memcpy(SAPORT(&dst), base + i, 2);
i += 2;
if (!havedst
|| SAADDRLEN(&lastdst) != SAADDRLEN(&dst)
|| memcmp(SAADDR(&lastdst), SAADDR(&dst), SAADDRLEN(&dst))
|| memcmp(SAPORT(&lastdst), SAPORT(&dst), 2)
|| strncmp(lastname, dstname? dstname : "", sizeof(lastname) - 1)) {
int ares = 0, reconnect = 0;
if ((param->srv->udpauth & 1) && havedst && !lastres) {
PROXYSOCKADDRTYPE newdst = dst;
param->req = lastdst;
if (PARAMSTR(param, hostname, lastname)) *param->hostname = 0;
udplog(param);
dst = newdst;
}
if (PARAMSTR(param, hostname, dstname)) *param->hostname = 0;
param->req = dst;
if (!nhops) param->sinsr = dst;
if (!havedst) reconnect = 1;
else if (!nhops && *SAFAMILY(&dst) != *SAFAMILY(&param->sinsl)) reconnect = 1;
if (havedst && (param->srv->udpauth & 2) && !param->dstindep) {
param->preauth = 2;
ares = (*param->srv->authfunc)(param);
param->preauth = 0;
if (ares == 2) {
if (param->lastace != lastace) reconnect = 1;
ares = 0;
}
else if (!ares && (param->redirected ||
(lastace && lastace->chains))) reconnect = 1;
}
if (reconnect) {
ares = udpreconnect(param);
if (param->remsock == INVALID_SOCKET) return ares;
nhops = param->udp_nhops;
udpfds(param, &p);
}
lastres = ares;
lastdst = dst;
strncpy(lastname, dstname? dstname : "", sizeof(lastname) - 1);
lastname[sizeof(lastname) - 1] = 0;
havedst = 1;
if (!ares) lastace = param->lastace;
else if (param->srv->udpauth & 1) {
param->res = ares;
udplog(param);
param->res = 0;
}
}
if (lastres) continue;
if (!nhops) {
if (len > i) {
param->srv->so._sendto(param->sostate, param->remsock,
(char *)base + i, len - i, 0,
(struct sockaddr *)&param->sinsr, SASIZE(&param->sinsr));
param->statscli64 += (len - i);
param->nwrites++;
}
} else {
off = 0;
for (k = 1; k < nhops; k++)
off += 4 + (int)SAADDRLEN(&param->udp_relay[k]) + 2;
for (k = 1, w = UDPHDROFF - off; k < nhops; k++)
w += socks5_udp_build_hdr(param->srvbuf + w, &param->udp_relay[k]);
param->srv->so._sendto(param->sostate, param->remsock,
(char *)param->srvbuf + (UDPHDROFF - off), off + len, 0,
(struct sockaddr *)&param->udp_relay[0], SASIZE(&param->udp_relay[0]));
param->statscli64 += len;
param->nwrites++;
}
}
/* datagram from server / parent relay */
if (p.fds[0].revents) {
int hdrsize = (nhops == 0) ? 4 + (int)SAADDRLEN(&param->sinsl) + 2 : 0;
int sendoff = 0, sendlen;
sasize = sizeof(from);
len = param->srv->so._recvfrom(param->sostate, param->remsock,
(char *)param->srvbuf + hdrsize, UDPBUFSIZE - hdrsize, 0,
(struct sockaddr *)&from, &sasize);
if (len < 0 && (errno == EAGAIN || errno == EINTR)) continue;
if (len <= 0) return 486;
if (nhops >= 1) {
if (!SAISNULL(&param->sinsr) && *SAPORT(&param->sinsr)) {
if (SAADDRLEN(&from) != SAADDRLEN(&param->sinsr) ||
memcmp(SAADDR(&from), SAADDR(&param->sinsr), SAADDRLEN(&from)) ||
memcmp(SAPORT(&from), SAPORT(&param->sinsr), 2))
continue;
}
}
param->statssrv64 += len;
param->nreads++;
if(param->bandlimfunc && (*param->bandlimfunc)(param, len, 0)) continue;
sendlen = len;
if (nhops == 0) {
param->srvbuf[0] = param->srvbuf[1] = param->srvbuf[2] = 0;
param->srvbuf[3] = (*SAFAMILY(&from) == AF_INET) ? 1 : 4;
memcpy(param->srvbuf + 4, SAADDR(&from), SAADDRLEN(&param->sinsl));
memcpy(param->srvbuf + 4 + SAADDRLEN(&param->sinsl), SAPORT(&from), 2);
sendlen = len + hdrsize;
} else if (nhops >= 2) {
int off = 0, k;
int bad = 0;
for (k = 1; k < nhops; k++) {
int next = socks5_udp_skip_hdr(param->srvbuf + off, len - off);
if (next < 0) { bad = 1; break; }
off += next;
}
if (bad) continue;
sendoff = off;
sendlen = len - off;
}
if (sendlen > 0)
param->srv->so._sendto(param->sostate, param->clisock,
(char *)param->srvbuf + sendoff, sendlen, 0,
(struct sockaddr *)&sin, SASIZE(&sin));
}
if ((p.ctrl >= 0 && p.fds[p.ctrl].revents && sockreadable(param->ctrlsock)) ||
(p.ctrlsrv >= 0 && p.fds[p.ctrlsrv].revents && sockreadable(param->ctrlsocksrv))) return 0;
}
return 0;
}