The objective of this lab is to practice basic network programming using TCP sockets that provide reliable data transmission and UDP sockets that implement unreliable data transport. The two network interfaces complement each other, the former incurring higher overhead to export reliability and the latter being lightweight that may be suited in environments where reliable transport is not an essential requirement.
Read chapter 2 from Peterson & Davie (textbook).
An extension of Problem 4, lab1, modify the client/server
app so that client and server processes run on different machines in
HAAS G050; instead of FIFOs clients submit requests to a server
using TCP sockets.
Create a subdirectory v1/ under lab2 where the
source code of server, mysh4, and client, mycl are deposited
along with a README and Makefile.
When mysh4 is executed at a server machine, it accepts two command-line
arguments:
% mysh4
<seckey>
<portnum>
where seckey is a 4-character string comprised of lower
case alphabet and numeric characters that serves as a rudimentary authentication
token;
portnum is a port number on which the server listens for client
requests.
A client is executed by running
% mycl
<seckey>
<servip> <portnum>
where seckey is the same 4-character string entered on
command-line at server side;
servip is the IPv4 address of the server machine (in dotted decimal
format) and portnum
specifies the port number on which the server is expecting client requests.
The request message format is modified so that a request starts with a 4-character string entered as the first command-line argument of mycl. If the seckey string supplied by a client does not match the server's seckey string, a message is output to stdout indicating that an incorrect key has been provided along with the client's IP address, port number, and client's command request. The requested command is ignored, i.e., not executed. These checks are performed by the parent. Hence a child process is not spawned if a provided secret key is invalid. As in lab1 before, a request message is delineated by newline character '\n'. All other client/server behavioral specification and interaction remain unchanged.
Code the server, mysh4, using system calls socket(), bind(), listen(), accept(), and read(), along with ancillary calls to implement the specified server behavior. Code the client using socket(), connect(), and write() system calls along with any other needed ancillary calls. They include inet_addr() (or inet_pton()) to translate dotted decimal IPv4 address (a string) into 4-byte IPv4 address (in big endian byte order), htons() to translate a 2-byte port number of type short into network byte order. Use PF_INET (or AF_INET) as first argument of socket() to specify the protocol family and AF_INET as value of the address family field for struct sockaddr_in. Use bzero() or memset() to clear the bits of sockaddr_in (or sockaddr). Not following the above implementation constraints will incur significant point deduction irrespective of whether code works correctly or not. As noted in class, an important consideration when implementing network software is to work within the boundary of provided constraints.
Test and verify that the remote command client/server app works correctly. As in lab1, work at identifying your own test cases starting with simple, straightforward ones (i.e., "low-hanging fruit") that help demonstrate correct operation of your implementation. Then consider if there are more subtle but still relevant test cases that should be evaluated to achieve higher quality standards of correctness. Because we are not creating production software that will be released for outside use, we have the luxury of not needing to test software to stricter standards which become increasingly time consuming and difficult.
Code a UDP-based ping app in v2/ where the ping server is called upings, the
client upingc, along with README and Makefile. The ping server is executed through
% upings
<secret>
<portnum>
where secret is a 4-character secret string as in Problem 1 and
portnum specifies the port number on which the server waits for client
requests.
The client is executed using
% upingc
<secret>
<servip> <portnum>
<micsec>
where
secret is the key specified as command-line argument of upings; servip and portnum
are the coordinates of the ping server. The last command-line argument,
micsec, specifies the time interval (in microseconds) between successive
ping packets sent by the client upingc where the client always transmits
12 ping packets when invoked. For example, if micsec is 10000 then the client
should send 12 ping packets to the server where a delay of 10 msec is
inserted between successive packets (but for the first packet).
Use nanosleep() to insert the inter-packet delays by having the sending process
sleep. upingc binds to an ephemeral port number (i.e., unused port assigned
by the operating system) before commencing communication.
upingc follows an asynchronous software design comprised of a synchronous part that transmits 12 ping packets to a ping server, and an asynchronous part that handles ping response packets received from upings using a signal handler for SIGPOLL (equivalently SIGIO). Note that packets arriving at the client machine generate an interrupt that is translated by Linux running on our lab machines into signals that upingc can respond to by registering a signal handler (i.e., callback function).
The synchronous part is just a loop that transmits 12 UDP ping packets interspersed with calls to nanosleep() that pace how fast the packets are transmitted. A ping request packet carried as payload of UDP contains the 4-byte secret key followed by a 2-byte unsigned integer that acts as a sequence number to identify the request packet (i.e., integers 0, 1, ..., 11 specifying the 12 ping request packets). Before transmitting a ping packet, upingc calls gettimeofday() to remember the current time stamp associated with the packet to be transmitted. It uses a 1-D array, struct timeval pingstart[12], to save the time stamps of the 12 ping request packets. The time stamps when responses to the ping request packets are received from upings will be stored by the asynchronous component of upingc (see below) in a data structure, struct timeval pingend[12]. After transmitting the 12th packet, the synchronous part will call nanosleep() to sleep for 750 msec. After waking up, it computes the difference between the time stamps stored in pingend[] and pingstart[] to estimate the RTT (round-trip time) of each ping request/response pair. Diff values larger than 1 second will be discarded as outliers. upingc calculates the average, max, min values which are output to stdout before upingc terminates. Time unit must be in microseconds.
The asynchronous part of upingc is implemented as signal handler, void pingrespcb(int), coded in v2/pingrespcb.c that is registered when upingc starts up. pingrespcb() will be executed asynchronously by Linux when SIGPOLL (or SIGIO) is raised. In our case, when a ping response packet is received from upings. pingrespcb() calls recvfrom() to fetch the received packet and reads its 2-byte payload to identify its sequence number. Then gettimeofday() is called and pingend[] is updated to record the time when the ping response packet has been received.
Both client and server use socket() and bind() to set up sockets, and utilize sendto() and recvfrom() system calls to transmit/receive UDP datagrams (i.e., packets). Note that when SIGPOLL is raised it will interrupt the synchronous code's call to nanosleep(), cutting the sleep period short. Make sure to check the return value of nanosleep() so that nanosleep() is called again with the time remaining unslept so that the time interval between successive packets approximates micsec specified as command-line argument.
Test and verify that your UDP ping app works correctly on the lab machines in HAAS G050. After establishing correctness, benchmark the app at two different time of day periods (e.g., morning and afternoon) and check if there is a noticeable difference. For the same benchmark periods, run the legacy ping app in /bin/ping and compare against your UDP ping estimates. Discuss your finding in lab2ans.pdf.
Use the legacy ping app to estimate RTT to 198.46.92.100 (west coast, around LA), 206.71.50.230 (east coast, around NYC), and must.edu.my (Malaysia Univ. of Science and Technology). Using map apps, estimate the ballpark distance from Purdue to LA, NYC, and Malaysia (use its capital Kuala Lumpur as approximate location). Using SOL (speed of light) as lower bound, estimate the minimum RTT to the two sites. Compare the values against your ping RTT values. Do not transmit more than 10 probes per site to avoid potential false alarms being triggered at server side. Discuss, in decreasing order of importance, what factors may contribute to the discrepancy of RTT values observed. Explain your reasoning behind the ordering.
The Bonus Problem is completely optional. It serves to provide additional exercises to understand material. Bonus problems help more readily reach the 40% contributed by lab component to the course grade.
Electronic turn-in instructions:
i) For problems that require answering/explaining questions, submit a write-up as a pdf file called lab2ans.pdf. Place lab2ans.pdf in your directory lab2/. You can use your favorite editor subject to that it is able to export pdf files which several freeware editors do. Files submitted in any other format will not be graded.
ii) We will use turnin to manage lab assignment submissions. Please check that the relevant source code and files are included in the respective subdirectories of lab2. In the parent directory of lab2, run the command
turnin -c cs422 -p lab2 lab2
You can check/list the submitted files using
turnin -c cs422 -p lab2 -v