HTTP traffic via company MDM VPN tunnel
I'm trying to develop an in-house app for deployment within our company. This requires the use of a VPN tunnel which is provided by Mobile Iron (MDM solution). When I try to reach the internal API through Flutter I can see the app opens the VPN tunnel, but I'm getting an exception message that the connection failed using the following code (exact logs below):
import 'package:http/http.dart' as http;
// ...
var client = http.Client();
return await client.get(
'https://internal-api.internalcorp.net:8443/api/<!--redacted-->',
);
The same issue exists using either the dart:io or the http package.
I tried implementing the HTTP calls in native iOS and call that via a method channel using the exact same details, and that seems to work okay. So I'm thinking that the http implementation in Flutter does something different than the native iOS http client via URLRequest:
let session = URLSession.shared
let url = URL(string: "https://internal-api.internalcorp.net:8443/api/<!--redacted-->")!
var request = URLRequest(url: url)
let task = session.dataTask(with: request) { data, response, error in
result([
"data": String(data: data!, encoding: .utf8)!,
"statusCode": (response as? HTTPURLResponse)?.statusCode
]);
}
task.resume()
I'm not a Swift expert so the above code could probably be implemented in a better way.
PS: We only support iOS with our MDM solution, so I'm unable to test if the issue exists for Android as well.
Steps to Reproduce
I did not find a way to reproduce the issue outside of the company environment yet. I tried getting more insight on the network calls but can't find anything since the traffic is all encrypted via the tunnel.
Logs
Redacted a little to prevent exposing secure details.
[ +763 ms] [VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: SocketException: Connection failed, address =
internal-api.internalcorp.net, port = 8443
[ ] #0 IOClient.send (package:http/src/io_client.dart:33:23)
[ ] <asynchronous suspension>
[ ] #1 BaseClient._sendUnstreamed (package:http/src/base_client.dart:169:38)
[ ] <asynchronous suspension>
[ ] #2 BaseClient.get (package:http/src/base_client.dart:32:7)
[ ] #3 Test.getData (package:example/test.dart:8:25)
[ ] <asynchronous suspension>
[ ] #4 _LoginPageState.build.<anonymous closure> (package:example/main.dart:160:53)
[ ] <asynchronous suspension>
[ ] #5 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:635:14)
[ ] #6 _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:711:32)
[ ] #7 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
[ ] #8 TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:365:11)
[ ] #9 TapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:275:7)
[ ] #10 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:455:9)
[ ] #11 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:75:13)
[ ] #12 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:102:11)
[ ] #13 _WidgetsFlutterBinding&BindingBase&GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:218:19)
[ ] #14 _WidgetsFlutterBinding&BindingBase&GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:198:22)
[ +4 ms] #15 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:156:7)
[ ] #16 _WidgetsFlutterBinding&BindingBase&GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:102:7)
[ ] #17 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:86:7)
[ ] #18 _rootRunUnary (dart:async/zone.dart:1136:13)
[ ] #19 _CustomZone.runUnary (dart:async/zone.dart:1029:19)
[ ] #20 _CustomZone.runUnaryGuarded (dart:async/zone.dart:931:7)
[ ] #21 _invoke1 (dart:ui/hooks.dart:250:10)
[ ] #22 _dispatchPointerDataPacket (dart:ui/hooks.dart:159:5)
[ +302 ms] [VERBOSE-2:shell.cc(184)] Dart Error: Unhandled exception:
[ ] Bad state: Future already completed
[ ] #0 _AsyncCompleter.complete (dart:async/future_impl.dart:39:31)
[ ] #1 _NativeSocket.startConnect.<anonymous closure>.connectNext.<anonymous closure> (dart:io-patch/socket_patch.dart:521:23)
[ ] #2 _NativeSocket.issueWriteEvent.issue (dart:io-patch/socket_patch.dart:874:14)
[ ] #3 _NativeSocket.issueWriteEvent (dart:io-patch/socket_patch.dart:881:12)
[ ] #4 _NativeSocket.multiplex (dart:io-patch/socket_patch.dart:902:11)
[ ] #5 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:172:12)
[✓] Flutter (Channel master, v1.10.7-pre.25, on Mac OS X 10.14.4 18E226, locale en-NL)
• Flutter version 1.10.7-pre.25 at /Users/tim/development/flutter
• Framework revision e47a1dc216 (29 hours ago), 2019-09-26 07:18:25 -0700
• Engine revision 9fd35df9bb
• Dart version 2.6.0 (build 2.6.0-dev.0.0 dd65f97118)
[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
• Android SDK at /Users/tim/Library/Android/sdk
• Android NDK location not configured (optional; useful for native profiling support)
• Platform android-28, build-tools 28.0.3
• Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 11.0)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 11.0, Build version 11A420a
• CocoaPods version 1.7.3
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 3.4)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin version 35.3.1
• Dart plugin version 183.6270
• Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01)
[✓] VS Code (version 1.38.1)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.4.1
Hi, Did you found a solution for working with Mobile Iron by Flutter ?
Unfortunately I have not found the solution yet. My workaround does work for normal API calls, but does not allow usage of Image.Network() for example.
See https://gist.github.com/Mardaneus86/7edd9aa068a0eb4aab40254b3fc44e0c for a gist of the current workaround on iOS. It's far from perfect though, so be cautious in using this as-is. The gist is using the Dio library for easy swapping between the workaround and Dio in the end.
Hi, We're in same problem with an other tunnel application, Android is work but ios can not open connection, did you found a solution
@kevmoo @natebosch It seems the http package is unable to take advantage of the phone's VPN to access internal servers. This issue is a bit old now, have we addressed this in http?
@gaaclarke - the http package has no impact on how the app uses the network. package:http is only a wrapper around the classes in dart:io or dart:html, it does not introduce it's own capabilities.
cc @mehmetf - does Image.network expose some way to handle this?
cc @ZichangG - does dart:io have anything that would allow or prevent using a VPN?
My concern is that maybe Apple doesn't allow access to the operating system's VPN settings unless accessing the network through their API (CFSocket). If Dart is using lower level unix sockets it might bypass the VPN. @ZichangG
IO will take arguments which is URL in this case and pass it into native unix sockets.
@ZichangG In iOS, POSIX networking is discouraged because it does not activate the cellular radio or on-demand VPN. Thus, as a general rule, you should separate the networking code from any common data processing functionality and rewrite the networking code using higher-level APIs.
source: https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/UsingSocketsandSocketStreams.html
Image.network uses dart:io HttpClient directly. Any change that exists only in package:http won't help.
We are facing a similar dilemma because we want to use Cronet on Android for all HTTP connections. The easiest way to do this is via HttpOverrides (https://api.dart.dev/stable/2.7.2/dart-io/HttpOverrides-class.html). That mechanism was originally designed for testing purposes (that is for providing a mock HttpClient). Unfortunately, you cannot extend HttpClient implementation because it is a private class in the SDK.
PS: There's a IOOverrides flavor of this as well that allows you to override Socket implementation.
I would not do this with overrides though; it likely needs to be fixed at the VM level.
I hope this issue gets resolved soon. We deployed our Flutter app through VMWare AirWatch and configured it to communicate through an on-demand VPN tunnel. But it does not work at all and we always get this Exception:
SocketException: ConnectionFailed, address = xxxxxx, port: xxx
I hope this issue gets resolved soon. We have our Flutter app through VMWare AirWatch per-app vpn. It works with Android but not IOS get this Exception: SocketException: ConnectionFailed, address = xxxxxx, port: xxx after tcp hand shake with server
One more to the victims' list. After days of struggling, we finally found this issue.
After building a custom plugin for this, that basically routes all HTTP requests to the native side, we are now facing the same problem with WebSockets... How can this issue have such a low priority?
Same issue here, how can this issue from 2019 not be fixed yet, its such a basic thing .. I expect when my end user device uses a VPN that my app will too ..
Noting here that I am also surprised this issue has not been given a higher priority. We want to use flutter to replace our existing mobile app, but some of our users require us to operate in an MDM environment. We may have to reconsider the project altogether based on this issuel
Same issue here...
Remember friends: please use the 👍 vote on the issue description. This helps us triage and prioritize!
Has anyone confirmed this is still an issue with flutter 2?
Yes :(. Flutter is still using Dart's BSD based sockets.
@gaaclarke
My concern is that maybe Apple doesn't allow access to the operating system's VPN settings unless accessing the network through their API (CFSocket). If Dart is using lower level unix sockets it might bypass the VPN. @zichangg
To quote the Apple support engineer Quinn "The Eskimo":
To support VPN On Demand you must use some sort of connect by name API. These includes
CFSocketStreamand anything layered on top of that (includingNSURLSession). Doing separate resolve then connect is more work and is incompatible with VPN On Demand. IMPORTANT BSD Sockets does not have a connect by name API. If you need to use BSD Sockets for other reasons — for example, you’re porting some cross-platform code — you can do some sneaky stuff to connect by name and then continue using BSD Sockets for the bulk of your I/O. See this post for an example of that.
Apple documents in Using Sockets and Socket Streams:
If you are writing code that cannot include Objective-C, use the
CFStreamAPI. It integrates more easily with other Core Foundation APIs thanCFSocket, and enables the cellular hardware on iOS (where applicable), unlike lower-level APIs. You can useCFStreamCreatePairWithSocketToHostorCFStreamCreatePairWithSocketToCFHostto open a socket connected to a given host and port and associate a pair of CFStream objects with it.
CFStreamCreatePairWithSocketToHost supports connect by name. It is also used in Eskimo's sample.
Connect by name changes the semantic of socket creation. Currently the dart side first calls to the runtime to resolve the hostname and then again to create the socket. Now the whole socket creation process needs to be done by the runtime.
Any updates?
Does anyone have a workaround?
@albatrosify
We eventually decided to create the following logic:
- On the "native" iOS side we implemented a small communication layer in swift, which can access our services even when the VPN is on.
- We try to reach the services with a timeout from flutter (because there is no other way to really tell if the user is on VPN or not... the libraries that we've found for this were all unreliable), and if the request fails, we call the native part, and ask that to handle the requests (with timeout) -> if it works than the user is on a VPN, if the time out is reached, the user simply has no connection.
It's an ugly solution, but couldn't find any other way.
I am in desperate need of a solution for Android and iOS. We've tried method channels and using the fast network library in Android and that did not work for communicating over Airwatch vpn. Any pointers, code, enchantments anyone may have, I am open to trying. Thank you!
Did you try the pub.dev package? https://pub.dev/packages/airwatch_socket_workaround Im pretty sure it uses a method channel, but sure seems like that should get you going.
On Tue, Oct 26, 2021 at 7:09 PM Munsterlander @.***> wrote:
I am in desperate need of a solution for Android and iOS. We've tried method channels and using the fast network library in Android and that did not work for communicating over Airwatch vpn. Any pointers, code, enchantments anyone may have, I am open to trying. Thank you!
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/flutter/flutter/issues/41500#issuecomment-952425687, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABIEY7UUXZTKDDIQ2VZHULUI47MNANCNFSM4I3J4CAQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.
Yes, we are working on that but also need android. I only have access to one Mac and it will be next week before I get to test on it. I do have android though and method channels are not working.
This is perhaps a bit off-topic for the issue, but can you explain why the method channel approach isnt working for android? I agree it's likely not the most efficient way to make network calls, but dont see why that wouldnt work.
On Wed, Oct 27, 2021 at 8:18 PM Munsterlander @.***> wrote:
Yes, we are working on that but also need android. I only have access to one Mac and it will be next week before I get to test on it. I do have android though and method channels are not working.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/flutter/flutter/issues/41500#issuecomment-953424569, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABIEY2E3L45KQBPHMHUTMTUJCQHTANCNFSM4I3J4CAQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.
cupertino_http is a new experimental Flutter plugin that provides access to Apple's Foundation URL Loading System - which honors iOS VPN settings.
cupertino_http has the same interface as package:http Client so it is easy to use in a cross-platform way. For example:
late Client client;
if (Platform.isIOS) {
final config = URLSessionConfiguration.ephemeralSessionConfiguration()
# Do whatever configuration you want.
..allowsCellularAccess = false
..allowsConstrainedNetworkAccess = false
..allowsExpensiveNetworkAccess = false;
client = CupertinoClient.fromSessionConfiguration(config);
} else {
client = IOClient(); // Uses an HTTP client based on dart:io
}
final response = await client.get(Uri.https(
'www.googleapis.com',
'/books/v1/volumes',
{'q': 'HTTP', 'maxResults': '40', 'printType': 'books'}));
I would really appreciate it if you can try cupertino_http out and see if it solves the VPN issues for you.
Comments or bugs in the cupertino_http issue tracker would be appreciated!
This sounds promising! will try it out
cupertino_http is the recommended solution for iOS. If you have issues using it to access your company VPN, please create a bug request in the package:http issue tracker.