ruby
ruby copied to clipboard
Implement Happy Eyeballs Version 2 (RFC8305) in Socket.tcp
This change implements Happy Eyeballs Version 2 (RFC8305) in Socket.tcp. It enables fallback from IPv6 to IPv4 without a long waiting time.
Hi, I am not sure if the fix in Socket.tcp is supposed to resolve the issue reported https://bugs.ruby-lang.org/issues/15628. ruby -rnet/http -e "Net::HTTP.get(URI('http://example.com:8000'))" doesn't fallback to ipv4 with this patch applied.
Can you please elaborate on your plan? AFAIU, Net::HTTP uses this file[1] for creating sockets (not Socket.tcp).
[1] https://github.com/ruby/ruby/blob/master/ext/socket/tcpsocket.c
@sonalkr132
This change introduces Happy Eyeballs into Socket.tcp (implemented in Ruby).
This won't fix Net::HTTP that uses TCPSocket.open (implemented in C).
I'm also planning to introduce Happy Eyeballs into TCPSocket.open too.
I think a test should be implemented.
It should be possible by redefining Addrinfo.getaddrinfo.
I think we should consider a state machine for Happy Eyeballs v2.
This is my understanding of Happy Eyeballs v2
state Start
do
start IPv6 getaddrinfo
start IPv4 getaddrinfo
transition
IPv6 getaddrinfo finished -> state IPv6-getaddrinfo-finished
IPv4 getaddrinfo finished -> state IPv4-getaddrinfo-finished
state IPv6-getaddrinfo-finished
do
start IPv6 TCP handshake periodically (once for each CONNECTION_ATTEMPT_DELAY)
transition
TCP connection established -> state Success
IPv4 getaddrinfo finished -> state IPv6-IPv4-getaddrinfo-finished
state IPv4-getaddrinfo-finished
do
wait RESOLUTION_DELAY
transition
RESOLUTION_DELAY timeout -> state IPv4-getaddrinfo-and-RESOLUTION_DELAY-finished
IPv6 getaddrinfo finished -> state IPv6-IPv4-getaddrinfo-finished
state IPv4-getaddrinfo-and-RESOLUTION_DELAY-finished
do
start IPv4 TCP handshake periodically (once for each CONNECTION_ATTEMPT_DELAY)
transition
TCP connection established -> state Success
IPv6 getaddrinfo finished -> state IPv6-IPv4-getaddrinfo-finished
state IPv6-IPv4-getaddrinfo-finished
do
start IPv6 and IPv4 TCP handshake periodically (once for each CONNECTION_ATTEMPT_DELAY, interleave IPv6 and IPv4)
transition
TCP connection established -> state Success
no more unfinished TCP handshake -> state Error
state Success
do
cleanup
return the established TCP connection
state Error
do
cleanup
raise an error
I think we should consider a state machine for Happy Eyeballs v2.
This is my understanding of Happy Eyeballs v2
I updated the state machine. It describes timeouts on periodical connection attempts more explicitly.
state Start
do
start IPv6 getaddrinfo
start IPv4 getaddrinfo
transition
IPv6 getaddrinfo finished -> state IPv6-getaddrinfo-finished
IPv4 getaddrinfo finished -> state IPv4-getaddrinfo-finished
state IPv6-getaddrinfo-finished
do
if there is an IP address that TCP handshake not started
choose an IPv6 address and start TCP handshake
transition
TCP connection established -> state Success
IPv4 getaddrinfo finished -> state IPv6-IPv4-getaddrinfo-finished
CONNECTION_ATTEMPT_DELAY timeout since the last TCP handshake start (if there is an IP address that TCP handshake not started) -> state IPv6-getaddrinfo-finished
state IPv4-getaddrinfo-finished
do
wait RESOLUTION_DELAY
transition
RESOLUTION_DELAY timeout -> state IPv4-getaddrinfo-and-RESOLUTION_DELAY-finished
IPv6 getaddrinfo finished -> state IPv6-IPv4-getaddrinfo-finished
state IPv4-getaddrinfo-and-RESOLUTION_DELAY-finished
do
if there is an IP address that TCP handshake not started
choose an IPv4 address and start TCP handshake
transition
TCP connection established -> state Success
IPv6 getaddrinfo finished -> state IPv6-IPv4-getaddrinfo-finished
CONNECTION_ATTEMPT_DELAY timeout since the last TCP handshake start (if there is an IP address that TCP handshake not started) -> state IPv4-getaddrinfo-and-RESOLUTION_DELAY-finished
state IPv6-IPv4-getaddrinfo-finished
do
if there is an IP address that TCP handshake not started
if there is a socket currently trying TCP handshake
if CONNECTION_ATTEMPT_DELAY is not passed after the last TCP handshake start
wait
else
choose an IPv6 or IPv4 address (interleave IPv6 and IPv4 if possible) and start TCP handshake
else
choose an IPv6 or IPv4 address (prefer IPv6) and start TCP handshake
transition
TCP connection established -> state Success
CONNECTION_ATTEMPT_DELAY timeout since the last TCP handshake start (if there is an IP address that TCP handshake not started) -> state IPv6-IPv4-getaddrinfo-finished
no more connecting socket -> state Error
state Success
do
cleanup
return the established TCP connection
state Error
do
cleanup
raise an error
This is interesting PR.
I don't think connecting a socket should be creating threads.
Any plan to merge this work?
+1 Is this abandoned? We could very much use this for AWS SDK for Ruby, as services are pushing for more ipv6 support. I would like to see happy eyeballs support for Net::HTTP (also making changes to TCPSocket.open)
I too would love to see this merged. This has bitten me time and time again and "disabling ipv6" is such an anti-fix it kills me.
@shaneshort I tracked this issue for a rely long time. I too, want to use the feature. However, I have a mixed feeling about the PR; once it is merged, there is no going back.
I can't point at the code and tell where should we improve. Maybe, I am hoping for something better than spinning up new threads; or some reputable C implementation from big tech we can retrofit into ruby.
However, seeing the implementation now, I can wait until we absolutely sure about it. I'm sure the maintainers who remain silent feels the same. We just can't argue with the code productively. I feels like we should try something similar with gems first. But I am not the one who put in the work so I can't really suggest that.
@shaneshort I tracked this issue for a rely long time. I too, want to use the feature. However, I have a mixed feeling about the PR; once it is merged, there is no going back.
I can't point at the code and tell where should we improve. Maybe, I am hoping for something better than spinning up new threads; or some reputable C implementation from big tech we can retrofit into ruby.
However, seeing the implementation now, I can wait until we absolutely sure about it. I'm sure the maintainers who remain silent feels the same. We just can't argue with the code productively. I feels like we should try something similar with gems first. But I am not the one who put in the work so I can't really suggest that.
I can appreciate that. I'm certainly no expert at low level ruby programming but I do have a solid networking background. The fact that ruby doesn't currently support happy eyeballs is only going to become a bigger problem as time progresses. I'd be happy if it was a flag that we could choose to enable at least initially, with the understanding that behaviour may be different. If that shifts the roadblock to getting some kind of progress on this, I'd be down for it.
Is this planned for ruby 3.4?
Btw. meanwhile this was in super simple version integrated into RubyGems at https://github.com/rubygems/rubygems/commit/f1d27c9d1b04129c3a1c239b805155145a9928f4 / https://github.com/rubygems/rubygems/blob/2ca7f8fa376f3689e2131b06602896cdaa55cfe7/lib/rubygems/core_ext/tcpsocket_init.rb.
#9374 has been merged :tada:, so this can be closed!
Awesome!! 🎉