Book Image

iOS and OS X Network Programming Cookbook

By : Jon Hoffman
Book Image

iOS and OS X Network Programming Cookbook

By: Jon Hoffman

Overview of this book

Table of Contents (15 chapters)
iOS and OS X Network Programming Cookbook
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Performing a network address resolution


Most applications will eventually need to convert host/service names to sockaddr structures and sockaddr structures to host/service names. The BSD Socket Library has two functions to assist with these conversions:

  • Getaddrinfo(): This is a function that will return information about a given host/service name. The results are returned in an addrinfo structure.

  • Getnameinfo(): This is a function that will return the host and service names, given a sockaddr structure.

The getaddrinfo() and getnameinfo() functions make the gethostbyname(), gethostbyaddr(), and getservbyport() functions obsolete. One of the main advantages that the getaddrinfo() and getnameinfo() functions has over the obsolete functions is that they are compatible with both IPv4 and IPv6 addresses.

In this recipe, we will encapsulate getaddrinfo() and getnameinfo() into an Objective-C class. This class will not hide most of the complexity of the two functions; however, it will save you from having to worry about NSString to character array conversions and will also handle the memory management of the addrinfo structures for you.

Getting ready

This recipe is compatible with both iOS and OS X. No extra frameworks or libraries are required.

How to do it…

Let's get started with the AddrInfo class.

Creating the AddrInfo header file

The header file for the AddrInfo class looks like the following:

 #import <Foundation/Foundation.h>
 
 @interface AddrInfo : NSObject 
 
 @property (nonatomic, strong) NSString *hostname, *service;
 @property (nonatomic) struct addrinfo *results;
 @property (nonatomic) struct sockaddr *sa;
 @property (nonatomic, readonly) int errorCode;
 
 -(void)addrWithHostname:(NSString*)lHostname Service:(NSString *)lService andHints:(struct addrinfo*)lHints;
 -(void)nameWithSockaddr:(struct sockaddr *)saddr;
 
 -(NSString *)errorString;
 
@end

The addrinfo header file defines four properties. The hostname, service, and results properties will contain the results of the address resolution queries, and the errorCode property will contain any error code that is returned.

We are also defining three methods in our header file. The addrWithHostname:Service:andHints: method, which takes supplied hostname, service, and hints (we will discuss the hints structure when we discuss how to use the AddrInfo class) and populates the results property using the getaddrinfo() function. The nameWithSockaddr: method, which takes supplied sockaddr and populates the hostname and service properties using the getnameinfo() function. If there is an error with either of the methods, the errorCode property is set to the returned error code.

The errorString method takes the error code from the errorCode property and returns a string that tells what the error code is.

Creating the AddrInfo implementation file

To create the AddrInfo implementation file, we use the following code:

  #import "AddrInfo.h"
  #import <netdb.h>
  #import <netinet/in.h>
  #import <netinet6/in6.h>
  
  @implementation AddrInfo
 
  -(instancetype)init {
      self = [super init];
      if (self) {
          [self setVars];
      }
      return self;
  }

We begin the implementation file by importing the headers that are needed. We also define an init constructor for our class that uses the setVars method to reset our properties to default values. Let's look at the addrWithHostname:Service:andHints: method:

-(void)addrWithHostname:(NSString*)lHostname Service:(NSString *)lService andHints:(struct addrinfo*)lHints {
    
    [self setVars];
    self.hostname = lHostname;
    self.service = lService;
    
    struct addrinfo *res;
    
    _errorCode = getaddrinfo([_hostname UTF8String], [_service UTF8String], lHints, &res);
    self.results = res;
    
}

The addrWithHostname:Service:andHints: method will retrieve the addresses for a given hostname. We start off by resetting the properties to default values using the setVars method. We then set the hostname and service properties with the values passed to the method.

Since the getaddrinfo() function expects character arrays for hostname and service, we need to convert our NSString values to character arrays. This is done by using the UTF8String method of the NSString class. We also pass the addrinfo hints structure and the address of the res addrinfo structure. The results of the getaddrinfo() function are put into the errorCode property. If the getaddrinfo() function call was successful, errorCode will be equal to 0.

When the getaddrinfo() function returns, the res structure contains the results that we use to set the results property:

-(void)nameWithSockaddr:(struct sockaddr *)saddr {
    
    [self setVars];
    char host[1024];
    char serv[20];
    

    _errorCode = getnameinfo(saddr, sizeof saddr, host, sizeof host, serv, sizeof serv, 0);
    
    self.hostname = [NSString stringWithUTF8String:host];
    self.service = [NSString stringWithUTF8String:serv];
    
}

The nameWithSockaddr: method will retrieve the names associated with a given IP address. We start this method by calling the setVars method to initialize the object's properties. We then define the two character arrays that will contain the results of the getnameinfo() function call.

The getnameinfo() function will take the address information from the saddr sockaddr structure, perform a lookup for the host/service name, and put the results into the host and serv character arrays. If the getnameinfo() function was successful, it will return 0, otherwise it will return -1.

Finally, we convert the host and serv character arrays to NSStrings and put the values into the hostname and service properties:

-(void)setVars {
    self.hostname = @"";
    self.service = @"";
    self.results = @"";
    _errorCode = 0;
}

The setVars method simply sets all the method's NSString properties to empty strings and the errorcode property to 0. This gives us a well-defined starting point for the method properties to make sure they do not contain stale information. Let's look at the errorString: method:

-(NSString *)errorString {
    return [NSString stringWithCString:gai_strerror(_errorCode) encoding:NSASCIIStringEncoding];
}

The errorStiring method uses the gai_strerror() function to convert the error code from either the getnameinfo() or getaddrinfo() function calls to an actual error method that can tell us what went wrong; let's look at the setResults: method:

-(void)setResults:(struct addrinfo *)lResults {
    freeaddrinfo(self.results);
    _results = lResults;
}

We create the setResults: method because we need to call the freeaddrinfo() function to release the results before setting the new results. This will avoid memory leaks in our application.

Using the AddrInfo class to perform the address/hostname resolution

In the following sample code, we will show how to get the hostname www.packtpub.com to list the IP addresses and then convert those IP addresses back to the hostnames:

  struct addrinfo *res;
  struct addrinfo hints;
  
  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_UNSPEC;   
  hints.ai_socktype = SOCK_STREAM;

We begin our address/hostname resolution code by setting up two addrinfo structures. The res structure will be used as a temporary store when we loop though the linked list of results that are returned to us from the addrWithHostname:Service:andHints: method. The hints structure will store the hints that we are going to pass to the addrWithHostname:Service:andHints: method to let the method know what type of addresses we are looking for.

Whenever you create a new structure that you plan on setting the values for, you should always use the memset() function to clear the memory of the structure. This will ensure that there is nothing in the memory that will corrupt the structure.

We set ai_family to AF_UNSPEC and ai_socktype to SOCK_STREAM. This tells the getaddrinfo() function that we are looking for any IP version (IPv4 or IPv6) but limiting our socket type to socket streams (these settings are used when we want to make a TCP connection). We could set the ai_family to AF_INET4 to limit the results to only IPv4 results, or set it to AF_INET6 for only IPv6 results. Let's look at how we would initiate the AddrInfo object:

AddrInfo *ai = [[AddrInfo alloc] init];
[ai addrWithHostname:@"www.packtpub.com" Service:@"443" andHints:&hints];
if (ai.errorCode != 0) {
     NSLog(@"Error in getaddrinfo():  %@",[ai getErrorString]);
     return -1;
 }

We now initiate our AddrInfo object and call the addrWithHostname:Service:andHints: method. For our example, we are requesting an address lookup for the www.packtpub.com hostname. The service we are requesting is port 443, which is HTTPS, and we are also supplying our hints structure, which specifies the type of addresses we are looking for.

The code then checks to see if we have any errors; if so, it logs them and exits. Depending on what your application does, you will probably want to catch the error and display a message to the user. Let's loop though the addresses and display the results:

  struct addrinfo *results = ai.results;
  for (res = results; res!= NULL; res = res->ai_next) {
      void *addr;
      NSString *ipver = @"";
      char ipstr[INET6_ADDRSTRLEN];

      if (res->ai_family == AF_INET) {
          struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr;
          addr = &(ipv4->sin_addr);
          ipver = @"IPv4";
      } else if (res->ai_family == AF_INET6){
          struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr;
          addr = &(ipv6->sin6_addr);
          ipver = @"IPv6";
      } else {
          continue;
      }
    inet_ntop(res->ai_family, addr, ipstr,sizeof ipstr);
      NSLog(@"     %@  %s", ipver, ipstr);
      AddrInfo *ai2 = [[AddrInfo alloc] init];
      [ai2 getNameWithSockaddr:res->ai_addr];
      if (ai2.errorCode ==0)
          NSLog(@"--%@ %@",ai2.hostname, ai2.service);
  }
  freeaddrinfo(results);

If there are no errors, we loop though the results. After we initialize the variables, we check to see if the address family is AF_INET (IPv4 address). If so, we create a sockaddr_in structure, retrieve the address from the sin_addr variable, and set ipver to IPv4.

If the address family was not AF_INET, we check to see if the address family is AF_INET6 (IPv6 address). If so, we create a sockaddr_in6 structure, retrieve the address from the sin_addr6 variable, and set ipver to IPv6.

If the address family is neither AF_INET nor AF_INET6, we continue the for loop without logging the address.

The inet_ntop() function converts the address from binary to text form so that we can display it. The NSLog line will display the IP version followed by the IP address.

Now that we have retrieved the IP address, we will need to send it back to the hostname. For this, we take the sockaddr from our results structure and send it to the nameWithSockaddr: method of the AddrInfo class. When the nameWithSockaddr: method completes, it will populate the hostname and service properties of the AddrInfo object.

Finally, we use the freeaddrinfo()function to release the results in order to prevent any memory leaks.

How it works…

In this recipe, we used the getaddrinfo() and getnameinfo() functions to get the IP address and hostname. These functions are provided as part of the standard POSIX API.

While these functions are black-box functions, there is really nothing magical about them. Internally, these functions call lower-level functions to send our requests to the appropriate DNS server to perform the resolution.