From 6ab12d88ca5641bdfd2356a0d9b2a680f7624d1a Mon Sep 17 00:00:00 2001 From: yazhog Date: Mon, 10 Aug 2026 13:00:28 +0300 Subject: [PATCH] feat: scope local port ranges to extip parents --- CMakeLists.txt | 5 +++++ Makefile.Linux | 4 ++++ man/3proxy.cfg.5 | 5 +---- src/common.c | 26 ++++++++++-------------- src/conf.c | 29 ++++++++++++++++++++++++++- src/dnspr.c | 4 ---- src/ftppr.c | 4 +++- src/proxy.h | 7 +++---- src/proxymain.c | 52 +----------------------------------------------- src/redirect.c | 3 +++ src/resolve.c | 4 ---- src/socks.c | 6 +++--- src/structures.h | 9 ++++++--- src/udppm.c | 4 +++- 14 files changed, 70 insertions(+), 92 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b324a1..e219353 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ option(3PROXY_USE_SPLICE "Build Linux splice() support, slower than read/write f option(3PROXY_USE_POLL "Use poll() instead of select() (Unix only)" ON) option(3PROXY_USE_WSAPOLL "Use WSAPoll instead of select() (Windows only)" ON) option(3PROXY_USE_NETFILTER "Enable Linux netfilter support (Linux only)" ON) +option(3PROXY_USE_LOCAL_PORT_RANGE "Enable per-connection local port ranges (Linux 6.3+ only)" ON) option(3PROXY_USE_UNIX_SOCKETS "Enable Unix domain socket support (Unix only)" ON) if(NOT WIN32 AND NOT APPLE) @@ -185,6 +186,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_compile_definitions(WITH_NETFILTER) endif() + if(3PROXY_USE_LOCAL_PORT_RANGE) + add_compile_definitions(WITH_LOCAL_PORT_RANGE) + endif() + if(3PROXY_USE_UNIX_SOCKETS) add_compile_definitions(WITH_UN) endif() diff --git a/Makefile.Linux b/Makefile.Linux index ab6a8a7..0d094bf 100644 --- a/Makefile.Linux +++ b/Makefile.Linux @@ -25,6 +25,10 @@ LDFLAGS += -fno-strict-aliasing -pthread # makefile, including the += above and the STATIC/LIBSTATIC handling below. CFLAGS += $(EXTRA_CFLAGS) LDFLAGS += $(EXTRA_LDFLAGS) +LOCAL_PORT_RANGE ?= true +ifeq ($(LOCAL_PORT_RANGE),true) + CFLAGS += -DWITH_LOCAL_PORT_RANGE +endif DLFLAGS ?= -shared DLSUFFICS = .ld.so # -lpthreads may be reuqired on some platforms instead of -pthreads diff --git a/man/3proxy.cfg.5 b/man/3proxy.cfg.5 index b19f296..94784be 100644 --- a/man/3proxy.cfg.5 +++ b/man/3proxy.cfg.5 @@ -176,9 +176,6 @@ listen on given local HOST:port for incoming connections instead of making remot .B -r\fIHOST\fB:\fIport\fR connect to given remote HOST:port instead of listening local connection on -p or default port. Can be used with another 3proxy service running -R option for connect back functionality. Most commonly used with proxy or socks. HOST can be given as IP or hostname, useful in case of dynamic DNS. .br -.B -Tr\fIFIRST\fB-\fILAST\fR, -Ur\fIFIRST\fB-\fILAST\fR, -UAr\fIFIRST\fB-\fILAST\fR -(Linux) use the inclusive local ephemeral port range FIRST-LAST for outgoing IPv4 TCP (-Tr) or UDP (-Ur) sockets of this service. -UAr applies only to the UDP relay socket returned to a SOCKS5 UDP ASSOCIATE client. Use it when this port range must be forwarded by NAT and allowed by a firewall. Requires kernel support for IP_LOCAL_PORT_RANGE (Linux 6.3 or newer). The option is applied before bind(2); no system-wide sysctl is changed. For example, \fBsocks -p1080 -Ur20000-30000 -UAr30000-31000\fR. -.br .B -oc\fIOPTIONS\fB, -os\fIOPTIONS\fB, -ol\fIOPTIONS\fB, -or\fIOPTIONS\fB, -oR\fIOPTIONS\fR options for proxy-to-client (\fB-oc\fR), proxy-to-server (\fB-os\fR), proxy listening (\fB-ol\fR), connect back client (\fB-or\fR), connect back listening (\fB-oR\fR) sockets. Options like TCP_CORK, TCP_NODELAY, TCP_DEFER_ACCEPT, TCP_QUICKACK, TCP_TIMESTAMPS, TCP_FASTOPEN, SO_REUSEADDR, SO_REUSEPORT, SO_EXCLUSIVEADDRUSE, SO_PORT_SCALABILITY, SO_REUSE_UNICASTPORT, SO_KEEPALIVE, SO_DONTROUTE may be supported depending on OS. @@ -820,7 +817,7 @@ with probability of 0.7) for outgoing web connections. Chains are only applied t .br type is one of: .br - \fBextip\fR does not actually redirect the request; it sets the external address for this request to \fI\fR. It can be chained with another parent type. It's useful to set the external IP based on ACL or make it random. +\fBextip\fR does not actually redirect the request; it sets the external address for this request to \fI\fR. It can be chained with another parent type. It's useful to set the external IP based on ACL or make it random. When built with \fBWITH_LOCAL_PORT_RANGE\fR on Linux 6.3 or newer, \fI\fR may be an inclusive IPv4 local port range \fIFIRST-LAST\fR. The range is applied to outgoing sockets for requests matching this parent; DNS resolver traffic is not affected. For example, \fBparent 1000 extip 192.0.2.10 20000-30000\fR. .br \fBtcp\fR simply redirect connection. TCP is always last in chain. This type of proxy is a simple TCP redirection, it does not support parent authentication. .br diff --git a/src/common.c b/src/common.c index df6a44f..9a77a23 100644 --- a/src/common.c +++ b/src/common.c @@ -10,7 +10,7 @@ #include "proxy.h" -#ifdef __linux__ +#ifdef WITH_LOCAL_PORT_RANGE #ifndef IP_LOCAL_PORT_RANGE #define IP_LOCAL_PORT_RANGE 51 #endif @@ -684,7 +684,9 @@ int doconnect(struct clientparam * param){ } *SAPORT(¶m->sinsl) = 0; setopts(param->remsock, param->srv->srvsockopts); - if(set_local_port_range(param->srv, param->remsock, (struct sockaddr *)¶m->sinsl, LOCAL_PORT_RANGE_TCP)) return 12; +#ifdef WITH_LOCAL_PORT_RANGE + if(set_local_port_range(param, param->remsock, (struct sockaddr *)¶m->sinsl)) return 12; +#endif param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg)); #if defined SO_BINDTODEVICE @@ -730,23 +732,15 @@ int doconnect(struct clientparam * param){ return 0; } -int set_local_port_range(struct srvparam *srv, SOCKET sock, const struct sockaddr *sa, int range_type){ -#ifdef __linux__ - uint32_t range = range_type == LOCAL_PORT_RANGE_TCP ? srv->tcp_local_port_range : - range_type == LOCAL_PORT_RANGE_UDP ? srv->udp_local_port_range : srv->udp_associate_port_range; - - if(range && sa->sa_family == AF_INET && +#ifdef WITH_LOCAL_PORT_RANGE +int set_local_port_range(struct clientparam *param, SOCKET sock, const struct sockaddr *sa){ + if(param->local_port_range && sa->sa_family == AF_INET && ((const struct sockaddr_in *)sa)->sin_port == 0 && - srv->so._setsockopt(srv->so.state, sock, IPPROTO_IP, IP_LOCAL_PORT_RANGE, - (char *)&range, sizeof(range))) return -1; -#else - (void)srv; - (void)sock; - (void)sa; - (void)range_type; -#endif + param->srv->so._setsockopt(param->srv->so.state, sock, IPPROTO_IP, IP_LOCAL_PORT_RANGE, + (char *)¶m->local_port_range, sizeof(param->local_port_range))) return -1; return 0; } +#endif int scanaddr(const unsigned char *s, uint32_t * ip, uint32_t * mask) { unsigned d1, d2, d3, d4, m; diff --git a/src/conf.c b/src/conf.c index 3acb3a0..e1970b6 100644 --- a/src/conf.c +++ b/src/conf.c @@ -838,7 +838,34 @@ static int h_parent(int argc, unsigned char **argv){ *cidr = '/'; chains->cidr = atoi(cidr + 1); } - *SAPORT(&chains->addr) = htons((uint16_t)atoi((char *)argv[4])); + { + char *end; + unsigned long port; + + errno = 0; + port = strtoul((char *)argv[4], &end, 10); +#ifdef WITH_LOCAL_PORT_RANGE + if(chains->type == R_EXTIP && *end == '-') { + unsigned long last; + + errno = 0; + last = strtoul(end + 1, &end, 10); + if(errno || *end || !port || !last || port > last || last > 65535) { + free(chains->exthost); + free(chains); + return 3; + } + chains->local_port_range = ((uint32_t)last << 16) | (uint32_t)port; + port = 0; + } +#endif + if(errno || *end || port > 65535) { + free(chains->exthost); + free(chains); + return 3; + } + *SAPORT(&chains->addr) = htons((uint16_t)port); + } if(argc > 5) chains->extuser = (unsigned char *)strdup((char *)argv[5]); if(argc > 6) chains->extpass = (unsigned char *)strdup((char *)argv[6]); if(!acl->chains) { diff --git a/src/dnspr.c b/src/dnspr.c index afbbf1b..78ab32f 100644 --- a/src/dnspr.c +++ b/src/dnspr.c @@ -149,10 +149,6 @@ void * dnsprchild(struct clientparam* param) { } memset(¶m->sinsl, 0, sizeof(param->sinsl)); *SAFAMILY(¶m->sinsl) = *SAFAMILY(&nservers[0].addr); - if(set_local_port_range(param->srv, param->remsock, (struct sockaddr *)¶m->sinsl, - nservers[0].usetcp ? LOCAL_PORT_RANGE_TCP : LOCAL_PORT_RANGE_UDP)) { - RETURN(819); - } if(param->srv->so._bind(param->sostate, param->remsock,(struct sockaddr *)¶m->sinsl,SASIZE(¶m->sinsl))) { RETURN(819); } diff --git a/src/ftppr.c b/src/ftppr.c index aafea01..2f25ed8 100644 --- a/src/ftppr.c +++ b/src/ftppr.c @@ -121,7 +121,9 @@ void * ftpprchild(struct clientparam* param) { } if ((clidatasock=socket(SASOCK(¶m->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);} *SAPORT(¶m->sincl) = 0; - if(set_local_port_range(param->srv, clidatasock, (struct sockaddr *)¶m->sincl, LOCAL_PORT_RANGE_TCP)){RETURN(822);} +#ifdef WITH_LOCAL_PORT_RANGE + if(set_local_port_range(param, clidatasock, (struct sockaddr *)¶m->sincl)){RETURN(822);} +#endif if(param->srv->so._bind(param->sostate, clidatasock, (struct sockaddr *)¶m->sincl, SASIZE(¶m->sincl))){RETURN(822);} if (pasv) { if(param->srv->so._listen(param->sostate, clidatasock, 1)) {RETURN(823);} diff --git a/src/proxy.h b/src/proxy.h index a4a5083..24b1d9a 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -347,10 +347,9 @@ unsigned char * dologname (unsigned char *buf, unsigned char *name, const unsign int readconfig(FILE * fp); void initcommands(void); int connectwithpoll(struct clientparam *param, SOCKET sock, struct sockaddr *sa, SASIZETYPE size, int to); -#define LOCAL_PORT_RANGE_TCP 1 -#define LOCAL_PORT_RANGE_UDP 2 -#define LOCAL_PORT_RANGE_UDP_ASSOCIATE 3 -int set_local_port_range(struct srvparam *srv, SOCKET sock, const struct sockaddr *sa, int range_type); +#ifdef WITH_LOCAL_PORT_RANGE +int set_local_port_range(struct clientparam *param, SOCKET sock, const struct sockaddr *sa); +#endif uint32_t myrand(void); diff --git a/src/proxymain.c b/src/proxymain.c index 8410f75..e17710d 100644 --- a/src/proxymain.c +++ b/src/proxymain.c @@ -342,7 +342,6 @@ int MODULEMAINFUNC (int argc, char** argv){ "\n" " -iIP ip address or internal interface (clients are expected to connect)\n" " -eIP ip address or external interface (outgoing connection will have this)\n" - " -TrFIRST-LAST, -UrFIRST-LAST, -UArFIRST-LAST Linux: local TCP, UDP or UDP ASSOCIATE ranges\n" " -rHOST:PORT Use IP:port for connect back proxy instead of listen port\n" " -RHOST:PORT Use PORT to listen connect back proxy connection to pass data to\n" " -4 Use IPv4 for outgoing connections\n" @@ -566,35 +565,6 @@ int MODULEMAINFUNC (int argc, char** argv){ srv.needuser = 0; if(*(argv[i] + 2)) srv.needuser = atoi(argv[i] + 2); break; - case 'U': -#ifdef __linux__ - { - char *end; - const char *range_arg; - unsigned long first, last; - uint32_t *range; - - if(argv[i][2] == 'r') { - range_arg = argv[i] + 3; - range = &srv.udp_local_port_range; - } - else if(argv[i][2] == 'A' && argv[i][3] == 'r') { - range_arg = argv[i] + 4; - range = &srv.udp_associate_port_range; - } - else { error = 1; break; } - errno = 0; - first = strtoul(range_arg, &end, 10); - if(errno || end == range_arg || *end != '-') { error = 1; break; } - errno = 0; - last = strtoul(end + 1, &end, 10); - if(errno || *end || !first || !last || first > last || last > 65535) { error = 1; break; } - *range = ((uint32_t)last << 16) | (uint32_t)first; - } -#else - error = 1; -#endif - break; case 'x': srv.nostarttls = 1; break; @@ -617,27 +587,7 @@ int MODULEMAINFUNC (int argc, char** argv){ } break; case 'T': - if(!argv[i][2]) { - srv.transparent = 1; - break; - } -#ifdef __linux__ - { - char *end; - unsigned long first, last; - - if(argv[i][2] != 'r') { error = 1; break; } - errno = 0; - first = strtoul(argv[i] + 3, &end, 10); - if(errno || end == argv[i] + 3 || *end != '-') { error = 1; break; } - errno = 0; - last = strtoul(end + 1, &end, 10); - if(errno || *end || !first || !last || first > last || last > 65535) { error = 1; break; } - srv.tcp_local_port_range = ((uint32_t)last << 16) | (uint32_t)first; - } -#else - error = 1; -#endif + srv.transparent = 1; break; case 'S': srv.stacksize = atoi(argv[i]+2); diff --git a/src/redirect.c b/src/redirect.c index a021874..f7efd22 100644 --- a/src/redirect.c +++ b/src/redirect.c @@ -295,6 +295,9 @@ int handleredirect(struct clientparam * param, struct ace * acentry){ if(!connected){ if(cur->type == R_EXTIP){ param->sinsl = cur->addr; +#ifdef WITH_LOCAL_PORT_RANGE + param->local_port_range = cur->local_port_range; +#endif if(SAISNULL(¶m->sinsl) && (*SAFAMILY(¶m->sincr) == AF_INET || *SAFAMILY(¶m->sincr) == AF_INET6))param->sinsl = param->sincr; #ifndef NOIPV6 else if(cur->cidr && *SAFAMILY(¶m->sinsl) == AF_INET6){ diff --git a/src/resolve.c b/src/resolve.c index 12b5ca4..dbb91fe 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -44,10 +44,6 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_ *SAFAMILY(sinsl) = *SAFAMILY(&nservers[i].addr); } if((sock=so._socket(so.state, SASOCK(sinsl), usetcp?SOCK_STREAM:SOCK_DGRAM, usetcp?IPPROTO_TCP:IPPROTO_UDP)) == INVALID_SOCKET) break; - if(param && set_local_port_range(param->srv, sock, (struct sockaddr *)sinsl, usetcp ? LOCAL_PORT_RANGE_TCP : LOCAL_PORT_RANGE_UDP)) { - so._closesocket(so.state, sock); - break; - } if(so._bind(so.state, sock,(struct sockaddr *)sinsl,SASIZE(sinsl))){ so._shutdown(so.state, sock, SHUT_RDWR); so._closesocket(so.state, sock); diff --git a/src/socks.c b/src/socks.c index e9f8a13..e542f12 100644 --- a/src/socks.c +++ b/src/socks.c @@ -250,8 +250,9 @@ void * sockschild(struct clientparam* param) { } if(command > 1) { - if(set_local_port_range(param->srv, param->remsock, (struct sockaddr *)¶m->sinsl, - command == 2 ? LOCAL_PORT_RANGE_TCP : LOCAL_PORT_RANGE_UDP)) RETURN(12); +#ifdef WITH_LOCAL_PORT_RANGE + if(set_local_port_range(param, param->remsock, (struct sockaddr *)¶m->sinsl)) RETURN(12); +#endif if(param->srv->so._bind(param->sostate, param->remsock,(struct sockaddr *)¶m->sinsl,SASIZE(¶m->sinsl))) { *SAPORT(¶m->sinsl) = 0; if(param->srv->so._bind(param->sostate, param->remsock,(struct sockaddr *)¶m->sinsl,SASIZE(¶m->sinsl)))RETURN (12); @@ -276,7 +277,6 @@ fflush(stderr); #endif sin = param->sincl; *SAPORT(&sin) = 0; - if(set_local_port_range(param->srv, param->clisock, (struct sockaddr *)&sin, LOCAL_PORT_RANGE_UDP_ASSOCIATE)) {RETURN (12);} if(param->srv->so._bind(param->sostate, param->clisock,(struct sockaddr *)&sin,SASIZE(&sin))) {RETURN (12);} sasize = SASIZE(&sin); param->srv->so._getsockname(param->sostate, param->clisock, (struct sockaddr *)&sin, &sasize); diff --git a/src/structures.h b/src/structures.h index 07c03eb..0593b32 100644 --- a/src/structures.h +++ b/src/structures.h @@ -335,6 +335,9 @@ struct chain { unsigned char * extpass; unsigned short weight; unsigned short cidr; +#ifdef WITH_LOCAL_PORT_RANGE + uint32_t local_port_range; +#endif }; struct period { @@ -589,9 +592,6 @@ struct srvparam { unsigned char *udpbuf; unsigned char *udpbuf2; int udplen; - uint32_t tcp_local_port_range; - uint32_t udp_local_port_range; - uint32_t udp_associate_port_range; }; struct clientparam { @@ -669,6 +669,9 @@ struct clientparam { maxtrafout64; PROXYSOCKADDRTYPE sincl, sincr; PROXYSOCKADDRTYPE sinsl, sinsr, req; +#ifdef WITH_LOCAL_PORT_RANGE + uint32_t local_port_range; +#endif uint64_t statscli64, statssrv64; diff --git a/src/udppm.c b/src/udppm.c index 70e7937..025b687 100644 --- a/src/udppm.c +++ b/src/udppm.c @@ -46,7 +46,9 @@ void * udppmchild(struct clientparam* param) { *SAPORT(¶m->sinsl) = 0; param->remsock = param->srv->so._socket(param->srv->so.state, SASOCK(¶m->sinsl), SOCK_DGRAM, IPPROTO_UDP); if(param->remsock == INVALID_SOCKET) { RETURN(202); } - if(set_local_port_range(param->srv, param->remsock, (struct sockaddr *)¶m->sinsl, LOCAL_PORT_RANGE_UDP)) { RETURN(203); } +#ifdef WITH_LOCAL_PORT_RANGE + if(set_local_port_range(param, param->remsock, (struct sockaddr *)¶m->sinsl)) { RETURN(203); } +#endif if(param->srv->so._bind(param->srv->so.state, param->remsock, (struct sockaddr *)¶m->sinsl, SASIZE(¶m->sinsl))) { RETURN(203); } #ifdef _WIN32 { unsigned long ul2 = 1; ioctlsocket(param->remsock, FIONBIO, &ul2); }