diff --git a/.github/workflows/c-cpp-Windows.yml b/.github/workflows/c-cpp-Windows.yml index 3bfde5c..41ec3f6 100644 --- a/.github/workflows/c-cpp-Windows.yml +++ b/.github/workflows/c-cpp-Windows.yml @@ -28,7 +28,9 @@ jobs: LDFLAGS: '-L "c:/msys64/mingw64/lib"' CFLAGS: '-I "c:/msys64/mingw64/include"' - name: regression tests (MinGW) - run: python tests\run.py --bin bin\3proxy.exe + run: | + $env:PATH = "c:\msys64\mingw64\bin;$env:PATH" + python tests\run.py --bin bin\3proxy.exe - name: make clean Windows run: make -f Makefile.win clean - name: Add msbuild to PATH @@ -42,5 +44,6 @@ jobs: set "LIB=%LIB%;c:/vcpkg/installed/x64-windows-static/lib;c:/vcpkg/installed/x64-windows/lib" set "INCLUDE=%INCLUDE%;c:/vcpkg/installed/x64-windows-static/include;c:/vcpkg/installed/x64-windows/include" nmake /F Makefile.msvc WOLFSSL=1 || exit /b 1 + set "PATH=%PATH%;c:/vcpkg/installed/x64-windows/bin" python tests\run.py --bin bin\3proxy.exe || exit /b 1 nmake /F Makefile.msvc clean diff --git a/man/3proxy.cfg.5 b/man/3proxy.cfg.5 index b74fe9a..57e7615 100644 --- a/man/3proxy.cfg.5 +++ b/man/3proxy.cfg.5 @@ -871,7 +871,7 @@ with probability of 0.7) for outgoing web connections. Chains are only applied t .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. .br - \fBextport\fR does not redirect the request; it sets the range the local port of outgoing connections is taken from, given as \fIFIRST-LAST\fR inclusive in place of the port argument, with 0.0.0.0 as the address, for example \fBparent 1000 extport 0.0.0.0 40000-40100\fR. Where the system can be asked to pick the port itself (Linux \fBIP_LOCAL_PORT_RANGE\fR) it does, otherwise a port is picked at random from the range and retried if it is already in use, up to ten times. It can be chained with another parent type, and the access rule it belongs to decides which requests it applies to, so \fBallow * * * * UDPASSOC\fR followed by \fBparent 1000 extport 0.0.0.0 40000-40100\fR limits it to UDP associations. The range is applied when the outgoing connection is made, so a kept alive connection carrying several requests uses the rule that matched when it was opened. + \fBextport\fR does not redirect the request; it sets the range the local port of outgoing connections is taken from, given as \fIFIRST-LAST\fR inclusive in place of the port argument, with 0.0.0.0 as the address, for example \fBparent 1000 extport 0.0.0.0 40000-40100\fR. Where the system can be asked to pick the port itself (Linux \fBIP_LOCAL_PORT_RANGE\fR) it does, otherwise a port is picked at random from the range and retried if it is already in use, up to ten times. On Linux the range has to lie within \fInet.ipv4.ip_local_port_range\fR, commonly 32768-60999: the kernel ignores a range outside it and picks an ordinary ephemeral port instead. If no port in the range can be bound, an ephemeral port is used rather than failing the connection. It can be chained with another parent type, and the access rule it belongs to decides which requests it applies to, so \fBallow * * * * UDPASSOC\fR followed by \fBparent 1000 extport 0.0.0.0 40000-40100\fR limits it to UDP associations. The range is applied when the outgoing connection is made, so a kept alive connection carrying several requests uses the rule that matched when it was opened. .br \fBintport\fR is the same for sockets bound on the side facing the client: the port a UDP association tells the client to send its datagrams to, and the FTP proxy data connection. .br diff --git a/src/common.c b/src/common.c index 2857205..2ad41d2 100644 --- a/src/common.c +++ b/src/common.c @@ -182,7 +182,7 @@ int timeouts[12] = { EINVAL below it and the thread silently gets the 8M system default stack. */ size_t threadstacksize(int extra){ - long size = BASESTACKSIZE + extra; + long size = BASESTACKSIZE + TLSSTACKSIZE + extra; if(size < (long)PTHREAD_STACK_MIN) size = (long)PTHREAD_STACK_MIN; return (size_t)size; @@ -803,8 +803,11 @@ int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, if(!param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa))) return 0; } + /* Every port tried was taken. Fall back to an ephemeral one, which is + what the kernel option above does when it cannot honour the range, so + an exhausted range behaves the same way on every platform. */ *SAPORT(sa) = 0; - return -1; + return param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa)); } /* Host lists in access rules have always accepted name, name*, *name and diff --git a/src/conf.c b/src/conf.c index 787c93b..0b271e7 100644 --- a/src/conf.c +++ b/src/conf.c @@ -157,7 +157,12 @@ int start_proxy_thread(struct child * chp){ pthread_attr_init(&pa); pthread_attr_setstacksize(&pa,threadstacksize(conf.stacksize)); pthread_attr_setdetachstate(&pa,PTHREAD_CREATE_DETACHED); - pthread_create(&thread, &pa, startsrv, (void *)chp); + if(pthread_create(&thread, &pa, startsrv, (void *)chp)){ + pthread_attr_destroy(&pa); + fprintf(stderr, "Failed to create service thread on line %d, try to set larger stacksize\n", linenum); + _3proxy_sem_unlock(conf.threadinit); + return(40); + } pthread_attr_destroy(&pa); #endif _3proxy_sem_lock(conf.threadinit); diff --git a/src/proxy.h b/src/proxy.h index 92c03bf..8aa2b3f 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -135,6 +135,23 @@ void daemonize(void); #endif #endif +/* wolfSSL reserves around 48K of static thread-local storage. glibc counts + that against the thread stack, so pthread_create() fails with EINVAL and + no thread starts at all. musl places the block next to the stack instead + of inside it and needs nothing extra, and OpenSSL has no static TLS. + musl identifies itself by no macro of its own, but it does not define + __GLIBC__, which any libc header pulled in above would have set. + */ +#ifndef TLSSTACKSIZE +#if defined(__linux__) && !defined(__GLIBC__) +#define TLSSTACKSIZE 0 +#elif defined(WITH_WOLFSSL) +#define TLSSTACKSIZE 49152 +#else +#define TLSSTACKSIZE 0 +#endif +#endif + #ifndef _WIN32 size_t threadstacksize(int extra); #endif diff --git a/tests/harness.py b/tests/harness.py index dd64c73..a0eabf7 100644 --- a/tests/harness.py +++ b/tests/harness.py @@ -134,8 +134,15 @@ class Tester: for port in ports: if not self.wait_port(port): + code = proc.poll() + if code is None: + died = "the process is still running" + else: + died = f"the process exited with code {code}" + if os.name == "nt" and code is not None and code & 0xFFFFFFFF == 0xC0000135: + died += " (a DLL it needs was not found)" raise Failure( - f"{name} never listened on port {port}\n" + f"{name} never listened on port {port}: {died}\n" f"--- configuration ---\n{open(path).read()}" f"--- output ---\n{server.output()}") return server