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

In Dart HTTP Client, an option is required to set the IPv4 or IPv6 for DNS resolution for the developer #50868

Closed
SijuKJ opened this issue Jan 1, 2023 · 31 comments
Assignees
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. area-vm Use area-vm for VM related issues, including code coverage, FFI, and the AOT and JIT backends. library-io os-android P2 A bug or feature request we're likely to work on triaged Issue has been triaged by sub team

Comments

@SijuKJ
Copy link

SijuKJ commented Jan 1, 2023

This feature is a related dart HTTP client, I raised a new feature request in Flutter GitHub ( flutter/flutter#116537). They asked to raise in Dart SDK GitHub as it is a bug in the dart HTTP client.

Target Platform: Android
Target OS version/browser: Android 12+
Devices:

Many Flutter-based Android and iOS apps are very slow during HTTP service calls in the Wifi networks due to failed IPv6 DNS resolution.
As I am requesting this feature as a solution for the three important issues reported by Flutter developers.

  1. In Android 12+, the REST API calls are very slow in flutter, In WiFi that takes 10 seconds whereas in mobile data it is less than a second flutter/flutter#116477
  2. Flutter: API Calls are so slow in flutter, taking more than 20s to load with WiFi and less than a sec with Mobile Data flutter/flutter#112285
  3. Slow HttpClient operation caused by DNS resolution failure on iOS #41451

In essence, we are facing slowness in the API call execution while using Wifi networks due to failed IPv6 DNS resolution, thereafter it fallback to IPv4 DNS resolution. Hence each API call execution is getting delayed a minimum of 10+ seconds.

It would be great If there would be a feature for manually specifying IPv4 or IPv6 versions during HTTP client initialization.

So I request your kind attention on this important issue, as we are developing business-critical applications using Flutter, for iOS/ Android, and Windows.

@mraleph
Copy link
Member

mraleph commented Jan 1, 2023

For iOS this was supposed to be fixed by d7483c3, so you should not see it. We could enable it on Android as well.

/cc @brianquinlan

@mraleph mraleph added area-vm Use area-vm for VM related issues, including code coverage, FFI, and the AOT and JIT backends. area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-io triaged Issue has been triaged by sub team labels Jan 1, 2023
@Sreeharikr
Copy link

Any update in this issue? I'm also facing the same.... Tried to use same http client after initializing once... But connection is losing after some time.... 🤕

@bugrevealingbme
Copy link

++

@mraleph
Copy link
Member

mraleph commented Jan 25, 2023

Seems like a high impact issue for anybody who uses default IO client.

/cc @a-siva

@brianquinlan brianquinlan added the P2 A bug or feature request we're likely to work on label Jan 26, 2023
@nswim
Copy link

nswim commented Feb 3, 2023

critical issue for my app
any update?

@EdsonMello-code
Copy link

Any solution?

@iamsureshsharma
Copy link

I am also facing the same issue

@mammuthone
Copy link

Same issue here.
On Android app works at speed of light, on iOS emulator app works at speed of light.
On physical device iPhone app become slower as crazy during http requests

@brianquinlan brianquinlan self-assigned this Mar 2, 2023
@brianquinlan
Copy link
Contributor

brianquinlan commented Mar 2, 2023

Could someone experiencing this issue on Android please try running this code and report the output:

    // Test IPv4 performance
    final start = DateTime.now();
    InternetAddress.lookup(<whatever host that you use that is slow>, type: InternetAddressType.IPv4)
        .then((_) => print(DateTime.now().difference(start)));

and then, after rebooting your device:

    // Test IPv6 performance
    final start = DateTime.now();
    InternetAddress.lookup(<whatever host that you use that is slow>, type: InternetAddressType.IPv6)
        .then((_) => print(DateTime.now().difference(start)));

I just want to confirm that the issue is with IPv6 lookup.

@brianquinlan
Copy link
Contributor

Actually, there is an easier way. If you run your flutter project with:

flutter run --dart-define=dart.library.io.force_staggered_ipv6_lookup=true

or build it with:

flutter build appbundle --dart-define=dart.library.io.force_staggered_ipv6_lookup=true

then it will attempt to lookup the host address using IPv4 and IPv6 in parallel.

Could someone please try that and see if it fixes the issue? Note that this flag will have no effect on iOS.

@hashimchargemod
Copy link

Yes, This actually fixed that. i dont have issues with iOS, this works for android. will HTTP package supports this in future?

@brianquinlan
Copy link
Contributor

hashimchargemod@ thanks!

If you are referring to package:http then this flag will also apply to that package if you are using IOClient (the default - so if you don't know whether you are using it or not then you are ;-))

@brianquinlan
Copy link
Contributor

Unless there is a reason not too, I'm going to enable staggered IPv6 resolution on all platforms and remove the flag.

@hashimchargemod
Copy link

okay, Happy to help. Just a simple question: Is there any option built for disabling the ipv6 entirely and look only ipv4?

@brianquinlan
Copy link
Contributor

There is no way to disable lookup with IPv6.

@brianquinlan
Copy link
Contributor

I've been looking into how name resolution works right now. Currently we are using getaddrinfo with the AI_ADDRCONFIG flag.

So the order of the addresses returned should match RFC 3484 - which specifies that, all else being equal, IPv6 addresses should be tried first. When using glibc, gai.conf can be used to control the order of the returned addresses.

If we always prioritize IPv4 connections then we will be violating the RFC and ignoring the user's gai.conf.

I'm looking into how other mobile HTTP client implementations deal with this.

Also, it occurs to me that there is currently a way to force HttpClient to use IPv4 connections.

void main() async {
  HttpClient client = HttpClient()
    ..connectionFactory = (Uri uri, String? proxyHost, int? proxyPort) async {
      if (proxyHost != null && proxyPort != null) {
        return Socket.startConnect(proxyHost, proxyPort);
      }
      final ipv4addresses = await InternetAddress.lookup(uri.host,
          type: InternetAddressType.IPv4);
      // Should actually iterate over every address until we get a successful
      // connection.
      return Socket.startConnect(ipv4addresses.first, uri.port);
    };

  final request = await client.getUrl(Uri.http('www.google.com', '/'));
  final response = await request.close();
  print(response.statusCode);
  client.close();
}

@brianquinlan
Copy link
Contributor

It looks like Chromium runs a probe once a second to check if IPv6 is available:
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/net/dns/host_resolver_manager.cc#156

@brianquinlan
Copy link
Contributor

@cbracken added you as a CC in case you think that the resolution of this issue could affect your work.

@brianquinlan
Copy link
Contributor

There are also some semantic differences between requesting IPv4 and IPv6 addresses in one system call vs. two e.g.

>>> socket.getaddrinfo('127.0.0.1', 80, proto=socket.IPPROTO_TCP)
[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('127.0.0.1', 80))]
>>> # The address is clearly a numeric IPv4 address so only providing AF_INET totally makes sense to me.
>>> socket.getaddrinfo('127.0.0.1', 80, proto=socket.IPPROTO_TCP, family=socket.AF_INET)
[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('127.0.0.1', 80))]
>>> # Sure, filtering by IPv4 returns the same result because the result was IPv4-only anyway.
>>> socket.getaddrinfo('127.0.0.1', 80, proto=socket.IPPROTO_TCP, family=socket.AF_INET6)
[(<AddressFamily.AF_INET6: 30>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('::ffff:127.0.0.1', 80, 0, 0))]
>>> # Ahhh... so if I explicitly ask for IPv6 then you'll give me this address with the IPv4 subnet prefix.
>>> # So asking for IPv4 and IPv6 address separately is *not* the same as asking for both at once.

@brianquinlan
Copy link
Contributor

I have the outline of a change here: https://dart-review.googlesource.com/c/sdk/+/288621/6/sdk/lib/_internal/vm/bin/socket_patch.dart

I'm worried that it doesn't work correctly for servers bound to IPv6 addresses that allow IPv4 connections. Relevant section:


       IPv4 connections can be handled with the v6 API by using the
       v4-mapped-on-v6 address type; thus a program needs to support
       only this API type to support both protocols.  This is handled
       transparently by the address handling functions in the C library.

       IPv4 and IPv6 share the local port space.  When you get an IPv4
       connection or packet to an IPv6 socket, its source address will
       be mapped to v6 and it will be mapped to v6.

I need to think about this a bit more.

copybara-service bot pushed a commit that referenced this issue Mar 28, 2023
Bug: #50868
Change-Id: I5ad57f4634287b4299fbf74fde075d518154bf08
Tested: unit
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/288621
Commit-Queue: Brian Quinlan <bquinlan@google.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
@brianquinlan
Copy link
Contributor

I modified the socket connection logic to favor IPv4 connections over IPv6 connections, as we already did on iOS.

copybara-service bot pushed a commit that referenced this issue Apr 3, 2023
Bug: #50868
Change-Id: I8186d95d1a24c77c4cfade533af26695c7a13b24
CoreLibraryReviewExempt: documentation-only change
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/293020
Commit-Queue: Brian Quinlan <bquinlan@google.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
@AlexV525
Copy link
Contributor

AlexV525 commented Aug 25, 2023

Fixes didn't address the proposal yet, those fixes are for the SocketException. We should reconsider if manually set lookup address type can be overridable.

@Stitch-Taotao
Copy link

Stitch-Taotao commented Jan 1, 2024

There is no way to disable lookup with IPv6.

@brianquinlan
I'm currently facing a network issue that I suspect is related to DNS resolution. At certain times, network requests take excessively long. However, when using other frameworks like the web's fetch, I don't encounter this prolonged delay in network request times. To clarify, when Flutter's requests are slow, other frameworks or tools like Postman don't experience the same slowness. As you mentioned earlier, DNS resolution requires resolving IPV6 first and then IPV4, as per standards. I understand that. My query is: How do other frameworks handle DNS resolution logic, and why doesn't Dart adopt a similar approach? So far, I've only noticed this slow network behavior occurring in Dart。I am using Flutter stable version 3.13.9 on android device.

@a-siva
Copy link
Contributor

a-siva commented Jan 2, 2024

@Stitch-Taotao can you try with a newer version of Flutter, a change was made to favor IPv4 connections over IPv6 connections here https://dart-review.googlesource.com/c/sdk/+/288621 which is probably not it version 3.13.9

@lukehutch
Copy link

I am still seeing this with Flutter 3.21.0-16.0.pre.11 / Dart 3.4.0 (build 3.4.0-279.0.dev).

Requests randomly and sporadically will take multiple seconds, sometimes even timing out after 20 seconds. At other times, the requests are lightning fast. The slow requests happen about 10% of the time.

Sometimes I don't even get timeouts, I get "no address associated with hostname" failures:

flutter ( 5114): [https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=37.4219983&longitude=-122.084&localityLanguage=en] An error occurred during request, trying again (attempt: 2/3, wait 5000 ms, error: SocketException: Failed host lookup: 'api.bigdatacloud.net' (OS Error: No address associated with hostname, errno = 7))

Meanwhile, as others have commented, getting the same URL with other tools (e.g. in Chrome) at or around the same time as failed requests returns the result instantly.

Actually, there is an easier way. If you run your flutter project with:

flutter run --dart-define=dart.library.io.force_staggered_ipv6_lookup=true

or build it with:

flutter build appbundle --dart-define=dart.library.io.force_staggered_ipv6_lookup=true

then it will attempt to lookup the host address using IPv4 and IPv6 in parallel.

Can we get a programmatic way to set this flag at runtime in main, please? I can't use commandline switches when building or running my app.

Personally I don't care if the spec says IPv4 addresses can't be used until IPv6 lookup fails -- I need my app to work fast, every time.

@lukehutch
Copy link

@brianquinlan can you please reopen this issue -- it is not fixed.

@brianquinlan
Copy link
Contributor

I am still seeing this with Flutter 3.21.0-16.0.pre.11 / Dart 3.4.0 (build 3.4.0-279.0.dev).

Requests randomly and sporadically will take multiple seconds, sometimes even timing out after 20 seconds. At other times, the requests are lightning fast. The slow requests happen about 10% of the time.

Sometimes I don't even get timeouts, I get "no address associated with hostname" failures:

flutter ( 5114): [https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=37.4219983&longitude=-122.084&localityLanguage=en] An error occurred during request, trying again (attempt: 2/3, wait 5000 ms, error: SocketException: Failed host lookup: 'api.bigdatacloud.net' (OS Error: No address associated with hostname, errno = 7))

Meanwhile, as others have commented, getting the same URL with other tools (e.g. in Chrome) at or around the same time as failed requests returns the result instantly.

What platform are you seeing this error on?

Actually, there is an easier way. If you run your flutter project with:

flutter run --dart-define=dart.library.io.force_staggered_ipv6_lookup=true

or build it with:

flutter build appbundle --dart-define=dart.library.io.force_staggered_ipv6_lookup=true

then it will attempt to lookup the host address using IPv4 and IPv6 in parallel.

Can we get a programmatic way to set this flag at runtime in main, please? I can't use commandline switches when building or running my app.

Personally I don't care if the spec says IPv4 addresses can't be used until IPv6 lookup fails -- I need my app to work fast, every time.

This flag is obsolete - Dart currently prioritizes IPv4 addresses over IPv6 addresses on all platforms.

@lukehutch
Copy link

lukehutch commented Apr 1, 2024

@brianquinlan

What platform are you seeing this error on?

Sorry, it's on the Android emulator, running on Linux. The issue occurs with the standard http client, and also with dio.

This flag is obsolete - Dart currently prioritizes IPv4 addresses over IPv6 addresses on all platforms.

OK, so there may be something else going on. The behavior seems to exactly match the IPv6 name lookup failures that others were seeing, so I assumed that was the cause.

Response times are extremely sporadic, either returning instantly or taking 8-20 seconds, or failing with timeout after 20 seconds.

This happens with multiple domains -- api.bigdatacloud.net, as shown in the log, but also with my own DyDNS-mapped domain name that is only two hops (basically I expose a server on my local machine via a static IP to my ISP, then connect to my machine from the Android emulator process on the same machine, rather than collecting to localhost, to better simulate hosting in the cloud). It's one hop out to the ISP, then one hop back to my machine. Even those requests can take 20 seconds to fill.

I have debugged this as far down the client stack as I could, and as far down the server stack as I could. The problem seems to be happening in the HttpClient async code, but at that point it's impossible to follow the code because it is very old (pre async/await) and therefore very difficult to debug. But the delays do seem to be happening somewhere in the Dart stack on the client.

@lukehutch
Copy link

I also see these errors with CachedNetworkImage in Flutter, while loading the same URL in the browser works fine:

════════ Exception caught by image resource service ════════════════════════════
ClientException with SocketException: Failed host lookup: 'images.pexels.com' (OS Error: No address associated with hostname, errno = 7), uri=https://images.pexels.com/photos/992734/pexels-photo-992734.jpeg?auto=compress&cs=tinysrgb&h=350
════════════════════════════════════════════════════════════════════════════════

@brianquinlan
Copy link
Contributor

@lukehutch

On Android, I'd suggest that you use package:cronet_http, which is a wrapper around the android Cronet library.

package:http has a decision table to help you pick an HTTP client implementation.

@lukehutch
Copy link

@brianquinlan Thanks, I didn't know that was an option.

With these changes, I'm no longer seeing 20 second delays, but I see 2-3 second delays sometimes. The domain name resolution issues seem to be fixed though.

I guess I'll have to assume that there is something going on between the emulator environment and my local network.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. area-vm Use area-vm for VM related issues, including code coverage, FFI, and the AOT and JIT backends. library-io os-android P2 A bug or feature request we're likely to work on triaged Issue has been triaged by sub team
Projects
None yet
Development

No branches or pull requests