Skip to content

Fix/tcp ip literal in TCP.connect() and SSL.Connect() - #8

Open
neutrino38 wants to merge 4 commits into
witchtails:masterfrom
neutrino38:fix/tcp-ip-literal
Open

neutrino38 wants to merge 4 commits into
witchtails:masterfrom
neutrino38:fix/tcp-ip-literal

Conversation

@neutrino38

@neutrino38 neutrino38 commented Aug 31, 2026 •

Copy link
Copy Markdown

Depends on PR#7 (Socket.Address.parse/1 on an IPv6 reference).

Socket.TCP.connect/3 takes an address in every form Socket.Address.t()
covers: a tuple, a literal such as "::1" or "127.0.0.1", the bracketed
"[::1]", or a host name.

A literal reaches :gen_tcp.connect/4 as a tuple. The tuple carries its own
family, so the call needs no resolution at all and cannot land in the wrong one.
Only a real host name travels as a charlist to the resolver.

Socket.SSL.connect/3 gets the same treatment, in one line.

The @spec of Socket.TCP.connect/3 and connect!/3 now reads
Socket.Address.t(): the type the code accepts.

Tests

Two tests in test/ipv6_test.exs, both against a listener bound on ::1. Both
fail without the change.

A URI authority and a Host header carry an IPv6 literal inside square
brackets (RFC 3986 §3.2.2), and that is the form a caller holding an
address taken from a URI has in hand. parse/1 answered nil on it, so
every such caller had to strip the brackets itself, and the one who
forgot dialed the wrong address family with no diagnostic.

Only a v6 address is written that way: "[127.0.0.1]" stays nil, and so
does a bracket that never closes.
connect/3 turned every binary into a charlist and handed it to
:gen_tcp.connect/4, which resolves it as a name in the socket's family.
"::1" therefore came back :nxdomain on a socket nobody had declared
:inet6, while a tuple carries its own family. Parse first, and fall back
to a charlist only for what really is a host name.

The @SPEC already announced :inet.ip_address(), which the code could not
handle. It now says Socket.Address.t(), which it does.
connect/3 carried the same charlist conversion as Socket.TCP.connect/3,
with the same effect: the literal went to the resolver instead of to the
socket.

Its test reads a failure rather than a success — no TLS server answers on
the other end — because :nxdomain is what the resolution path returns and
a handshake timeout is not.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant