7 Replies
      Latest reply on Feb 21, 2019 1:44 AM by eskimo
      iOSerKi Level 1 Level 1 (0 points)

        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!

        • Re: The kCFStreamNetworkServiceTypeBackground for CFReadStream doesn't work.
          eskimo Apple Staff Apple Staff (11,025 points)

          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"

            • Re: The kCFStreamNetworkServiceTypeBackground for CFReadStream doesn't work.
              iOSerKi Level 1 Level 1 (0 points)

              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.

                • Re: The kCFStreamNetworkServiceTypeBackground for CFReadStream doesn't work.
                  eskimo Apple Staff Apple Staff (11,025 points)

                  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"

                    • Re: The kCFStreamNetworkServiceTypeBackground for CFReadStream doesn't work.
                      iOSerKi Level 1 Level 1 (0 points)

                      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?

                        • Re: The kCFStreamNetworkServiceTypeBackground for CFReadStream doesn't work.
                          eskimo Apple Staff Apple Staff (11,025 points)

                          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"

                            • Re: The kCFStreamNetworkServiceTypeBackground for CFReadStream doesn't work.
                              iOSerKi Level 1 Level 1 (0 points)

                              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?

                                • Re: The kCFStreamNetworkServiceTypeBackground for CFReadStream doesn't work.
                                  eskimo Apple Staff Apple Staff (11,025 points)

                                  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"