Jens Geyer created THRIFT-6191:
----------------------------------
Summary: C++ server sockets and TSocket resolve the same host with
different getaddrinfo flags
Key: THRIFT-6191
URL: https://issues.apache.org/jira/browse/THRIFT-6191
Project: Thrift
Issue Type: Bug
Components: C++ - Library
Reporter: Jens Geyer
h2. What happens
{{TServerSocket::listen()}} and {{TNonblockingServerSocket::listen()}} resolve
the bind address
with {{AI_PASSIVE | AI_V4MAPPED}}. {{TSocket::open()}} resolves the connect
address with
{{AI_PASSIVE | AI_ADDRCONFIG}}.
On a host where {{/etc/hosts}} lists {{::1 localhost}} but no IPv6 address is
configured on
{{lo}} -- a default Docker container, for instance -- the two disagree about
what
{{"localhost"}} means:
||Caller||Flags||First address returned||
|{{TServerSocket::listen()}}|{{AI_PASSIVE}} + {{AI_V4MAPPED}}|{{::1}}, and the
bind succeeds|
|{{TSocket::open()}}|{{AI_PASSIVE}} + {{AI_ADDRCONFIG}}|{{127.0.0.1}}|
A C++ server bound to {{"localhost"}} therefore listens on {{::1}} while a C++
client connecting
to {{"localhost"}} on the same machine dials {{127.0.0.1}}, and the connect
fails with
{{ECONNREFUSED}}.
The {{IPV6_V6ONLY=0}} that {{listen()}} sets on an {{AF_INET6}} socket does not
rescue it: the
bind is to {{::1}} specifically rather than to {{::}}, and a v4-mapped address
is not covered by
that.
h2. Where it shows
{{lib/cpp/test/TServerSocketTest.cpp}}'s {{test_bind_to_address}} fails in
exactly this
environment, and has nothing to do with the test itself:
{noformat}
unknown location(0): fatal error: in "TServerSocketTest/test_bind_to_address":
apache::thrift::transport::TTransportException: connect() failed: Connection
refused
{noformat}
CI does not catch it. GitHub runners have {{::1}} on {{lo}}, so
{{AI_ADDRCONFIG}} returns IPv6
for the client as well and both sides agree again.
h2. Measured
A standalone probe that resolves and binds with each flag set and then
connects, run in a
container with no IPv6 address on {{lo}}:
{noformat}
server pre-0.14.0 (AI_PASSIVE|AI_ADDRCONFIG) server binds IPv4, client tries
IPv4 -> CONNECTED
server today (AI_PASSIVE|AI_V4MAPPED) server binds IPv6, client tries
IPv4 -> Connection refused
server today, without IPV6_V6ONLY=0 server binds IPv6, client tries
IPv4 -> Connection refused
{noformat}
h2. When it changed
Last worked in 0.13.0. THRIFT-5186 removed {{AI_ADDRCONFIG}} from the server
sockets in
{{9b9567b23}} (2020-04-27), first released in 0.14.0. That change was right on
its own terms --
{{AI_ADDRCONFIG}} does not count the loopback address as a configured address,
so a host with no
other address could not resolve localhost at all. The client side kept the
flag, and the
asymmetry is what breaks.
THRIFT-5880 ({{25202e1b0}}, 0.23.0) later gave {{TSocket::open()}} a retry
without
{{AI_ADDRCONFIG}}, but it is conditional on {{EAI_NODATA}} / {{EAI_ADDRFAMILY}}
and does not fire
here, because the first call succeeds.
h2. Possible directions
Not decided, and worth discussing before anyone writes a patch:
* bind {{::}} rather than the first resolved address, keeping {{IPV6_V6ONLY=0}};
* try every resolved address instead of stopping at the first that binds;
* drop {{AI_ADDRCONFIG}} on the client too, matching what THRIFT-5186 did for
the server.
{{TServerSocket}} and {{TNonblockingServerSocket}} carry the same line and
would need the same
treatment.
_Filed with AI assistance (Claude Opus 5); reviewed and filed by Jens Geyer._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)