Section 17.5. Networking Machines with TCPIP

   


17.5. Networking Machines with TCP/IP

The primary use for sockets is to allow applications running on different machines to talk to one another. The TCP/IP protocol family [Stevens, 1994] is the protocol used on the Internet, the largest set of networked computers in the world. Linux provides a complete, robust TCP/IP implementation that allows it to act as both a TCP/IP server and client.

The most widely deployed version of TCP/IP is version 4 (IPv4). Version 6 of TCP/IP (IPv6) has become available for most operating systems and network infrastructure products, although IPv4 is still dominant. We concentrate here on writing applications for IPv4, but we touch on the differences for IPv6 applications, as well as for programs that need to support both.

17.5.1. Byte Ordering

TCP/IP networks are usually heterogenous; they include a wide variety of machines and architectures. One of the most common differences between architectures is how they store numbers.

Computer numbers are made up of a sequence of bytes. C integers are commonly 4 bytes (32 bits), for example. There are quite a few ways of storing those four bytes in memory. Big-endian architectures store the most significant byte at the lowest hardware address, and the other bytes follow in order from most significant to least significant. Little-endian machines store multibyte values in exactly the opposite order: The least significant byte is stored at the smallest memory address. Other machines store bytes in different orders yet.

Because multiple byte quantities are needed as part of the TCP/IP protocol, the protocol designers adopted a single standard for how multibyte values are sent across the network.[13] TCP/IP mandates that big-endian byte order be used for transmitting protocol information and suggests that it be used for application data, as well (although no attempt is made to enforce the format of an application's data stream).[14] The ordering used for multibyte values sent across the network is known as the network byte order.

[13] Although it may seem like this is an obvious thing to do, some protocols allow the sender to use any byte order and depend on the recipient to convert the information to the proper (native) order. This gives a performance boost when like machines communicate, at the expense of algorithmic complexity.

[14] All Intel and Intel-compatible processors store data as little-endian, so getting the conversions right is important for programs to ever work properly.

Four functions are available for converting between host byte order and network byte order:

 #include <netinet/in.h> unsigned int htonl(unsigned int hostlong); unsigned short htons(unsigned short hostshort); unsigned int ntohl(unsigned int netlong); unsigned short ntohs(unsigned short netshort); 


Although each of these functions is prototyped for unsigned quantities, they all work fine for signed quantities, as well.

The first two functions, htonl() and htons(), convert longs and shorts, respectively, from host order to network order. The final two, ntohl() and ntohs(), convert longs and shorts from network order to the host byte ordering.

Although we use the term long in the descriptions, that is a misnomer. htonl() and ntohl() both expect 32-bit quantities, not values that are C long s. We prototype both functions as manipulating int values, as all Linux platforms currently use 32-bit integers.

17.5.2. IPv4 Addressing

IPv4 connections are a 4-tuple of (local host, local port, remote host, remote port). Each part of the connection must be determined before a connection can be established. Local host and remote host are each IPv4 addresses. IPv4 addresses are 32-bit (4-byte) numbers unique across the entire connected network. Usually they are written as aaa. bbb. ccc. ddd, with each element in the address being the decimal representation of one of the bytes in the machine's address. The left-most number in the address corresponds to the most significant byte in the address. This format for IPv4 addresses is known as dotted-decimal notation.

As most machines need to run multiple concurrent TCP/IP applications, an IP number does not provide a unique identification for a connection on a single machine. Port numbers are 16-bit numbers that uniquely identify one endpoint of a connection on a single host. The combination of an IPv4 address and a port number identifies a connection endpoint anywhere on a single TCP/IP network (the Internet is a single TCP/IP network). Two connection endpoints form a TCP connection, so two IP number/port number pairs uniquely identify a TCP/IP connection on a network.

Determining which port numbers to use for various protocols is done by a part of the Internet standards known as well-known port numbers, maintained by the Internet Assigned Numbers Authority (IANA).[15] Common Internet protocols, such as ftp, telnet, and http, are each assigned a port number. Most servers provide those services at the assigned numbers, making them easy to find. Some servers are run at alternate port numbers, usually to allow multiple services to be provided by a single machine.[16] As well-known port numbers do not change, Linux uses a simple mapping between protocol names (commonly called services) and port numbers through the /etc/services file.

[15] See http://www.iana.org for information on the IANA.

[16] Or, increasingly, to hide them from broadband ISPs who do not want their low-dollar customers running servers at home.

Although the port numbers range from 0 to 65,535, Linux divides them into two classes. The reserved ports, numbering from 0 to 1,024, may be used only by processes running as root. This allows client programs to trust that a program running on a server is not a Trojan horse started by a user.[17]

[17] It may still be a Trojan horse started by the superuser, however.

IPv4 addresses are stored in struct sockaddr_in, which is defined as follows:

 #include <sys/socket.h> #include <netinet/in.h> struct sockaddr_in {     short int            sin_family; /* AF_INET */     unsigned short int   sin_port;   /* port number */     struct in_addr       sin_addr;   /* IP address */ } 


The first member must be AF_INET, indicating that this is an IP address. The next member is the port number in network byte order. The final member is the IP number of the machine for this TCP address. The IP number, stored in sin_addr, should be treated as an opaque type and not accessed directly.

If either sin_port or sin_addr is filled with \0 bytes (normally, by memset()), that indicates a "do not care" condition. Server processes usually do not care what IP address is used for the local connection, for example, as they are willing to accept connections to any address the machine has. If an application wishes to listen for connections only on a single interface, however, it would specify the address. This address is sometimes called unspecified as it is not a complete specification of the connection's address, which also needs an IP address.[18]

[18] The value for IPv4 unspecified addresses is contained in the constant INADDR_ANY, which is a 32-bit numeric value.

17.5.3. IPv6 Addressing

IPv6 uses the same (local host, local port, remote host, remote port) tuple as IPv4, and the port numbers are the same between both versions (16-bit values).

The local and remote host IPv6 addresses are 128-bit (16 bytes) numbers instead of the 32-bit numbers used for IPv4. Using such large addresses gives the protocol plenty of addresses for the future (it could easily give a unique address to every atom in the Milky Way). While this probably seems like overkill, network architectures tend to waste large numbers of addresses, and the designers of IPv6 felt that it was better to change to 128-bit addresses now than to worry about the need for a possible address change in the future.

IPv6's equivalent of IPv4's dotted-decimal notation is colon-separated notation. As the name suggests, colons are used to separate each pair of bytes in the address (instead of a period separating each individual byte). As the addresses are so long, IPv6 addresses are written in hexadecimal (instead of decimal) form to help keep the length of the addresses down. Here are some examples of what an IPv6 address looks like in colon-separated notation:[19]:

[19] These examples were borrowed from RFC 1884, which defines the IPv6 addressing architecture

 1080:0:0:0:8:800:200C:417A FF01:0:0:0:0:0:0:43 0:0:0:0:0:0:0:1 


As these addresses are quite unwieldy and tend to contain quite a few zeros, a shorthand is available. Any zeros may be left out of the written address, and groups of more than two sequential colons may be written as exactly two colons. Applying these rules to the addresses above leaves us with

 1080::8:800:200C:417A FF01::43 ::1 


Taken to the extreme, the address 0:0:0:0:0:0:0:0 becomes just ::.[20]

[20] This is the unspecified address for IPv6.

A final method for writing IPv6 addresses is to write the last 32 bits in dotted-decimal notation, and the first 96 bits in colon-separated form. This lets us write the IPv6 loopback address, ::1, as either ::0.0.0.1 or 0:0:0:0:0:0:0.0.0.1.

IPv6 defines any address with 96 leading zeros (except for the loopback address and unspecified address) as a compatible IPv4 address, which allows network routers to easily route (tunnel) packets intended for IPv4 hosts through IPv6 networks. The colon shorthand makes it easy to write an IPv4 address as an IPv6 address by prepending the normal dotted-decimal address with ::; this type of address is called an "IPv4-compatible IPv6 address." This addressing is used only by routers; normal programs cannot take advantage of it.

Programs running on IPv6 machines that need to address IPv4 machines can use mapped IPv4 addresses. They prepend the IPV4 address with 80 leading zeros and the 16-bit value 0xffff, which is written as ::ffff: followed by the machine's dotted-decimal IPv4 address. This addressing lets most programs on an IPv6-only system communicate transparently with an IPv4-only node.

IPv6 addresses are stored in variables of type struct sockaddr_in6.

 #include <sys/socket.h> #include <netinet/in.h> struct sockaddr_in6 {     short int             sin6_family;     /* AF_INET6 */     unsigned short int    sin6_port;       /* port number */     unsigned int          sin6_flowinfo;   /* IPv6 traffic flow info */     struct in6_addr       sin6_addr;       /* IP address */     unsigned int          sin6_scope_id;   /* set of scope interfaces */ } 


This structure is quite similar to struct sockaddr_in, with the first member holding the address family (AF_INET6 in this case) and the next holding a 16-bit port number in network byte order. The fourth member holds the binary representation of an IPv6 address, performing the same function as the final member of struct sockaddr_in. The other two members of the structure, sin6_flowinfo and sin6_scope_id, are for advanced uses, and should be set to zero for most applications.

The standards restrict struct sockaddr_in to exactly three members, while struct sockaddr_in6 may have extra members. For that reason programs that manually fill in a struct sockaddr_in6 should zero out the data structure using memset().

17.5.4. Manipulating IP Addresses

Applications often need to convert IP addresses between a human readable notation (either dotted-decimal or colon-separated) and struct in_addr's binary representation. inet_ntop() takes a binary IP address and returns a pointer to a string containing the dotted-decimal or colon-separated form.

 #include <arpa/inet.h> const char * inet_ntop(int family, const void * address, char * dest,                        int size); 


The family is the address family of the address being passed as the second parameter; only AF_INET and AF_INET6 are supported. The next parameter points to a struct in_addr or a struct in6_addr6 as specified by the first parameter. The dest is a character array in which the human-readable address is stored, and is an array of size elements. If the address was successfully formatted, inet_ntop() returns dest; otherwise, it returns NULL. There are only two reasons inet_ntop() can fail: If the destination buffer is not large enough to hold the formatted address errno is set to ENOSPC, or if the family is invalid, errno contains EAFNOSUPPORT.

INET_ADDRSTRLEN is a constant that defines the largest size dest needs to be to hold any IPv4 address while INET6_ADDRSTRLEN defines the maximum array size for an IPv6 address.

The netlookup.c sample program gives an example of using inet_ntop(); the full program starts on page 445.

 120:         if (addr->ai_family == PF_INET) { 121:             struct sockaddr_in * inetaddr = (void *) addr->ai_addr; 122:             char nameBuf[INET_ADDRSTRLEN]; 123: 124:             if (serviceName) 125:                 printf("\tport %d", ntohs(inetaddr->sin_port)); 126: 127:             if (hostName) 128:                 printf("\thost %s", 129:                        inet_ntop(AF_INET, &inetaddr->sin_addr, 130:                                  nameBuf, sizeof(nameBuf))); 131:         } else if (addr->ai_family == PF_INET6) { 132:             struct sockaddr_in6 * inetaddr = 133:                                     (void *) addr->ai_addr; 134:             char nameBuf[INET6_ADDRSTRLEN]; 135: 136:             if (serviceName) 137:                 printf("\tport %d", ntohs(inetaddr->sin6_port)); 138: 139:             if (hostName) 140:                 printf("\thost %s", 141:                        inet_ntop(AF_INET6, &inetaddr->sin6_addr, 142:                                  nameBuf, sizeof(nameBuf))); 143:         } 


To perform the inverse, converting a string containing a dotted-decimal or colon-separated address into a binary IP address, use inet_pton().

 #include <arpa/inet.h> int inet_pton(int family, const char * address, void * dest); 


The family specifies the type of address being converted, either AF_INET or AF_INET6, and address points to a string containing the character representation of the address. If AF_INET is used, the dotted-decimal string is converted to a binary address stored in the struct in_addr dest points to. For AF_INET6, the colon-separated string is converted and stored in the struct in6_addr dest points to. Unlike most library functions, inet_pton() returns 1 if the conversion was successful, 0 if dest did not contain a valid address, and -1 if the family was not AF_INET or AF_INET6.

The example program reverselookup, which starts on page 451, uses inet_pton() to convert IPv4 and IPv6 addresses given by the user into struct sockaddr structures. Here is the code region that performs the conversions of the IP address pointed to by hostAddress. At the end of this code, the struct sockaddr * addr points to a structure containing the converted address.

  79:     if (!hostAddress) {  80:         addr4.sin_family = AF_INET;  81:         addr4.sin_port = portNum;  82:     } else if (!strchr(hostAddress, ':')) {  83:         /* If a colon appears in the hostAddress, assume IPv6.  84:            Otherwise, it must be IPv4 */  85:  86:         if (inet_pton(AF_INET, hostAddress,  87:                       &addr4.sin_addr) <= 0) {  88:             fprintf(stderr, "error converting IPv4 address %s\n",  89:                     hostAddress);  90:             return 1;  91:         }  92:  93:         addr4.sin_family = AF_INET;  94:         addr4.sin_port = portNum;  95:     } else {  96:  97:         memset(&addr6, 0, sizeof(addr6));  98:  99:         if (inet_pton(AF_INET6, hostAddress, 100:                       &addr6.sin6_addr) <= 0) { 101:             fprintf(stderr, "error converting IPv6 address %s\n", 102:                     hostAddress); 103:             return 1; 104:         } 105: 106:         addr6.sin6_family = AF_INET6; 107:         addr6.sin6_port = portNum; 108:         addr = (struct sockaddr *) &addr6; 109:         addrLen = sizeof(addr6); 110:     } 


17.5.5. Turning Names into Addresses

Although long strings of numbers are a perfectly reasonable identification method for computers to use for recognizing each other, people tend to get dismayed at the idea of dealing with large numbers of digits. To allow humans to use alphabetic names for computers instead of numeric ones, the TCP/IP protocol suite includes a distributed database for converting between hostnames and IP addresses. This database is called the Domain Name System (DNS) and is covered in depth by many books [Stevens, 1994] [Albitz, 1996].

DNS provides many features, but the only one we are interested in here is its ability to convert between IP addresses and hostnames. Although it may seem like this should be a one-to-one mapping, it is actually a many-to-many mapping: Every IP address corresponds to zero or more hostnames and every hostname corresponds to zero or more IP addresses.

Although using a many-to-many mapping between hostnames and IP addresses may seem strange, many Internet sites use a single machine for their ftp site and their Web site. They would like www.some.org and ftp.some.org to refer to a single machine, and they have no need for two IP addresses for the machine, so both hostnames resolve to a single IP address. Every IP address has one primary, or canonical hostname, which is used when an IP address needs to be converted to a single hostname during a reverse name lookup.

The most popular reason for mapping a single hostname to multiple IP addresses is load balancing. Name servers (programs that provide the service of converting hostnames to IP addresses) are often configured to return different addresses at different times for the same name, allowing multiple physical machines to provide a single service.

The arrival of IPv6 provides another reason for a single hostname to have multiple addresses; many machines now have both IPv4 and IPv6 addresses.

The getaddrinfo()[21] library function provides programs easy access to DNS's hostname resolution.

[21] If you have done much socket programming in the past (or are comparing this book to its first edition) you'll notice that the functions used for hostname resolution have changed dramatically. These changes allow programs to be written completely independently of the protocol they use. The changes were made to make it easy to write programs that work on both IPv4 and IPv6 machines, but they should extend to other protocols as well, although getaddrinfo() works only on IPv4 and IPv6 at this time.

 #include <sys/types.h> #include <socket.h> #include <netdb.h> int getaddrinfo(const char * hostname, const char * servicename,                 const struct addrinfo * hints, struct addrinfo ** res); 


This function is conceptually quite simple, but it is very powerful, so the details are a bit tricky. The idea is for it to take a hostname, a service name, or both, turn it into a list of IP addresses, and use the hints to filter out some of those addresses that the application is not interested in. The final list is returned as a linked list in res.

The hostname that is being looked up is in the first parameter, and may be NULL if only a service lookup is being done. The hostname may be a name (such as www.ladweb.net) or an IP address in either dotted-decimal or colon-separated form, which getaddrinfo() converts to a binary address.

The second parameter, servicename, specifies the name of the service whose well-known port is needed. If it is NULL, no service lookup is done.

struct addrinfo is used for both the hints that are used to filter the full list of addresses and for returning the final list of addresses to the application.

 #include <netdb.h> struct addrinfo {     int ai_flags;     int ai_family;     int ai_socktype;     int ai_protocol;     socklen_t ai_addrlen;     struct sockaddr_t * ai_addr;     char * ai_canonname;     struct addrinfo * next; } 


When struct addrinfo is used for the hints parameter, only the first four members are used; the rest should be set to zero or NULL. If ai_family is set, getaddrinfo() returns addresses only for the protocol family (such as PF_INET) it specifies. Similarly, if ai_socktype is set, only addresses for that type of socket are returned.

The ai_protocol member allows the results to be limited to a particular protocol. It should never be used unless ai_family is set as well, as the numeric value of a protocol (such as IPPROTO_TCP) is not unique across all protocols; it is well defined only for PF_INET and PF_INET6.

The final member that is used for the hints is ai_flags, which is one or more of the following values logically OR'ed together:

AI_ADDRCONFIG

By default, getaddrinfo() returns all of the addresses that match the query. This flag tells it to return only addresses for protocols that have addresses configured on the local system. In other words, it returns only IPv4 addresses on systems with IPv4 interfaces configured, and returns only IPv6 addresses on systems with IPv6 interfaces configured.

AI_CANONNAME

On return, the ai_canonname field will contain the canonical hostname for the address specified by that struct addrinfo. Finding this takes an extra search in the DNS, and is not normally necessary.

AI_NUMERICHOST

The hostname parameter must be an address in colon-separated or dotted-decimal form; no hostname conversions are done. This prevents getaddrinfo() from performing any hostname lookups, which can be a lengthy process.

AI_PASSIVE

When hostname is NULL and this flag is present, the address returned is unspecified, which allows a server to wait for connections on all interfaces. If this flag is not specified when hostname is NULL, the loopback address is returned.[22]


[22] The loopback address is a special address that lets programs talk through TCP/IP to applications on the same machine only.

The final parameter to getaddrinfo(), res, should be the address of a pointer to a struct addrinfo. On successful completion, the variable pointed to by res is set to point to the first entry in a singly linked list of addresses that match the query. The ai_next member of the struct addrinfo points to the next member in the linked list, and the last node in the list has ai_next set to NULL. When the application is finished with the linked list that is returned, freeaddrinfo() frees the memory used by the list.

 #include <sys/types.h> #include <socket.h> #include <netdb.h> void freeaddrinfo(struct addrinfo * res); 


The sole parameter to freeaddrinfo is a pointer to the first node in the list.

Each node in the returned list is of type struct addrinfo, and specifies a single address that matches the query. Each address includes not only the IPv4 or IPv6 address, but also specifies the type of connection (datagram, for example) and the protocol (such as udp). If the query matches multiple types of connections for a single IP address, that address is included in multiple nodes.

Each node contains the following information:

  • ai_family is the protocol family (PF_INET or PF_INET6) the address belongs to.

  • ai_socktype is the type of connection for the address and is normally SOCK_STREAM, SOCK_DGRAM, or SOCK_RAW.

  • ai_protocol is the protocol for the address (normally IPPROTO_TCP or IPPROTO_UDP).

  • If AI_CANONNAME was specified in the hints parameter, ai_canonname contains the canonical name for the address.

  • ai_addr points to a struct sockaddr for the appropriate protocol. If ai_family is PF_INET, then ai_addr points to a struct sockaddr_in, for example. The ai_addrlen member contains the length of the structure ai_addr points to.

  • If a servicename was provided, the port number in each address is set to the well-known port for that service; otherwise, the port number for each address is zero.

  • If no hostname was provided, the port numbers are set for each address but the IP address is set to either the loopback address or the unspecified address, as specified above in the description of the AI_PASSIVE flag.

While all of this seems very complicated, there are only two different ways getaddrinfo() is normally used. Most client programs want to turn a hostname supplied by the user and a service name known by the program into a fully specified address to which the client can connect. Accomplishing this is pretty straightforward; here is a program that takes a hostname as its first argument and a service name as its second and performs the lookups:

  1: /* clientlookup.c */  2:  3: #include <netdb.h>  4: #include <stdio.h>  5: #include <string.h>  6:  7: int main(int argc, const char ** argv) {  8:     struct addrinfo hints, * addr;  9:     const char * host = argv[1], * service = argv[2]; 10:     int rc; 11: 12:     if (argc != 3) { 13:         fprintf(stderr, "exactly two arguments are needed\n"); 14:         return 1; 15:     } 16: 17:     memset(&hints, 0, sizeof(hints)); 18: 19:     hints.ai_socktype = SOCK_STREAM; 20:     hints.ai_flags = AI_ADDRCONFIG; 21:     if ((rc = getaddrinfo(host, service, &hints, &addr))) 22:         fprintf(stderr, "lookup failed\n"); 23:     else 24:         freeaddrinfo(addr); 25: 26:     return 0; 27: } 


The interesting part of this program is lines 17-24. After clearing the hints structure, the application asks for SOCK_STREAM addresses that use a protocol configured on the local system (by setting the AI_ADDRCONFIG flag). It then calls getaddrinfo() with the hostname, service name, and hints, and displays a message if the lookup failed. On success, the first node in the linked list pointed to by addr is an appropriate address for the program to use to contact the specified service and host; whether that connection is best made using IPv4 or IPv6 is transparent to the program.

Server applications are a little bit simpler; they normally want to accept connection on a particular port, but on all addresses. Setting the AI_PASSIVE flags tells getaddrinfo() to return the address that tells the kernel to allow connections to any address it knows about when NULL is passed as the first parameter. As in the client example, AI_ADDRCONFIG is used to ensure that the returned address is for a protocol the machine supports.

  1: /* serverlookup.c */  2:  3: #include <netdb.h>  4: #include <stdio.h>  5: #include <string.h>  6:  7: int main(int argc, const char ** argv) {  8:     struct addrinfo hints, * addr;  9:     const char * service = argv[1]; 10:     int rc; 11: 12:     if (argc != 3) { 13:         fprintf(stderr, "exactly one argument is needed\n"); 14:         return 1; 15:     } 16: 17:     memset(&hints, 0, sizeof(hints)); 18: 19:     hints.ai_socktype = SOCK_STREAM; 20:     hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE; 21:     if ((rc = getaddrinfo(NULL, service, &hints, &addr))) 22:         fprintf(stderr, "lookup failed\n"); 23:     else 24:         freeaddrinfo(addr); 25: 26:     return 0; 27: } 


After getaddrinfo() returns successfully, the first node in the linked list can be used by the server for setting up its socket.

The next example is a much more useful program. It provides a command line interface to most of the capabilities of getaddrinfo(). It allows the user to specify the hostname or service name to look up (or both), the type of socket (stream or datagram), the address family, and the protocol (TCP or UDP). The user can also ask that it display the canonical name, and that it display only addresses for protocols for which the machine is configured (via the AI_ADDRCONFIG flag). Here is how the program can be used to find the addresses to use for a telnet connection to the local machine (this machine has been configured for both IPv4 and IPv6):

 $./netlookup --host localhost --service telnet IPv6   stream  tcp     port 23 host: :1 IPv6   dgram   udp     port 23 host: :1 IPv4   stream  tcp     port 23 host 127.0.0.1 IPv4   dgram   udp     port 23 host 127.0.0.1 


As there is no protocol for telnet over a datagram connection defined (although a well-known port for such a service has been reserved), it is probably a good idea to restrict the search to stream protocols.

 [ewt@patton code]$./netlookup --host localhost --service telnet --stream IPv6    stream  tcp    port 23 host: :1 IPv4    stream  tcp    port 23 host 127.0.0.1 


After unconfiguring the local machine for IPv6, the same command looks like this:

 [ewt@patton code]$./netlookup --host localhost --service telnet --stream IPv4    stream  tcp    port 23 host 127.0.0.1 


Here is what a lookup looks like for an Internet host that has both IPv4 and IPv6 configurations:

 $./netlookup --host www.6bone.net --stream IPv6   stream  tcp     host 3ffe:b00:c18:1::10 IPv4   stream  tcp     host 206.123.31.124 


For a complete list of the command-line options netlookup.c supports, run it without any parameters.

   1: /* netlookup.c */   2:   3: #include <netdb.h>   4: #include <arpa/inet.h>   5: #include <netinet/in.h>   6: #include <stdio.h>   7: #include <string.h>   8: #include <stdlib.h>   9:  10: /* Called when errors occur during command line processing;  11:    this displays a brief usage message and exits */  12: void usage(void) {  13:     fprintf(stderr, "usage: netlookup [--stream] [--dgram] "  14:             "[--ipv4] [--ipv6] [--name] [--udp]\n");  15:     fprintf(stderr, "                 [--tcp] [--cfg] "  16:             "[--service <service>] [--host <hostname>]\n");  17:     exit(1);  18: }  19:  20: int main(int argc, const char ** argv) {  21:     struct addrinfo * addr, * result;  22:     const char ** ptr;  23:     int rc;  24:     struct addrinfo hints;  25:     const char * serviceName = NULL;  26:     const char * hostName = NULL;  27:  28:     /* clear the hints structure */  29:     memset(&hints, 0, sizeof(hints));  30:  31:     /* parse the command line arguments, skipping over argv[0]  32:  33:        The hints structure, serviceName, and hostName will be  34:        filled in based on which arguments are present. */  35:     ptr = argv + 1;  36:     while (*ptr && *ptr[0] == '-') {  37:         if (!strcmp(*ptr, "--ipv4"))  38:             hints.ai_family = PF_INET;  39:         else if (!strcmp(*ptr, "--ipv6"))  40:             hints.ai_family = PF_INET6;  41:         else if (!strcmp(*ptr, "--stream"))  42:             hints.ai_socktype = SOCK_STREAM;  43:         else if (!strcmp(*ptr, "--dgram"))  44:             hints.ai_socktype = SOCK_DGRAM;  45:         else if (!strcmp(*ptr, "--name"))  46:             hints.ai_flags |= AI_CANONNAME;  47:         else if (!strcmp(*ptr, "--cfg"))  48:             hints.ai_flags |= AI_ADDRCONFIG;  49:         else if (!strcmp(*ptr, "--tcp")) {  50:             hints.ai_protocol = IPPROTO_TCP;  51:         } else if (!strcmp(*ptr, "--udp")) {  52:             hints.ai_protocol = IPPROTO_UDP;  53:         } else if (!strcmp(*ptr, "--host")) {  54:             ptr++;  55:             if (!*ptr) usage();  56:             hostName = *ptr;  57:         } else if (!strcmp(*ptr, "--service")) {  58:             ptr++;  59:             if (!*ptr) usage();  60:             serviceName = *ptr;  61:         } else  62:             usage();  63:  64:         ptr++;  65:     }  66:  67:     /* we need a hostName, serviceName, or both */  68:     if (!hostName && !serviceName)  69:         usage();  70:  71:     if ((rc = getaddrinfo(hostName, serviceName, &hints,  72:                           &result))) {  73:         fprintf(stderr, "service lookup failed:     %s\n",  74:                 gai_strerror(rc));  75:         return 1;  76:     }  77:  78:     /* walk through the linked list, displaying all results */  79:     addr = result;  80:     while (addr) {  81:         switch (addr->ai_family) {  82:             case PF_INET:       printf("IPv4");  83:                                 break;  84:             case PF_INET6:      printf("IPv6");  85:                                 break;  86:             default:            printf("(%d)", addr->ai_family);  87:                                 break;  88:         }  89:  90:         switch (addr->ai_socktype) {  91:             case SOCK_STREAM:   printf("\tstream");  92:                                 break;  93:             case SOCK_DGRAM:    printf("\tdgram");  94:                                 break;  95:             case SOCK_RAW:      printf("\traw ");  96:                                 break;  97:             default:            printf("\t(%d)",  98:                                        addr->ai_socktype);  99:                                 break; 100:         } 101: 102:         if (addr->ai_family == PF_INET || 103:                 addr->ai_family == PF_INET6) 104:             switch (addr->ai_protocol) { 105:                 case IPPROTO_TCP:   printf("\ttcp"); 106:                                     break; 107:                 case IPPROTO_UDP:   printf("\tudp"); 108:                                     break; 109:                 case IPPROTO_RAW:   printf("\traw"); 110:                                     break; 111:                 default:            printf("\t(%d)", 112:                                            addr->ai_protocol); 113:                                     break; 114:             } 115:         else 116:             printf("\t"); 117: 118:         /* display information for both IPv4 and IPv6 addresses */ 119: 120:         if (addr->ai_family == PF_INET) { 121:             struct sockaddr_in * inetaddr = (void *) addr->ai_addr; 122:             char nameBuf[INET_ADDRSTRLEN]; 123: 124:             if (serviceName) 125:                 printf("\tport %d", ntohs(inetaddr->sin_port)); 126: 127:             if (hostName) 128:                 printf("\thost %s", 129:                        inet_ntop(AF_INET, &inetaddr->sin_addr, 130:                                  nameBuf, sizeof(nameBuf))); 131:         }  else if (addr->ai_family == PF_INET6) { 132:             struct sockaddr_in6 * inetaddr = 133:                                     (void *) addr->ai_addr; 134:             char nameBuf[INET6_ADDRSTRLEN]; 135: 136:             if (serviceName) 137:                 printf("\tport %d", ntohs(inetaddr->sin6_port)); 138: 139:             if (hostName) 140:                 printf("\thost %s", 141:                         inet_ntop(AF_INET6, &inetaddr->sin6_addr, 142:                                  nameBuf, sizeof(nameBuf))); 143:         } 144: 145:         if (addr->ai_canonname) 146:             printf("\tname %s", addr->ai_canonname); 147: 148:         printf("\n"); 149: 150:         addr = addr->ai_next; 151:     } 152: 153:     /* free the results of getaddrinfo() */ 154:     freeaddrinfo(result); 155: 156:     return 0; 157: } 


Unlike most library functions, getaddrinfo() returns an integer that is zero on success, and describes the error on failure; errno is not normally used by these functions. Table 17.3 summarizes the various error codes that these functions can return.

Table 17.3. Address and Name Lookup Errors

Error

Description

EAI_EAGAIN

The name could not be found, but trying again later might succeed.

EAI_BADFLAGS

The flags passed to the function were invalid.

EAI_FAIL

The lookup process had a permanent error occur.

EAI_FAMILY

The address family was not recognized.

EAI_MEMORY

A memory allocation request failed.

EAI_NONAME

The name or address cannot be converted.

EAI_OVERFLOW

An buffer passed was too small.

EAI_SERVICE

The service does not exist for the socket type.

EAI_SOCKTYPR

An invalid socket type was given.

EAI_SYSTEM

A system error occured, and the error is in errno.


These error codes can be converted to a string describing the failure through gai_strerror().

 #include <netdb.h> const char * gai_strerror(int error); 


The error should be the nonzero return value from getaddrinfo(). If the error is EAI_SYSTEM, the program should instead use strerror(errno) to get a good description.

17.5.6. Turning Addresses into Names

Fortunately, turning IP addresses and port numbers into host and service names is simpler than going the other way.

 #include <sys/socket.h> #include <netdb.h> int getnameinfo(struct sockaddr * addr, socklen_t addrlen,                 char * hostname, size_t hostlen,                 char * servicename, size_t servicelen,                 int flags); 


The addr parameter points to either a struct sockaddr_in or struct sockaddr_in6 structure, and addrlen contains the size of the structure addr points to. The IP address and port number specified by addr are converted to a hostname, which is stored at the location pointed to by hostname and a service name, which is stored in servicename. Either one may be NULL, causing getnameinfo() to not perform any name lookup for that parameter.

The hostlen and servicelen parameters specify how many bytes are available in the buffers pointed to by hostname and servicename respectively. If the either name does not fit in the space available, the buffers are filled up and an error (EAI_OVERFLOW) is returned.

The final argument, flags, changes how getnameinfo() performs name lookups. It should be zero or more of the following values logically OR'ed together:

NI_DGRAM

The UDP service name for the specified port is looked up instead of the TCP service name.[23]

NI_NAMEREQD

If the IP address to hostname lookup fails and this flag is specified, getnameinfo() returns an error. Otherwise, it returns the IP address formatted in dotted-decimal or colon-separated form.

NI_NOFQDN

Hostnames are normally returned as full qualified domain names; this means that the complete hostname is returned rather than a local abbreviation. If this flag is set, your host is digit.iana.org, and you lookup the IP address corresponding to www.iana.org, for example, then the returned hostname will be www. Hostname lookups for other machines are not affected; in the previous example, looking up the address for www.ietf.org will return the full hostname, www.ietf.org.

NI_NUMERICHOST

Rather than performing a hostname lookup, getnameinfo() converts the IP address to an IP address like inet_ntop() does.

NI_NUMERICSERV

The port number is placed in servicename as a formatted numeric string rather than converted to a service name.


[23] The two are almost always identical, but there are a few ports that are defined only for UDP ports (the SNMP trap protocol is one) and a few cases where the same port number is used for different TCP and UDP services (port 512 is used for the TCP exec service and the UDP biff service, for example).

The return codes for getnameinfo() are the same as for gethostinfo(); zero is returned on success and an error code is returned on failure. A full list of the possible errors is in Table 17.3, and gai_strerror() can be used to convert those errors into descriptive strings.

Here is an example of how to use getnameinfo() to perform some reverse name lookups for both IPv4 and IPv6 addresses:

 $ ./reverselookup --host ::1 hostname: localhost $ ./reverselookup --host 127.0.0.1 hostname: localhost $ ./reverselookup --host 3ffe:b00:c18:1::10 hostname: www.6bone.net $ ./reverselookup --host 206.123.31.124 --service 80 hostname: www.6bone.net service name: http   1: /* reverselookup.c */   2:   3: #include <netdb.h>   4: #include <arpa/inet.h>   5: #include <netinet/in.h>   6: #include <stdio.h>   7: #include <string.h>   8: #include <stdlib.h>   9:  10: /* Called when errors occur during command line processing; this  11:    displays a brief usage message and exits */  12: void usage(void) {  13:     fprintf(stderr, "usage: reverselookup [--numerichost] "  14:             "[--numericserv] [--namereqd] [--udp]\n");  15:     fprintf(stderr, "                     [--nofqdn] "  16:             "[--service <service>] [--host <hostname>]\n");  17:     exit(1);  18: }  19:  20: int main(int argc, const char ** argv) {  21:     int flags;  22:     const char * hostAddress = NULL;  23:     const char * serviceAddress = NULL;  24:     struct sockaddr_in addr4;  25:     struct sockaddr_in6 addr6;  26:     struct sockaddr * addr = (struct sockaddr *) &addr4;  27:     int addrLen = sizeof(addr4);  28:     int rc;  29:     int portNum = 0;  30:     const char ** ptr;  31:     char hostName[1024];  32:     char serviceName[256];  33:  34:     /* clear the flags */  35:     flags = 0;  36:  37:     /* parse the command line arguments, skipping over argv[0] */  38:     ptr = argv + 1;  39:     while (*ptr && *ptr[0] == '-') {  40:         if (!strcmp(*ptr, "--numerichost")) {  41:             flags |= NI_NUMERICHOST;  42:         } else if (!strcmp(*ptr, "--numericserv")) {  43:             flags |= NI_NUMERICSERV;  44:         } else if (!strcmp(*ptr, "--namereqd")) {  45:             flags |= NI_NAMEREQD;  46:         } else if (!strcmp(*ptr, "--nofqdn")) {  47:             flags |= NI_NOFQDN;  48:         } else if (!strcmp(*ptr, "--udp")) {  49:             flags |= NI_DGRAM;  50:         } else if (!strcmp(*ptr, "--host")) {  51:             ptr++;  52:             if (!*ptr) usage();  53:             hostAddress = *ptr;  54:         } else if (!strcmp(*ptr, "--service")) {  55:             ptr++;  56:             if (!*ptr) usage();  57:             serviceAddress = *ptr;  58:         } else  59:             usage();  60:  61:         ptr++;  62:     }  63:  64:     /* we need a hostAddress, serviceAddress, or both */  65:     if (!hostAddress && !serviceAddress)  66:         usage();  67:  68:     if (serviceAddress) {  69:         char * end;  70:  71:         portNum = htons(strtol(serviceAddress, &end, 0));  72:         if (*end) {  73:             fprintf(stderr, "failed to convert %s to a number\n",  74:                     serviceAddress);  75:             return 1;  76:         }  77:     }  78:  79:     if (!hostAddress) {  80:         addr4.sin_family = AF_INET;  81:         addr4.sin_port = portNum;  82:     } else if (!strchr(hostAddress, ':')) {  83:         /* If a colon appears in the hostAddress, assume IPv6.  84:            Otherwise, it must be IPv4 */  85:  86:         if (inet_pton(AF_INET, hostAddress,  87:                       &addr4.sin_addr) <= 0) {  88:             fprintf(stderr, "error converting IPv4 address %s\n",  89:                     hostAddress);  90:             return 1;  91:         }  92:  93:         addr4.sin_family = AF_INET;  94:         addr4.sin_port = portNum;  95:     } else {  96:  97:         memset(&addr6, 0, sizeof(addr6));  98:  99:         if (inet_pton(AF_INET6, hostAddress, 100:                       &addr6.sin6_addr) <= 0) { 101:             fprintf(stderr, "error converting IPv6 address %s\n", 102:                     hostAddress); 103:             return 1; 104:         } 105: 106:         addr6.sin6_family = AF_INET6; 107:         addr6.sin6_port = portNum; 108:         addr = (struct sockaddr *) &addr6; 109:         addrLen = sizeof(addr6); 110:     } 111: 112:     if (!serviceAddress) { 113:         rc = getnameinfo(addr, addrLen, hostName, sizeof(hostName), 114:                          NULL, 0, flags); 115:     } else if (!hostAddress) { 116:         rc = getnameinfo(addr, addrLen, NULL, 0, 117:                          serviceName, sizeof(serviceName), flags); 118:     } else { 119:         rc = getnameinfo(addr, addrLen, hostName, sizeof(hostName), 120:                          serviceName, sizeof(serviceName), flags); 121:     } 122: 123:     if (rc) { 124:         fprintf(stderr, "reverse lookup failed: %s\n", 125:                 gai_strerror(rc)); 126:         return 1; 127:     } 128: 129:     if (hostAddress) 130:         printf("hostname: %s\n", hostName); 131:     if (serviceAddress) 132:         printf("service name: %s\n", serviceName); 133: 134:     return 0; 135: } 


17.5.7. Listening for TCP Connections

Listening for TCP connections is nearly identical to listening for Unix domain connections. The only differences are the protocol and address families. Here is a version of the example Unix domain server that works over TCP sockets instead:

  1: /* tserver.c */  2:  3: /* Waits for a connection on port 4321. Once a connection has been  4:    established, copy data from the socket to stdout until the other  5:    end closes the connection, and then wait for another connection  6:    to the socket. */  7:  8: #include <arpa/inet.h>  9: #include <netdb.h> 10: #include <netinet/in.h> 11: #include <stdio.h> 12: #include <string.h> 13: #include <sys/socket.h> 14: #include <unistd.h> 15: 16: #include "sockutil.h"         /* some utility functions */ 17: 18: int main(void) { 19:     int sock, conn, i, rc; 20:     struct sockaddr address; 21:     size_t addrLength = sizeof(address); 22:     struct addrinfo hints, * addr; 23: 24:     memset(&hints, 0, sizeof(hints)); 25: 26:     hints.ai_socktype = SOCK_STREAM; 27:     hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; 28:     if ((rc = getaddrinfo(NULL, "4321", &hints, &addr))) { 29:         fprintf(stderr, "hostname lookup failed: %s\n", 30:                 gai_strerror(rc)); 31:         return 1; 32:     } 33: 34:     if ((sock = socket(addr->ai_family, addr->ai_socktype, 35:                        addr->ai_protocol)) < 0) 36:         die("socket"); 37: 38:     /* Let the kernel reuse the socket address. This lets us run 39:        twice in a row, without waiting for the (ip, port) tuple 40:        to time out. */ 41:     i = 1; 42:     setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); 43: 44:     if (bind(sock, addr->ai_addr, addr->ai_addrlen)) 45:         die("bind"); 46: 47:     freeaddrinfo(addr); 48: 49:     if (listen(sock, 5)) 50:         die("listen"); 51: 52:     while ((conn = accept(sock, (struct sockaddr *) &address, 53:                           &addrLength)) >= 0) { 54:         printf("---- getting data\n"); 55:         copyData(conn, 1); 56:         printf("---- done\n"); 57:         close(conn); 58:     } 59: 60:     if (conn < 0) 61:         die("accept"); 62: 63:     close(sock); 64:     return 0; 65: } 


Notice that the IP address bound to the socket specifies a port number, 4321, but not an IP address. This leaves the kernel free to use whatever local IP address it likes.

The other thing that needs some explaining is this code sequence on lines 41-42:

 41:     i = 1; 42:     setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); 


Linux's TCP implementation, like that of most Unix systems, restricts how soon a (local host, local port)can be reused.[24] This code sets an option on the socket that bypasses this restriction and allows the server to be run twice in a short period of time. This is similar to the reason the Unix domain socket example server removed any preexisting socket file before calling bind().

[24] For TCP ports, the combination may not be used within a two-minute period.

The setsockopt() allows you to set many socket- and protocol-specific options:

 #include <sys/socket.h> int setsockopt(int sock, int level, int option,                const void * valptr, int vallength); 


The first argument is the socket whose option is being set. The second argument, level, specifies what type of option is being set. In our server, we used SOL_SOCKET, which specifies that a generic socket option is being set. The option parameter specifies the option to be changed. A pointer to the new value of the option is passed through valptr and the size of the value pointed to by valptr is passed as vallength. For our server, we use a pointer to a non-zero integer, which turns on the SO_REUSEADDR option.

17.5.8. TCP Client Applications

TCP clients are similar to Unix domain clients. Usually, a socket is created and immediately connect() ed to the server. The only differences are in how the address passed to connect() is set. Rather than use a file name, most TCP clients look up the hostname to connect to through getaddrinfo(), which provides the information for connect().

Here is a simple TCP client that talks to the server presented in the previous section. It takes a single argument, which is the name or IP number (in dotted-decimal notation) of the host the server is running on. It otherwise behaves like the sample Unix domain socket client presented on page 422.

  1: /* tclient.c */  2:  3: /* Connect to the server whose hostname or IP is given as an  4:    argument, at port 4321. Once connected, copy everything on  5:    stdin to the socket, then exit. */  6:  7: #include <arpa/inet.h>  8: #include <netdb.h>  9: #include <netinet/in.h> 10: #include <stdio.h> 11: #include <stdlib.h> 12: #include <string.h> 13: #include <sys/socket.h> 14: #include <unistd.h> 15: 16: #include "sockutil.h"          /* some utility functions */ 17: 18: int main(int argc, const char ** argv) { 19:     struct addrinfo hints, * addr; 20:     struct sockaddr_in * addrInfo; 21:     int rc; 22:     int sock; 23: 24:     if (argc != 2) { 25:         fprintf(stderr, "only a single argument is supported\n"); 26:         return 1; 27:     } 28: 29:     memset(&hints, 0, sizeof(hints)); 30: 31:     hints.ai_socktype = SOCK_STREAM; 32:     hints.ai_flags = AI_ADDRCONFIG; 33:     if ((rc = getaddrinfo(argv[1], NULL, &hints, &addr))) { 34:         fprintf(stderr, "hostname lookup failed: %s\n", 35:                 gai_strerror(rc)); 36:         return 1; 37:     } 38: 39:     /* this lets us access the sin_family and sin_port (which are 40:        in the same place as sin6_family and sin6_port */ 41:     addrInfo = (struct sockaddr_in *) addr->ai_addr; 42: 43:     if ((sock = socket(addrInfo->sin_family, addr->ai_socktype, 44:                        addr->ai_protocol)) < 0) 45:         die("socket"); 46: 47:     addrInfo->sin_port = htons(4321); 48: 49:     if (connect(sock, (struct sockaddr *) addrInfo, 50:                 addr->ai_addrlen)) 51:         die("connect"); 52: 53:     freeaddrinfo(addr); 54: 55:     copyData(0, sock); 56: 57:     close(sock); 58: 59:     return 0; 60: } 



       
    top
     


    Linux Application Development
    Linux Application Development (paperback) (2nd Edition)
    ISBN: 0321563220
    EAN: 2147483647
    Year: 2003
    Pages: 168

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net