You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Happy Eyeballs support (address IPv6 issues in Node 17) #41625
The lack of Happy Eyeballs support can result in connections failing. In particular, there are a lot of data points related to similar support lacking from the OkHttp library which is used heavily on Android and results in connectivity issues for a relatively big number of users. Ref: lysine-dev/okhttp#506 (comment)
The problem is that consumer routers sometimes glitch out on IPv6.
A router I manage in one particular location, which is a consumer grade TP-Link router, will routinely hang on IPv6 connections about once per month, until it is restarted. More on this here: lysine-dev/okhttp#6954 (comment)
I have noticed that Node 17 will then pretty much hang on outgoing connections for up to one minute before eventually being able to connect. Restarting the router resolves the issue.
What is the feature you are proposing to solve the problem?
I am not sure what subsystem this would fit into. It seems that Happy Eyeballs not only applies to HTTP (http, http2, https), but also to other IPv4/IPv6 sockets, i.e., any TCP (net) or UDP socket (dgram).
The updated implementation performs the following:
Query the DNS resolver for A and AAAA.
If the DNS records are not in the cache, the requests are sent back to back on the wire, AAAA first.
If the first reply we get is AAAA, we send out the v6 SYN immediately
If the first reply we get is A and we're expecting a AAAA, we start a 25ms timer
If the timer fires, we send out the v4 SYN
If we get the AAAA during that 25ms window, we move on to address selection
When we have a list of IP addresses (either from the DNS cache or by receiving them close together with v4 before v6),
we perform our own address selection algorithm to sort them. This algorithm uses historical RTT data to prefer addresses
that have lower latency - but has a 25ms leeway: if the historical RTT of two compared address are within 25ms of each
other, we use RFC3484 to pick the best one.
Once the list is sorted, we send out the SYN for the first address and start timers based on average and variance of the
historical TCP RTT. Roughly speaking, we start the second address around the same time we send out a SYN retransmission
for the first address.
The first address to reply with a SYN-ACK wins the race, we then cancel the other TCP connection attempts.
Since it deals with SYN packets, I'm not sure if this can be implemented in JS land or it needs to touch the C++ networking layer. I'm not familiar at all with nodejs networking internals.
Doesn't the http module make use of net in the end as well?
Mostly, yes, at least by default.
As of 2017, IPv6 brokenness is now generally regarded as a non-problem. Wikipedia
I am wondering how much complexity we can justify just to work around broken IPv6 stacks. The algorithms seem useful even beyond IPv6 compatibility, but so far I have no idea how we could implement this. We probably cannot do this for all TCP connections, at least not by default. We could maybe add an option to socket.connect() to enable this behavior. However, an implementation would probably require changes within libuv first, which is responsible for establishing TCP connections.
As of 2017, IPv6 brokenness is now generally regarded as a non-problem.
That statement is probably not factual. At least not at scale. Even if 1% of users are affected, depending on the number of users, that can add up quite a bit.
There's also a huge thread here: aws-amplify/amplify-js#5539 about an Android only issue that I was also running into, and which was specifically about Happy Eyeballs.
There are tons of reports still popping up. And it goes unreported most of the time, because it's very random and pretty much unreproducible since it's hard to actually pin it down to IPv6 being the culprit.
@andreialecu Don't get me wrong, I am not saying there are no issues. I frequently run into some myself. That was just me quoting Wikipedia. IPv6 is older than myself and I really wish we didn't have to worry about broken IPv6 stacks at this point 😄
Either way, as I said in my last comment, someone would probably have to implement this in libuv before we could consider adding it in Node.js.
someone would probably have to implement this in libuv before we could consider adding it in Node.js
Not necessary, just call uv_tcp_connect() on two uv_tcp_t handles and keep the winner.
My understanding was that libuv might establish multiple connections in that case, whereas the algorithm above only sends SYNs until one receives a reply. Maybe that's wrong, or maybe establishing connections isn't that bad.
Second test (multiple fetch in order) fails, and all run fine locally. I run tests on default GitHub runners using both latest node 20 and 18. I get same errors:
After days of research and trial/error, this is how I got this working:
Create a script called force-ipv4.sh, that configures system to prefer IPv4 over IPv6, call it to configure the machine. It was not easy to find a reliable cross-platform solution and I went Cloudflare WARP for DNS resolution along with some system configurations.
To easily use the script in GitHub workflows, create GitHub action called force-ipv4 and call it in GitHub runners.
Fixes the IPv6 request issues, and you can happily run e.g. fetch API from Node.
Activity
aduh95 commentedon Jan 21, 2022
I agree Node.js would benefit having this built-in. Is this something you'd be interested in working on @andreialecu?
tniessen commentedon Jan 21, 2022
I am not sure what subsystem this would fit into. It seems that Happy Eyeballs not only applies to HTTP (
http,http2,https), but also to other IPv4/IPv6 sockets, i.e., any TCP (net) or UDP socket (dgram).andreialecu commentedon Jan 21, 2022
There's a writeup on how this is implemented in OSX here by an Apple engineer:
https://mailarchive.ietf.org/arch/msg/v6ops/DYiI9v_O66RNbMJsx0NsatFkubQ
There's also an official RFC at: https://datatracker.ietf.org/doc/html/rfc8305
Since it deals with SYN packets, I'm not sure if this can be implemented in JS land or it needs to touch the C++ networking layer. I'm not familiar at all with nodejs networking internals.
treysis commentedon Jan 21, 2022
Yes, I believe it should go into
net. Doesn't thehttpmodule make use ofnetin the end as well?Happy Eyeballs with UDP being connectionless is a bit more complicated.
tniessen commentedon Jan 21, 2022
Mostly, yes, at least by default.
I am wondering how much complexity we can justify just to work around broken IPv6 stacks. The algorithms seem useful even beyond IPv6 compatibility, but so far I have no idea how we could implement this. We probably cannot do this for all TCP connections, at least not by default. We could maybe add an option to
socket.connect()to enable this behavior. However, an implementation would probably require changes within libuv first, which is responsible for establishing TCP connections.treysis commentedon Jan 21, 2022
Why not? If I am not wrong this is the default for all Happy Eyeballs implementations. Though it uses some kind of cache.
andreialecu commentedon Jan 21, 2022
That statement is probably not factual. At least not at scale. Even if 1% of users are affected, depending on the number of users, that can add up quite a bit.
Reddit has recently disabled IPv6 due to issues they were having:
https://reddit.com/r/ipv6/comments/r047ah/redditcom_has_deleted_aaaa_records_and_is_back_to
Also their Android app does not implement Happy Eyeballs, and here's a report specifically pinpointing this to be the problem:
https://reddit.com/r/ipv6/comments/qy70hv/new_phone_failing_to_load_any_ipv6_websites_on/hlebckp/?utm_source=reddit&utm_medium=web2x&context=3
There's also a huge thread here: aws-amplify/amplify-js#5539 about an Android only issue that I was also running into, and which was specifically about Happy Eyeballs.
Additionally, there are many other threads in other JS ecosystems related to Happy Eyeballs: react/react-native#29608 (comment)
There are tons of reports still popping up. And it goes unreported most of the time, because it's very random and pretty much unreproducible since it's hard to actually pin it down to IPv6 being the culprit.
tniessen commentedon Jan 21, 2022
@andreialecu Don't get me wrong, I am not saying there are no issues. I frequently run into some myself. That was just me quoting Wikipedia. IPv6 is older than myself and I really wish we didn't have to worry about broken IPv6 stacks at this point 😄
Either way, as I said in my last comment, someone would probably have to implement this in
libuvbefore we could consider adding it in Node.js.andreialecu commentedon Jan 21, 2022
Perhaps this might help with that: https://github.com/shtrom/happy-eyeballs-c
bnoordhuis commentedon Jan 24, 2022
Not necessary, just call
uv_tcp_connect()on twouv_tcp_thandles and keep the winner.tniessen commentedon Jan 24, 2022
My understanding was that libuv might establish multiple connections in that case, whereas the algorithm above only sends SYNs until one receives a reply. Maybe that's wrong, or maybe establishing connections isn't that bad.
11 remaining items
undergroundwires commentedon Mar 15, 2024
How did you solve this? GitHub runners do not support IPv6 and I cannot use
fetch(native fetch i.e. nodejs/undici)I tried the following but it did not solve it:
treysis commentedon Mar 15, 2024
@undergroundwires which NodeJS version are you using.
undergroundwires commentedon Mar 16, 2024
Thank you @treysis for showing interest.
First I tested with latest 18 (
18.19.1), then latest 20 (20.11.1), I send requests using (fetch) on GitHub runner.It fails specifically on
wikipedia.orgrequests.Related issue for
fetch: nodejs/undici#1531Related issue for GitHub runners not being able to do IPv6: actions/runner-images#668, actions/runner#3138
treysis commentedon Mar 18, 2024
@undergroundwires does your host support IPv4? Or is it running on IPv6-only AWS?
undergroundwires commentedon Mar 19, 2024
I created a repository to reproduce this in as minimal way as possible:
https://github.com/undergroundwires/node-fetch-ipv6
I run fetch on using
vitetest runner on node and without any test runner. I get same results.I realize that the first two requests are successful, I start getting the error after the second or third one.
See the test: test file
Second test (
multiple fetch in order) fails, and all run fine locally. I run tests on default GitHub runners using both latest node 20 and 18. I get same errors:ci/cd: fix IPv6 timeouts with `force-ipv4` action
undergroundwires commentedon Mar 29, 2024
Here's my workaround (open-source and documented) that I hope that can help you too:
force-ipv4.sh.: Script for general use.After days of research and trial/error, this is how I got this working:
force-ipv4.sh, that configures system to prefer IPv4 over IPv6, call it to configure the machine. It was not easy to find a reliable cross-platform solution and I went Cloudflare WARP for DNS resolution along with some system configurations.force-ipv4and call it in GitHub runners.fetchAPI from Node.Related commit introducing this fix: undergroundwires/privacy.sexy@52fadcd
ci/cd: bump Node.js environment to 20.x
ci/cd: bump Node.js environment to 20.x
🐳 Fix `ECONNREFUSED ::1:3000` error
🐳 Fix `ECONNREFUSED ::1:3000` error
🐳 Fix `ECONNREFUSED ::1:3000` error