S742 Assignment # 3 Due Date 10/17/2001 Syed Shahzad Ali (ssali) Q1. Draw a state transition diagram similar to Figure 2.5 in Stevens's text to show a scenario where both the server and the client enter the TIME_WAIT state from the original CLOSED state. (If you have some difficulty in drawing the diagram with a computer, you can just draw it on a piece of paper and attach this paper to your hard copy submission). A: The diagram is attached with the hard copy of the assignment. Q2. Read Section 6.6 of Stevens' text and answer the following questions: I. Compare the difference of the following two operations on an open socket sock: close (sock); shutdown (sock, SHUT_RDWR); A: close() function call is used in the socket programming to tear down a connection. But there are at least two major issues that makes shutdown() more important that are as follows 1. In close function the file descriptor is decremented which is actually a reference count and close the socket only when the count is reached to 0. The shutdown() function can tear down the TCP connection regardless of the reference count so it behaves differently from close() function call. 2. TCP connection is full duplex in nature and close() terminates reading and writing of data at the same time. But there could be situation when one end wants to stop sending the data but does want to receive the data at the same time. So a socket can be closed just for read operations but still provide write at same time. shutdown(sock,SHUT_RDWR) The operation of close depends on the value of the SO_LINGER sock. While the read_half and write_half of the connection are both closed. This is equivalent to calling shutdown twice: first with SHUT_RD and then with SHUT_WR. II. Modify the daytime server and client programs in the following way: The client sleeps for 1 second after connect returns, and calls shutdown on the socket with SHUT_RD as the value of the second parameter. After that the client tries to get data from the server until read returns 0. the server writes a date string to the client after accept returns, and sleeps for 5 seconds, and then write the same date string to the client. Observe the result and compare the result with the description for SHUT_RD on Page 160 of Stevens' text. A: In this situation when shutdown() is called with the read it doesn't behave in the way it should. When the client calls shutdown after the read request the data is presented in the output buffer and written on the client screen. While according to the function call the should not arrive in the client incoming buffere (or read buffer). But since the data was sent before the shutdown was called by the client hence its not discarded and available to the client. Any further or subsequent read request will fail later. In the Steven's text book its mentioned that the data will not be available to the client but this is not the case here and an opposite behavior is observed. 3. Describe the process to elicit a SIG_PIPE signal from the write operation on an open socket in detail. Write a snippet of code to prevent your program from terminating for this signal. A: After arrival of RST signal if a process tries to write to a socket then SIGPIPE signal is sent back to the originating signal. The default behavior in this situation is to terminate the process, so to prevent this situation the process must catch the signal to avoid being abnormally terminated. If the process either catches the signal and returns from the signal handle ignores the signal, the write operation returns EPIPE. A critical issue is that signal cannot be obtained on the first write but can be on the second. Since the first write elicits the RST and the second write elicits the signal so it is possible to write to a socket that has received a FIN, but it is an error to write to a socket that has received an RST. In general the normal behavior against this action for SIGPIPE to terminate the process without producing an error or core file so in this case nothing will be printed. This is not desired since program terminated by SIGPIPE, normally gives nothing as output even by the command shell to show what has happened. One gets SIGPIPE during TCP operation if one end of the connection has received an RST from the other end. So if someone were using select instead of write, the select would have indicated the socket as being readable, since the RST is there for you to read (read will return an error with errno set to ECONNRESET). A common problem is when the peer closes the connection (sending you a FIN) but you ignore it because you're writing and not reading. RST signal is a TCP's response to some packet that it doesn't expect and has no other way of dealing with. So using select() call is a better choice. So writing to a connection that has been closed by the other end causes other end's TCP to responds with an RST. The modified server program looks like this #include #include #include #include #include #include #include #include #include #include #define MAXLINE 1024 #define LISTENQ 100 #define PORT_NUM 6062 void sig_handler(int s); int main(int argc, char **argv) { int listenfd, connfd; socklen_t len; struct sockaddr_in servaddr, cliaddr; char buff[MAXLINE]; time_t ticks; struct sigaction sa; int yes=1; signal(13,sig_handler); listenfd = socket (AF_INET, SOCK_STREAM, 0); memset (&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(PORT_NUM); /* daytime server */ bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); if (listen(listenfd, LISTENQ) == -1) { perror("listen"); exit(1); } for ( ; ; ) { len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &len); printf("connection from %s, port %d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port)); ticks = time(NULL); snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); write(connfd, buff, strlen(buff)); sleep(5); write(connfd, buff, strlen(buff)); close(connfd); } } void sig_handler(int s) { if (s == 13 ) { printf(" Broken Connection !!! "); } } 4. From the client's point of view describe the difference between the cases where the server program crashed and the case where the server host crashed. Case1: There are situation when server programs do crash since server program is a demon program that runs in the background and in those cases client calls to the server issues a broken pipe error in response and the client is involuntarily closed. This situation can easily be understood by giving an example. Suppose when a client ignored the error and tried to write data to the server before reading anything back, with the first write this will cause RST signal. When the process writes to a socket that has already got an RST signal then the SIGPIPE signal is sent back to the initiating process. The normal behavior of this signal is to terminate the process so the process must catch the signal to avoid being terminated abnormally. If the process either catches the signal and returns from the signal handle ignores the signal, the write operation returns EPIPE. Signal cannot be obtained on the first write but can be on the second. Since the first write give rise to the RST signal and the second write generates the signal so it is possible to write to a socket that has received a FIN, but it is an error to write to a socket that has received an RST. The default action of SIGPIPE is to terminate the process without generating a core file, nothing is printed. This is the problem with programs terminated by SIGPIPE: normally nothing is output even by the shell to indicate what has happened. The recommended way to handle SIGPIPE depends on what the application wants to do when this occurs. If there is nothing special to do, then setting the signal disposition to SIG_IGN is easy, assuming that subsequent output operations will catch the error of EPIPE and terminate. If special actions are needed when the signal occurs (writing to a log file perhaps), then the signal should be caught and any desired actions can be performed in the signal handler. Be aware, however, that if multiple sockets are in use, the delivery of the signal does not tell us which socket encountered the error. If we need to know which write caused the error, then we must either ignore the signal or return from the signal handler and handle EPIPE from the write. Case2 : Crashing of server host means that the computer/ router (or any device) on which the server program is running is due to some reason crashed. This situation can easily be simulated. Suppose we are running FTP server and client on two different machines then when we connect to the server via client software we can upload or download a file. Now if we turn off the server computer itself then it will clearly depict the situation we are referring to here. There could be situations when some intermediate router is down after the connection being setup. When the server host goes down in our case say FTP server, nothing is sent out on the existing network connections. That is, we are assuming the host crashes, and is not shut down by an operator. The client TCP continually retransmits the data segment, trying to receive an ACK from the server. When the client TCP finally gives up assuming the server host has not been rebooted during this time, or if the server host has not crashed but was unreachable on the network, assuming the host was still unreachable, an error is returned to the client process. Assuming the server host had crashed and there were no responses at all to the client's data segments, the error is ETIMEDOUT. But if some intermediate router determined that the server host was unreachable and responded with an ICMP destination unreachable message, the error is either EHOSTUNREACH or ENETUNREACH. All TCP information is lost when the server reboots after a crash. Thus the server TCP responds to the received data segment from the client with an RST and this is the behavior that we see in this case specially. Suppose the server host is deliberately being shut down by operator for general maintenance while our FTP server was running on it. Then server program should be smart enough to send some kil signal to any process still in the process namespace. This will give all running client FTP processes a short amount of grace time period to clean up their memory space and association and terminate normally. When the process is closed all open descriptors are also closed on the back. Here select() call is useful to have the client detect the termination of the server process as soon as it arrives. Client may deploy KEEPALIVE technique for added surety about discovering whether its peer is down or not. This mechanism is used when we want quicker detection because somehow client knows about the server shutdown in either way. KEEPALIVE mechanism is popular technique in HTTP protocol to check server connection. 5. Read Section 2.5 and 2.6 of Stevens' text and explain the reasons why a socket should be in the TIME_WAIT state for 2 MSL time. A: The TIME-WAIT state is a state entered by all TCP connections when the connection has been closed. The length of time for this state is typically 2MSL (240 seconds) to allow any duplicate segments still in the network from the previous connection to expire. The recommended value for the MSL is 120 seconds. As with the original TCP, the host that sends the first FIN is required to remain in the TIME-WAIT state for twice the MSL once the connection is completely closed at both ends. This implies that the TIME-WAIT state with the original TCP is 240 seconds, even though some implementations of TCP have the TIME-WAIT set to 60 seconds. Remember that TCP guarantees all data transmitted will be delivered, if at all possible. When you close a socket, the server goes into a TIME_WAIT state, just to be really really sure that all the data has gone through. When a socket is closed, both sides agree by sending messages to each other that they will send no more data. This, it seemed to me was good enough, and after the handshaking is done, the socket should be closed. The problem is two-fold. First, there is no way to be sure that the last ack was communicated successfully. Second, there may be "wandering duplicates" left on the net that must be dealt with if they are delivered. TCP includes a mechanism to ensure that packets associated with one connection that are delayed in the network are not accepted by later connections between the same hosts. The mechanism is implemented by the TIME_WAIT state of the TCP protocol. When an endpoint closes a TCP connection, it keeps state about that connection, usually a copy of the TCB (TCP protocol control block), for twice the maximum segment lifetime (MSL). A connection in this state is in TIME_WAIT, and the endpoint holding the TIME_WAIT TCB rejects any packets addressed to the TIME_WAIT connection from the other endpoint. Keeping this TIME_WAIT TCB at either of the hosts prevents a new connection with the same combination of source address, source port, destination address, destination port from being created. Either endpoint being in TIME_WAIT prevents data transfer on the connection, so protocol correctness is unaffected by which host holds the TIME_WAIT TCB. 6. Function select is used to test if an open socket is ready for reading or writing. Answer the following questions about this function: 1. Under what condition a socket is considered ready for reading? 2. Under what condition a socket is considered ready for writing? 3. What's the return value when the time specified in the last parameter expired? 1. Socket Ready for Reading A socket is ready for reading if any of the following four conditions is true: The number of bytes of data in the socket receive buffer is greater than or equal to the current size of the low?water mark for the socket receive buffer. A read operation on the socket will not block and will return a value greater than 0 (i.e., the data that is ready to be read). We can set this low?water mark using the SO_RCVLOWAT socket option. It defaults to 1 for TCP and UDP sockets. The read?half of the connection is closed (i.e., a TCP connection that has received a FIN). A read operation on the socket will not block and will return 0 (i.e., end?of?file). The socket is a listening socket and the number of completed connections is nonzero. An accept()on the listening socket will normally not block. A socket error is pending. A read operation on the socket will not block and will return an error (?1) with errno set to the specific error condition. 2. Socket Ready for Writing A socket is ready for writing if any of the following three conditions is true: The number of bytes of available space in the socket send buffer is greater than or equal to the current size of the low?water mark for the socket sent buffer either using TCP/ UDP. This means that if we set the socket non-blocking, a write operation will not block and will return a positive value (e.g., the number of bytes accepted by the transport layer). We can set this low?water mark using the SO _SNDLOWAT socket option. This low?water mark normally defaults to 2048 for TCP and UDP sockets. The write?half of the connection is closed. A write operation on the sock will generate SIGPIPE. A socket error is pending. A write operation on the socket will not block and return an error (?1) with errno set to the specific error condition. These pending errors can also be fetched and cleared by calling getsockopt() with the SO_ERROR socket option. 3. Return value The return value is 0 when the timer expires. 7.Read Section 20.4 of Stenvens' text and give a strategy to choose whether you should use TCP or UDP for a specific application. Communication can be made using either TCP/IP or UDP/IP as the underlying network transport. For many applications it doesn't matter which of these two "protocol sequences" is choosen to use. UDP, a datagram-type communications protocol, is commonly referred to as being an "unreliable network transport", while TCP, a connection-oriented protocol, is described as being a "reliable transport". This is because at the OS level, no attempt is made to detect dropped or out-of-sequence UDP packets, whereas the OS implementation of TCP makes use of sophisticated error-correcting techniques to prevent data loss. Differences There are some situations where one communication protocol is a better choice than the other: Heavily-used servers: TCP uses substantially more OS resources than UDP does and therefore UDP is preferred (often strongly preferred) in environments where servers must handle many simultaneous clients. Overall scalability is much better with UDP. Running out of file descriptors is a real concern for servers that handle many TCP requests, for example. (The file-descriptor problem is most serious on Solaris, where the fopen family of standard I/O functions can make use only of file descriptors less than 256 -- so failure to open files via fopen can occur in even a moderately loaded server on Solaris if that server uses TCP. Since Kerberos uses fopen, these failures can cause serious havoc.) Debugging: in a development environment where a client or server process may be stopped in a debugger, TCP is preferred. This is because the OS will maintain the TCP connection, but with UDP the peer process may fail because its partner unexpectedly halted. Network topology: some network configurations, particularly those involving low bandwidth or variable-bandwidth connections, may favor TCP. Some other aspects of the network, for example firewalls, may prefer one of the connection type over the other. This is an area where you may have to perform controlled experiments in order to determine if any significant differences exist. All this assumes that the underlying OS implementation of both TCP and UDP is adequate. If on the other hand your OS has a poor UDP implementation, then TCP might be preferred for large applications despite the other factors listed in the above. Other Notes A few more comments and differences: TCP and UDP are the most commonly used transport layer protocols. TCP provides a reliable connection and is used by the majority of current Internet applications. TCP, besides being responsible for error checking and correcting, is also responsible for controlling the speed at which this data is sent. TCP is capable of detecting congestion in the network and will back off transmission speed when congestion occurs. These features protect the network from congestion collapse. Real time service such as VoIP and real video suffer most due to the heavy TCP load and UDP is used to send such data because even if some packets are lost during tramsmission then we really done't need to send them again. Because of its real time nature so its better to avoid those packets and it will show up on the client side as just degradation in voice or picture quality for mili seconds period. For real-time properties to be guaranteed to be met, a network with QoS must be used to provide fixed delay and bandwidth. It has already been said that IP cannot provide this. This then presents a choice. If IP is a requirement, which transport layer should be used to provide a system that is most likely to meet real-time constraints. As TCP provides features such as congestion control, it would be the preferred protocol to use. Unfortunately due to the fact that TCP is a reliable service, delays will be introduced whenever a bit error or packet loss occurs. This delay is caused by retransmission of the broken packet, along with any successive packets that may have already been sent. This can be a large source of jitter. TCP uses a combination of four algorithms to provide congestion control, slow start, congestion avoidance, fast retransmit and fast recovery . These algorithms all use packet loss as an indication of congestion, and all alter the number of packets TCP will send before waiting for acknowledgments of those packets. These alterations affect the bandwidth available and also change delays seen on a link, providing another source of jitter. Combined, TCP raises jitter to an unacceptable level rendering TCP unusable for real-time services. Voice communication has the advantage of not requiring a completely reliable transport level. The loss of a packet or bit error will often only introduce a click or a minor break into the output. For these reasons most VoIP applications use UDP for the voice data transmission. UDP is a thin layer on top of IP that provides a way to distinguish among multiple programs running on a single machine. UDP also inherits all of the properties of IP that TCP attempts to hide. UDP is therefore also a packet based, connectionless, best-effort service. It is up to the application to split data into packets, and provide any necessary error checking that is required. Because of this, UDP allows the fastest and most simple way of transmitting data to the receiver. There is no interference in the stream of data that can be possibly avoided. This provides the way for an application to get as close to meeting real-time constraints as possible. UDP however provides no congestion control systems. A congested link that is only running TCP will be approximately fair to all users. When UDP data is introduced into this link, there is no requirement for the UDP data rates to back off, forcing the remaining TCP connections to back off even further. This can be though of as UDP data not being a ``good citizen''. The aim of this project is to characterise the quantity of this drop off in TCP performance. 8. Another way to handle multiple clients concurrently is to use non-blocking sockets. Describe your implementation to let the server serve multiple clients without using multiplexing functions, such as select and poll. Non-Blocking Sockets The default mode of a socket is Blocking. In this situation the system call does not returns until the datagram arrives and is copied in to the application buffer, or an error occurs. So the process is blocked for the entire time until the system call returns. The most common error is using a signal handling routine that interrupts the system call through a signal. As opposed to that when we set the socket as non-blocking the process does not wait or gets in to a sleep state (e.g. if the data is not available in a recvfrom function call) instead it returns an error of EWOULDBLOCK. Another Implementation Fcntl() function (control open file descriptors) could be useed that is described in the fcntl.h header file. The format for the function is int fcntl(int fd, int cmd, …….); Apart from that there are three main file locking mechanisms available. They rely on programs cooperating in order to work. It is therefore vital that all programs in an application should be consistent in their locking regime, and great care is required when your programs may be sharing files with third-party software. Some applications use lock files -- something like `FILENAME.lock'. Simply testing for the existence of such files is inadequate though, since a process may have been killed while holding the lock. The method used by UUCP (probably the most notable example: it uses lock files for controlling access to modems, remote systems etc.) is to store the PID in the lockfile, and test if that pid is still running. Even this isn't enough to be sure (since PIDs are recycled); it has to have a backstop check to see if the lockfile is old, which means that the process holding the lock must update the file regularly. Messy. The locking functions are: flock(); lockf(); fcntl(); flock() originates with BSD, and is now available in most (but not all) Unices. It is simple and effective on a single host, but doesn't work at all with NFS. It locks an entire file. Perhaps rather deceptively, the popular Perl programming language implements its own flock() where necessary, conveying the illusion of true portability. fcntl() is the only POSIX-compliant locking mechanism, and is therefore the only truly portable lock. It is also the most powerful, and the hardest to use. For NFS-mounted file systems, fcntl() requests are passed to a daemon (rpc.lockd), which communicates with the lockd on the server host. Unlike flock() it is capable of record-level locking. lockf() is merely a simplified programming interface to the locking functions of fcntl(). Whatever locking mechanism you use, it is important to sync all your file IO while the lock is active: In fcntl() function fd is the socket file descriptor. Since fcntl can perform various functions so the operation in question is determined by cmd. In our case we can use the F_SETFL. This sets the descriptor's flags to the value specified by arg. Which are O_APPEND, O_NONBLOCK and O_ASYNC. Again our interest is in O_NONBLOCK. This would implement the same functionality (some what) required by select and poll functions. So in the daytime server we can turn the listening socket (listenfd) to non-blocking and accordingly set it to non-blocking mode.