14

I have an IoT application that uses Socket Programming to communicate with the device. All good up until iOS 11 released. It doesn't communicate in iOS 11 and above but it works in earlier versions ( Up to 10). Here is the code.

Establishing Socket Connection

(void)setUpSocketConnection {
    @try {
        CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef) VeranoHost,VeranoPort , &readStream, &writeStream);
        [self open];
    }
    @catch (NSException *exception) {
        NSLog(@"Open Exception:%@", exception.reason);
    }   
}

Opening Streams

(void)open {
    //NSLog(@"Opening streams.");
    _outputStream = (__bridge  NSOutputStream *)writeStream;
    _inputStream = (__bridge  NSInputStream *)readStream;
    [_outputStream setDelegate:self];
    [_inputStream setDelegate:self];

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
    dispatch_async(queue, ^ {
            [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [[NSRunLoop currentRunLoop] run];
    });

    [_outputStream open];
    [_inputStream open];

    //[self disableNaglesAlgorithmForStream:_inputStream];
   // NSLog(@"Connected");
//    self.timeOutTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
//                                                         target:self
//                                                       selector:@selector(timerTimeOutAction:)
//                                                       userInfo:nil
//                                                        repeats:NO];
}

Writing to Output Stream

(void)writeData:(NSString *)message forSocketType:(SOCKETTTYPE)socketType {
    self.socketType = socketType;
    NSData *data = [[NSData alloc] initWithData:[message dataUsingEncoding:NSASCIIStringEncoding]];
    [_outputStream write:[data bytes] maxLength:[data length]];
    [_outputStream close];

}

Event Handler

(void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
   // NSLog(@"stream event %lu", (unsigned long)streamEvent);
    switch (streamEvent) {
        case NSStreamEventOpenCompleted:{

           // NSLog(@"NSStreamEventOpenCompleted :::: Stream opened and connected");
        }
            break;
        case NSStreamEventHasBytesAvailable:
            // NSLog(@"NSStreamEventHasBytesAvailable :::: Stream opened and connected");
            if (theStream == _inputStream) {
                uint8_t buffer[1024];
                NSInteger len;

                while ([_inputStream hasBytesAvailable])
                {
                    len = [_inputStream read:buffer maxLength:sizeof(buffer)];
                    if (len > 0)
                    {
                        NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];

                        if (nil != output)
                        {
                            NSLog(@"server said: %@", output);
                            [self messageReceived:output];
                        }
                    }
                }
            }
            break;
        case NSStreamEventHasSpaceAvailable:
           // NSLog(@"NSStreamEventHasSpaceAvailable :::: Stream has space available now");
            break;

        case NSStreamEventErrorOccurred:{
           NSError *theError = [theStream streamError];
            NSLog(@"Error Description:%@",theError.localizedDescription);
            [self close];
            if(self.delegate &&[self.delegate respondsToSelector:@selector(socketHandlerItem:failureWithError:forSocketType:)]){
                [self.delegate socketHandlerItem:self failureWithError:[theStream streamError] forSocketType:self.socketType];
            }
             //NSLog(@"NSStreamEventErrorOccurred :::: %@",[theStream streamError].localizedDescription);
        }
            break;

        case NSStreamEventEndEncountered:
            [theStream close];
            [theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            if(self.delegate &&[self.delegate respondsToSelector:@selector(socketHandlerItem:eventStopedWithstatus:forSocketType:)]){
                [self.delegate socketHandlerItem:self eventStopedWithstatus:YES forSocketType:self.socketType];
            }
           // NSLog(@"NSStreamEventEndEncountered :::: close stream Disconnected");
            break;
        default:
           // NSLog(@"Unknown event");
            break;
    }
}

So, when connecting to the socket it enters to the NSStreamEventErrorOccurred in the event handler and logs - The operation couldn’t be completed. No route to host

Any help would be greatly appreciated.

Update Dec 19, 2017

  • Got a Reachability wrapper that allows to discover host address and I can confirm that it returns the NetworkStatus ReachableViaWiFi
  • Socket communication works fine, I tried by creating a sample server socket, able to send and receive data.

Update Dec 20, 2017 (1)

IoT device Details - USR-WIFI232-S Low Power WiFi Module

User Manual Here

Update Dec 20, 2017 (2)

Eurekaa, but the solution can't be applied in real time. While setting a nearest range of static IP address in iOS 11 corresponding to IoT module's host address the communication works fine. but it doesn't work with the dynamically allocated IP address.

Stella
  • 1,817
  • 6
  • 30
  • 55
  • What is the error message/code? – Paulw11 Dec 18 '17 at 09:59
  • @Paulw11 - `NSError *theError = [theStream streamError];` I've casted to `NSError` then logged `localizedDescription` and it shows `The operation couldn’t be completed. No route to host` – Stella Dec 18 '17 at 10:02
  • 1
    So, it can't reach the host since it received an ICMP response stating that there was no way of routing to it. Is the host on the local network? Have you tried a network capture to seed ios is event attempting a connection? – Paulw11 Dec 18 '17 at 10:04
  • @Paulw11, basically its an IoT device and I'm connecting iPhone to the WiFi provided by the device. WiFi communication works in older versions of iOS. So, we can assume this is not the issue with device right? – Stella Dec 18 '17 at 10:09
  • Yes, the host is on the local network as I'm connecting to device WiFi. – Stella Dec 18 '17 at 10:11
  • Better change ur code to SocketRocket then it will be ok. – GeneCode Dec 19 '17 at 02:16
  • @GeneCode `SocketRocket` wrapper is used for communicating with web socket right? Well, that's fine, but why this code doesn't work in iOS 11 and above, any idea? – Stella Dec 19 '17 at 04:57
  • 1
    Yup. As for why it fail in iOS11, I am guessing it's a bug. Have u tried asking in Apple Developer forum? iOS11 is so buggy i tell u that – GeneCode Dec 19 '17 at 05:59
  • @GeneCode Yes, It has been reported [thread](https://forums.developer.apple.com/thread/94002) – Stella Dec 19 '17 at 09:14
  • 1
    @Stella. Can you try this https://www.raywenderlich.com/157128/real-time-communication-streams-tutorial-ios – Vaisakh Dec 20 '17 at 12:56
  • @Vaisakh Thanks, Let me try this out. – Stella Dec 20 '17 at 13:54
  • Are you using pods for sockets? – User511 Dec 27 '17 at 05:38

1 Answers1

1

I Wonder if it App Transport Security (ATS), See: https://www.nowsecure.com/blog/2017/08/31/security-analysts-guide-nsapptransportsecurity-nsallowsarbitraryloads-app-transport-security-ats-exceptions/

For iOS 11 ATS updates some ATS updates are expected as a part of that:

  • TLSv1.3 will receive preliminary support
  • 3DES will be removed from the approved list of ciphers
  • Certificates signed with SHA1 will no longer be accepted
  • Certificates signed with RSA keys must have 2048-bit key lengths or larger

PLIST and Entitlements are you using in your app?

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    <key>NSExceptionDomains</key>
    <dict>

        <key>creativecommons.org</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
            <false/>
        </dict>

        <key>localhost</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>

    </dict>
</dict>

Also one more option, if you want to disable ATS you can use this :

<key>NSAppTransportSecurity</key>  
 <dict>  
      <key>NSAllowsArbitraryLoads</key><true/>  
 </dict>

But this is not recommended !

Mark
  • 78
  • 6