Check out the new USENIX Web site. next up previous
Next: Socket Addresses Up: New Interfaces Previous: New Interfaces

Name Resolution

Figure 5 shows a brief summary of the new name resolution interfaces.

Figure 4: Using an Integer for an Address (from vat 4.0b2)
int Network::dorecv(u_char* buf, int len, u_int32_t& from, int fd)
{
        sockaddr_in sfrom;
        int fromlen = sizeof(sfrom);
        int cc = ::recvfrom(fd, (char*)buf, len, 0,
                            (sockaddr*)&sfrom, &fromlen);
        if (cc < 0) {
                if (errno != EWOULDBLOCK)
                        perror("recvfrom");
                return (-1);
        }
        from = sfrom.sin_addr.s_addr;

Figure 5: Summary of New Name Resolution APIs
#define AI_PASSIVE     /* Socket address is intended for bind() */
#define AI_CANONNAME   /* Request for canonical name */
#define AI_NUMERICHOST /* Don't ever try nameservice */

struct addrinfo {
  int ai_flags;             /* input flags */
  int ai_family;            /* protocol family for socket */
  int ai_socktype;          /* socket type */
  int ai_protocol;          /* protocol for socket */
  int ai_addrlen;           /* length of socket-address */
  struct sockaddr *ai_addr; /* socket-address for socket */
  char *ai_canonname;       /* canonical name for service location (iff req) */
  struct addrinfo *ai_next; /* pointer to next in list */
};

int getaddrinfo(const char *name, const char *service,
                const struct addrinfo *req, struct addrinfo **pai);

void freeaddrinfo(struct addrinfo *ai);

char *gai_strerror(int ecode);

#define NI_MAXHOST          /* Maximum host name buffer length needed */
#define NI_MAXSERV          /* Max. service name buffer length needed */
#define NI_NUMERICHOST      /* Don't do name resolution for the host */
#define NI_NUMERICSERV      /* Don't do name resolution for the service */
#define NI_NOFQDN           /* Don't fully qualify host names */
#define NI_NAMEREQD         /* Fail if name resolution for the host fails */
#define NI_DGRAM            /* Service is for a DGRAM socket (not a STREAM) */

int getnameinfo(const struct sockaddr *sa, size_t addrlen, char *host,
                size_t hostlen, char *serv, size_t servlen, int flags);

int nrl_afnametonum(const char *name);       /* (Nonstandard) */
const char *nrl_afnumtoname(int num);        /* (Nonstandard) */
int nrl_socktypenametonum(const char *name); /* (Nonstandard) */
const char *nrl_socktypenumtoname(int num);  /* (Nonstandard) */

The getaddrinfo() and getnameinfo() functions provide a protocol-independent way of mapping names to addresses information and of mapping address information back to names. Given a host name, a service name, and other information to constrain the lookup, getaddrinfo() returns either an integer error or a list of filled in addrinfo structures. Each contains the information that needs to be passed to socket() to open a socket as well as the information that needs to be passed to connect() or sendmsg() to reach the named endpoint.

Many programs can simply take the returned list and iterate through it, executing socket() and connect() calls with the information in each list element, until one attempt succeeds completely or the list has been exhausted. Figure 6 gives an example of how to do this. Notice how the program never needs to manipulate addresses directly. The program only needs to take information out of the addrinfo structure and feed it into other functions. This simple block of code is capable of obtaining a connected socket with any stream protocol that is supported in both the kernel and in the runtime library. If the runtime library does not support a protocol, it will not be returned by getaddrinfo(). If the kernel does not support a protocol, this function will print an error for those sockets and skip that protocol. This is especially important for binaries to be shipped on systems where the protocols available in the runtime library and/or kernel can be configured by the end user; one binary will be able to work as long as the system is configured so that there is one protocol that the entire system supports.

Note that getaddrinfo() and getnameinfo() handle both host names and printable numeric addresses, as appropriate. One historical problem with functions like gethostbyname() and gethostbyaddr() is that on some systems they handle printable numeric addresses and on some systems they do not. Portable programs must be written to attempt printable-numeric conversion separately, just in case - programs that assume the system handles these have encountered portability problems. Some programs have bugs caused by the old printable numeric conversion functions, making this even more of a problem. These new functions should hopefully put these problem to rest.

As shown in the example, the gai_strerror() function converts the errors returned by getaddrinfo() and getnameinfo() into human-printable form. There are also constants for the error values, but few programs need to distinguish between the types of failures beyond giving an appropriate error message. The freeaddrinfo() function releases the memory used by the result list, and must be called when the result is no longer needed.

The functions nrl_afnametonum() and nrl_afnumtoname() convert address family names (inet, inet6, local, etc.) to numbers and back. This is needed in order to support user entry of an address family to constrain getaddrinfo() lookups. For example, many NRL IPv6-enabled applications support a command line flag that the user can use to specify a family, such as ``inet'' or ``inet6,'' that selects what protocol to use. The number-to-name function is also helpful for diagnostic output. Similarly, the functions nrl_socktypenametonum() and nrl_socktypenumtoname convert socket type names (stream, dgram, seqpacket, etc.) to numbers and back. These are less useful for user input, but are still useful for diagnostic purposes.

Figure 6: Using getaddrinfo() to Get one Stream Connection
int get_stream(char *host, *service)
{
  int error, fd;
  struct addrinfo req, *ai, *ai2;
  char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

  memset(&req, 0, sizeof(struct addrinfo));
  req.ai_socktype = SOCK_STREAM;

  if (error = getaddrinfo(host, service, NULL, &ai)) {
    fprintf(stderr, "getaddrinfo(%s, %s, ...): %s(%d)", gai_strerror(error),
      error);
    return -1;
  }

  for (ai2 = ai; ai = ai->ai_next) {
    if (error = getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, sizeof(hbuf),
        sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV)) {
      fprintf(stderr, "getnameinfo(%p, %d, %p, %d, %p, %d, %d): %s(%d)\n",
        ai->ai_addr, ai->ai_addrlen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
        NI_NUMERICHOST | NI_NUMERICSERV, gai_strerror(error), error);
      continue;
    }

    fprintf(stdout, "Trying %s.%s...\n", hbuf, sbuf);

    if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
      fprintf(stderr, "socket(%d, %d, %d): %s(%d)\n", ai->ai_family,
        ai->ai_socktype, ai->ai_protocol, strerror(errno), errno);
      continue;
    }

    if (connect(fd, ai->ai_addr, ai->ai_addrlen) < 0) {
      fprintf(stderr, "connect(%d, %p, %d): %s(%d)\n", fd, ai->ai_addr,
        ai->ai_addrlen, strerror(errno), errno);
      close(fd);
      continue;
    }

    freeaddrinfo(ai2);
    return fd;
  }

  freeaddrinfo(ai2);
  fprintf(stderr, "No connections result.\n");
  return -1;
}


next up previous
Next: Socket Addresses Up: New Interfaces Previous: New Interfaces
Craig Metz 2000-05-08