Unix Network Programming, Volume 1: The Sockets Networking API (3rd Edition)
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.
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.
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 |