Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Happy Eyeballs #506

Closed
swankjesse opened this issue Feb 1, 2014 · 41 comments
Closed

Happy Eyeballs #506

swankjesse opened this issue Feb 1, 2014 · 41 comments
Labels
enhancement Feature not a bug
Milestone

Comments

@swankjesse
Copy link
Collaborator

http://en.wikipedia.org/wiki/Happy_Eyeballs

@vinc3m1
Copy link

vinc3m1 commented Mar 11, 2014

+1. I recently misconfigured my router to a bad IPv6 setting and half my apps were failing but chrome and others would still work. Took me a couple days to figure out what was going wrong.

dlubarov added a commit that referenced this issue Jul 29, 2014
This introduces a `ConnectionStrategy` interface for creating connections to a `Route`. The default `SimpleConnectionStrategy` implemention preserves the old behavior, passing hostnames down to the java.net stack which will (arbitrarily) chose one of the underlying IPs. The `ParallelConnectionStrategy` implementation will initiate a TCP handshake to each IP concurrently.

I tested `ParallelConnectionStrategy` with our API endpoint and got the datacenter I'm close to, as expected, and the time it look was comparable to my RTT to that datacenter (5-10ms in most cases).

Somewhat related to issue #506 (Happy Eyeballs).
dlubarov added a commit that referenced this issue Jul 29, 2014
This introduces a `ConnectionStrategy` interface for creating connections to a `Route`. The default `SimpleConnectionStrategy` implemention preserves the old behavior, passing along hostnames to Socket which will chose one of the underlying IPs. The `ParallelConnectionStrategy` implementation will initiate a TCP handshake to each IP concurrently and return the first connection to complete.

I tested `ParallelConnectionStrategy` with our API endpoint and got the datacenter I'm close to, as expected, and the time it look was comparable to my RTT to that datacenter (5-15ms in most cases).

Somewhat related to issue #506 (Happy Eyeballs).
dlubarov added a commit that referenced this issue Jul 29, 2014
This introduces a `ConnectionStrategy` interface for creating connections to a `Route`. The default `SimpleConnectionStrategy` implemention preserves the old behavior, passing along hostnames to Socket which will chose one of the underlying IPs. The `ParallelConnectionStrategy` implementation will initiate a TCP handshake to each IP concurrently and return the first connection to complete.

I tested `ParallelConnectionStrategy` with our API endpoint and got the datacenter I'm close to, as expected, and the time it look was comparable to my RTT to that datacenter (5-15ms in most cases).

Somewhat related to issue #506 (Happy Eyeballs).
@ghost
Copy link

ghost commented Oct 5, 2015

@dlubarov , @JakeWharton do you folks happen to be working on bringing Happy Eyeballs support to OkHttp? There are more DHCPv6-based IPv6 networks every day, especially in corporate settings, and the ability to pick the better transport in parallel will greatly benefit user experience.

@swankjesse
Copy link
Collaborator Author

It’s possible to implement this manually by making two HTTP requests yourself and picking the one that returns first. Doing it inside of OkHttp is more difficult. For example, what should the interceptors see? We might be able to do further work later to unblock this, but for now it seems very difficult to do well.

@mikelambert
Copy link

So this is causing issues for IPv6 http requests with React Native, linked above.

It sounds like the workaround suggested above is to do two requests in the above layer, with one forced to IPv4. Curious, how does one force IPv4 in an okhttp request?

Do I need to resolve the DNS to the IPv4 and IPv6 address and add in Host headers manually? Or is there some easier way?

@swankjesse
Copy link
Collaborator Author

Implement the Dns interface and strip the addresses you don't want. Be careful to not return an empty list; that case needs to throw an exception.

@ericmguimaraes
Copy link

Hey guys, by any chance you still planing on doing that? I'm facing the same problem here and the workaround doesn't really look good. It would be great to have a more sophisticated approach built-in the lib.

@yschimke
Copy link
Collaborator

Linking directly to RFC - https://tools.ietf.org/html/rfc8305

@exceptionplayer
Copy link

So, what is the plan? Will okhttp support Happy Eyeballs ?

@swankjesse swankjesse modified the milestones: 5.0, Icebox Jan 7, 2020
@dbrgn
Copy link

dbrgn commented Feb 29, 2020

In the docs, I read:

OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and services hosted in redundant data centers.

However, when I run this code in an API 21 (5.0) emulator...

public class MainActivity extends AppCompatActivity {

    @SuppressLint("StaticFieldLeak")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... voids) {
                OkHttpClient client = new OkHttpClient();
                String url = "https://directory.spaceapi.io/";
                Request request = new Request.Builder().url(url).build();
                try (Response response = client.newCall(request).execute()) {
                    Log.i("MainActivity", "Body: " + response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }.execute();
    }
}

...it fails with the following exception:

02-29 17:27:37.293 5855-5873/? W/System.err: java.net.ConnectException: Failed to connect to directory.spaceapi.io/2a01:4f8:1c0c:8127::2:443
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.kt:285)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:195)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:233)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:107)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:75)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:245)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:82)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:74)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:197)
02-29 17:27:37.293 5855-5873/? W/System.err:     at okhttp3.internal.connection.RealCall.execute(RealCall.kt:148)
02-29 17:27:37.293 5855-5873/? W/System.err:     at ch.dbrgn.happyeyeballs.MainActivity$1.doInBackground(MainActivity.java:31)
02-29 17:27:37.293 5855-5873/? W/System.err:     at ch.dbrgn.happyeyeballs.MainActivity$1.doInBackground(MainActivity.java:23)
02-29 17:27:37.293 5855-5873/? W/System.err:     at android.os.AsyncTask$2.call(AsyncTask.java:288)
02-29 17:27:37.293 5855-5873/? W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:237)
02-29 17:27:37.293 5855-5873/? W/System.err:     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
02-29 17:27:37.293 5855-5873/? W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
02-29 17:27:37.293 5855-5873/? W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
02-29 17:27:37.293 5855-5873/? W/System.err:     at java.lang.Thread.run(Thread.java:818)
02-29 17:27:37.293 5855-5873/? W/System.err: Caused by: java.net.ConnectException: failed to connect to directory.spaceapi.io/2a01:4f8:1c0c:8127::2 (port 443) after 10000ms: isConnected failed: ENETUNREACH (Network is unreachable)
02-29 17:27:37.293 5855-5873/? W/System.err:     at libcore.io.IoBridge.isConnected(IoBridge.java:238)
02-29 17:27:37.293 5855-5873/? W/System.err:     at libcore.io.IoBridge.connectErrno(IoBridge.java:171)
02-29 17:27:37.294 5855-5873/? W/System.err:     at libcore.io.IoBridge.connect(IoBridge.java:122)
02-29 17:27:37.294 5855-5873/? W/System.err:     at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:183)
02-29 17:27:37.294 5855-5873/? W/System.err:     at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:456)
02-29 17:27:37.294 5855-5873/? W/System.err:     at java.net.Socket.connect(Socket.java:882)
02-29 17:27:37.294 5855-5873/? W/System.err:     at okhttp3.internal.platform.AndroidPlatform.connectSocket(AndroidPlatform.kt:57)
02-29 17:27:37.294 5855-5873/? W/System.err:     at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.kt:283)
02-29 17:27:37.294 5855-5873/? W/System.err: 	... 23 more
02-29 17:27:37.294 5855-5873/? W/System.err: Caused by: android.system.ErrnoException: isConnected failed: ENETUNREACH (Network is unreachable)
02-29 17:27:37.294 5855-5873/? W/System.err:     at libcore.io.IoBridge.isConnected(IoBridge.java:223)
02-29 17:27:37.294 5855-5873/? W/System.err: 	... 30 more

That host (directory.spaceapi.io) is accessible both via IPv4 and IPv6. However, the emulator only has link-local and site-local IPv6 addresses.

root@generic_x86_64:/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1440 qdisc pfifo_fast state UP qlen 1000
    link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fec0::5054:ff:fe12:3456/64 scope site dynamic
       valid_lft 86236sec preferred_lft 14236sec
    inet6 fe80::5054:ff:fe12:3456/64 scope link
       valid_lft forever preferred_lft forever
3: sit0: <NOARP> mtu 1480 qdisc noop state DOWN
    link/sit 0.0.0.0 brd 0.0.0.0

Am I right when I assume that this connection failure is due to the missing happy eyeballs implementation?

@yschimke
Copy link
Collaborator

Does that host work from chrome on the same mobile?

@dbrgn
Copy link

dbrgn commented Feb 29, 2020

Does that host work from chrome on the same mobile?

Hmm, good question. From the built-in emulator browser I get a net::ERR_SSL_VERSION_OR_CIPHER_MISMATCH error. The target host has disabled TLS < 1.2. Could this be a TLS version mismatch masked as a IPv6 connection problem? The same code does work on an Android 9 emulator. However, I thought that starting from 4.4 or 5.0, TLSv2 should work out of the box.

Edit: According to the Qualys SSL Labs client test, the client does support TLS 1.2. Then I assume the ciphersuites on the server are not yet supported by this device.

@yschimke
Copy link
Collaborator

I would suggest starting with a Stackoverflow question. That request works for me with a JDK OkHttp request. But I get an IPv4 address back.

But debugging on this github issue is likely to spam 8 people.

@yschimke
Copy link
Collaborator

yschimke commented Feb 29, 2020

One thing to consider is whether we should add suppressed errors when we fail after multiple hosts.

If we fail with IPv4 with a useful error but swallow it to deliver the IPv6 error then it's misleading.

For discussion #5836

@swankjesse
Copy link
Collaborator Author

This is done.

@andreialecu
Copy link

Awesome, thank you so much for this!

I was wondering when a stable 5.0 release may be available. The milestone here doesn't help much (says past due by 1 year 🙂)
https://github.com/square/okhttp/milestone/21

@swankjesse
Copy link
Collaborator Author

If you're prepared to deal with small API changes between now and 5.0 final you can put it into production today. For OkHttp ‘alpha’ means ‘API instability’ but the code is extremely stable.

@yschimke
Copy link
Collaborator

I'd echo that but also say that feedback on this will probably help build confidence in OkHttp 5 and this rather big change.

Remember it needs an opt-in #506 (comment)

@balajithiru1
Copy link

balajithiru1 commented Aug 25, 2022

Do we know when this feature will be out of alpha , so that we can start using them in production?

@SuperQ
Copy link

SuperQ commented Aug 25, 2022

It's marked "alpha", but according to Reddit, it's good enough for production.

@vaibhav-sharechat
Copy link

When will this be moved to production from alpha ?

@yschimke
Copy link
Collaborator

yschimke commented Aug 8, 2023

It's a way off, we have some API changes (more KMP, not affecting existing 4.x APIs) we need to make. I can't give a date.

But it's production ready, just not API stable

@sauravgk
Copy link

Is happy eyeball also supported for web-socket initial connection?

@swankjesse
Copy link
Collaborator Author

Yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Feature not a bug
Projects
None yet
Development

No branches or pull requests