Category Archives: TCP/IP

Programmatically query specific DNS servers on iOS

 
DNS servers handle the mapping of hostnames to IP addresses. This happens under the hood automatically, unless you want to access some non-public resources that are managed by a non-public DNS server.

In my use case this was needed during the early phase of the development of an iOS app. So all team members had access to test resources without the need to change local network settings.

JetBrains has with CLion a new C/C++ IDE in development, publicly available through their Early Access Program. So the small routines were a good opportunity for me to get a first impression of their new tool. Jetbrains would not be themselves without offering comfortable editing and refactoring features – refactoring of C code, nice!

So here is a screencast of creating the small routines in CLion and integrating them into an iOS app in Xcode.

The header file:


#import <Foundation/Foundation.h>

@interface ResolveUtil : NSObject

+ (NSString *)resolveHost:(NSString *)host usingDNSServer:(NSString *)dnsServer;


@end


The implementation file:


#include <resolv.h>
#include <arpa/inet.h>
#include <string.h>

#import "ResolveUtil.h"


void setup_dns_server(res_state res, const char *dns_server);
void query_ip(res_state res, const char *host, char ip[]);



@implementation ResolveUtil



+ (NSString *)resolveHost:(NSString *)host usingDNSServer:(NSString *)dnsServer
{
    
    struct __res_state res;
    char ip[16];
    memset(ip, '\0', sizeof(ip));
    
    setup_dns_server(&res, [dnsServer cStringUsingEncoding:NSASCIIStringEncoding]);
    
    query_ip(&res, [host cStringUsingEncoding:NSUTF8StringEncoding], ip);

    return [[NSString alloc] initWithCString:ip encoding:NSASCIIStringEncoding];
    
}

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_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)));
        }
    }
}

void setup_dns_server(res_state res, const char *dns_server)
{
    res_ninit(res);
    struct in_addr addr;
    inet_aton(dns_server, &addr);
    
    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;
}

@end

TCP and Fragmented Messages

Protocols

A protocol is a set of rules that define how digital messages have to be structured and how to exchange these messages between computers.

When two applications are communicating they usually use a protocol stack, the most popular one being TCP/IP. A widespreadly used application protocol on top of TCP for example is SMTP, that transfers your email messages to the inbox of the receiver.

TCP/IP

The big picture:

  • Application Layer (e.g. your application protocol to implement)
  • Transport Layer (TCP)
  • Internet Layer (IP)
  • Network Layer (e.g. Ethernet, WiFi)

So when implementing an application protocol to be able to communicate with your counterpart you have to understand TCP.

In plain C you use send() to send out a message and on the other side recv() to receive data:


  int send(int socket, char *message, int length, int flags);
  int recv(int socket, char *message, int length, int flags);

TCP is a stream protocol

And now comes the first and most important thing to understand: TCP is a stream protocol.

Application programmer tend to think in messages: but being a stream protocol TCP has no implicit message boundaries. The sender writes bytes to the stream with send() and the receiver reads bytes from the stream with recv().

Fragments, Blocking, and Timeouts

When receiving data with recv() be prepared that your receive the message in fragments. If the sender calls send() subsequently you could also receive data of more then one message at once. It’s a stream – there is no one-to-one relationship between send() and receive().

recv() will block until at least one byte and at most length (see function prototype above) bytes are received.

Since there are no message boundaries you need to know the length of the message to receive. What you do to reassemble the fragments into a message is:

        int numLeft = lengthOfMessage;
        int len = 0;
        int numBytes;
        do {
            numBytes = recv(socket, buf + len, numLeft, 0);
            len += numBytes;
            numLeft -= numBytes;
        } while(numBytes > 0 && len < lengthOfMessage);

        //check result of recv for closed socket (numBytes == 0) and errors (numBytes < 0)
        //if no errors, then...

To avoid that recv() blocks in case of a failure on the sender side, one configures a timeout or makes a check with select() wether the socket is ready for I/O.

Splitting a Message into Header and User Data

Since most messages have a variable length usually a message on the application protocol layer is divided into a header and user data. The header has a fixed size and includes information about the length of the message.

So you first receive the header (fixed size), extract the message size information and then receive in a second loop the real user data.

Alternatively some protocols are using delimiters to mark message boundaries.

TCP/IP in C# or Java

TCP/IP in C# or Java works the same as in C, it’s even more obvious, because you are asking the socket explicitly for streams (GetStream() in C# respectively getInputStream() in Java)!

Summary

When developing network routines there are several things to get right, communication threads, error handling, closing operating system resources, and so on. But avoid the most basic TCP beginner mistake and don’t assume that there are message boundaries. And don’t aggressivly blame the broken network or other developers to send data in fragments or as once happened to me: it doesn’t make a good impression 🙂