Network Interface Guide

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.