My iOS app is having networking issues when it loads images from an HTTPS nginx web server with SPDY enabled. The issue is described here: Sending SPDY requests results in "The request timed out" errors with NSUrlSession in iOS
I am using NSURLSession for my networking. I did experiments and confirmed that my problem is fixed if I turn SPDY off on the server side. Unfortunately I can not turn SPDY off on production nginx server because I have no control over it. Can I turn SPDY off in the iOS app instead?
OS X: 10.10.4 (14E46), iOS: 8 and 9, Xcode: 7.0 (7A218), nginx: 1.9.4
You might be able to pre-populate the Upgrade header field in your NSURLRequest with something that nginx will handle as a non-SPDY, non-HTTP/2 request, e.g.
Upgrade: TLS/1.2
but the URL loading system might just stomp on it, in which case the only way to disable it would by configuring the server to compare the user agent string (or any other header that you provide) and refusing to upgrade the connection.
Related
In our app we are reliant on a web socket inside a WKWebview. In previous releases this web socket worked well. In the iOS 15 betas though this web socket behaves differently: it connects to our server successfully but once the client tries to send any data through it the web socket throws an error and closes with a non descriptive error:
The operation couldn’t be completed. (kNWErrorDomainPOSIX error 54 -
Connection reset by peer)
Looking into the system log the deepest error I can make out is:
nw_protocol_boringssl_error(1772) [C12.1.1:2][0x102e0d540] Lower
protocol stack error post TLS handshake. [54: ]
A test web socket to another server seems to be working.
I also notices that a MitM proxy like Charles no longer shows web socket connections in the iOS 15 beta. This just indicates that something might have changed.
Because the communication via this socket is very important for the functionality of our app I must know what the issue is. I tried adding ATS exceptions for the URL of the socket to no avail.
Maybe this is a temporary bug in iOS 15 that will be fixed until its released? Or maybe anyone has experienced this kind of error in the past?
It seems that the issue is related with websocket compression on IOS 15 (permessage deflate). Disabling the compression for ios 15 devices on the server side helped.
This is obviously not a solution, but only a quick fix (if you have an access to the server). Here is a discussion on the same topic.
I need to implement SNI (Server Name Indication) on client side for iOS and OS X.
I tried google about this but I was unable to find anything useful (only some instruction for openSSL). Nothing related to NSURLSession or CFNetwork.
I need solution when using both NSURLSession or CFNetwork (CFStreamCreatePairWithSocketToHost - this is used in Socket Rocket).
Is it automatically supported by Apple framework? Or do I need add some extra code to enforce it? Or maybe it is completely unsupported?
SNI is supported on iOS. If you specify the kCFStreamSSLPeerName value in kCFReadStreamPropertySSLSettings of your CFReadStream, it will be used as the server name during the TLS handshake.
I have an issue with a single (at least for now) iPhone not being able to connect to websockets. Unfortunately I don't have access to the device and I can't run more tests on it at the moment, so no proper network dumps or anything else deeper level debugging available. Can't test on other network connections either at the moment.
Everything works perfectly with Chrome 45, Firefox 40, Chrome 45 on Android (many devices), Safari on iOS9 (device A), Safari on iOS8 (device B) etc. No errors, no cutoffs, nothing. Just works.
My websocket server runs on IIS 8.5 / ASP.NET, created as an API controller with the typical way (PullHandler is an async Task whileing until connection closes)
if (HttpContext.Current.IsWebSocketRequest)
{
HttpContext.Current.AcceptWebSocketRequest(PullHandler);
return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
}
else
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Bad request");
This should not cause any issues, especially this one. The client side JavaScript is also the simple
new WebSocket("ws://" + window.location.host + "/api/Some/Endpoint);
I am using plain HTTP, no SSL/TLS.
When trying on iPhone device X with iOS9 (13A344, not entirely sure of which model) it just can't get the connection. I am also using SignalR which automatically goes into longPolling transport on this device. With others it's nicely on webSockets transport.
Quick testing on UK Orange 3G connection showed on my HTTP logs that for some reason the request for my endpoint has gone to the else branch, like something had stripped the upgrade headers away.
GET /api/Some/Endpoint 80 - Mozilla/5.0+(iPhone;+CPU+iPhone+OS+9_0+like+Mac+OS+X)+AppleWebKit/601.1.46+(KHTML,+like+Gecko)+Version/9.0+Mobile/13A344+Safari/601.1 - 400
Status 400 is clearly showing the bad request that my code is sending if the request is not a proper websocket request. I don't know if there could be some transparent proxy, that could explain this. The 400 response was received without any delays.
But this was a problem also through wifi on the same device. Again, a laptop with OS X and Safari had no issues on the same wifi to connect to SignalR and my websocket server. Only the iPhone had issues. On wifi I didn't get any attempts to connect on the logs and SignalR also immediately went into longPolling transport. The wifi apparently used Virgin Media's network based on IP address.
On wifi there is first an open event. Then after some time there is a close event with status 1006, which is abnormal termination without a close packet. My code tries to connect again and the same happens. Strangely there is no log entry at the web server, but it might just be Safari's way to post the open event even though an actual connection was not established and after a timeout it sends the close event.
The device should not have any proxies set up and other browsing works without issues. Unfortunately I couldn't get any information about any other site that would use websockets working or not on that device.
Has anyone run into this kind of situation? I do understand that the 3G might just be a misbehaving proxy, but the wifi issue cannot be explained with that.
Just want to expand the universe for this issue.
I Have an app that runs great in simulator. App has both http Jason IO and web socket IO.
Now that I can target my iPhone and not the simulator. App on iPhone successfully does the http Json stuff but not the web socket stuff.
While I am not using safari and js my symptoms are very similar to yours. I am using Xcode 7 to generate code that targets to iPhone 5 using iOS 9.0.
My iOS app loads images from an nginx HTTP server. After I send 400+ such requests the networking 'gets stuck' and all subsequent HTTP requests result in "The request timed out" error. I can make the images load again only when I restart the app.
Details:
I am using NSURLSession.sharedSession().dataTaskWithURL to send four hundred HTTP GET requests to jpeg files.
Requests are sent sequentially, one after another. The interval between requests is 10 ms.
Each previous unfinished request is cancelled with cancel() method of NSURLSessionDataTask object.
Interestingly:
I can only have this issue with HTTPS requests and when SPDY is enabled on the server.
Non-secure HTTP requests work fine.
Non-SPDY HTTPS requests work fine. I tested it by turning SPDY off on the server side, in the nginx config.
Problem appears both on iOS 8 and 9, on physical device and in the simulator. Both on Wi-Fi and LTE.
When I look at nginx access logs, I can still see the 'stuck' requests coming in. Important nuance: the request log record appears at the exact moment when the iOS app is giving up on it after the time out period ends.
I was hoping to analyze HTTP requests with Charles Proxy but the problem cures itself when requests go through Charles. That is - everything works with Charles, much like effect in quantum mechanics when the fact of looking influences the outcome.
I was able to reproduce the issue when the iOS app connected to two different servers with vastly different nginx configurations. This probably means that the issue is not related to a particular nginx setup.
I analyzed the app using "Activity Monitor" instrument. The number of threads it is using during the bulk HTTP requests jumps from 5 to 10. In comparison, when I send just a single HTTP requests the number of threads jumps to 8. CPU load rarely goes above 30%.
What can be the cause of the issue? Can anyone recommend other ways or tools for analysing and debugging it?
Analysing with scheduling instrument
Demo app
This demo app reproduces the issue 100% of the time for me.
https://github.com/exchangegroup/ImageLoadDemo
Versions and settings
My nginx config: http://pastebin.com/pYYjdxfP
OS X: 10.10.4 (14E46), iOS: 8 and 9, Xcode: 7.0 (7A218), nginx: 1.9.4
Not ideal workaround
I managed to keep requests working only if I create a new NSURLSession for each individual request and clear the previous session with finishTasksAndInvalidate or invalidateAndCancel.
// Request 1
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration)
session.dataTaskWithURL ...
// Request 2
// clear the previous request
session.finishTasksAndInvalidate()
let session2 = NSURLSession(configuration: configuration)
session2.dataTaskWithURL ...
One possibility is that iOS started sending the request, and then packet loss prevented the headers and request body from being fully delivered.
Another possibility that comes to mind is that your server may not be logging the request until it actually finishes trying to deliver it, which would make the time stamps in the server logs line up with when the connection was closed, rather than when it was opened. (IIRC, that's what Apache does; I haven't worked with nginx, so I can't speak for its behavior.) If that's the case, then this is just a simple connection stall. As for why it is stalling, I couldn't guess.
Does the problem occur exclusively for HTTPS traffic? If you can reproduce it with HTTP, you don't need Charles Proxy; just use OS X's "Internet Sharing" feature, and capture the packets with tcpdump or wireshark, listening on the bridge interface. If you can't reproduce it with HTTP, my money would be on a problem with fetching the CRLs or performing the OCSP check while validating the server's certificate.
Is your app ending up with a huge number of threads as a result of excessive async dispatching to new queues, by any chance? Because that could easily cause all sorts of odd misbehavior.
How long is the timeout? If it is too short, your app might simply be running up against performance limitations of the hardware while processing the results of 400 requests delivered in only four seconds.
Also, are you trying to schedule these requests simultaneously? Because I seem to recall reading about a bug that causes NSURLSession to hit a brick wall if you start too many tasks in a single session at the same time. You might try adding tasks only after the number of tasks in a session drops below some threshold and see if that fixes the problem.
Problem summary: Same client-server configuration, same network topology, same device (Bold 9900) - works perfectly well on OS 7.0 but doesn't work as expected on OS 7.1 and the secured tls connection is being closed by the server after a very short time.
Question: Is there supposed to be any difference in secured tls connection opening between OS 7.0 and OS 7.1? Did RIM change anything in tls infrastructure in 7.1? Is there something that may cause premature secured tls connection closure in 7.1?
My application opens a secured tls connection to a server. The connection is kept alive by a application layer keep-alive mechanism and remains open until the client closes it. Attached is a simplified version of the actual code that opens connection and reads from the socket. The code works perfectly on OS 5.0-7.0 but doesn't work as expected on OS 7.1.
When running on OS 7.1, the blocking read() returns with -1 (end of the stream has been reached) after very short time (10-45 seconds). For OS 5.0-7.0 the call to read() remains blocking until next data arrives and the connection is never closed by the server.
Connection connection = Connector.open(connectionString);
connInputStream = connection.openInputStream();
while (true) {
try {
retVal = connInputStream.read();
if (-1 == retVal) {
break; // end of stream has been reached
}
} catch (Exception e ) {
// do error handling
}
// data read from stream is handled here
}
UPDATE 1:
Apparently, the problem appears only when I use secured tls connection (either using mobile network or WiFi) on OS 7.1. Everything works as expected when opening a non secured connection on OS 7.1.
For tls on mobile networks I use the following connection string:
connectionString = "tls://someipaddress:443;deviceside=false;ConnectionType=mds-**secret**;EndToEndDesired";
For tls on Wifi I use the following connection string:
connectionString = "tls://someipaddress:443;interface=wifi;EndToEndRequired"
UPDATE 2:
The connection is never idle. I am constantly receiving and sending data on it. The issue appears both when using mobile connection and WiFi. The issue appears both on real OS 7.1 devices and simulators. I am starting to suspect that it is somehow related either to the connection string or to the tls handshake.
UPDATE 3:
According to Wireshark's captures that I made with the OS 7.1 simulator, the secured tls connection is being closed by the server (client receives FIN). For the moment I don't have the server's private key therefore I unable to debug the tls handshake, but I am more sure than ever that the root cause is tls handshake.
UPDATE 4:
The secured tls connection drop appears when RSA 2048 AES 256 cipher suite is negotiated on OS 7.1 only. Same cipher suite works perfectly well on OS 7.0. On the other hand, when using DHE/DSS 768 AES 128 cipher suite, everything works as expected on OS 7.1 and the connection remains stable. It must be somehow related to RSA 2048 AES 256 cipher suite.ideas?
I haven't worked with tls connections, but for plain sockets you can specify an explicit timeout in milliseconds in the connection URL, via an appender: ";ConnectionTimeout=60000"
Also, you will likely need to add some sort of ping mechanism on the socket, otherwise intermediate routers will likely shut down the connection eventually, even with keep-alive.
I've finally figured it out with RIM's help (you can find the relevant ticket here). All credit goes to RIM.
In OS 7.1, a new security measure when creating TLS/SSL connection. Here is a quote from RIM's article.
A new attack was recently discovered that allows an adversary to decrypt TLS 1.0 and SSL 3.0 traffic using a combination of eavesdropping and chosen plaintext attack when CBC chaining mode is used.
To combat this, we implemented a change that was compliant with SSL specifications and was widely adopted by most browsers such as Mozilla® Firefox® and Google Chrome™. We have implemented a counter measure where we split TLS records into two records: the first record containing a single byte of the data and the second records containing the rest of the data, which stops an attacker from exploiting this vulnerability.
Full article can be accessed here.
To make a long story short, in order to reduce incompatibility issues with my server I had to add "DisableCbcSecurity=true" attribute to the connection string before I opened the connection.
Note that this workaround will work for devices running version 7.1.0.288 and higher though I also so it working properly on Torch 9860 running 7.1.0.267.