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)

Reply via email to