Is it possible to activate TCP keepalive on Apple iOS devices - ios

Apple device === Router === WiFi module
Apple device(iPhone) is connecting to WiFi module port 2000 with TCP connection. I want to activate TCP keepalive packet sending on Apple device to find out when TCP connection to WiFi module is lost (module is switched off).
My stream setup
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)CFBridgingRetain(moduleIPaddress), port2000, &readStream, &writeStream);
outputStream = (NSOutputStream *)CFBridgingRelease(writeStream);
inputStream = (NSInputStream *)CFBridgingRelease(readStream);
[outputStream setDelegate:(id)self];
[inputStream setDelegate:(id)self];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
[inputStream open];
I tried to activate keepalive according to David H post Keeping socket connection alive in iOS
- (void) stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
switch (streamEvent) {
case NSStreamEventOpenCompleted:
if (theStream == outputStream) {
/*
CFDataRef data = (CFDataRef)CFWriteStreamCopyProperty((__bridge CFWriteStreamRef)theStream, kCFStreamPropertySocketNativeHandle);
if(data) {
CFSocketNativeHandle socket_handle = *(CFSocketNativeHandle *)CFDataGetBytePtr(data);
CFRelease(data);
NSLog(#"SOCK HANDLE: %x", socket_handle);
//Enabling keep alive
int opt = 1;
if( setsockopt( socket_handle, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof( opt ) ) < 0 )
{
NSLog(#"Yikes 2: failed to set keepalive! ERRNO: %s", strerror(errno));
}
}
*/
NSData *data = (NSData *)[theStream propertyForKey:(__bridge NSString *)kCFStreamPropertySocketNativeHandle];
if(data) {
CFSocketNativeHandle socket_handle = *(CFSocketNativeHandle *)[data bytes];
NSLog(#"SOCK HANDLE: %x", socket_handle);
//Enabling keep alive
int opt = 1;
if( setsockopt( socket_handle, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof( opt ) ) < 0 )
{
NSLog(#"Yikes 2: failed to set keepalive! ERRNO: %s", strerror(errno));
}
}
}
Both options print out SOCK HANDLE: 9 , no error messages. When WiFi module is switched off connection still stays open for 30 minutes or more when I do not send data to outputstream. If I send data to outputstream I get NSStreamEventErrorOccurred - Error Domain=NSPOSIXErrorDomain Code=60 "The operation couldn’t be completed. Operation timed out" after around 60 seconds. I tried this with Apple device. When I tried with iOS Simulator I did not see keepalive packets with Wireshark.
NSStream tcp keepalive in iOS also describes keepalive setup. Martin R example code activates keepalive for inputstream that seems wrong.
Is it possible to activate TCP keepalive on Apple iOS devices like iPhone (should be according to David H)? If it is possible how it should be done (what is missing from my code)?

Thank you rintaro for directing me to right direction.
Stream setup stayed the same as in my question.
I tested different setups and did not find difference between socket handle detection examples described in my question.
Code that activates keepalive with iPod device and iOS 7.1
case NSStreamEventOpenCompleted:
#try {
if (theStream == outputStream)
{
NSData *data = (NSData *)[theStream propertyForKey:(__bridge NSString *)kCFStreamPropertySocketNativeHandle];
if(data)
{
CFSocketNativeHandle socket_handle = *(CFSocketNativeHandle *)[data bytes];
//NSLog(#"SOCK HANDLE: %x", socket_handle);
//SO_KEEPALIVE option to activate
int option = 1;
//TCP_NODELAY option to activate
int option2 = 1;
//Idle time used when SO_KEEPALIVE is enabled. Sets how long connection must be idle before keepalive is sent
int keepaliveIdle = 10;
//Interval between keepalives when there is no reply. Not same as idle time
int keepaliveIntvl = 2;
//Number of keepalives before close (including first keepalive packet)
int keepaliveCount = 4;
//Time after which tcp packet retransmissions will be stopped and the connection will be dropped.Stream is closed
int retransmissionTimeout = 5;
if (setsockopt(socket_handle, SOL_SOCKET, SO_KEEPALIVE, &option, sizeof (int)) == -1)
{
NSLog(#"setsockopt SO_KEEPALIVE failed: %s", strerror(errno));
}else
{
NSLog(#"setsockopt SO_KEEPALIVE ok");
}
if (setsockopt(socket_handle, IPPROTO_TCP, TCP_KEEPCNT, &keepaliveCount, sizeof(int)) == -1)
{
NSLog(#"setsockopt TCP_KEEPCNT failed: %s", strerror(errno));
}else
{
NSLog(#"setsockopt TCP_KEEPCNT ok");
}
if (setsockopt(socket_handle, IPPROTO_TCP, TCP_KEEPALIVE, &keepaliveIdle, sizeof(int)) == -1)
{
NSLog(#"setsockopt TCP_KEEPALIVE failed: %s", strerror(errno));
}else
{
NSLog(#"setsockopt TCP_KEEPALIVE ok");
}
if (setsockopt(socket_handle, IPPROTO_TCP, TCP_KEEPINTVL, &keepaliveIntvl, sizeof(int)) == -1)
{
NSLog(#"setsockopt TCP_KEEPINTVL failed: %s", strerror(errno));
}else
{
NSLog(#"setsockopt TCP_KEEPINTVL ok");
}
if (setsockopt(socket_handle, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &retransmissionTimeout, sizeof(int)) == -1)
{
NSLog(#"setsockopt TCP_RXT_CONNDROPTIME failed: %s", strerror(errno));
}else
{
NSLog(#"setsockopt TCP_RXT_CONNDROPTIME ok");
}
if (setsockopt(socket_handle, IPPROTO_TCP, TCP_NODELAY, &option2, sizeof(int)) == -1)
{
NSLog(#"setsockopt TCP_NODELAY failed: %s", strerror(errno));
}else
{
NSLog(#"setsockopt TCP_NODELAY ok");
}
}
}
When TCP connection is idle, app starts to send keepalive after 10 second intervals. When there is no answer, app starts to send keepalive packets with 2 second interval and closes stream when there is 4 keepalive packets without reply. This means that when WiFi module is turned off after successful keepalive exchange it takes maximum 18 seconds on idle to close stream when connection is lost.
Another parameter is retransmission timeout value. Default seems to be around 6 seconds. This timer starts when there is first packet retransmission. App tries to retransmit packet but if packet retransmission fails during 5 seconds stream is closed.
NB! Different results in device and simulator.
iPod settings activation log
setsockopt SO_KEEPALIVE ok
setsockopt TCP_KEEPCNT ok
setsockopt TCP_KEEPALIVE ok
setsockopt TCP_KEEPINTVL ok
setsockopt TCP_RXT_CONNDROPTIME ok
setsockopt TCP_NODELAY ok
Simulator settings activation log
setsockopt SO_KEEPALIVE ok
setsockopt TCP_KEEPCNT failed: Protocol not available
setsockopt TCP_KEEPALIVE ok
setsockopt TCP_KEEPINTVL failed: Protocol not available
setsockopt TCP_RXT_CONNDROPTIME ok
setsockopt TCP_NODELAY ok
This means that it is not possible to activate and trace with iOS simulator.
I did not find why error message "Protocol not available" is displayed in simulator.

see: http://en.wikipedia.org/wiki/Keepalive#TCP_keepalive
Usually, keepalive time (net.inet.tcp.keepidle) is 7200sec by default.
I don't know that is true in iOS or not, but "no less than 2 hours" is clearly required by RFC 1122.
This interval MUST be
configurable and MUST default to no less than two hours.
So, how could you configure it in your application? try:
#import <sys/types.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <netinet/tcp.h>
int on = 1;
int delay = 120;
setsockopt(socket_handle, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));
setsockopt(socket_handle, IPPROTO_TCP, TCP_KEEPALIVE, &delay, sizeof(delay));
see man tcp.
I confirmed this works on iOS Simulator (iPhone 5s / iOS 8.0).

Related

sim808 POST request not sending data

I am trying to send data to a web app using the TCP connection of a SIM808 that is connected to my NodeMcu. Everything appears to work fine, connection is perfect but at the send part, the post request does not execute fully.
below is my code and output
please help check the http_cmd line. In my output, after the content is shown in the output I am suppose to see something like Recv: ### bytes but instead I get ☐fetch over
#include <ESP8266WiFi.h>
#include <DFRobot_sim808.h>
#include <sim808.h>
#include <SoftwareSerial.h>
DFRobot_SIM808 sim808(&Serial);
char http_cmd[] = "POST /tracker/ HTTP/1.1\r\nContent-Type: application/json\r\nContent-Length: 34\r\nHost: haul1.herokuapp.com\r\n\r\n{ \"trackerId\": \"2222\",\"height\": \"42\" }";
char buffer[512];
void setup(){
//mySerial.begin(9600);
Serial.begin(9600);
//******** Initialize sim808 module *************
while(!sim808.init()) {
delay(1000);
Serial.print("Sim808 init error\r\n");
}
delay(3000);
//*********** Attempt DHCP *******************
while(!sim808.join()) {
Serial.println("Sim808 join network error");
delay(2000);
}
//************ Successful DHCP ****************
Serial.print("IP Address is ");
Serial.println(sim808.getIPAddress());
//*********** Establish a TCP connection ************
if(!sim808.connect(TCP,"haul.herokuapp.com", 80)) {
Serial.println("Connect error");
}else{
Serial.println("Connect haul.herokuapp.com success");
}
//*********** Send a GET request *****************
Serial.println("waiting to fetch...");
sim808.send(http_cmd, sizeof(http_cmd)-1);
while (true) {
int ret = sim808.recv(buffer, sizeof(buffer)-1);
if (ret <= 0){
Serial.println("fetch over...");
break;
}
buffer[ret] = '\0';
Serial.print("Recv: ");
Serial.print(ret);
Serial.print(" bytes: ");
Serial.println(buffer);
break;
}
//************* Close TCP or UDP connections **********
sim808.close();
//*** Disconnect wireless connection, Close Moving Scene *******
sim808.disconnect();
}
void loop(){
}
OUTPUT:
OUTPUT from Serial Monitor
Sim808 init error
AT
Sim808 init error
AT
AT+CFUN=1
AT+CPIN?
AT+CSTT="","",""
AT+CIICR
AT+CIFSR
IP Address is 10.10.124.205
AT+CIPSTART="TCP", “haul.herokuapp.com”, 80
Connect haul.herokuapp.com success
waiting to fetch...
AT+CIPSEND=145
POST /tracker/ HTTP/1.1
Content—Type: application/json
Content—Length: 34
Host: haul.herokuapp.com
{"trackerId": "2222","height": "42"}☐fetch over...
AT+CIPSTATUS
AT+CIPSHUT

iOS check non-default open ports

Trying to implement a JB check for non-default open ports (i.e., 22/TCP SSH):
/**
Checks for non-standard ports
*/
inline int isPortOpen(short port)__attribute__((always_inline));
-(BOOL)isPortOpen:(short) port
{
struct sockaddr_in addr;
int sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if(inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr)) {
int result = connect(sock,(struct sockaddr *)&addr, sizeof(addr));
// error :(
int w00t = errno;
NSLog(#"Error: %i",w00t);
if(result == 0){
NSLog(#"FAILED JB CHECK -- non-standard port open!");
return YES;
}
close(sock);
}
NSLog(#"PASSED JB CHECK -- non-standard ports closed.");
return NO;
}
But connect() fails (result is -1) and the errno is 1 -- EPERM
https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man2/intro.2.html
1 EPERM Operation not permitted. An attempt was made to perform an operation
limited to processes with appropriate privileges or to the
owner of a file or other resources.
Looking at the manual for connect(), EPERM is not one of the possible return errors.
https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man2/connect.2.html
So, the only reason I can see for getting EPERM is the sandbox, but:
Running on a jailbroken iPod (iOS 8.4)
The container seatbelt profile allows outbound network connections by default
I'd like to understand what's happening. Note that I've tried connecting to the wireless interface as well, instead of loopback.

NSInputStream send NSStreamEventEndEncountered after Facebook connexion

I use SocketIO project on my iOS app to connect to my node.js server and everything works great until I choose to connect to facebook. When I connect to Facebook I send some data to my server and he answer with some data like "user already connected, user created in database" etc. And after that, my NSInputStream seems to be at 0 and so my connexion is closed. I don't know what to do, I spent 14 hours on that and still get this issue...
Can you help me with ?
Edit: Looks like the problem is in SRWebSocket.m line 1462
For more details I added some log :
case NSStreamEventHasBytesAvailable: {
SRFastLog(#"NSStreamEventHasBytesAvailable %#", aStream);
const int bufferSize = 2048;
uint8_t buffer[bufferSize];
while (_inputStream.hasBytesAvailable) {
int bytes_read = [_inputStream read:buffer maxLength:bufferSize];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"BYTES_READ = %i", bytes_read);
});
if (bytes_read > 0) {
[_readBuffer appendBytes:buffer length:bytes_read];
} else if (bytes_read < 0) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"ERROR = %#", _inputStream.streamError);
});
[self _failWithError:_inputStream.streamError];
}
if (bytes_read != bufferSize) {
break;
}
};
[self _pumpScanner];
break;
}
And the result :
onData 5:::{"name":"initalLogin","args":[{"guid":"af78bdf0-f17d-ede2-7dd6-22708d1330f7","usedWithFaceBook":true}]}
start/reset timeout
CURRRENT SIZE = 0
READ BUFFER LENTGH = 373
event
CURRRENT SIZE = 0
BYTES_READ = 0
NSStreamEventEndEncountered <__NSCFInputStream: 0xab21660>
Reason = Stream end encountered
onDisconnect()
DECONNEXION = The operation couldn’t be completed. (SocketIOError error -4.)

TCP Socket Programming in iOS. Server Client Response

I'm almost done with this task but i'm stuck a point due to which i'm getting partial result.
I have server(linux or windows) and client(iOS) between which TCP IP socket connection exist. I have used form load in my iphone simulator where the connection between server and iphone happens automatically as the application opens. Server send the data back what ever I send on simulator and print it in log. But i'm not able to exactly receive the whole response. For "Innovations" I receive maybe just "in" or "Innova"etc.. Below are the code snippets.
void TCPClient()
{
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, port, &readStream, &writeStream);
[NSThread sleepForTimeInterval:2]; //Delay
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
if(!CFWriteStreamOpen(writeStream))
{
NSLog(#"Error Opening Socket");
}
else
{
UInt8 buf[] = "Innovations";
int bytesWritten = CFWriteStreamWrite(writeStream, buf, strlen((char*)buf));
NSLog(#"Written: %d", bytesWritten);
}
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
if(!CFReadStreamOpen(readStream))
{
NSLog(#"Error reading");
}
else
{
UInt8 bufr[15];
int bytesRead = CFReadStreamRead(readStream, bufr,strlen((char*)bufr));
NSLog(#"Read: %d", bytesRead);
NSLog(#"buffer: %s", bufr);
}
}
Notice in the read I did change the array size. But I still get the error. Same in the case of IBAction of a button. Even in that for every click i'm sending a data and i'm not getting the response of the same data.
Can valuable suggestion???
One error is that
int bytesRead = CFReadStreamRead(readStream, bufr,strlen((char*)bufr));
should be
int bytesRead = CFReadStreamRead(readStream, bufr, sizeof(bufr));
The last parameter of CFReadStreamRead is the capacity of the read buffer and determines the maximum number of bytes read. strlen((char*)bufr) is the length of the string currently in the buffer. You should also NULL-terminat the string in bufr before printing it.
With this modification, your program might work with short strings. But there will be problems as soon as you try to send/receive larger amounts of data.
A socket write can write less bytes than you asked it to, and a socket read can return less bytes than you requested.
Have a look at the Stream Programming Guide which describes how to register the socket streams with the runloop and handle stream events asynchronously.

WinSock UDP socket creation order in loopback.

I have a network application meant for a private LAN. I am doing my testing using loopback. When I test on the LAN the socket creation order does not matter. If I test using loop back 127.0.0.1 then there is a socket creation ordering issue. Why is it different on loop back?
Here are more details...
There is one server, and many client instances. The server is broad casting data over UDP. The clients receive the data and process it.
I need to have the network layer not care about the order in which either the server or clients start. It is hard to administer process creation for my case. The application instances should be able to start on the network in any order and just see the data broadcasted on the UDP port when it is sent.
But there is something in the way I setting up my UDP sockets which is forcing ordering to take place. I must start the clients, THEN start the server. If I start the clients AFTER the server doing the UDP broadcast, the client sockets do not receive the data. If I force a running server instance to tear down and rebuild its UDP socket, suddenly all the clients start receiving data.
There must be something wrong with how I creating the socket. The client and server code use a shared function library to make the UDP socket. So the server is sending on m_fdOut. Each instance of the client is receiving on m_fdIn.
What am I doing wrong here?
SOCKET m_fdIn;
SOCKET m_fdOut;
if ((m_fdIn = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
{
WARNF("socket failed, winsock error %d\n", WSAGetLastError());
exit(1);
}
if ((m_fdOut = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
{
WARNF("socket failed, winsock error %d\n", WSAGetLastError());
exit(1);
}
int sockopt = 1;
if (setsockopt(m_fdOut, SOL_SOCKET, SO_BROADCAST, (char *)&sockopt,
sizeof(sockopt)) < 0)
{
WARNF("setsockopt failed, winsock error %d\n", WSAGetLastError());
exit(1);
}
sockopt = readPreference<int>("SOL_RCVBUF", 512*1024);
if (setsockopt(m_fdIn, SOL_SOCKET, SO_RCVBUF, (char *)&sockopt, sizeof(sockopt)) < 0)
{
WARNF("setsockopt failed, winsock error %d\n", WSAGetLastError());
exit(1);
}
sockopt = 1;
if (setsockopt(m_fdIn, SOL_SOCKET, SO_REUSEADDR, (char *)&sockopt, sizeof(sockopt)) < 0)
{
WARNF("setsockopt failed, winsock error %d\n", WSAGetLastError());
exit(1);
}
sockopt = readPreference<int>("IP_MULTICAST_TTL", 32);
if (setsockopt(m_fdOut, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&sockopt, sizeof(sockopt)) < 0)
{
WARNF("setsockopt failed, winsock error %d\n", WSAGetLastError());
exit(1);
}
String destAdd = "255.255.255.255"
int portNumber = 1234;
int n1, n2, n3 ,n4;
if (sscanf(destAddr, "%d.%d.%d.%d", &n1, &n2, &n3, &n4) != 4)
{
n1 = n2 = n3 = n4 = 255;
}
u_long bcastAddr = (n1<<24) | (n2<<16) | (n3<<8) | n4;
outAddr.sin_family = AF_INET;
outAddr.sin_port = htons(portNumber);
outAddr.sin_addr.s_addr = htonl(bcastAddr);
struct sockaddr_in in_name;
in_name.sin_family = AF_INET;
in_name.sin_addr.s_addr = INADDR_ANY;
in_name.sin_port = htons(portNumber);
if (bind(m_fdIn, (struct sockaddr *)&in_name, sizeof(in_name)) < 0)
{
WARNF("bind failed, winsock error %d\n", WSAGetLastError());
exit(1);
}
So I did change the implementation from UDP broadcast to multicast. That seems to work in loopback so multiple processes can share the port.

Resources