feat(tcp): connect over IPv4 and IPv6, and say why a connect failed

- ClientTCP resolves with AF_UNSPEC and tries every address the name
  resolves to, in getaddrinfo's order, the way Python's
  socket.create_connection does. It used to ask for AF_INET only and keep
  the first answer, so `localhost` could not reach a server listening on
  ::1 alone, which is what a dev server on a recent Node does. The connect
  timeout bounds each address on its own; when none connects, what the
  last one failed with is thrown.
- A failed resolve, socket() or connect throws SocketErrorException: the
  std::runtime_error it always was, with the same message, plus the errno
  (SocketErrorKind::System) or the getaddrinfo code
  (SocketErrorKind::Resolver). A caller that reports the cause in its own
  words, such as a Python-style "[Errno 111] Connection refused", no
  longer has to parse it back out of the text.
- The move constructor carries the resolved addresses along.

New: ShouldConnectDualStack, ShouldReportConnectErrors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 05:06:41 +01:00
commit 78853033e8
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
6 changed files with 383 additions and 51 deletions

View file

@ -174,6 +174,8 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
if (cfg.target.find("linux") != std::string::npos) {
cfg.AddTest("ShouldConfigureTCP").Dependencies({ &cfg });
cfg.AddTest("ShouldRecieveTCP").Dependencies({ &cfg });
cfg.AddTest("ShouldConnectDualStack").Dependencies({ &cfg });
cfg.AddTest("ShouldReportConnectErrors").Dependencies({ &cfg });
cfg.AddTest("ShouldInteropCurlHTTP1").Dependencies({ &cfg });
cfg.AddTest("ShouldInteropCurlHTTPS1").Dependencies({ &cfg });
cfg.AddTest("ShouldParseHTTP1").Dependencies({ &cfg });