[ Team LiB ] Previous Section Next Section

11.5 getservbyname and getservbyport Functions

Services, like hosts, are often known by names, too. If we refer to a service by its name in our code, instead of by its port number, and if the mapping from the name to port number is contained in a file (normally /etc/services), then if the port number changes, all we need to modify is one line in the /etc/services file instead of having to recompile the applications. The next function, getservbyname, looks up a service given its name.

The canonical list of port numbers assigned to services is maintained by the IANA at http://www.iana.org/assignments/port-numbers (Section 2.9). A given /etc/services file is likely to contain a subset of the IANA assignments.

#include <netdb.h>

struct servent *getservbyname (const char *servname, const char *protoname);

Returns: non-null pointer if OK, NULL on error

This function returns a pointer to the following structure:


struct servent {
  char   *s_name;      /* official service name */
  char  **s_aliases;   /* alias list */
  int     s-port;      /* port number, network-byte order */
  char   *s_proto;     /* protocol to use */
};

The service name servname must be specified. If a protocol is also specified (protoname is a non-null pointer), then the entry must also have a matching protocol. Some Internet services are provided using either TCP or UDP (for example, the DNS and all the services in Figure 2.18), while others support only a single protocol (e.g., FTP requires TCP). If protoname is not specified and the service supports multiple protocols, it is implementation-dependent as to which port number is returned. Normally this does not matter, because services that support multiple protocols often use the same TCP and UDP port number, but this is not guaranteed.

The main field of interest in the servent structure is the port number. Since the port number is returned in network byte order, we must not call htons when storing this into a socket address structure.

Typical calls to this function could be as follows:


struct servent *sptr;

sptr = getservbyname("domain", "udp"); /* DNS using UDP */
sptr = getservbyname("ftp", "tcp");    /* FTP using TCP */
sptr = getservbyname("ftp", NULL);     /* FTP using TCP */
sptr = getservbyname("ftp", "udp");    /* this call will fail */

Since FTP supports only TCP, the second and third calls are the same, and the fourth call will fail. Typical lines from the /etc/services file are


freebsd % grep -e ^ftp -e ^domain /etc/services
ftp-data         20/tcp    #File Transfer [Default Data]
ftp              21/tcp    #File Transfer [Control]
domain           53/tcp    #Domain Name Server
domain           53/udp    #Domain Name Server
ftp-agent       574/tcp    #FTP Software Agent System
ftp-agent       574/udp    #FTP Software Agent System
ftps-data       989/tcp                 # ftp protocol, data, over TLS/SSL
ftps            990/tcp                 # ftp protocol, control, over TLS/SSL

The next function, getservbyport, looks up a service given its port number and an optional protocol.

#include <netdb.h>

struct servent *getservbyport (int port, const char *protoname);

Returns: non-null pointer if OK, NULL on error

The port value must be network byte ordered. Typical calls to this function could be as follows:


struct servent *sptr;

sptr = getservbyport (htons (53), "udp"); /* DNS using UDP */
sptr = getservbyport (htons (21), "tcp"); /* FTP using TCP */
sptr = getservbyport (htons (21), NULL);  /* FTP using TCP */
sptr = getservbyport (htons (21), "udp"); /* this call will fail */

The last call fails because there is no service that uses port 21 with UDP.

Be aware that a few port numbers are used with TCP for one service, but the same port number is used with UDP for a totally different service. For example, the following:


freebsd % grep 514 /etc/services
shell           514/tcp    cmd          #like exec, but automatic
syslog          514/udp

shows that port 514 is used by the rsh command with TCP, but with the syslog daemon with UDP. Ports 512–514 have this property.

Example: Using gethostbyname and getservbyname

We can now modify our TCP daytime client from Figure 1.5 to use gethostbyname and getservbyname and take two command-line arguments: a hostname and a service name. Figure 11.4 shows our program. This program also shows the desired behavior of attempting to connect to all the IP addresses for a multihomed server, until one succeeds or all the addresses have been tried.

Figure 11.4 Our daytime client that uses gethostbyname and getservbyname.

names/daytimetcpcli1.c

 1 #include     "unp.h"

 2 int
 3 main (int argc, char **argv)
 4 {
 5     int     sockfd, n;
 6     char     recvline [MAXLINE + 1];
 7     struct sockaddr_in servaddr;
 8     struct in_addr **pptr;
 9     struct in_addr *inetaddrp [2];
10     struct in_addr inetaddr;
11     struct hostent *hp;
12     struct servent *sp;

13     if (argc ! = 3)
14         err_quit ("usage: daytimetcpclil <hostname> <service>");

15     if ( (hp = gethostbyname (argv [1]) ) == NULL) {
16         if (inet_aton (argv [1], &inetaddr) == 0) {
17            err_quit ("hostname error for %s: %s", argv [1],
18                     hstrerror (h_errno) );
19         } else {
20             inetaddrp [0] = &inetaddr;
21             inetaddrp [1] = NULL;
22             pptr = inetaddrp;
23         }
24     } else {
25         pptr = (struct in_addr **) hp->h_addr_list;
26     }

27     if ( (sp = getservbyname (argv [2], "tcp") ) == NULL)
28         err_quit ("getservbyname error for %s", argv [2] );

29     for ( ; *pptr != NULL; pptr++) {
30         sockfd = Socket (AF_INET, SOCK_STREAM, 0) ;

31         bzero (&servaddr, sizeof (servaddr) ) ;
32         servaddr.sin_family = AF_INET;
33         servaddr.sin_port = sp->s_port;
34         memcpy (&servaddr.sin_addr, *pptr, sizeof (struct in_addr) ) ;
35         printf ("trying %s\n", Sock_ntop ( (SA *) &servaddr, sizeof (servaddr) ) ) ;

36         if (connect (sockfd, (SA *) &servaddr, sizeof (servaddr) ) == 0)
37             break;               /* success */
38         err_ret ("connect error");
39         close (sockfd) ;
40      }
41     if (*pptr == NULL)
42          err_quit ("unable to connect");

43      while ( (n = Read (sockfd, recvline, MAXLINE) ) > 0) {
44          recvline [n] = 0;               /* null terminate */
45          Fputs (recvline, stdout);
46      }
47      exit (0);
48 }
Call gethostbyname and getservbyname

13–28 The first command-line argument is a hostname, which we pass as an argument to gethostbyname, and the second is a service name, which we pass as an argument to getservbyname. Our code assumes TCP, and that is what we use as the second argument to getservbyname. If gethostbyname fails to look up the name, we try using the inet_aton function (Section 3.6) to see if the argument was an ASCII-format address. If it was, we construct a single-element list consisting of the corresponding address.

Try each server address

29–35 We now code the calls to socket and connect in a loop that is executed for every server address until a connect succeeds or the list of IP addresses is exhausted. After calling socket, we fill in an Internet socket address structure with the IP address and port of the server. While we could move the call to bzero and the subsequent two assignments out of the loop, for efficiency, the code is easier to read as shown. Establishing the connection with the server is rarely a performance bottleneck for a network client.

Call connect

36–39 connect is called, and if it succeeds, break terminates the loop. If the connection establishment fails, we print an error and close the socket. Recall that a descriptor that fails a call to connect must be closed and is no longer usable.

Check for failure

41–42 If the loop terminates because no call to connect succeeded, the program terminates.

Read server's reply

43–47 Otherwise, we read the server's response, terminating when the server closes the connection.

If we run this program specifying one of our hosts that is running the daytime server, we get the expected output.


freebsd % daytimetcpcli1 aix daytime
trying 192.168.42.2:13
Sun Jul 27 22:44:19 2003

What is more interesting is to run the program to a multihomed system that is not running the daytime server.


freebsd % daytimetcpcli1 gateway.tuc.noao.edu daytime
trying 140.252.108.1:13
connect error: Operation timed out
trying 140.252.1.4:13
connect error: Operation timed out
trying 140.252.104.1:13
connect error: Connection refused
unable to connect




    [ Team LiB ] Previous Section Next Section