The kCFStreamNetworkServiceTypeBackground for CFReadStream doesn't work.

I want to use the CFReadStream to implement downloading in background, via setting the kCFStreamNetworkServiceTypeBackground.

CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground);
NSDictionary *settings = @{CFBridgingRelease(kCFStreamSSLPeerName) : _request.host};
CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)settings);
CFReadStreamSetClient(readStream, kStreamEvents, requestCallback, &context);

static void requestCallback(CFReadStreamRef _Null_unspecified stream, CFStreamEventType type, void * _Null_unspecified clientCallBackInfo) {
    CFReadStreamViewController *controller = (__bridge CFReadStreamViewController *)clientCallBackInfo;
    switch (type) {
        // ....
        case kCFStreamEventHasBytesAvailable: {
            NSUInteger bytesRead = 0;
            uint8_t buffer[1024];
            bytesRead = [controller.inputStream  read:buffer maxLength:sizeof(buffer)];
            if (bytesRead > 0) {
                NSData *data = [[NSData alloc] initWithBytes:buffer length:bytesRead];
                NSLog(@"%ld bytes read------", data.length);
            }
        }
            break;
        // .....
    }
}

But when I press the home button of iPhone, the app enter the background, then no any logs in Xcode debug console. That means the download was suspended?


By the way, in order to support SNI, I use the CFReadStream instead of the NSURLSession.


Thanks for answering!

Replies

You have misunderstood what this stream type does. It does not enable your app to run a TCP connection for an arbitrarily long amount of time in the background. Rather, it’s all about quality of service (QoS), both within the kernel and on the ‘wire’.

Looking at the documentation for this API, it’s easy to see how you were lead astray. I’ve filed a bug (r. 48105066) about that.

In the meantime, the absolute best place to find information about these QoS values is the comments for the various

SO_NET_SERVICE_TYPE
values in
<sys/socket.h>
. And if you’re interested in more background info, watch WWDC 2016 Session 714 Networking for the Modern Internet.

Coming back to your actual goal, if you want to download a large resource while your app is in the background then

NSURLSession
is your only good choice. You wrote:

By the way, in order to support SNI, I use the

CFReadStream
instead of the
NSURLSession
.

What do you mean by this? I assume you’re referring to Server Name Indication, and that’s definitely supported by

NSURLSession
. Is that support insufficient? If so, please elaborate.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for answering!


Yeap! I meant the Server Name Indication. But in the official documentation of NSURLSession, I'm not able to locate it. Is there any property to set for NSURLSession or NSURLSessionConfiguration?


And then, we used the IP address to connect to server in HTTPS, not the domain.

And then, we used the IP address to connect to server in HTTPS, not the domain.

So, to confirm, you’d like to request a resource using an IP address, using a URL like

<https://1.2.3.4/MyResource.json>
, but explicitly set a DNS name so that the TLS connection created to fetch that resource presents an SNI extension with that name. Is that right?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Yeap! I request a resource by using a URL like https://1.2.3.4/MyResource.json. In http, the server can reponse correctly via the "Host" requestHeader key.

But I always get a SSL handshake error in https connecting to server via ip address.

I know that the server can not locate and reponse the appropriate certificate that client received in TLS connection.


So I need to use the SNI.But I read few articles from the web that NSURLSession/NSURLConnection unfortunately dosen't support any properties of SNI, CFReadStream-kCFStreamSSLPeerName is the only choice. Is that right?

OK, let’s take a step back here: Why are you connecting via an IP address rather than a DNS name?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Well, for improving network experience, because the DNS server may not analyse the optimal IP address(mean the optimal node of our servers) to clients.


And the important another one, avoiding the DNS hijacking from ISP.


Well, is that right I asked above:

But I read few articles from the web that NSURLSession/NSURLConnection unfortunately dosen't support any properties of SNI, CFReadStream-kCFStreamSSLPeerName is the only choice. Is that right?

Well, for improving network experience

My experience is that a lot of folks think they can improve the network experience by working at a lower level, but that’s really hard to do in general. For every case where you improve things (the user has a bogus DNS configured), there’s another case where you make things worse (IPv6-only networks). I wouldn’t go down this path unless you have very good analytics in place to confirm that you’re improving things overall.

And the important another one, avoiding the DNS hijacking from ISP.

IMO there are better ways to deal with this:

  • You can lean on Certificate Transparency.

  • For interactive requests, you can implement certificate pinning in your

    NSURLAuthenticationMethodServerTrust
    authentication challenge handler.
  • For large downloads, where you should use an

    NSURLSession
    background session and thus want to avoid dealing with authentication challenges, you can independently verify a signature on the download.

Anyway, regarding your SNI question:

  • Your are correct that

    NSURLSession
    has no way to force an SNI value for a connection that results from an request targeting an IP address. You should feel free to file an enhancement request for such a control. If you do, please post your bug number, just for the record.
  • Using

    CFStream
    isn’t your only option. At a minimum, you can use
    NSStream
    , which is bridged to
    CFStream
    and has a nicer API. Better yet, you can use the Network framework (a much nicer API!) and customise SNI via
    sec_protocol_options_set_tls_server_name
    .
  • However, this won’t really help because none of these APIs work while your app is suspended. So if you want to download a large file in the background, you have to use an

    NSURLSession
    background session, and that means either giving up on SNI or connecting via a DNS name.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"