Remote Pipe.
Introduction.
You will implement a "remote pipe" in this programming assignment. This
pipe will allow you to pipeline execution of two commands and works similar
to the local pipe except that the commands are executed on possibly different
computer systems. The example of such remote pipe is:
cat FileName | rpipe ernie wc
In this example the output of the cat command is sent as the input
of the wc command via the remote pipe and the wc is executed on the host
ernie(cat is executed locally).
Remote pipe will require two executables:
rpipe - remote pipe client
rpiped - remote pipe server (daemon process)
The client will be reading from its standard input (that can be redirected
as in the example above by using regular UNIX shell pipes) and sending
the data read to the remote pipe server via the socket. The server must
be concurrent so that several remote pipe clients can be served simultaneously.
The master server process will have to fork a separate process for each
incoming connection request and let the child process take care of the
request handling. The forked child process will read data from the socket
and feed it to the required executable.
Security.
You will have to implement a simple authentication mechanism so that the
server will reject connections from unauthorized clients. The authentication
will involve an exchange of some password and a connection confirmation
between the client and server. Both client and server will use a randomly
generated 32-bit string as a password that is hard-coded in both executables.
Note that without this security mechanism anyone, anywhere can connect
to your rpiped and execute commands as you.
Communication between client and server.
As communication media client and server will be using TCP/IP sockets.
Server will be listening on a "well-known" port number that both client
and server must agree on. You should use the last five digits of your social
security number + 10000 as this port number. The server will create and
bind socket to this port number, if this attempt fails (e.g. this port
number may be already in use) the server will try to bind the socket to
a different port number until it succeeds and will report the port number
to which it has bound the socket.
Client's command line syntax is:
rpipe [-h hostname] [-p port-number]
executable-name
where:
-
hostname - is where the server is executed, if the hostname is not
specified - local host is assumed
-
port-number - port-number other than the "well-known" port number;
if the server failed to bind to a well-known port number when starting,
the actual port-number that the server bound socket to will be reported
by the server (printed on the stdout) and this is the port-number that
the clients will use; if the port-number is omitted a "well-known"
port number is assumed
-
executable-name - the name of the executable to use as the other
end of the pipeline (data sink); this should not be an absolute path to
the executable (since this is a remote executable)
The syntax of the server command is:
rpiped [-m max-connections] [-p port-number]
where:
-
max-connections - is the maximum number of concurrent connections
that this server allows - this is the maximum number of child processes
that this server can fork to handle the incoming requests; if this parameter
is omitted the default value of 5 should be used
-
port-number - is the port number that the server should try
to use; if this parameter is omitted the "well-known" port number should
be used; the server will have to report the port number to which it bound
the socket unless the server managed to bind to a "well-known" port-number.
Design issues.
The server process should keep track of the created child processes. First
of all, the server will have to collect the exit status of each forked
child process - you will have to use waitpid system call and install
a signal handler for the SIGCHLD signal. The server main loop may look
like:
while(true){
newsockfd = accept(...);
if(fork() == 0) {// child process
handleRequest(newsockfd);
}
close(newsockfd);
}
Since this is a concurrent server, it cannot wait for the child processes
in the main loop. You will have to catch child termination signal and handle
it appropriately.
Secondly, the server may not create more than the max-connections
child
processes and, thus, it must have a counter of the number of currently
executing children; every time the child process terminates the signal
handler will decrement this counter and every time the server accepts a
new connection and forks a new child process, it (master server process)
will increment this counter.
The system call accept() is blocking until a new connection request
is received, at this point the server should fork a new child process (or
reject connection if the max is achieved); however, accept() may be interrupted
by a signal (such as a child termination notification signal) and will
return -1 (with errno set to EINT). Your server should not fork any dummy
processes if the accept() was interrupted by a signal and should simply
ignore such interruptions (just continue the loop).