THRIFT-6191: Resolve server bind addresses with the same flags TSocket uses - #3836
THRIFT-6191: Resolve server bind addresses with the same flags TSocket uses#3836Jens-G wants to merge 3 commits into
Conversation
…t uses Client: cpp TServerSocket::listen() and TNonblockingServerSocket::listen() resolved the bind address with AI_PASSIVE|AI_V4MAPPED, while TSocket::open() resolves the connect address with AI_PASSIVE|AI_ADDRCONFIG. AI_ADDRCONFIG does not count a loopback address as a configured one, so on a host that carries ::1 on lo and no other IPv6 address -- a default Docker container, for instance -- the two disagree about what "localhost" means: the server binds ::1 and the client dials 127.0.0.1, and the connect fails with ECONNREFUSED. The IPV6_V6ONLY=0 the server sets does not help, because the bind is to ::1 rather than to ::. Last worked in 0.13.0. THRIFT-5186 removed AI_ADDRCONFIG from the server sockets in 9b9567b so that a host with no configured address could still resolve localhost, and gave TSocket::open() a retry without the flag for the same case. The client kept the flag; the servers did not, and the asymmetry is what breaks. THRIFT-5880 later widened that client retry to EAI_ADDRFAMILY, but it is conditional on resolution failing and does not fire here. Resolve on the server with AI_ADDRCONFIG as well, and fall back to resolving without it when that leaves nothing to bind -- the same shape TSocket::open() already has, so both sides agree again while THRIFT-5186's case keeps working. The fallback is load-bearing rather than defensive: an explicit "::1" fails with EAI_ADDRFAMILY under AI_ADDRCONFIG on such a host. The ANDROID branch is now redundant and goes away; Android already passed AI_ADDRCONFIG and only gains the fallback, since AI_V4MAPPED is ignored for AF_UNSPEC queries. lib/cpp/test/TServerSocketTest.cpp's test_bind_to_address already covered this for TServerSocket and failed in such a container; TNonblockingServerSocket had no equivalent coverage and now has one. Both were confirmed to fail against the unmodified library before the fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Code reviewFound 1 issue:
thrift/lib/cpp/src/thrift/transport/TServerSocket.cpp Lines 456 to 462 in 00132b1 thrift/lib/cpp/src/thrift/transport/TNonblockingServerSocket.cpp Lines 384 to 390 in 00132b1 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
Client: cpp The previous commit resolved the bind address with AI_PASSIVE|AI_V4MAPPED|AI_ADDRCONFIG and fell back to AI_PASSIVE|AI_V4MAPPED, on the grounds that AI_V4MAPPED is ignored for AF_UNSPEC queries. That holds for glibc but not for Android: bionic's getaddrinfo() fails any flag outside its AI_MASK, which does not include AI_V4MAPPED, with EAI_BADFLAGS before it looks at the family. With the ANDROID branch gone, both attempts were rejected there and every TCP server socket's listen() threw "Could not resolve host for server socket." -- the case ab72ebe had worked around. Resolve with exactly the flags TSocket::open() uses instead, AI_PASSIVE|AI_ADDRCONFIG and then AI_PASSIVE alone. On glibc nothing changes -- both flag sets give the same addresses for "localhost", "::1", "127.0.0.1" and the wildcard -- and on Android the first attempt is what the ANDROID branch passed, so it resolves as before and only gains the fallback. AddressResolutionHelper's default flags carried the same AI_V4MAPPED. Nothing in the library relies on the default, but the header is installed, so it now defaults to AI_ADDRCONFIG alone. TServerExceptionTest.cpp explained its fixed 127.0.0.1 with the servers' old flags; that comment no longer describes the library and is reworded. Nothing in CI builds for Android, so bionic's flag check was emulated with an LD_PRELOAD getaddrinfo() that rejects flags outside its AI_MASK. Before this commit TNonblockingServerTest and TServerExceptionTest fail under it; after it they pass, and so does TServerSocketTest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0167dH5uDP18NpE5dNpnbzfK
|
Re the review above (#3836 (comment)): confirmed, and fixed in 0b4335f. Both server sockets now resolve with exactly Verified by emulating bionic's check with an The first commit's message still gives the wrong reason for dropping the |
…needs Client: cpp 405a95f (apache#3737) made the build define AI_V4MAPPED as 0 where the platform does not declare it, so that TServerSocket, TNonblockingServerSocket and AddressResolutionHelper compile on OpenBSD, which has no AI_V4MAPPED at all. The previous commit took the flag out of all three, so nothing in the tree refers to the symbol any more; the checks in configure.ac and ConfigureChecks.cmake and the define in config.h.in would only put an unused AI_V4MAPPED into the installed thrift/config.h. Remove them. 405a95f is not in a release yet, so no released config.h ever defined it. The AI_ADDRCONFIG fallback that 405a95f replaced does not come back either: TSocket::open() has used AI_ADDRCONFIG without one all along. On OpenBSD the flags passed to getaddrinfo() stay what they were with AI_V4MAPPED defined as 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0167dH5uDP18NpE5dNpnbzfK
|
@brad0 — a heads-up, since this touches what #3737 just fixed. This PR takes I can't build on OpenBSD here — could you check that this branch still builds for you, with CMake and/or autotools? |
The defect
TServerSocket::listen()andTNonblockingServerSocket::listen()resolve the bind address withAI_PASSIVE|AI_V4MAPPED;TSocket::open()resolves the connect address withAI_PASSIVE|AI_ADDRCONFIG.AI_ADDRCONFIGdoes not count a loopback address as a configured one. So on a host that carries::1onloand has no other IPv6 address — a default Docker container, for instance — the two disagree about what"localhost"means:TServerSocket::listen()AI_PASSIVE|AI_V4MAPPED::1, and the bind succeedsTSocket::open()AI_PASSIVE|AI_ADDRCONFIG127.0.0.1A C++ server on
"localhost"therefore listens on::1while a C++ client on"localhost"dials127.0.0.1, and the connect fails withECONNREFUSED. TheIPV6_V6ONLY=0the server sets does not rescue it: the bind is to::1specifically, not to::.Measured in such a container, resolving with each flag set:
When it changed
Last worked in 0.13.0. THRIFT-5186 (
9b9567b23, first released in 0.14.0) removedAI_ADDRCONFIGfrom the server sockets so a host with no configured address could still resolve localhost — correct on its own terms. That same commit handled the client differently: it kept the flag onTSocket::open()and instead extended the Windows-only retry-without-it to POSIX. THRIFT-5880 (25202e1b0) later widened that retry toEAI_ADDRFAMILY, but it is conditional on the first resolution failing and does not fire here, because it succeeds.CI never caught it: GitHub runners have
::1onlo, soAI_ADDRCONFIGreturns IPv6 for the client too and both sides agree again.The change
Give the servers the flags and the shape
TSocket::open()already has: resolve with exactly itsAI_PASSIVE|AI_ADDRCONFIG, and fall back toAI_PASSIVEalone when that leaves nothing to bind. Both sides then agree, and THRIFT-5186's case still works via the fallback.The fallback is load-bearing rather than defensive — as the measurement above shows, an explicit
"::1"fails outright withEAI_ADDRFAMILYunderAI_ADDRCONFIGon such a host.That drops
AI_V4MAPPED, deliberately. It does nothing for theAF_UNSPECquery the servers make — resolving with and without it gives the same addresses forlocalhost,::1,127.0.0.1and the wildcard in the container above — and Android's bionic rejects it outright: itsgetaddrinfo()fails any flag outsideAI_MASKwithEAI_BADFLAGSbefore it looks at the family (getaddrinfo.c, netdb.h). That is what the#ifdef ANDROIDbranch from ab72ebe worked around. With the flags now identical on every platform the branch goes away, and Android resolves exactly as before and only gains the fallback.AddressResolutionHelper's default flags carried the sameAI_V4MAPPED. Nothing in the library relies on that default, butTSocketUtils.his installed, so it now defaults toAI_ADDRCONFIGalone.TServerExceptionTest.cppexplained its fixed127.0.0.1with the servers' old flags; that comment is reworded.With that, nothing in the C++ library uses
AI_V4MAPPEDany more, so the fallback for it goes as well: #3737 had the build define it as0where the platform does not declare it — OpenBSD has noAI_V4MAPPEDat all — and the checks inconfigure.acandbuild/cmake/ConfigureChecks.cmakeand the define inbuild/cmake/config.h.inare removed. #3737 is not in a release yet, so no releasedthrift/config.hever carried that define. On OpenBSD the flags that reachgetaddrinfo()are the same as with #3737.The wildcard bind is unaffected: with an empty address the resolver already returns
0.0.0.0ahead of::, and where IPv6 is configuredAI_ADDRCONFIGdoes not remove it.Tests
TServerSocketTest/test_bind_to_addressalready covered this forTServerSocketand fails in such a container.TNonblockingServerSocketcarried the identical line with no equivalent coverage;TNonblockingServerTest/bind_and_connect_agree_on_hostnameadds it.Both were confirmed to fail against the unmodified library before the fix, with
connect() failed: Connection refused, and pass after it.No CI job builds for Android, so bionic's flag check was emulated in the same container with an
LD_PRELOADgetaddrinfo()that returnsEAI_BADFLAGSfor any flag outside itsAI_MASK:AI_V4MAPPEDstill passedTNonblockingServerTestTServerExceptionTest(usesTServerSocket)Each failure is the same one: both resolve attempts are rejected (
getaddrinfo() -> -1; Bad value for ai_flags) andlisten()throwsCould not resolve host for server socket.With this changeTServerSocketTestpasses under the emulation too, and the only flags that reachgetaddrinfo()areAI_PASSIVE|AI_ADDRCONFIGand, for the unresolvable addressTServerSocketTestfeeds it, theAI_PASSIVEfallback.Full C++ suite in
thrift:jammy, CMake Debug build: 58 of 58ctestpass. One caveat:TSSLSocketMatchNameTestis flaky there with or without this change — it dies withSIGPIPEin 3 of 10 runs on master and 4 of 10 with it — so a single full run can come out 57 of 58. Changed lines areclang-formatclean.(On master that Debug build does not link
UnitTestsorTTransportFactoryConfigTest:TConfiguration::DEFAULT_MAX_MESSAGE_SIZEis ODR-used without an out-of-class definition. For this run a definition was linked in from outside the tree; it is not part of this change.)Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com