Network Interface Guide

Chapter 2 Socket Interfaces

This chapter presents the socket interface and illustrates it with sample programs. The programs demonstrate the Internet family sockets.

Sockets Are Multithread Safe

The interface described in this chapter is multithread safe. Applications that contain socket function calls can be used freely in a multithreaded application. Note, however, that the degree of concurrency available to applications is not specified.

SunOS 4 Binary Compatibility

Two major changes from SunOS 4 hold true for SunOS 5 releases. The binary compatibility package allows SunOS 4-based dynamically linked socket applications to run on SunOS 5.

  1. You must explicitly specify the socket library (-lsocket or libsocket) on the compilation line.

  2. You may need to link with libnsl also (use -lsocket -lnsl, not -lnsl -lsocket).

  3. You must recompile all SunOS 4 socket-based applications with the socket library to run under SunOS 5.

Overview of Sockets

Sockets are the most commonly used low-level interface to network protocols. They have been an integral part of SunOS releases since 1981. A socket is an endpoint of communication to which a name can be bound. A socket has a type and one associated process. Sockets were designed to implement the client-server model for interprocess communication where:

Sockets make network protocols available, while behaving like UNIX files. Applications create sockets when they are needed. Sockets work with the close(2), read(2), write(2), ioctl(2), and fcntl(2) interfaces, and the operating system differentiates between the file descriptors for files and the file descriptors for sockets.

Socket Libraries

The socket interface routines are in a library that must be linked with the application. The library libsocket.so is contained in /usr/lib with the rest of the system service libraries. libsocket.so is used for dynamic linking.

Socket Types

Socket types define the communication properties visible to a user. The Internet family sockets provide access to the TCP/IP transport protocols. The Internet family is identified by the value AF_INET6, for sockets that can communicate over both IPv6 and IPv4. The value AF_INET is also supported for source compatibility with old applications and for "raw" access to IPv4.

Three types of sockets are supported:

  1. Stream sockets allow processes to communicate using TCP. A stream socket provides bidirectional, reliable, sequenced, and unduplicated flow of data with no record boundaries. After the connection has been established, data can be read from and written to these sockets as a byte stream. The socket type is SOCK_STREAM.

  2. Datagram sockets allow processes to use UDP to communicate. A datagram socket supports bidirectional flow of messages. A process on a datagram socket can receive messages in a different order from the sending sequence and can receive duplicate messages. Record boundaries in the data are preserved. The socket type is SOCK_DGRAM.

  3. Raw sockets provide access to ICMP. These sockets are normally datagram oriented, although their exact characteristics are dependent on the interface provided by the protocol. Raw sockets are not for most applications. They are provided to support developing new communication protocols or for access to more esoteric facilities of an existing protocol. Only superuser processes can use raw sockets. The socket type is SOCK_RAW.

See "Selecting Specific Protocols" for further information.

Interface Sets

SunOS 5.8 provides two sets of socket interfaces. The BSD socket interfaces are provided and, since SunOS 5.7 the XNS 5 (Unix98) Socket interfaces are also provided. The XNS 5 interfaces differ slightly from the BSD interfaces.

The XNS 5 Socket interfaces are documented in the man pages: accept(3XNET), bind(3XNET), connect(3XNET), endhostent(3XNET), endnetent(3XNET), endprotoent(3XNET), endservent(3XNET), gethostbyaddr(3XNET), gethostbyname(3XNET), gethostent(3XNET), gethostname(3XNET), getnetbyaddr(3XNET), getnetbyname(3XNET), getnetent(3XNET), getpeername(3XNET), getprotobyname(3XNET), getprotobynumber(3XNET), getprotoent(3XNET), getservbyname(3XNET), getservbyport(3XNET), getservent(3XNET), getsockname(3XNET), getsockopt(3XNET), htonl(3XNET), htons(3XNET), inet_addr(3XNET), inet_lnaof(3XNET), inet_makeaddr(3XNET), inet_netof(3XNET), inet_network(3XNET), inet_ntoa(3XNET), listen(3XNET), ntohl(3XNET), ntohs(3XNET), recv(3XNET), recvfrom(3XNET), recvmsg(3XNET), send(3XNET), sendmsg(3XNET), sendto(3XNET), sethostent(3XNET), setnetent(3XNET), setprotoent(3XNET), setservent(3XNET), setsockopt(3XNET), shutdown(3XNET), socket(3XNET), and socketpair(3XNET).

The traditional SunOS 5 BSD Socket behavior is documented in the corresponding 3N man pages. In addition, a number of new interfaces have been added to section 3N: freeaddrinfo(3SOCKET), freehostent(3SOCKET), getaddrinfo(3SOCKET), getipnodebyaddr(3SOCKET), getipnodebyname(3SOCKET), getnameinfo(3SOCKET), inet_ntop(3SOCKET), inet_pton(3SOCKET), See the standards(5) man page for information on building applications that use the XNS 5 (Unix98) socket interface.

Socket Basics

This section describes the use of the basic socket interfaces.

Socket Creation

The socket(3SOCKET) call creates a socket in the specified family and of the specified type.

s = socket(family, type, protocol);
If the protocol is unspecified (a value of 0), the system selects a protocol that supports the requested socket type. The socket handle (a file descriptor) is returned.

The family is specified by one of the constants defined in sys/socket.h. Constants named AF_suite specify the address format to use in interpreting names, as shown in Table 2-1.

Table 2-1 Protocol Family

AF_APPLETALK

Apple Computer Inc. Appletalk network 

AF_INET6

Internet family for IPv6 and IPv4 

AF_INET

Internet family for IPv4 only 

AF_PUP

Xerox Corporation PUP internet 

AF_UNIX

Unix file system 

Socket types are defined in sys/socket.h. These types--SOCK_STREAM, SOCK_DGRAM, or SOCK_RAW--are supported by AF_INET6, AF_INET, and AF_UNIX. The following creates a stream socket in the Internet family:

s = socket(AF_INET6, SOCK_STREAM, 0);

This call results in a stream socket with the TCP protocol providing the underlying communication. Use the default protocol (the protocol argument is 0) in most situations. You can specify a protocol other than the default, as described in "Advanced Topics".

Binding Local Names

A socket is created with no name. A remote process has no way to refer to a socket until an address is bound to it. Communicating processes are connected through addresses. In the Internet family, a connection is composed of local and remote addresses, and local and remote ports. There can never be duplicate ordered sets, such as: protocol, local address, local port, foreign address, foreign port. In most families, connections must be unique.

The bind(3SOCKET) call allows a process to specify the local address of the socket. This forms the set local address, local port. connect(3SOCKET), and accept(3SOCKET) complete a socket's association by fixing the remote half of the address tuple. The bind(3SOCKET) call is used as follows:

bind (s, name, namelen);

The socket handle is s. The bound name is a byte string that is interpreted by the supporting protocol(s). Internet family names contain an Internet address and port number.

This example demonstrates binding an Internet address:

#include <sys/types.h>
#include <netinet/in.h>
...
struct sockaddr_in6 sin6;
...
		s = socket(AF_INET6, SOCK_STREAM, 0);
		bzero (&sin6, sizeof (sin6));
		sin6.sin6_family = AF_INET6;
		sin6.sin6_addr.s6_addr = in6addr_arg;
		sin6.sin6_port = htons(MYPORT);
		bind(s, (struct sockaddr *) &sin6, sizeof sin6);

The content of the address sin6 is described in "Address Binding", where Internet address bindings are discussed.

Connection Establishment

Connection establishment is usually asymmetric, with one process acting as the client and the other as the server. The server binds a socket to a well-known address associated with the service and blocks on its socket for a connect request. An unrelated process can then connect to the server. The client requests services from the server by initiating a connection to the server's socket. On the client side, the connect(3SOCKET) call initiates a connection. In the Internet family, this might appear as:

struct sockaddr_in6 server;
 ...
connect(s, (struct sockaddr *)&server, sizeof server);

If the client's socket is unbound at the time of the connect call, the system automatically selects and binds a name to the socket. See "Address Binding". This is the usual way that local addresses are bound to a socket on the client side.

To receive a client's connection, a server must perform two steps after binding its socket. The first is to indicate how many connection requests can be queued. The second step is to accept a connection:

struct sockaddr_in6 from;
 ...
listen(s, 5);                /* Allow queue of 5 connections */
fromlen = sizeof(from);
newsock = accept(s, (struct sockaddr *) &from, &fromlen);

The socket handle s is the socket bound to the address to which the connection request is sent. The second parameter of listen(3SOCKET) specifies the maximum number of outstanding connections that might be queued. from is a structure that is filled with the address of the client. A NULL pointer might be passed. fromlen is the length of the structure. (In the UNIX family, from is declared a struct sockaddr_un.)

accept(3SOCKET) normally blocks. accept(3SOCKET) returns a new socket descriptor that is connected to the requesting client. The value of fromlen is changed to the actual size of the address.

A server cannot indicate that it accepts connections only from specific addresses. The server can check the from address returned by accept(3SOCKET) and close a connection with an unacceptable client. A server can accept connections on more than one socket, or avoid blocking on the accept call. These techniques are presented in "Advanced Topics".

Connection Errors

An error is returned if the connection is unsuccessful (however, an address bound by the system remains). Otherwise, the socket is associated with the server and data transfer can begin.

Table 2-2 lists some of the more common errors returned when a connection attempt fails.

Table 2-2 Socket Connection Errors

Socket Errors 

Error Description 

ENOBUFS

Lack of memory available to support the call. 

EPROTONOSUPPORT

Request for an unknown protocol. 

EPROTOTYPE

Request for an unsupported type of socket. 

ETIMEDOUT

No connection established in specified time. This happens when the destination host is down or when problems in the network result in lost transmissions. 

ECONNREFUSED

The host refused service. This happens when a server process is not present at the requested address. 

ENETDOWN or EHOSTDOWN

These errors are caused by status information delivered by the underlying communication interface. 

ENETUNREACH or EHOSTUNREACH

These operational errors can occur either because there is no route to the network or host, or because of status information returned by intermediate gateways or switching nodes. The status returned is not always sufficient to distinguish between a network that is down and a host that is down. 

Data Transfer

This section describes the functions to send and receive data. You can send or receive a message with the normal read(2) and write(2) interfaces:

write(s, buf, sizeof buf);
read(s,  buf, sizeof buf);

Or the calls send(3SOCKET) and recv(3SOCKET) can be used:

send(s, buf, sizeof buf, flags);
recv(s, buf, sizeof buf, flags);

send(3SOCKET) and recv(3SOCKET) are very similar to read(2) and write(2), but the flags argument is important. The flags, defined in sys/socket.h, can be specified as a nonzero value if one or more of the following is required:

MSG_OOB

Send and receive out-of-band data

MSG_PEEK

Look at data without reading

MSG_DONTROUTE

Send data without routing packets

Out-of-band data is specific to stream sockets. When MSG_PEEK is specified with a recv(3SOCKET) call, any data present is returned to the user but treated as still unread. The next read(2) or recv(3SOCKET) call on the socket returns the same data. The option to send data without routing packets applied to the outgoing packets is currently used only by the routing table management process and is unlikely to be interesting to most users.

Closing Sockets

A SOCK_STREAM socket can be discarded by a close(2) function call. If data is queued to a socket that promises reliable delivery after a close(2), the protocol continues to try to transfer the data. If the data is still undelivered after an arbitrary period, it is discarded.

A shutdown(3SOCKET) closes SOCK_STREAM sockets gracefully. Both processes can acknowledge that they are no longer sending. This call has the form:

shutdown(s, how);

Where how is defined as:

Disallows further receives 

Disallows further sends 

Disallows both further sends and receives 

Connecting Stream Sockets

Figure 2-1 and the next two examples illustrate initiating and accepting an Internet family stream connection.

Figure 2-1 Connection-Oriented Communication Using Stream Sockets

Graphic

The program in Example 2-1 is a server. It creates a socket and binds a name to it, then displays the port number. The program calls listen(3SOCKET) to mark the socket ready to accept connection requests and initialize a queue for the requests. The rest of the program is an infinite loop. Each pass of the loop accepts a new connection and removes it from the queue, creating a new socket. The server reads and displays the messages from the socket and closes it. The use of in6addr_any is explained in "Address Binding".


Example 2-1 Accepting an Internet Stream Connection (Server)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
 
#define TRUE 1
 
/*
* This program creates a socket and then begins an infinite loop.
* Each time through the loop it accepts a connection and prints
* data from it. When the connection breaks, or the client closes
* the connection, the program accepts a new connection.
*/
 
main()
{
   int sock, length;
   struct sockaddr_in6 server;
   int msgsock;
   char buf[1024];
   int rval;
 
   /* Create socket. */
   sock = socket(AF_INET6, SOCK_STREAM, 0);
   if (sock == -1) {
      perror("opening stream socket");
      exit(1);
   }
   /* Bind socket using wildcards.*/
   bzero (&server, sizeof(server));
   bzero (&sin6, sizeof (sin6));
   server.sin6_family = AF_INET6;
   server.sin6_addr.s6_addr = in6addr_any;
   server.sin6_port = 0;
   if (bind(sock, (struct sockaddr *) &server, sizeof server)
         == -1) {
      perror("binding stream socket");
      exit(1);
   }
   /* Find out assigned port number and print it out. */
   length = sizeof server;
   if (getsockname(sock,(struct sockaddr *) &server,&length)
         == -1) {
      perror("getting socket name");
      exit(1);
   }
   printf("Socket port #%d\n", ntohs(server.sin6_port));
   /* Start accepting connections. */
   listen(sock, 5);
   do {
      msgsock = accept(sock,(struct sockaddr *) 0,(int *) 0);
      if (msgsock == -1
         perror("accept");
      else do {
         memset(buf, 0, sizeof buf);
         if ((rval = read(msgsock,buf, 1024)) == -1)
            perror("reading stream message");
         if (rval == 0)
            printf("Ending connection\n");
         else
            /* assumes the data is printable */
            printf("-->%s\n", buf);
      } while (rval > 0);
      close(msgsock);
   } while(TRUE);
   /*
    * Since this program has an infinite loop, the socket "sock" is
    * never explicitly closed. However, all sockets will be closed
    * automatically when a process is killed or terminates normally.
    */
   exit(0);
}

To initiate a connection, the client program in Example 2-2 creates a stream socket and calls connect(3SOCKET), specifying the address of the socket for connection. If the target socket exists and the request is accepted, the connection is complete and the program can send data. Data are delivered in sequence with no message boundaries. The connection is destroyed when either socket is closed. For more information about data representation routines, such as ntohl(3SOCKET), ntohs(3SOCKET), htons(3SOCKET), and htonl(3XNET), in this program, see the byteorder(3SOCKET) man page.


Example 2-2 Internet family Stream Connection (Client)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
 
#define DATA "Half a league, half a league . . ."
 
/*
 * This program creates a socket and initiates a connection with
 * the socket given in the command line. Some data are sent over the
 * connection and then the socket is closed, ending the connection.
 * The form of the command line is: streamwrite hostname portnumber
 * Usage: pgm host port
 */
main(argc, argv)
    int argc;
    char *argv[];
{
    int sock, errnum h_addr_index;
    struct sockaddr_in6 server;
    struct hostent *hp;
    char buf[1024];
 
    /* Create socket. */
    sock = socket( AF_INET6, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("opening stream socket");
        exit(1);
    }
    /* Connect socket using name specified by command line. */
    bzero (&sin6, sizeof (sin6));
    server.sin6_family = AF_INET6;
    hp = getipnodebyname(AF_INET6, argv[1], AI_DEFAULT, &errnum);
/*
 * getinodebyname returns a structure including the network address
 * of the specified host.
 */
    if (hp == (struct hostent *) 0) {
        fprintf(stderr, "%s: unknown host\n", argv[1]);
        exit(2);
    }

    h_addr_index = 0;
    while (hp->h_addr_list[h_addr_index] != NULL) {
        bcopy(hp->h_addr_list[h_addr_index], &server.sin6_addr,
                    hp->h_length);
        server.sin6_port = htons(atoi(argv[2]));
        if (connect(sock, (struct sockaddr *) &server,
                    sizeof (server)) == -1) {
            if (hp->h_addr_list[++h_addr_index] != NULL) {
                /* Try next address */
                continue;
            }
            perror("connecting stream socket");
            freehostent(hp);
            exit(1);
        }
        break;
    }
    freehostent(hp);
    if (write( sock, DATA, sizeof DATA) == -1)
        perror("writing on stream socket");
    close(sock);
    freehostent (hp);
    exit(0);
}

Datagram Sockets

A datagram socket provides a symmetric data exchange interface. There is no requirement for connection establishment. Each message carries the destination address. Figure 2-2 shows the flow of communication between server and client.


Note -

The bind(3SOCKET) step shown below for the server is optional.


Figure 2-2 Connectionless Communication Using Datagram Sockets

Graphic

Datagram sockets are created as described in "Socket Creation". If a particular local address is needed, the bind(3SOCKET) operation must precede the first data transmission. Otherwise, the system sets the local address and/or port when data is first sent. To send data, sendto(3SOCKET) is used:

sendto(s, buf, buflen, flags, (struct sockaddr *) &to, tolen);

The s, buf, buflen, and flags parameters are the same as in connection-oriented sockets. The to and tolen values indicate the address of the intended recipient of the message. A locally detected error condition (such as an unreachable network) causes a return of -1 and errno to be set to the error number.

To receive messages on a datagram socket, recvfrom(3SOCKET) is used:

recvfrom(s, buf, buflen, flags, (struct sockaddr *) &from, &fromlen);

Before the call, fromlen is set to the size of the from buffer. On return, it is set to the size of the address from which the datagram was received.

Datagram sockets can also use the connect(3SOCKET) call to associate a socket with a specific destination address. It can then use the send(3SOCKET) call. Any data sent on the socket without explicitly specifying a destination address is addressed to the connected peer, and only data received from that peer is delivered. Only one connected address is permitted for one socket at a time. A second connect(3SOCKET) call changes the destination address. Connect requests on datagram sockets return immediately. The system records the peer's address. accept(3SOCKET), and listen(3SOCKET) are not used with datagram sockets.

While a datagram socket is connected, errors from previous send(3SOCKET) calls can be returned asynchronously. These errors can be reported on subsequent operations on the socket, or an option of getsockopt(3SOCKET), SO_ERROR, can be used to interrogate the error status.

Example 2-3 shows how to send an Internet call by creating a socket, binding a name to the socket, and sending the message to the socket.


Example 2-3 Sending an Internet Family Datagram

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
 
#define DATA "The sea is calm, the tide is full . . ."
 
/*
 * Here I send a datagram to a receiver whose name I get from
 * the command line arguments. The form of the command line is:
 * dgramsend hostname portnumber
 */
main(argc, argv)
   int argc, errnum;
   char *argv[];
{
   int sock;
   struct sockaddr_in6 name;
   struct hostent *hp;
 
   /* Create socket on which to send. */
   sock = socket(AF_INET6,SOCK_DGRAM, 0);
   if (sock == -1) {
      perror("opening datagram socket");
      exit(1);
   }
   /*
    * Construct name, with no wildcards, of the socket to ``send''
    * to. getinodebyname returns a structure including the network
    * address of the specified host. The port number is taken from
    * the command line.
    */
   hp = getipnodebyname(AF_INET6, argv[1], AI_DEFAULT, &errnum);
   if (hp == (struct hostent *) 0) {
      fprintf(stderr, "%s: unknown host\n", argv[1]);
      exit(2);
   }
   bzero (&sin6, sizeof (sin6));
   bzero (&name, sizeof (name));
   memcpy((char *) &name.sin6_addr, (char *) hp->h_addr,
      hp->h_length);
   name.sin6_family = AF_INET6;
   name.sin6_port = htons(atoi(argv[2]));
   /* Send message. */
   if (sendto(sock,DATA, sizeof DATA ,0,
      (struct sockaddr *) &name,sizeof name) == -1)
      perror("sending datagram message");
   close(sock);
   exit(0);
}

Example 2-4 shows how to read an Internet call by creating a socket, binding a name to the socket, and then reading from the socket.


Example 2-4 Reading Internet Family Datagrams

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
 
/*
 * This program creates a datagram socket, binds a name to it, then
 * reads from the socket.
 */
 
main()
{
   int sock, length;
   struct sockaddr_in6 name;
   char buf[1024];
 
   /* Create socket from which to read. */
   sock = socket(AF_INET6, SOCK_DGRAM, 0);
   if (sock == -1) {
      perror("opening datagram socket");
      exit(1);
   }
   /* Create name with wildcards. */
   bzero (&sin6, sizeof (sin6));
   name.sin6_family = AF_INET6;
   name.sin6_addr.s6_addr = in6addr_any;
   name.sin6_port = 0;
   if (bind(sock,(struct sockaddr *)&name, sizeof name) == -1) {
      perror("binding datagram socket");
      exit(1);
   }
   /* Find assigned port value and print it out. */
   length = sizeof(name);
   if (getsockname(sock,(struct sockaddr *) &name, &length)
         == -1) 	{
      perror("getting socket name");
      exit(1);
   }
   printf("Socket port #%d\n", ntohs(name.sin6_port));
   /* Read from the socket. */
   if (read(sock, buf, 1024) == -1 )
      perror("receiving datagram packet");
   /* Assumes the data is printable */
   printf("-->%s\n", buf);
   close(sock);
   exit(0);
}

Input/Output Multiplexing

Requests can be multiplexed among multiple sockets or files. Use select(3C) to do this:

#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
 ...
fd_set readmask, writemask, exceptmask;
struct timeval timeout;
 ...
select(nfds, &readmask, &writemask, &exceptmask, &timeout);

The first argument of select(3C) is the number of file descriptors in the lists pointed to by the next three arguments.

The second, third, and fourth arguments of select(3C) point to three sets of file descriptors: a set of descriptors to read on, a set to write on, and a set on which exception conditions are accepted. Out-of-band data is the only exceptional condition. Any of these pointers can be a properly cast null. Each set is a structure containing an array of long integer bit masks. The size of the array is set by FD_SETSIZE (defined in select.h). The array is long enough to hold one bit for each FD_SETSIZE file descriptor.

The macros FD_SET(fd, &mask) and FD_CLR(fd, &mask) add and delete, respectively, the file descriptor fd in the set mask. The set should be zeroed before use, and the macro FD_ZERO(&mask) clears the set mask.

The fifth argument of select(3C) allows a time-out value to be specified. If the timeout pointer is NULL, select(3C) blocks until a descriptor is selectable, or until a signal is received. If the fields in timeout are set to 0, select(3C) polls and returns immediately.

select(3C) normally returns the number of file descriptors selected. select(3C) returns a 0 if the time-out has expired. select(3C) returns -1 for an error or interrupt with the error number in errno and the file descriptor masks unchanged. For a successful return, the three sets indicate which file descriptors are ready to be read from, written to, or have exceptional conditions pending.

You should test the status of a file descriptor in a select mask with the FD_ISSET(fd, &mask) macro. It returns a nonzero value if fd is in the set mask, and 0 if it is not. Use select(3C) followed by a FD_ISSET(fd, &mask) macro on the read set to check for queued connect requests on a socket.

Example 2-5 shows how to select on a "listening" socket for readability to determine when a new connection can be picked up with a call to accept(3SOCKET). The program accepts connection requests, reads data, and disconnects on a single socket.


Example 2-5 Using select(3C) to Check for Pending Connections

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
 
#define TRUE 1
 
/*
 * This program uses select to check that someone is
 * trying to connect before calling accept.
 */
 
main()
{
   int sock, length;
   struct sockaddr_in6 server;
   int msgsock;
   char buf[1024];
   int rval;
   fd_set ready;
   struct timeval to;
 
   /* Open a socket and bind it as in previous examples. */
 
   /* Start accepting connections. */
   listen(sock, 5);
   do {
      FD_ZERO(&ready);
      FD_SET(sock, &ready);
      to.tv_sec = 5;
      to.tv_usec = 0;
      if (select(sock + 1, &ready, (fd_set *)0, (fd_set *)0, &to) == -1) {
         perror("select");
         continue;
      }
      if (FD_ISSET(sock, &ready)) {
         msgsock = accept(sock, (struct sockaddr *)0,
            (int *)0);
         if (msgsock == -1)
            perror("accept");
         else do {
            memset(buf, 0, sizeof buf);
            if ((rval = read(msgsock, buf, 1024)) == -1)
               perror("reading stream message");
            else if (rval == 0)
               printf("Ending connection\n");
            else
               printf("-->%s\n", buf);
         } while (rval > 0);
         close(msgsock);
      } else
         printf("Do something else\n");
   } while (TRUE);
	   exit(0);
}

In previous versions of the select(3C) routine, its arguments were pointers to integers instead of pointers to fd_sets. This style of call still works if the number of file descriptors is smaller than the number of bits in an integer.

select(3C) provides a synchronous multiplexing scheme. The SIGIO and SIGURG signals (described in "Advanced Topics") provide asynchronous notification of output completion, input availability, and exceptional conditions.

Standard Routines

You might need to locate and construct network addresses. This section describes the routines that manipulate network addresses. Unless otherwise stated, functions presented in this section apply only to the Internet family.

Locating a service on a remote host requires many levels of mapping before client and server communicate. A service has a name for human use. The service and host names must be translated to network addresses. Finally, the address is used to locate and route to the host. The specifics of the mappings can vary between network architectures. Preferably, a network does not require that hosts be named, thus protecting the identity of their physical locations. It is more flexible to discover the location of the host when it is addressed.

Standard routines map host names to network addresses, network names to network numbers, protocol names to protocol numbers, and service names to port numbers, and the appropriate protocol to use in communicating with the server process. The file netdb.h must be included when using any of these routines.

Host and Service Names

The interfaces getaddrinfo(3SOCKET), getnameinfo(3SOCKET), and freeaddrinfo(3SOCKET) provide a simplified method of translating between the names and addresses of a service on a host. For IPv6, these interfaces can be used instead of calling getipnodebyname(3SOCKET) and getservbyname(3SOCKET) and then figuring out how to combine the addresses. Similarly, for IPv4, these interfaces can be used instead of gethostbyname(3NSL) and getservbyname(3SOCKET). Both IPv6 and IPv4 addresses are handled transparently.

getaddrinfo(3SOCKET) returns the combined address and port number of the specified host and service names. Since all of the information returned by getaddrinfo(3SOCKET) is dynamically allocated, it must be freed by freeaddrinfo(3SOCKET) to prevent memory leaks.getnameinfo(3SOCKET) returns the host and services names associated with a specified address and port number. To print error messages based on the EAI_xxx codes returned by getaddrinfo(3SOCKET) and getnameinfo(3SOCKET), call gai_strerror(3SOCKET).

An example of using getaddrinfo(3SOCKET) follows:

    struct addrinfo         *res, *aip;
    struct addrinfo         hints;
    int                     sock = -1;
    int                     error;

    /* Get host address.  Any type of address will do. */
    bzero(&hints, sizeof (hints));
    hints.ai_flags = AI_ALL|AI_ADDRCONFIG;
    hints.ai_socktype = SOCK_STREAM;

    error = getaddrinfo(hostname, servicename, &hints, &res);
    if (error != 0) {
        (void) fprintf(stderr, "getaddrinfo: %s for host %s service %s\n",
                 gai_strerror(error), hostname, servicename);
        return (-1);
    }
 

After processing the information returned by getaddrinfo(3SOCKET) in the structure pointed to by res, the storage should be released by

        freeaddrinfo(res);

getnameinfo(3SOCKET) is particularly useful in identifying the cause of an error as in the following example:

    struct sockaddr_storage faddr;
    int                     sock, new_sock;
    socklen_t               faddrlen;
    int                     error;
    char                    hname[NI_MAXHOST];
    char                    sname[NI_MAXSERV];

     ...
         faddrlen = sizeof (faddr);
         new_sock = accept(sock, (struct sockaddr *)&faddr, &faddrlen);
         if (new_sock == -1) {
             if (errno != EINTR && errno != ECONNABORTED) {
                 perror("accept");
             }
             continue;
         }        
         error = getnameinfo((struct sockaddr *)&faddr, faddrlen, hname, 
                     sizeof (hname), sname, sizeof (sname), 0);
         if (error) {
             (void) fprintf(stderr, "getnameinfo: %s\n",
                         gai_strerror(error));
         } else {
             (void) printf("Connection from %s/%s\n", hname, sname);
         }

hostent - Host Names

An Internet host-name-to-address mapping is represented by the hostent structure:

struct hostent {
	   char  *h_name;            /* official name of host */
	   char  **h_aliases;        /* alias list */
	   int   h_addrtype;         /* hostaddrtype(e.g.,AF_INET6) */
	   int   h_length;           /* length of address */
	   char  **h_addr_list;      /* list of addrs, null terminated */
};
/*1st addr, net byte order*/
#define h_addr h_addr_list[0]

getipnodebyname(3SOCKET) maps an Internet host name to a hostent structure, getipnodebyaddr(3SOCKET) maps an Internet host address to a hostent structure, freehostent(3SOCKET) frees the memory of a hostent structure, and inet_ntop(3SOCKET) maps an Internet host address to a displayable string.

The routines return a hostent structure containing the name of the host, its aliases, the address type (address family), and a NULL-terminated list of variable length addresses. The list of addresses is required because a host can have many addresses. The h_addr definition is for backward compatibility, and is the first address in the list of addresses in the hostent structure.

netent - Network Names

The routines to map network names to numbers and back return a netent structure:

/*
 * Assumes that a network number fits in 32 bits.
 */
struct netent {
   char     *n_name;      /* official name of net */
   char     **n_aliases;  /* alias list */
   int      n_addrtype;   /* net address type */
   int      n_net;        /* net number, host byte order */
};

getnetbyname(3SOCKET), getnetbyaddr_r(3SOCKET), and getnetent(3SOCKET) are the network counterparts to the host routines described above.

protoent - Protocol Names

The protoent structure defines the protocol-name mapping used with getprotobyname(3SOCKET), getprotobynumber(3SOCKET), and getprotoent(3SOCKET):

struct protoent {
   char     *p_name;          /* official protocol name */
   char     **p_aliases       /* alias list */
   int      p_proto;          /* protocol number */
};

servent - Service Names

An Internet family service resides at a specific, well-known port and uses a particular protocol. A service-name-to-port-number mapping is described by the servent 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 */
};
getservbyname(3SOCKET) maps service names and, optionally, a qualifying protocol to a servent structure. The call:
sp = getservbyname("telnet", (char *) 0);
returns the service specification of a telnet server using any protocol. The call:
sp = getservbyname("telnet", "tcp");

returns the telnet server that uses the TCP protocol. getservbyport(3SOCKET) and getservent(3SOCKET) are also provided. getservbyport(3SOCKET) has an interface similar to that of getservbyname(3SOCKET); an optional protocol name can be specified to qualify lookups.

Other Routines

In addition to address-related database routines, there are several other routines that simplify manipulating names and addresses. Table 2-3 summarizes the routines for manipulating variable-length byte strings and byte-swapping network addresses and values.

Table 2-3 Runtime Library Routines

Call 

Synopsis 

memcmp(3C)

Compares byte-strings; 0 if same, not 0 otherwise

memcpy(3C)

Copies n bytes from s2 to s1

memset(3C)

Sets n bytes to value starting at base

htonl(3SOCKET)

32-bit quantity from host into network byte order 

htons(3SOCKET)

16-bit quantity from host into network byte order 

ntohl(3SOCKET)

32-bit quantity from network into host byte order 

ntohs(3SOCKET)

16-bit quantity from network into host byte order 

The byte-swapping routines are provided because the operating system expects addresses to be supplied in network order. On some architectures, the host byte ordering is different from network byte order, so programs must sometimes byte-swap values. Routines that return network addresses do so in network order. Byte-swapping problems occur only when interpreting network addresses. For example, the following code formats a TCP or UDP port:

printf("port number %d\n", ntohs(sp->s_port));

On certain machines, where these routines are not needed, they are defined as null macros.

Client-Server Programs

The most common form of distributed application is the client/server model. In this scheme, client processes request services from a server process.

An alternate scheme is a service server that can eliminate dormant server processes. An example is inetd(1M), the Internet service daemon. inetd(1M) listens at a variety of ports, determined at start up by reading a configuration file. When a connection is requested on an inetd(1M) serviced port, inetd(1M) spawns the appropriate server to serve the client. Clients are unaware that an intermediary has played any part in the connection. inetd(1M) is described in more detail in "inetd(1M) Daemon".

Servers

Most servers are accessed at well-known Internet port numbers or UNIX family names. Example 2-6 illustrates the main loop of a remote-login server.


Example 2-6 Remote Login Server

main(argc, argv)
   int argc;
   char *argv[];
{
   int f;
   struct sockaddr_in6 from;
   struct sockaddr_in6 sin;
   struct servent *sp;
 
   sp = getservbyname("login", "tcp");
 
   if (sp == (struct servent *) NULL) {
      fprintf(stderr, "rlogind: tcp/login: unknown service");
      exit(1);
   }
   ...
   #ifndef DEBUG
   /* Disassociate server from controlling terminal. */
   ...
   #endif
   	sin.sin6_port = sp->s_port;	/* Restricted port */
      sin.sin6_addr.s6_addr = in6addr_any;
      ...
      f = socket(AF_INET6, SOCK_STREAM, 0);
      ...
      if (bind( f, (struct sockaddr *) &sin, sizeof sin ) == -1) {
      ...
      }
      ...
      listen(f, 5);
      while (TRUE) {
         int g, len = sizeof from;
         g = accept(f, (struct sockaddr *) &from, &len);
         if (g == -1) {
            if (errno != EINTR)
               syslog(LOG_ERR, "rlogind: accept: %m");
            continue;
         }
         if (fork() == 0) {
            close(f);
            doit(g, &from);
         }
         close(g);
      }
      exit(0);
}

Example 2-7 shows how the server gets its service definition.


Example 2-7 Remote Login Server: Step 1

sp = getservbyname("login", "tcp");
if (sp == (struct servent *) NULL) {
		fprintf(stderr, "rlogind: tcp/login: unknown service\n");
		exit(1);
}

The result from getservbyname(3SOCKET) is used later to define the Internet port at which the program listens for service requests. Some standard port numbers are in /usr/include/netinet/in.h.

Example 2-8 shows how the server dissociates from the controlling terminal of its invoker in the non-DEBUG mode of operation.


Example 2-8 Dissociating From the Controlling Terminal

   (void) close(0);
   (void) close(1);
   (void) close(2);
   (void) open("/", O_RDONLY);
   (void) dup2(0, 1);
   (void) dup2(0, 2);
   setsid();

This prevents the server from receiving signals from the process group of the controlling terminal. After a server has dissociated itself, it cannot send reports of errors to a terminal and must log errors with syslog(3C).

A server next creates a socket and listens for service requests. bind(3SOCKET) ensures that the server listens at the expected location. (The remote login server listens at a restricted port number, so it runs as superuser.)

Example 2-9 illustrates the main body of the loop.


Example 2-9 Remote Login Server: Main Body

while(TRUE) {
		int g, len = sizeof(from);
		if (g = accept(f, (struct sockaddr *) &from, &len) == -1) {
			if (errno != EINTR)
				syslog(LOG_ERR, "rlogind: accept: %m");
			continue;
		}
		if (fork() == 0) {		/* Child */
			close(f);
			doit(g, &from);
		}
		close(g);					/* Parent */
}

accept(3SOCKET) blocks messages until a client requests service. accept(3SOCKET) returns a failure indication if it is interrupted by a signal, such as SIGCHLD. The return value from accept(3SOCKET) is checked and an error is logged with syslog(3C) if an error has occurred.

The server then fork(2)s a child process and invokes the main body of the remote login protocol processing. The socket used by the parent to queue connection requests is closed in the child. The socket created by accept(3SOCKET) is closed in the parent. The address of the client is passed to the server application's doit() routine, which performs the actual application protocol with the client, for authenticating clients.

Clients

This section describes the steps taken by the client remote login process. As in the server, the first step is to locate the service definition for a remote login:

sp = getservbyname("login", "tcp");
if (sp == (struct servent *) NULL) {
		fprintf(stderr,"rlogin: tcp/login: unknown service");
		exit(1);
}

Next, the destination host is looked up by a call togetipnodebyname(3SOCKET):

hp = getipnodebyname (AF_INET6, argv[1], AI_DEFAULT, &errnum);
if (hp == (struct hostent *) NULL) {
		fprintf(stderr, "rlogin: %s: unknown host", argv[1]);
		exit(2);
}

The next step is to connect to the server at the requested host and start the remote login protocol. The address buffer is cleared and filled with the Internet address of the foreign host and the port number at which the login server listens:

memset((char *) &server, 0, sizeof server);
bzero (&sin6, sizeof (sin6));
memcpy((char*) &server.sin6_addr,hp->h_addr,hp->h_length);
server.sin6_family = hp->h_addrtype;
server.sin6_port = sp->s_port;

A socket is created, and a connection initiated. connect(3SOCKET) implicitly does a bind(3SOCKET), since s is unbound.

s = socket(hp->h_addrtype, SOCK_STREAM, 0);
if (s < 0) {
		perror("rlogin: socket");
		exit(3);
}
 ...
if (connect(s, (struct sockaddr *) &server, sizeof server) < 0) {
		perror("rlogin: connect");
		exit(4);
}

Connectionless Servers

Some services use datagram sockets. The rwho(1) service provides status information on hosts connected to a local area network. (Avoid running in.rwhod(1M) because it causes heavy network traffic.) This service requires the ability to broadcast information to all hosts connected to a particular network. It is an example of datagram socket use.

A user on a host running the rwho(1) server can get the current status of another host with ruptime(1). Typical output is illustrated in Example 2-10.


Example 2-10 Output of ruptime(1) Program

itchy up 9:45, 5 users, load 1.15, 1.39, 1.31
scratchy up 2+12:04, 8 users, load 4.67, 5.13, 4.59
click up 10:10, 0 users, load 0.27, 0.15, 0.14
clack up 2+06:28, 9 users, load 1.04, 1.20, 1.65
ezekiel up 25+09:48, 0 users, load 1.49, 1.43, 1.41
dandy 5+00:05, 0 users, load 1.51, 1.54, 1.56
peninsula down 0:24
wood down 17:04
carpediem down 16:09
chances up 2+15:57, 3 users, load 1.52, 1.81, 1.86

Status information is periodically broadcast by the rwho(1) server processes on each host. The server process also receives the status information and updates a database. This database is interpreted for the status of each host. Servers operate autonomously, coupled only by the local network and its broadcast capabilities.

Use of broadcast is fairly inefficient because a lot of net traffic is generated. Unless the service is used widely and frequently, the expense of periodic broadcasts outweighs the simplicity.

Example 2-11 shows a simplified version of the rwho(1) server. It performs two tasks: receives status information broadcast by other hosts on the network and supplies the status of its host. The first task is done in the main loop of the program: Packets received at the rwho(1) port are checked to be sure they were sent by another rwho(1) server process, and are stamped with the arrival time. They then update a file with the status of the host. When a host has not been heard from for an extended time, the database routines assume the host is down and logs it. This application is prone to error, as a server might be down while a host is up.


Example 2-11 rwho(1) Server

main()
{
   ...
   sp = getservbyname("who", "udp");
   net = getnetbyname("localnet");
   sin.sin6_addr = inet_makeaddr(net->n_net, in6addr_any);
   sin.sin6_port = sp->s_port;
   ...
   s = socket(AF_INET6, SOCK_DGRAM, 0);
   ...
   on = 1;
   if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof on)
         == -1) {
      syslog(LOG_ERR, "setsockopt SO_BROADCAST: %m");
      exit(1);
   }
   bind(s, (struct sockaddr *) &sin, sizeof sin);
   ...
   signal(SIGALRM, onalrm);
   onalrm();
   while(1) {
      struct whod wd;
	      int cc, whod, len = sizeof from;
      cc = recvfrom(s, (char *) &wd, sizeof(struct whod), 0,
         (struct sockaddr *) &from, &len);
      if (cc <= 0) {
      if (cc == -1 && errno != EINTR)
         syslog(LOG_ERR, "rwhod: recv: %m");
      continue;
      }
      if (from.sin6_port != sp->s_port) {
         syslog(LOG_ERR, "rwhod: %d: bad from port",
            ntohs(from.sin6_port));
         continue;
      }
      ...
      if (!verify( wd.wd_hostname)) {
         syslog(LOG_ERR, "rwhod: bad host name from %x",
            ntohl(from.sin6_addr.s6_addr));
         continue;
      }
      (void) sprintf(path, "%s/whod.%s", RWHODIR, wd.wd_hostname);
      whod = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0666);
      ...
      (void) time(&wd.wd_recvtime);
      (void) write(whod, (char *) &wd, cc);
      (void) close(whod);
   }
   exit(0);
}

The second server task is to supply the status of its host. This requires periodically acquiring system status information, packaging it in a message, and broadcasting it on the local network for other rwho(1) servers to hear. This task is run by a timer and triggered with a signal. Locating the system status information is involved but uninteresting.

Status information is broadcast on the local network. For networks that do not support broadcast, use another scheme.

Advanced Topics

For most programmers, the mechanisms already described are enough to build distributed applications. Others need some of the additional features in this section.

Out-of-Band Data

The stream socket abstraction includes out-of-band data. Out-of-band data is a logically independent transmission channel between a pair of connected stream sockets. Out-of-band data is delivered independent of normal data. The out-of-band data facilities must support the reliable delivery of at least one out-of-band message at a time. This message can contain at least one byte of data, and at least one message can be pending delivery at any time.

For communications protocols that support only in-band signaling (that is, urgent data is delivered in sequence with normal data), the message is extracted from the normal data stream and stored separately. This lets users choose between receiving the urgent data in order and receiving it out of sequence, without having to buffer the intervening data.

You can peek (with MSG_PEEK) at out-of-band data. If the socket has a process group, a SIGURG signal is generated when the protocol is notified of its existence. A process can set the process group or process ID to be informed by SIGURG with the appropriate fcntl(2) call, as described in "Interrupt-Driven Socket I/O" for SIGIO. If multiple sockets have out-of-band data waiting delivery, a select(3C) call for exceptional conditions can be used to determine the sockets with such data pending.

A logical mark is placed in the data stream at the point at which the out-of-band data was sent. The remote login and remote shell applications use this facility to propagate signals between client and server processes. When a signal is received, all data up to the mark in the data stream is discarded.

To send an out-of-band message, the MSG_OOB flag is applied to send(3SOCKET) or sendto(3SOCKET). To receive out-of-band data, specify MSG_OOB to recvfrom(3SOCKET) or recv(3SOCKET) (unless out-of-band data is taken in line, in which case the MSG_OOB flag is not needed). The SIOCATMARK ioctl(2) tells whether the read pointer currently points at the mark in the data stream:

int yes;
ioctl(s, SIOCATMARK, &yes);

If yes is 1 on return, the next read returns data after the mark. Otherwise, assuming out-of-band data has arrived, the next read provides data sent by the client before sending the out-of-band signal. The routine in the remote login process that flushes output on receipt of an interrupt or quit signal is shown in Example 2-12. This code reads the normal data up to the mark (to discard it), then reads the out-of-band byte.

A process can also read or peek at the out-of-band data without first reading up to the mark. This is more difficult when the underlying protocol delivers the urgent data in-band with the normal data, and only sends notification of its presence ahead of time (for example, TCP, the protocol used to provide socket streams in the Internet family). With such protocols, the out-of-band byte might not yet have arrived when a recv(3SOCKET) is done with the MSG_OOB flag. In that case, the call returns the error of EWOULDBLOCK. Also, there might be enough in-band data in the input buffer that normal flow control prevents the peer from sending the urgent data until the buffer is cleared. The process must then read enough of the queued data before the urgent data can be delivered.


Example 2-12 Flushing Terminal I/O on Receipt of Out-of-Band Data

#include <sys/ioctl.h>
#include <sys/file.h>
...
oob()
{
		int out = FWRITE;
		char waste[BUFSIZ];
		int mark = 0;
 
		/* flush local terminal output */
		ioctl(1, TIOCFLUSH, (char *) &out);
		while(1) {
			if (ioctl(rem, SIOCATMARK, &mark) == -1) {
				perror("ioctl");
				break;
			}
			if (mark)
				break;
			(void) read(rem, waste, sizeof waste);
		}
		if (recv(rem, &mark, 1, MSG_OOB) == -1) {
			perror("recv");
			...
		}
		...
}

There is also a facility to retain the position of urgent in-line data in the socket stream. This is available as a socket-level option, SO_OOBINLINE. See the getsockopt(3SOCKET) manpage for usage. With this option, the position of urgent data (the mark) is retained, but the urgent data immediately follows the mark in the normal data stream returned without the MSG_OOB flag. Reception of multiple urgent indications causes the mark to move, but no out-of-band data are lost.

Nonblocking Sockets

Some applications require sockets that do not block. For example, requests that cannot complete immediately and would cause the process to be suspended (awaiting completion) are not executed. An error code would be returned. After a socket is created and any connection to another socket is made, it can be made nonblocking by issuing a fcntl(2) call, as shown in Example 2-13.


Example 2-13 Set Nonblocking Socket

#include <fcntl.h>
#include <sys/file.h>
...
int fileflags;
int s;
...
s = socket(AF_INET6, SOCK_STREAM, 0);
...
if (fileflags = fcntl(s, F_GETFL, 0) == -1)
		perror("fcntl F_GETFL");
		exit(1);
}
if (fcntl(s, F_SETFL, fileflags | FNDELAY) == -1)
		perror("fcntl F_SETFL, FNDELAY");
		exit(1);
}
...

When doing I/O on a nonblocking socket, check for the error EWOULDBLOCK (in errno.h), which occurs when an operation would normally block. accept(3SOCKET), connect(3SOCKET), send(3SOCKET), recv(3SOCKET), read(2), and write(2) can all return EWOULDBLOCK. If an operation such as a send(3SOCKET) cannot be done in its entirety, but partial writes work (such as when using a stream socket), the data that can be sent immediately are processed, and the return value is the amount actually sent.

Asynchronous Socket I/O

Asynchronous communication between processes is required in applications that handle multiple requests simultaneously. Asynchronous sockets must be SOCK_STREAM type. To make a socket asynchronous, you issue a fcntl(2) call, as shown in Example 2-14.


Example 2-14 Making a Socket Asynchronous

#include <fcntl.h>
#include <sys/file.h>
...
int fileflags;
int s;
...
s = socket(AF_INET6, SOCK_STREAM, 0);
...
if (fileflags = fcntl(s, F_GETFL ) == -1)
		perror("fcntl F_GETFL");
		exit(1);
}
if (fcntl(s, F_SETFL, fileflags | FNDELAY | FASYNC) == -1)
		perror("fcntl F_SETFL, FNDELAY | FASYNC");
		exit(1);
}
...

After sockets are initialized, connected, and made asynchronous, communication is similar to reading and writing a file asynchronously. A send(3SOCKET), write(2), recv(3SOCKET), or read(2) initiates a data transfer. A data transfer is completed by a signal-driven I/O routine, described in the next section.

Interrupt-Driven Socket I/O

The SIGIO signal notifies a process when a socket (actually any file descriptor) has finished a data transfer. The steps in using SIGIO are:

Example 2-15 shows some sample code to allow a given process to receive information on pending requests as they occur for a socket. With the addition of a handler for SIGURG, this code can also be used to prepare for receipt of SIGURG signals.


Example 2-15 Asynchronous Notification of I/O Requests

#include <fcntl.h>
#include <sys/file.h>
 ...
signal(SIGIO, io_handler);
/* Set the process receiving SIGIO/SIGURG signals to us. */
if (fcntl(s, F_SETOWN, getpid()) < 0) {
		perror("fcntl F_SETOWN");
		exit(1);
}

Signals and Process Group ID

For SIGURG and SIGIO, each socket has a process number and a process group ID. These values are initialized to zero, but can be redefined at a later time with the F_SETOWN fcntl(2), as in the previous example. A positive third argument to fcntl(2) sets the socket's process ID. A negative third argument to fcntl(2) sets the socket's process group ID. The only allowed recipient of SIGURG and SIGIO signals is the calling process. A similar fcntl(2), F_GETOWN, returns the process number of a socket.

Reception of SIGURG and SIGIO can also be enabled by using ioctl(2) to assign the socket to the user's process group:

/* oobdata is the out-of-band data handling routine */
sigset(SIGURG, oobdata);
int pid = -getpid();
if (ioctl(client, SIOCSPGRP, (char *) &pid) < 0) {
		perror("ioctl: SIOCSPGRP");
}

Another signal that is useful in server processes is SIGCHLD. This signal is delivered to a process when any child process changes state. Normally, servers use the signal to "reap" child processes that have exited without explicitly awaiting their termination or periodically polling for exit status. For example, the remote login server loop shown previously can be augmented as shown in Example 2-16.


Example 2-16 SIGCHLD Signal

int reaper();
...
sigset(SIGCHLD, reaper);
listen(f, 5);
while (1) {
		int g, len = sizeof from;
		g = accept(f, (struct sockaddr *) &from, &len);
		if (g < 0) {
			if (errno != EINTR)
				syslog(LOG_ERR, "rlogind: accept: %m");
			continue;
		}
		...
}
 
#include <wait.h>
 
reaper()
{
		int options;
		int error;
		siginfo_t info;
 
		options = WNOHANG | WEXITED;
		bzero((char *) &info, sizeof(info));
		error = waitid(P_ALL, 0, &info, options);
}

If the parent server process fails to reap its children, zombie processes result.

Selecting Specific Protocols

If the third argument of the socket(3SOCKET) call is 0, socket(3SOCKET) selects a default protocol to use with the returned socket of the type requested. The default protocol is usually correct, and alternate choices are not usually available. When using "raw" sockets to communicate directly with lower-level protocols or hardware interfaces, it can be important for the protocol argument to set up de-multiplexing. For example, raw sockets in the Internet family can be used to implement a new protocol on IP, and the socket receives packets only for the protocol specified. To obtain a particular protocol, determine the protocol number as defined in the protocol family. For the Internet family, use one of the library routines discussed in "Standard Routines", such as getprotobyname(3SOCKET):

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
 ...
pp = getprotobyname("newtcp");
s = socket(AF_INET6, SOCK_STREAM, pp->p_proto);

This results in a socket s using a stream-based connection, but with protocol type of newtcp instead of the default tcp.

Address Binding

TCP and UDP use a 4-tuple of local IP address, local port number, foreign IP address, and foreign port number to do their addressing. TCP requires these 4-tuples to be unique. UDP does not. It is unrealistic to expect user programs to always know proper values to use for the local address and local port, since a host can reside on multiple networks and the set of allocated port numbers is not directly accessible to a user. To avoid these problems, you can leave parts of the address unspecified and let the system assign the parts appropriately when needed. Various portions of these tuples may be specified by various parts of the sockets API.

bind(3SOCKET)

Local address or local port or both

connect(3SOCKET)

Foreign address and foreign port

A call to accept(3SOCKET) retrieves connection information from a foreign client, so it causes the local address and port to be specified to the system (even though the caller of accept(3SOCKET) didn't specify anything), and the foreign address and port to be returned.

A call to listen(3SOCKET) can cause a local port to be chosen. If no explicit bind(3SOCKET) has been done to assign local information, listen(3SOCKET) causes an ephemeral port number to be assigned.

A service that resides at a particular port, but which does not care what local address is chosen, can bind(3SOCKET) itself to its port and leave the local address unspecified (set to in6addr_any, a variable with a constant value in <netinet/in.h>). If the local port need not be fixed, a call to listen(3SOCKET) causes a port to be chosen. Specifying an address of in6addr_any or a port number of 0 is known as wildcarding. (For AF_INET, INADDR_ANY is used in place of in6addr_any.)

The wildcard address simplifies local address binding in the Internet family. The sample code below binds a specific port number, MYPORT, to a socket, and leaves the local address unspecified.


Example 2-17 Bind Port Number to Socket

#include <sys/types.h>
#include <netinet/in.h>
...
struct sockaddr_in6 sin;
...
		s = socket(AF_INET6, SOCK_STREAM, 0);
		bzero (&sin6, sizeof (sin6));
		sin.sin6_family = AF_INET6;
		sin.sin6_addr.s6_addr = in6addr_any;
		sin.sin6_port = htons(MYPORT);
		bind(s, (struct sockaddr *) &sin, sizeof sin);

Each network interface on a host typically has a unique IP address. Sockets with wildcard local addresses can receive messages directed to the specified port number and sent to any of the possible addresses assigned to a host. For example, if a host has two interfaces with addresses 128.32.0.4 and 10.0.0.78, and a socket is bound as in Example 2-17, the process can accept connection requests addressed to 128.32.0.4 or 10.0.0.78. To allow only hosts on a specific network to connect to it, a server binds the address of the interface on the appropriate network.

Similarly, a local port number can be left unspecified (specified as 0), in which case the system selects a port number. For example, to bind a specific local address to a socket, but to leave the local port number unspecified:

bzero (&sin, sizeof (sin));
(void) inet_pton (AF_INET6, ":ffff:127.0.0.1", sin.sin6_addr.s6_addr);
sin.sin6_family = AF_INET6;
sin.sin6_port = htons(0);
bind(s, (struct sockaddr *) &sin, sizeof sin);

The system uses two criteria to select the local port number:

The port number and IP address of the client is found through either accept(3SOCKET) (the from result) or getpeername(3SOCKET).

In certain cases, the algorithm used by the system to select port numbers is unsuitable for an application. This is because associations are created in a two-step process. For example, the Internet file transfer protocol specifies that data connections must always originate from the same local port. However, duplicate associations are avoided by connecting to different foreign ports. In this situation, the system would disallow binding the same local address and port number to a socket if a previous data connection's socket still existed. To override the default port selection algorithm, you must perform an option call before address binding:

 ...
int on = 1;
...
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on);
bind(s, (struct sockaddr *) &sin, sizeof sin);

With this call, local addresses already in use can be bound. This does not violate the uniqueness requirement, because the system still verifies at connect time that any other sockets with the same local address and port do not have the same foreign address and port. If the association already exists, the error EADDRINUSE is returned.

Using Multicast

IP multicasting is only supported on AF_INET6 and AF_INETsockets of type SOCK_DGRAM and SOCK_RAW, and only on subnetworks for which the interface driver supports multicasting.

Sending IPv4 Multicast Datagrams

To send a multicast datagram, specify an IP multicast address in the range 224.0.0.0 to 239.255.255.255 as the destination address in a sendto(3SOCKET) call.

By default, IP multicast datagrams are sent with a time-to-live (TTL) of 1, which prevents them from being forwarded beyond a single subnetwork. The socket option IP_MULTICAST_TTL allows the TTL for subsequent multicast datagrams to be set to any value from 0 to 255, to control the scope of the multicasts:

    u_char ttl;
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,sizeof(ttl))

Multicast datagrams with a TTL of 0 are not transmitted on any subnet, but can be delivered locally if the sending host belongs to the destination group and if multicast loopback has not been disabled on the sending socket (see below). Multicast datagrams with TTL greater than one can be delivered to more than one subnet if one or more multicast routers are attached to the first-hop subnet. To provide meaningful scope control, the multicast routers support the notion of TTL "thresholds", which prevent datagrams with less than a certain TTL from traversing certain subnets. The thresholds enforce the following convention that multicast datagrams with initial TTL:

Are restricted to the same host 

Are restricted to the same subnet 

32 

Are restricted to the same site 

64 

Are restricted to the same region 

128 

Are restricted to the same continent 

255 

Are unrestricted in scope 

"Sites" and "regions" are not strictly defined, and sites can be subdivided into smaller administrative units, as a local matter.

An application can choose an initial TTL other than the ones listed above. For example, an application might perform an "expanding-ring search" for a network resource by sending a multicast query, first with a TTL of 0, and then with larger and larger TTLs, until a reply is received, using (for example) the TTL sequence 0, 1, 2, 4, 8, 16, 32.

The multicast router refuses to forward any multicast datagram with a destination address between 224.0.0.0 and 224.0.0.255, inclusive, regardless of its TTL. This range of addresses is reserved for the use of routing protocols and other low-level topology discovery or maintenance protocols, such as gateway discovery and group membership reporting.

Each multicast transmission is sent from a single network interface, even if the host has more than one multicast-capable interface. (If the host is also a multicast router and the TTL is greater than 1, a multicast can be forwarded to interfaces other than originating interface.) A socket option is available to override the default for subsequent transmissions from a given socket:

    struct in_addr addr;
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr))
where addr is the local IP address of the outgoing interface you want. Revert to the default interface by specifying the address INADDR_ANY. The local IP address of an interface is obtained with the SIOCGIFCONF ioctl. To determine if an interface supports multicasting, fetch the interface flags with the SIOCGIFFLAGS ioctl and test if the IFF_MULTICAST flag is set. (This option is intended primarily for multicast routers and other system services specifically concerned with internet topology.)

If a multicast datagram is sent to a group to which the sending host itself belongs (on the outgoing interface), a copy of the datagram is, by default, looped back by the IP layer for local delivery. Another socket option gives the sender explicit control over whether or not subsequent datagrams are looped back:

    u_char loop;
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop))  
where loop is 0 to disable loopback, and 1 to enable loopback. This option provides a performance benefit for applications that have only one instance on a single host (such as a router or a mail demon), by eliminating the overhead of receiving their own transmissions. It should not normally be used by applications that can have more than one instance on a single host (such as a conferencing program) or for which the sender does not belong to the destination group (such as a time querying program).

If the sending host belongs to the destination group on another interface, a multicast datagram sent with an initial TTL greater than 1 can be delivered to the sending host on the other interface. The loopback control option has no effect on such delivery.

Receiving IPv4 Multicast Datagrams

Before a host can receive IP multicast datagrams, it must become a member of one, or more, IP multicast group. A process can ask the host to join a multicast group by using the following socket option:

    struct ip_mreq mreq;
    setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) 
where mreq is the structure
    struct ip_mreq {
        struct in_addr imr_multiaddr;   /* multicast group to join */
        struct in_addr imr_interface;   /* interface to join on */
    }  
Each membership is associated with a single interface, and it is possible to join the same group on more than one interface. Specify imr_interface to be in6addr_any to choose the default multicast interface, or one of the host's local addresses to choose a particular (multicast-capable) interface.

To drop a membership, use

    struct ip_mreq mreq;
    setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq))  
where mreq contains the same values used to add the membership. The memberships associated with a socket are also dropped when the socket is closed or the process holding the socket is killed. More than one socket can claim a membership in a particular group, and the host remains a member of that group until the last claim is dropped.

Incoming multicast packets are accepted by the kernel IP layer if any socket has claimed a membership in the destination group of the datagram. Delivery of a multicast datagram to a particular socket is based on the destination port and the memberships associated with the socket (or protocol type, for raw sockets), just as with unicast datagrams. To receive multicast datagrams sent to a particular port, bind to the local port, leaving the local address unspecified (such as, INADDR_ANY).

More than one process can bind to the same SOCK_DGRAM UDP port if the bind(3SOCKET) is preceded by:

    int one = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))  
In this case, every incoming multicast or broadcast UDP datagram destined to the shared port is delivered to all sockets bound to the port. For backwards compatibility reasons, this does not apply to incoming unicast datagrams. Unicast datagrams are never delivered to more than one socket, regardless of how many sockets are bound to the datagram's destination port. SOCK_RAW sockets do not require the SO_REUSEADDR option to share a single IP protocol type.

The definitions required for the new, multicast-related socket options are found in <netinet/in.h>. All IP addresses are passed in network byte-order.

Sending IPv6 Multicast Datagrams

To send a multicast datagram, specify an IP multicast address in the range ff00::0/8 as the destination address in a sendto(3SOCKET) call.

By default, IP multicast datagrams are sent with a hop limit of 1, which prevents them from being forwarded beyond a single subnetwork. The socket option IPV6_MULTICAST_HOPS allows the hoplimit for subsequent multicast datagrams to be set to any value from 0 to 255, to control the scope of the multicasts:

    uint_l;
    setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops,sizeof(hops))

Multicast datagrams with a hoplimit of 0 are not transmitted on any subnet, but can be delivered locally if the sending host belongs to the destination group and if multicast loopback has not been disabled on the sending socket (see below). Multicast datagrams with hoplimit greater than one can be delivered to more than one subnet if one or more multicast routers are attached to the first-hop subnet. The IPv6 multicast addresses, unlike their IPv4 counterparts, contain explicit scope information encoded in the first part of the address. The defined scopes are (where X is unspecified):

ffX1::0/16

Node-local scope -- restricted to the same node

ffX2::0/16

Link-local scope

ffX5::0/16

Site-local scope

ffX8::0/16

Organization-local scope

ffXe::0/16

Global scope

An application can, separately from the scope of the multicast address, use different hoplimit values. For example, an application might perform an "expanding-ring search" for a network resource by sending a multicast query, first with a hoplimit of 0, and then with larger and larger hoplimits, until a reply is received, using (for example) the hoplimit sequence 0, 1, 2, 4, 8, 16, 32.

Each multicast transmission is sent from a single network interface, even if the host has more than one multicast-capable interface. (If the host is also a multicast router and the hoplimit is greater than 1, a multicast can be forwarded to interfaces other than originating interface.) A socket option is available to override the default for subsequent transmissions from a given socket:

    uint_t ifindex;

    ifindex = if_nametoindex )"hme3");
    setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex))
where ifindex is the interface index for the desired outgoing interface. Revert to the default interface by specifying the value 0.

If a multicast datagram is sent to a group to which the sending host itself belongs (on the outgoing interface), a copy of the datagram is, by default, looped back by the IP layer for local delivery. Another socket option gives the sender explicit control over whether or not subsequent datagrams are looped back:

    uint_t loop;
    setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof(loop))  
where loop is 0 to disable loopback, and 1 to enable loopback. This option provides a performance benefit for applications that have only one instance on a single host (such as a router or a mail demon), by eliminating the overhead of receiving their own transmissions. It should not normally be used by applications that can have more than one instance on a single host (such as a conferencing program) or for which the sender does not belong to the destination group (such as a time querying program).

If the sending host belongs to the destination group on another interface, a multicast datagram sent with an initial hoplimit greater than 1 can be delivered to the sending host on the other interface. The loopback control option has no effect on such delivery.

Receiving IPv6 Multicast Datagrams

Before a host can receive IP multicast datagrams, it must become a member of one, or more, IP multicast group. A process can ask the host to join a multicast group by using the following socket option:

    struct ipv6_mreq mreq;
    setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) 
where mreq is the structure
    struct ipv6_mreq {
        struct in6_addr ipv6mr_multiaddr;   /* IPv6 multicast addr */
        unsigned int    ipv6mr_interface;   /* interface index */
    }  
Each membership is associated with a single interface, and it is possible to join the same group on more than one interface. Specify ipv6_interface to be 0 to choose the default multicast interface, or an interface index for one of the host's interfaces to choose that (multicast capable) interface.

To leave a group, use

    struct ipv6_mreq mreq;
    setsockopt(sock, IPPROTO_IPV6, IP_LEAVE_GROUP, &mreq, sizeof(mreq))  
where mreq contains the same values used to add the membership. The memberships associated with a socket are also dropped when the socket is closed or the process holding the socket is killed. More than one socket can claim a membership in a particular group, and the host remains a member of that group until the last claim is dropped.

Incoming multicast packets are accepted by the kernel IP layer if any socket has claimed a membership in the destination group of the datagram. Delivery of a multicast datagram to a particular socket is based on the destination port and the memberships associated with the socket (or protocol type, for raw sockets), just as with unicast datagrams. To receive multicast datagrams sent to a particular port, bind to the local port, leaving the local address unspecified (such as, INADDR_ANY).

More than one process can bind to the same SOCK_DGRAM UDP port if the bind(3SOCKET) is preceded by:

    int one = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))  
In this case, every incoming multicast UDP datagram destined to the shared port is delivered to all sockets bound to the port. For backwards compatibility reasons, this does not apply to incoming unicast datagrams. Unicast datagrams are never delivered to more than one socket, regardless of how many sockets are bound to the datagram's destination port. SOCK_RAW sockets do not require the SO_REUSEADDR option to share a single IP protocol type.

The definitions required for the new, multicast-related socket options are found in <netinet/in.h>. All IP addresses are passed in network byte-order.

Zero Copy and Checksum Off-load

In SunOS 5.6 and later, the TCP/IP protocol stack has been enhanced to support two new features: zero copy and TCP checksum off-load.


Note -

Although zero copy and checksum off-loading are functionally independent of one another, they have to work together to obtain the optimal performance. Checksum off-loading requires hardware support from the network interface and, without this hardware support, zero copy is not enabled.


Zero copy requires that the applications supply page-aligned buffers before VM page remapping can be applied. Applications should use large, circular buffers on the transmit side to avoid expensive copy-on-write faults. A typical buffer allocation is sixteen 8K buffers.

Socket Options

You can set and get several options on sockets through setsockopt(3SOCKET) and getsockopt(3SOCKET); for example by changing the send or receive buffer space. The general forms of the calls are:

setsockopt(s, level, optname, optval, optlen);
and
getsockopt(s, level, optname, optval, optlen);

In some cases, such as setting the buffer sizes, these are only hints to the operating system. The operating system reserves the right to adjust the values appropriately.

Table 2-4 shows the arguments of the calls.

Table 2-4 setsockopt(3SOCKET) and getsockopt(3SOCKET) Arguments

Arguments 

Description 

s

Socket on which the option is to be applied 

level

Specifies the protocol level, such as socket level, indicated by the symbolic constant SOL_SOCKET in sys/socket.h

optname

Symbolic constant defined in sys/socket.h that specifies the option

optval

Points to the value of the option 

optlen

Points to the length of the value of the option 

For getsockopt(3SOCKET), optlen is a value-result argument, initially set to the size of the storage area pointed to by optval and set on return to the length of storage used.

It is sometimes useful to determine the type (for example, stream or datagram) of an existing socket. Programs invoked by inetd(1M) can do this by using the SO_TYPE socket option and the getsockopt(3SOCKET) call:

#include <sys/types.h>
#include <sys/socket.h>
 
int type, size;
 
size = sizeof (int);
if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &type, &size) <0) {
 	...
}

After getsockopt(3SOCKET), type is set to the value of the socket type, as defined in sys/socket.h. For a datagram socket, type would be SOCK_DGRAM.

inetd(1M) Daemon

One of the daemons provided with the system is inetd(1M). It is invoked at start-up time, and gets the services for which it listens from the /etc/inet/inetd.conf file. The daemon creates one socket for each service listed in /etc/inet/inetd.conf, binding the appropriate port number to each socket. See the inetd(1M) man page for details.

inetd(1M) polls each socket, waiting for a connection request to the service corresponding to that socket. For SOCK_STREAM type sockets, inetd(1M) does an accept(3SOCKET) on the listening socket, fork(2)s, dup(2)s the new socket to file descriptors 0 and 1 (stdin and stdout), closes other open file descriptors, and exec(2)s the appropriate server.

The primary benefit of inetd(1M) is that services that are not in use are not taking up machine resources. A secondary benefit is that inetd(1M) does most of the work to establish a connection. The server started by inetd(1M) has the socket connected to its client on file descriptors 0 and 1, and can immediately read(2), write(2), send(3SOCKET), or recv(3SOCKET). Servers can use buffered I/O as provided by the stdio conventions, as long as they use fflush(3C) when appropriate.

getpeername(3SOCKET) returns the address of the peer (process) connected to a socket; it is useful in servers started by inetd(1M). For example, to log the Internet address (such as fec0::56:a00:20ff:fe7d:3dd2, which is conventional for representing the IPv6 address of a client), an inetd(1M) server could use the following:

    struct sockaddr_storage name;
    int namelen = sizeof (name);
    char abuf[INET6_ADDRSTRLEN];
    struct in6_addr addr6;
    struct in_addr addr;

    if (getpeername(fd, (struct sockaddr *)&name, &namelen) == -1) {
        perror("getpeername");
        exit(1);
    } else {
        addr = ((struct sockaddr_in *)&name)->sin_addr;
        addr6 = ((struct sockaddr_in6 *)&name)->sin6_addr;
        if (name.ss_family == AF_INET) {
                (void) inet_ntop(AF_INET, &addr, abuf, sizeof (abuf));
        } else if (name.ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&addr6)) {
                /* this is a IPv4-mapped IPv6 address */
                IN6_MAPPED_TO_IN(&addr6, &addr);
                (void) inet_ntop(AF_INET, &addr, abuf, sizeof (abuf));
        } else if (name.ss_family == AF_INET6) {
                (void) inet_ntop(AF_INET6, &addr6, abuf, sizeof (abuf));

        }
        syslog("Connection from %s\n", abuf);
    }

Broadcasting and Determining Network Configuration

Broadcasting is not supported in IPv6. It is supported only in IPv4.

Messages sent by datagram sockets can be broadcast to reach all of the hosts on an attached network. The network must support broadcast; the system provides no simulation of broadcast in software. Broadcast messages can place a high load on a network since they force every host on the network to service them. Broadcasting is usually used for either of two reasons: to find a resource on a local network without having its address, or functions like routing require that information be sent to all accessible neighbors.

To send a broadcast message, create an Internet datagram socket:

s = socket(AF_INET, SOCK_DGRAM, 0);
and bind a port number to the socket:
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_port = htons(MYPORT);
bind(s, (struct sockaddr *) &sin, sizeof sin);

The datagram can be broadcast on only one network by sending to the network's broadcast address. A datagram can also be broadcast on all attached networks by sending to the special address INADDR_BROADCAST, defined in netinet/in.h.

The system provides a mechanism to determine a number of pieces of information (including the IP address and broadcast address) about the network interfaces on the system. The SIOCGIFCONF ioctl(2) call returns the interface configuration of a host in a single ifconf structure. This structure contains an array of ifreq structures, one for each address family supported by each network interface to which the host is connected. Example 2-18 shows these structures defined in net/if.h.


Example 2-18 net/if.h Header File

struct ifreq {
#define IFNAMSIZ 16
char ifr_name[IFNAMSIZ]; /* if name, e.g., "en0" */
union {
		struct sockaddr ifru_addr;
		struct sockaddr ifru_dstaddr;
		char ifru_oname[IFNAMSIZ]; /* other if name */
		struct sockaddr ifru_broadaddr;
		short ifru_flags;
		int ifru_metric;
		char ifru_data[1]; /* interface dependent data */
		char ifru_enaddr[6];
} ifr_ifru;
#define ifr_addr ifr_ifru.ifru_addr
#define ifr_dstaddr ifr_ifru.ifru_dstaddr
#define ifr_oname ifr_ifru.ifru_oname
#define ifr_broadaddr ifr_ifru.ifru_broadaddr
#define ifr_flags ifr_ifru.ifru_flags
#define ifr_metric ifr_ifru.ifru_metric
#define ifr_data ifr_ifru.ifru_data
#define ifr_enaddr ifr_ifru.ifru_enaddr
};

The call that obtains the interface configuration is:

/*
 * Do SIOCGIFNUM ioctl to find the number of interfaces
 *
 * Allocate space for number of interfaces found
 *
 * Do SIOCGIFCONF with allocated buffer
 *
 */
if (ioctl(s, SIOCGIFNUM, (char *)&numifs) == -1) {
        numifs = MAXIFS;
}
bufsize = numifs * sizeof(struct ifreq);
reqbuf = (struct ifreq *)malloc(bufsize);
if (reqbuf == NULL) {
        fprintf(stderr, "out of memory\n");
        exit(1);
}
ifc.ifc_buf = (caddr_t)&reqbuf[0];
ifc.ifc_len = bufsize;
if (ioctl(s, SIOCGIFCONF, (char *)&ifc) == -1) {
        perror("ioctl(SIOCGIFCONF)");
        exit(1);
}
...
}

After this call, buf contains an array of ifreq structures, one for each network to which the host is connected. These structures are ordered first by interface name, then by supported address families. ifc.ifc_len is set to the number of bytes used by the ifreq structures.

Each structure has a set of interface flags that tell whether the corresponding network is up or down, point-to-point or broadcast, and so on. Example 2-19 shows the SIOCGIFFLAGS ioctl(2) returning these flags for an interface specified by an ifreq structure.


Example 2-19 Obtaining Interface Flags

struct ifreq *ifr;
ifr = ifc.ifc_req;
for (n = ifc.ifc_len/sizeof (struct ifreq); --n >= 0; ifr++) {
   /*
    * Be careful not to use an interface devoted to an address
    * family other than those intended.
    */
   if (ifr->ifr_addr.sa_family != AF_INET)
      continue;
   if (ioctl(s, SIOCGIFFLAGS, (char *) ifr) < 0) {
      ...
   }
   /* Skip boring cases */
   if ((ifr->ifr_flags & IFF_UP) == 0 ||
      (ifr->ifr_flags & IFF_LOOPBACK) ||
      (ifr->ifr_flags & (IFF_BROADCAST | IFF_POINTOPOINT)) == 0)
      continue;
}

Example 2-20 shows the broadcast of an interface can be obtained with the SIOGGIFBRDADDR ioctl(2).


Example 2-20 Broadcast Address of an Interface

if (ioctl(s, SIOCGIFBRDADDR, (char *) ifr) < 0) {
		...
}
memcpy((char *) &dst, (char *) &ifr->ifr_broadaddr,
		sizeof ifr->ifr_broadaddr);

The SIOGGIFBRDADDR ioctl(2) can also be used to get the destination address of a point-to-point interface.

After the interface broadcast address is obtained, transmit the broadcast datagram with sendto(3SOCKET):

sendto(s, buf, buflen, 0, (struct sockaddr *)&dst, sizeof dst);

Use one sendto(3SOCKET) for each interface to which the host is connected that supports the broadcast or point-to-point addressing.