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 76fa4f7..efc0c87 100644 --- a/man/3proxy.cfg.5 +++ b/man/3proxy.cfg.5 @@ -817,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. Its \fI\fR argument is optional and defaults to 0. 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 0c21ff2..cf7ff85 100644 --- a/src/common.c +++ b/src/common.c @@ -10,6 +10,11 @@ #include "proxy.h" +#ifdef WITH_LOCAL_PORT_RANGE +#ifndef IP_LOCAL_PORT_RANGE +#define IP_LOCAL_PORT_RANGE 51 +#endif +#endif char * copyright = COPYRIGHT; @@ -680,6 +685,9 @@ int doconnect(struct clientparam * param){ } *SAPORT(¶m->sinsl) = 0; setopts(param->remsock, param->srv->srvsockopts); +#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 @@ -725,6 +733,16 @@ int doconnect(struct clientparam * param){ return 0; } +#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 && + 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; int res; diff --git a/src/conf.c b/src/conf.c index 3acb3a0..ce33907 100644 --- a/src/conf.c +++ b/src/conf.c @@ -814,6 +814,11 @@ static int h_parent(int argc, unsigned char **argv){ free(chains); return(4); } + if(argc < 5 && chains->type != R_EXTIP) { + fprintf(stderr, "Chaining error: port is required for parent type %s\n", argv[2]); + free(chains); + return(3); + } #ifdef WITH_UN if(!strncmp((char *)argv[3], "unix:", 5)){ make_un(argv[3] + 5, (struct sockaddr_un*)&chains->addr); @@ -838,7 +843,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])); + if(argc > 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) { @@ -1696,7 +1728,7 @@ struct commands commandhandlers[]={ {NULL, "system", h_system, 2, 2}, {NULL, "pidfile", h_pidfile, 2, 2}, {NULL, "monitor", h_monitor, 2, 2}, - {NULL, "parent", h_parent, 5, 0}, + {NULL, "parent", h_parent, 4, 0}, {NULL, "allow", h_ace, 1, 0}, {NULL, "deny", h_ace, 1, 0}, {NULL, "redirect", h_ace, 3, 0}, diff --git a/src/dnspr.c b/src/dnspr.c index d6372e7..78ab32f 100644 --- a/src/dnspr.c +++ b/src/dnspr.c @@ -222,4 +222,3 @@ CLEANRET: #endif return (NULL); } - diff --git a/src/ftppr.c b/src/ftppr.c index fbd2b4f..2f25ed8 100644 --- a/src/ftppr.c +++ b/src/ftppr.c @@ -121,6 +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; +#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 d5b1ec9..24b1d9a 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -347,6 +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); +#ifdef WITH_LOCAL_PORT_RANGE +int set_local_port_range(struct clientparam *param, SOCKET sock, const struct sockaddr *sa); +#endif uint32_t myrand(void); @@ -428,4 +431,3 @@ extern char * ceargv[32]; #define WEBBANNERS 35 #endif - diff --git a/src/proxymain.c b/src/proxymain.c index ec65ca9..e17710d 100644 --- a/src/proxymain.c +++ b/src/proxymain.c @@ -502,8 +502,8 @@ int MODULEMAINFUNC (int argc, char** argv){ } break; case 'N': - if(argv[i][3] == 'e') getip46(46, (unsigned char *)argv[i]+3, (struct sockaddr *)&srv.extNat); - else if(argv[i][3] == 'i') getip46(46, (unsigned char *)argv[i]+3, (struct sockaddr *)&srv.intNat); + if(argv[i][2] == 'e') getip46(46, (unsigned char *)argv[i]+3, (struct sockaddr *)&srv.extNat); + else if(argv[i][2] == 'i') getip46(46, (unsigned char *)argv[i]+3, (struct sockaddr *)&srv.intNat); else getip46(46, (unsigned char *)argv[i]+2, (struct sockaddr *)&srv.extNat); break; case 'n': @@ -1791,5 +1791,3 @@ FILTER_ACTION handledatfltsrv(struct clientparam *cparam, unsigned char ** buf_p } return PASS; } - - 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 3d4eacc..dbb91fe 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -198,4 +198,4 @@ uint32_t fakeresolver (int af, unsigned char *name, unsigned char * value){ value[3] = 2; } return 1; -} \ No newline at end of file +} diff --git a/src/socks.c b/src/socks.c index 1874745..e542f12 100644 --- a/src/socks.c +++ b/src/socks.c @@ -250,6 +250,9 @@ void * sockschild(struct clientparam* param) { } if(command > 1) { +#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); diff --git a/src/structures.h b/src/structures.h index 8d7eeb2..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 { @@ -666,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 6d6b4bb..025b687 100644 --- a/src/udppm.c +++ b/src/udppm.c @@ -46,6 +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); } +#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); }