Swift: TCP socket keep-alive - ios

I'm developing an iOS app (using Swift) which uses a TCP connection to a TCP server. Currently, whenever I've sent something, the connection closes automatically. I want to keep the connection open/alive until I manually close it.
From this Objective-C-based question I found that it could be done like this in Objective-C:
#include <sys/socket.h>
...
CFDataRef socketData = CFReadStreamCopyProperty((__bridge CFReadStreamRef)(stream), kCFStreamPropertySocketNativeHandle);
CFSocketNativeHandle socket;
CFDataGetBytes(socketData, CFRangeMake(0, sizeof(CFSocketNativeHandle)), (UInt8 *)&socket);
CFRelease(socketData);
int on = 1;
if (setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) {
NSLog(#"setsockopt failed: %s", strerror(errno));
}
My current Swift implementation/translation looks like this:
var socketData = CFReadStreamCopyProperty(inputStream, kCFStreamPropertySocketNativeHandle) as CFDataRef
var socket: CFSocketNativeHandle
CFDataGetBytes(socketData, CFRangeMake(0, sizeof(CFSocketNativeHandle)), (UInt8).self&socket)
var on: UInt8 = 1;
if setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &on, 255) == -1 {
}
A few notes:
inputStream is declared as: var inputStream: NSInputStream?
I'm not sure if using 255 as an alternative to sizeof(on) is a good idea.
The code happens in the function func stream(theStream:NSStream, handleEvent streamEvent:NSStreamEvent) which is required by using the NSStreamDelegate protocol.
I'm not sure if using inputStream instead of theStream (function parameter) is a good idea.
I'm getting an Xcode error on the CFDataGetBytes function. It says the following:
'NSData' is not a subtype of of 'CFData'
Any idea how to fix that?
Also, how do I import/include <sys/socket.h> in my Swift file? I've seen something called bridging headers, but isn't that only for Obj-C side-by-side with Swift implementations?

CFDataRef is "toll-free bridged" to NSData, therefore the first part can be
more simply written as
var socketData = CFReadStreamCopyProperty(inputStream, kCFStreamPropertySocketNativeHandle) as NSData
var socket: CFSocketNativeHandle = 0
socketData.getBytes(&socket, length: sizeofValue(socket))
The "option_value" argument of setsockopt() is the address of an int (which is mapped to Swift as UInt32), and the last argument has to be the size of that variable:
var on: UInt32 = 1;
if setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &on, socklen_t(sizeofValue(on))) == -1 {
let errmsg = String.fromCString(strerror(errno))
println("setsockopt failed: \(errno) \(errmsg)")
}
<sys/socket.h> is imported by default.

Related

iOS: How to specify DNS to be used to resolve hostname to IP address?

As the title says I have hostname (eg www.example.com) that I want to resolve using specified DNS server. For example in one case I want to use google's IPv4 DNS and in other case google's IPv6 DNS.
I have browsed SO for something like this on iOS, and found questions like this one (Swift - Get device's IP Address), so I am sure it can be done, but I am unclear how?
How can I do this?
EDIT 06/07/2018
#mdeora suggested solution from http://www.software7.com/blog/programmatically-query-specific-dns-servers-on-ios/
This solution works but only if I use IPv4 DNS, for example google's "8.8.8.8". If I try to use IPv6 DNS 2001:4860:4860::8888, i get nothing.
I have managed to change conversion:
void setup_dns_server(res_state res, const char *dns_server)
{
res_ninit(res);
struct in_addr addr;
// int returnValue = inet_aton(dns_server, &addr);
inet_pton(AF_INET6, dns_server, &addr); // for IPv6 conversion
res->nsaddr_list[0].sin_addr = addr;
res->nsaddr_list[0].sin_family = AF_INET;
res->nsaddr_list[0].sin_port = htons(NS_DEFAULTPORT);
res->nscount = 1;
};
But still have trouble with this:
void query_ip(res_state res, const char *host, char ip[])
{
u_char answer[NS_PACKETSZ];//NS_IN6ADDRSZ NS_PACKETSZ
int len = res_nquery(res, host, ns_c_in, ns_t_a, answer, sizeof(answer));
ns_msg handle;
ns_initparse(answer, len, &handle);
if(ns_msg_count(handle, ns_s_an) > 0) {
ns_rr rr;
if(ns_parserr(&handle, ns_s_an, 0, &rr) == 0) {
strcpy(ip, inet_ntoa(*(struct in_addr *)ns_rr_rdata(rr)));
}
}
}
I get -1 for len. From what I gather it seems I need to configure res_state for IPv6.
Here the code from my blogpost, that was already mentioned above, just slightly adapted to use IPv6.
Adapt setup_dns_server
First we could start with the changes to setup_dns_server:
void setup_dns_server(res_state res, const char *dns_server) {
struct in6_addr addr;
inet_pton(AF_INET6, dns_server, &addr);
res->_u._ext.ext->nsaddrs[0].sin6.sin6_addr = addr;
res->_u._ext.ext->nsaddrs[0].sin6.sin6_family = AF_INET6;
res->_u._ext.ext->nsaddrs[0].sin6.sin6_port = htons(NS_DEFAULTPORT);
res->nscount = 1;
}
Add __res_state_ext
This wouldn't compile because of a missing struct __res_state_ext. This structure is unfortunately in a private header file.
But the definition of that one can be take from here:
https://opensource.apple.com/source/libresolv/libresolv-65/res_private.h.auto.html :
struct __res_state_ext {
union res_sockaddr_union nsaddrs[MAXNS];
struct sort_list {
int af;
union {
struct in_addr ina;
struct in6_addr in6a;
} addr, mask;
} sort_list[MAXRESOLVSORT];
char nsuffix[64];
char bsuffix[64];
char nsuffix2[64];
};
The struct can be added e.g. at the top of the file.
Adapt resolveHost
The changes here include the longer buffer for ip (INET6_ADDRSTRLEN). res_ninit moved from setup_dns_server into this method and is matched now with a res_ndestroy.
+ (NSString *)resolveHost:(NSString *)host usingDNSServer:(NSString *)dnsServer {
struct __res_state res;
char ip[INET6_ADDRSTRLEN];
memset(ip, '\0', sizeof(ip));
res_ninit(&res);
setup_dns_server(&res, [dnsServer cStringUsingEncoding:NSASCIIStringEncoding]);
query_ip(&res, [host cStringUsingEncoding:NSUTF8StringEncoding], ip);
res_ndestroy(&res);
return [[NSString alloc] initWithCString:ip encoding:NSASCIIStringEncoding];
}
Retrieving IPv6 addresses
The changes above are already sufficient if you just want to use a IPv6 address for your DNS server. So in query_ip there are no changes necessary if you still want to retrieve the IPv4 addresses.
In case you would like to retrieve IPv6 addresses from the DNS server also, you can do this:
void query_ip(res_state res, const char *host, char ip[]) {
u_char answer[NS_PACKETSZ];
int len = res_nquery(res, host, ns_c_in, ns_t_aaaa, answer, sizeof(answer));
ns_msg handle;
ns_initparse(answer, len, &handle);
if(ns_msg_count(handle, ns_s_an) > 0) {
ns_rr rr;
if(ns_parserr(&handle, ns_s_an, 0, &rr) == 0) {
inet_ntop(AF_INET6, ns_rr_rdata(rr), ip, INET6_ADDRSTRLEN);
}
}
}
Please note: we use here ns_t_aaaa to get AAAA resource records (quad-A record), because in DNS this specifies the mapping between IPv6 address and hostname. For many hosts, there is no such quad-A record, meaning you can just reach them via IPv4.
Call
You would call it e.g. like so:
NSString *resolved = [ResolveUtil resolveHost:#"www.google.com" usingDNSServer:#"2001:4860:4860::8888"];
NSLog(#"%#", resolved);
The result would the look like this:
Disclaimer
These are just simple example calls, that demonstrate the basic usage of the functions. There is no error handling.
You can do this using below swift code -
import Foundation
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = ["dig", "#8.8.8.8", "google.com"]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
print(output!)
In the above code use the DNS server of your choice by replacing 8.8.8.8
For Objective-C iOS refer below link -
https://www.software7.com/blog/programmatically-query-specific-dns-servers-on-ios/
Below is the revised code for setting up dns -
void setup_dns_server(res_state res, const char *dns_server)
{
res_ninit(res);
struct in_addr6 addr;
// int returnValue = inet_aton(dns_server, &addr);
inet_pton(AF_INET6, dns_server, &addr); // for IPv6 conversion
res->nsaddr_list[0].sin_addr = addr;
res->nsaddr_list[0].sin_family = AF_INET6;
res->nsaddr_list[0].sin_port = htons(NS_DEFAULTPORT);
res->nscount = 1;
};
And the query code -
void query_ip(res_state res, const char *host, char ip[])
{
u_char answer[NS_PACKETSZ];//NS_IN6ADDRSZ NS_PACKETSZ
int len = res_nquery(res, host, ns_c_in, ns_t_a, answer, sizeof(answer));
ns_msg handle;
ns_initparse(answer, len, &handle);
if(ns_msg_count(handle, ns_s_an) > 0) {
ns_rr rr;
if(ns_parserr(&handle, ns_s_an, 0, &rr) == 0) {
strcpy(ip, inet_ntoa(*(struct in_addr6 *)ns_rr_rdata(rr)));
}
}
}
PS - I have not been able to test it, but it should work for ipv6 dns.

Not getting data callbacks in iOS UDP socket

I'm trying to set up a UDP socket on iOS to listen for datagrams coming over a multicast socket:
#import <CoreFoundation/CoreFoundation.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void getSocketDataCallBack (CFSocketRef cfSocketRef, CFSocketCallBackType cbType, CFDataRef address, const void *data, void *info) {
if (cbType == kCFSocketDataCallBack) {
cout << "o";
} else {
cout << "x";
}
}
void main () {
CFSocketError cfErr;
CFSocketContext udpSocketContext = {0, NULL, NULL, NULL, NULL};
udpSocketContext.info = &cbData;
CFSocketRef udpSocketRef = CFSocketCreate(kCFAllocatorDefault,
PF_INET,
SOCK_DGRAM,
IPPROTO_UDP,
kCFSocketDataCallBack,
&getSocketDataCallBack,
&udpSocketContext);
if ( udpSocketRef == NULL) {
cout << "CFSocketCreate failed\n";
} else {
cout << "UDP socket created\n";
CFRunLoopSourceRef source = CFSocketCreateRunLoopSource( kCFAllocatorDefault, udpSocketRef, 0 );
CFRunLoopAddSource( CFRunLoopGetMain(), source, kCFRunLoopCommonModes );
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(MC_PORT); //4194
inet_aton(MC_ADDR, &addr.sin_addr); //239.0.123.45
//Tell socket to listen on this address
CFDataRef cfDataRef = CFDataCreate(NULL, (const UInt8 *)&addr, sizeof(addr));
cfErr = CFSocketSetAddress(udpSocketRef, cfDataRef);
}
}
All the socket calls succeed, but I don't get any callbacks (I am sending UDP datagrams to the MC address from a separate macOS application).
What am I doing wrong?
Thanks for any an all assistance!
Cheers.
The problem is that CFSocket does not in itself enable receipt of datagrams sent to an IPv4 (nor IPv6) multicast address. But all is not hopeless!
At https://justanapplication.wordpress.com/category/posix/posix-system-calls/posix-socket/ I found this: "Fortunately the function CFSocketCreateWithNative can turn a theoretical POSIX socket into a theoretical CFSocket." The author of this, Simon Lewis, also says that this actually works, too, "at least on an iPad running iOS 7.0.4," and he is nice enough to provide some code to try it out with. Good Luck!

Get peer IP address and port on OSX in objective-c from NSStream, CFStream or Socket

I have written a server that listens on a specific port for incoming tcp connections. To manage the network connectivity I am using Streams (CFStream/NSStream). When a connection is esthablished I save all the information about this very connection in another instance of a dedicated class which is also set as the delegate for the streams.
Now I want to get the public IP of the device that sends me a message through the already esthablished streams, in other words, I would like to store the IP of the stream's peer. I tried many things, but unfortunately I only get null-values.
Is there a possibility to get the peer's address (ip and port) from an existing stream of the described form?
Here is some code I have tried:
CFDataRef peerAddress = CFSocketCopyPeerAddress(_sockRef);
// _sockRef is saved when connection is established in listening callback and is not null
I also tried to get the information direct in the listening callback method:
NSData *peer = nil;
CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
struct sockaddr *addressinfo = NULL;
uint8_t name[SOCK_MAXADDRLEN];
socklen_t namelen = sizeof(addressinfo);
int result = getpeername(nativeSocketHandle, addressinfo, &namelen);
if (result == 0) {
peer = [NSData dataWithBytes:name length:namelen];
}
struct sockaddr_in *s = (struct sockaddr_in*)name;
char *ipstr = malloc(INET_ADDRSTRLEN);
ipstr = inet_ntoa(s->sin_addr); // is 0.0.0.0 :-(
And I tried another method:
_publicIP = CFWriteStreamCopyProperty((__bridge CFWriteStreamRef)(_writeStream), kCFStreamPropertySocketRemoteHostName);
Why do I always get null-values? Can anyone help me?
Thank you in advance!
Okay I figured it out by now. This is what I did:
- (void)getPublicClientAddress {
// Get public IP from stream
// Get hands on appropriate data structures via the socket number
CFSocketNativeHandle nativeSocketHandle = _socketnumber;
uint8_t name[SOCK_MAXADDRLEN];
socklen_t namelen = sizeof(name);
NSData *peer = nil;
if (0 == getpeername(nativeSocketHandle, (struct sockaddr *)name, &namelen)) {
peer = [NSData dataWithBytes:name length:namelen];
}
if (_ipv6){
// If ipv6 is used
struct sockaddr_in6 *socketaddress = (struct sockaddr_in6*)name;
// convert ip to string
char *ipstr = malloc(INET6_ADDRSTRLEN);
struct in6_addr *ipv6addr = &socketaddress->sin6_addr;
inet_ntop(AF_INET6, ipv6addr, ipstr, sizeof(ipstr));
// convert port to int
int portnumber = socketaddress->sin6_port;
// Save in properties
_publicIP = [NSString stringWithFormat:#"%s", ipstr];
_publicPort = [NSString stringWithFormat:#"%d", portnumber];
} else {
// If ipv4 is used
struct sockaddr_in *socketaddress = (struct sockaddr_in*)name;
// convert ip to string
char *ipstr = malloc(INET_ADDRSTRLEN);
struct in_addr *ipv4addr = &socketaddress->sin_addr;
ipstr = inet_ntoa(*ipv4addr);
//inet_ntop(AF_INET, ipv4addr, ipstr, sizeof(ipstr));
// convert port to int
int portnumber = socketaddress->sin_port;
// Save in properties
_publicIP = [NSString stringWithFormat:#"%s", ipstr];
_publicPort = [NSString stringWithFormat:#"%d", portnumber];
}
}
Afterwards you will have the public IP in the property _publicIP and the public port in the property _publicPort. All information is gathered from a connection on the server side.
I hope this post will help someone =)

iOS CFSocket and C Socket

in iOS CFSocket the way handle callback function is as followed
void receiveData(CFSocketRef s,
CFSocketCallBackType type,
CFDataRef address,
const void *data,
void *info)
{
}
int main ()
{
CFSocketRef s = CFSocketCreate(NULL, PF_INET,
SOCK_STREAM, IPPROTO_TCP,
kCFSocketDataCallBack,
receiveData,
NULL);
struct sockaddr_in sin;
struct hostent *host;
host = gethostbyname("localhost");
memset(&sin, 0, sizeof(sin));
memcpy(&(sin.sin_addr), host->h_addr,host->h_length);
sin.sin_family = AF_INET;
sin.sin_port = htons(888);
CFDataRef address, data;
UInt8 message[] = "Hello world";
CFRunLoopSourceRef source;
address = CFDataCreate(NULL, (UInt8 *)&sin, sizeof(sin));
data = CFDataCreate(NULL, message, sizeof(message));
CFSocketConnectToAddress(s, address, 0);
CFSocketSendData(s, NULL, data, 0);
}
in CFSocket when we do a CFSocketCreate we put the callback function in the SocketCreate function.
But for C Code the Socket and read from Socket is as followed.
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");
printf("Please enter the message: ");
bzero(buffer,256);
fgets(buffer,255,stdin);
n = write(sockfd,buffer,strlen(buffer));
if (n < 0)
error("ERROR writing to socket");
bzero(buffer,256);
n = read(sockfd,buffer,255);
I don't how to implement the callback read function inside the C Code. The callback function would be implemented every time something come into the buffer.
As you noted BSD sockets is not callback based, you need to poll from read to receive new data. You can call this periodically on the main thread or create a custom thread for reading data repeatedly.
CFSocket wraps BSD sockets at a higher level. It has some nice features, I believe that it automatically handles threading, and sleeps the thread until data is received. Reimplemnenting these features would not be a trivial process.
Good resources:
http://beej.us/guide/bgnet/
BSD Sockets - How to use non-blocking sockets?
http://man7.org/linux/man-pages/man2/poll.2.html

Libnet11 build IPv6 packet manually

I am trying to use Libnet11 function:
int libnet_write_raw_ipv6 (libnet_t *l, u_int8_t *packet, u_int32_t size)
to inject IPv6 packet on network layer.
I had created IPv6 packet and captured it
with Wireshark. Wireshark reported:
malformed packet(wireshark says that next
header value in IPv6 is wrong and payload
size is too big in my opinion)
I hope, someone could help me with minimal code example,
showing how to manually build IPv6 packet (with ICMPv6
extension header) with libnet11 (libnet_write_raw_ipv6()).
I assume that the minimal code might look like this:
packet_len = 40 + 16; // 40B ~ IPv6 packet, 16B ~ ICMPv6 header
u_char *buf = NULL;
struct ip6_hdr *ip6 = NULL;
struct icmp6_hdr *icmp6 = NULL;
l = libnet_init();
if ( (buf = malloc(packet_len)) == NULL ) {
// error
}
// create IPv6 header
ip6 = (struct ip6_hdr *) buf;
ip6->ip6_flow = 0;
ip6->ip6_vfc = 6 << 4;
ip6->ip6_plen = 16; // ICMPv6 packet size
ip6->ip6_nxt = IPPROTO_ICMPV6; // 0x3a
ip6->ip6_hlim = 64;
memcpy(&(ip6->ip6_src), &src_addr, sizeof(struct in6_addr));
memcpy(&(ip6->ip6_dst), &dst_addr, sizeof(struct in6_addr));
// create ICMPv6 header
icmp6 = (struct icmp6_hdr *) (buf + 40); // 40B ~ IPv6 packet size
icmp6->icmp6_type = ICMP6_ECHO_REQUEST;
icmp6->icmp6_code = 0;
icmp6->icmp6_cksum= 0;
icmp6->icmp6_data32[0] = 0;
libnet_do_checksum(l, (u_int8_t *)buf, IPPROTO_ICMPV6, packet_len);
written = libnet_write_raw_ipv6(l, buf, packet_len);
if ( written != packet_len )
perror("Failed to send packet");
libnet_destroy(l);
free(buf);
I tried to find code examples but with no success.
Thank you in advance.
Martin
If you're using C++, then I'd recommend you libtins, a packet crafting a sniffing library. This short snippet does exactly what you want:
#include <tins/tins.h>
using namespace Tins;
void test(const IPv6Address &dst, const IPv6Address &src) {
PacketSender sender;
IPv6 ipv6 = IPv6(dst, src) / ICMPv6();
ipv6.hop_limit(64);
sender.send(ipv6);
}
int main() {
// now use it
test("f0ef:1234::1", "f000::1");
}
You can create it with raw sockets though. I also had to do something similar but couldn't find anything as a reference.
To do it with raw sockets, this link gives you a nice explanation

Resources