NXDOMAIN for AAAA record with Req: no match of right hand side value: {:error, %Req.TransportError{reason: :nxdomain}}

Hello,

I have been working on an app that uses Req to access a REST API at an AAAA domain record. The domain record is resolvable globally, and also present in /etc/hosts. Everything had been working correctly.

A week ago I reinstalled everything due to an SSD failure. I still use the same versions of Elixir and Erlang installed with asdf:

elixir          1.18.2-otp-27
erlang          27.2.2

The dependencies of my project in mix.exs have not changed.

However, I now get this:

** (MatchError) no match of right hand side value: {:error, %Req.TransportError{reason: :nxdomain}}

Why would Req suddenly stop resolving the AAAA domain record? Has anyone seen this happen? Any ideas what I can do to fix this?

Edit: I can resolve the domain just fine with dig AAAA, and ping it. Also, If I hardcode the IPv6 in the URL of the REST API used by Req, it works – so it’s clear that it’s not an issue caused by IPv6 connectivity, but by AAAA domain resolution.

:wave: @waseigo

What if you supply inet6: true as options?

Req.get!("http://ipv6.tlund.se", inet6: true)

More info: Req.Steps — req v0.5.8

Seems there is a similar issue opened in this regard: Non-existing domain IPv6 domains in gen_tcp · Issue #9422 · erlang/otp · GitHub

Are you using http or https?

Using http://, as it’s a self-hosted, internal REST API that I’m accessing.

Thanks for the hint – I might have to adapt my code then to first attempt to resolve the domain as A, if not successful then as AAAA, and then add the option accordingly?

The problem is that the Elixir package I’m using already uses Req internally, but thankfully its init/1 function returns a struct that contains a %Req.Request{} struct that I could modify.

I thought that this would be automatic within Req. Weird that it isn’t, and even weirder that it used to work earlier.

[…] first attempt to resolve the domain as A, if not successful then as AAAA, and then add the option accordingly?

Mint’s inet6: true would attempt that automatically, but in the opposite order: mint/lib/mint/core/transport/tcp.ex at 8a9b8173220372279a07c7606d70790bf10c5523 · elixir-mint/mint · GitHub

1 Like

Thanks! Paging @wojtekmach to get his input.

We could default to inet6 in Req, please send a PR. Wdyt about that approach Ruslan?

Btw, if someone knows how to configure ipv4only. and ipv6only. subdomains for a fly app, please DM me, I’d appreciate your help setting up a server that can be used for testing stuff like this.

:wave: Wojtek

I think it would be interesting to try out :slight_smile:

Some potential problems:

  • Mint’s SSL inet6 → inet fallback can hide IPv6 TLS errors (TLS error while inet6 + unreachable IPv4 result in nxdomain error) but the error is still printed in the logs at [notice] level
Details
# ipv6-only, but bad internal tls
$ tail /etc/hosts
::1	testssl.local

$ cat Caddyfile
:443 {
    tls internal
    respond "Bad TLS Cert Served!"
}

$ docker run --rm -p 443:443 --name caddy -v "$(pwd)/Caddyfile:/etc/caddy/Caddyfile" caddy
iex> Req.get!("https://testssl.local", inet6: true)

11:17:59.210 [notice] TLS :client: In state :hello received SERVER ALERT: Fatal - Internal Error

** (Req.TransportError) non-existing domain
    (req 0.5.8) lib/req.ex:1108: Req.request!/2
    iex:2: (file)
  • slower connections for hosts with misconfigured IPv6 (e.g. bad IPv6 and working IPv4 for a domain in /etc/hosts results in a 5 seconds hang on my laptop by default, IPv4-only connects immediately) but curl is slow too (also takes 5 seconds)
Details
# bad ipv6, good ipv4
$ tail /etc/hosts
2001:db8::deadbeef    testsite.local
127.0.0.1             testsite.local

# starts nginx on ipv4
$ docker run --rm -d -p 80:80 nginx:alpine
iex> :timer.tc fn -> Mint.HTTP1.connect(:http, "testsite.local", 80); :ok end
#==> {2218, :ok}

iex> :timer.tc fn -> Mint.HTTP1.connect(:http, "testsite.local", 80, transport_opts: [inet6: true]); :ok end
#==> {5005768, :ok}
$ time curl http://testsite.local > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   615  100   615    0     0    122      0  0:00:05  0:00:05 --:--:--   129

________________________________________________________
Executed in    5.03 secs      fish           external
   usr time    7.87 millis    0.26 millis    7.61 millis
   sys time   12.49 millis    1.36 millis   11.12 millis

$ time curl -4 http://testsite.local > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   615  100   615    0     0  82694      0 --:--:-- --:--:-- --:--:-- 87857

________________________________________________________
Executed in   27.48 millis    fish           external
   usr time    7.09 millis    0.16 millis    6.93 millis
   sys time    9.02 millis    1.06 millis    7.96 millis

I see, thanks, nevermind then, in that case probably best to wait for happy eyeballs to be implemented somewhere in our stack.