Fix: insufficient default stack for wolfSSL with glibc

This commit is contained in:
Vladimir Dubrovin 2026-08-25 19:07:20 +03:00
parent a0ae86957f
commit cb9effab9b
6 changed files with 41 additions and 6 deletions

View File

@ -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

View File

@ -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<ip>\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

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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