Proper use of getsockopt and setsockopt for SO_RCVTIMEO and SO_SNDTIMEO

cpaitor picture cpaitor · Jan 30, 2015 · Viewed 25.6k times · Source

By various reasons I would like to implement timeout on reading and writing to socket in a server but fail to get it running and therefore kindly ask for some insight into wherein the problem may reside.

In order to set the timeout on the read and write to the socket I'm trying to use of the functions setsocketopt() and getsocketopt(). However I must be doing something wrong as the return value indicates that a problem have occurred and perror outputs "Invalid argument". Strangely enough the error does not always occur at the first usage of setsocketopt() and getsocketopt(), which puzzles me bit.

The following server and client codes reproduces my problems (compiled using gcc)

server code:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <arpa/inet.h>  

int main() {
   struct sockaddr_in saddr,caddr; 
   socklen_t clen;
   int sock, csock, reuse = 1, ret=0;
   socklen_t ntrcv, ntsnd;
   struct timeval tout, tsnd, trcv;

   // create a new stream socket
   if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) perror("failed to create socket");
   else {          
      // enable the socket to reuse the address
      if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) perror("failed allowing server socket to reuse address");
      else {
         // set up the server address
         memset((char *) &saddr, 0, sizeof(saddr));
         saddr.sin_family = AF_INET;
         saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
         saddr.sin_port = htons(45454);

         // bind socket to address
         if (bind(sock, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) perror("failed to bind");
         else {
            // listen to the socket for connections 
            if (listen(sock,5) < 0) perror("failed to listen");
            else {
               clen = sizeof(caddr);
               if ((csock = accept(sock, (struct sockaddr *) &caddr, &clen)) < 0) perror("failed to accept");
               else {
                  tout.tv_sec=0;
                  tout.tv_usec=10000;

                  // check value of errno prior to setting timeout
                  perror("errno prior to timeout");

                  if (getsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &trcv, &ntrcv) < 0) perror("2");
                  else if (getsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tsnd, &ntsnd) < 0) perror("3");
                  else if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tout, sizeof(tout)) < 0) perror("4");
                  else if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tout, sizeof(tout)) < 0) perror("5");
                  else {
                     printf ("all ok so far in server\n");
                 sleep(1);
                     if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &trcv, ntrcv) < 0) perror("6");
                     else if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tsnd, ntsnd) < 0) perror("7");
                  }
                  if (close(csock) < 0) perror("failed to close csock");
               }
            }
         }
      }
   }
   if (close(sock) < 0) perror("failed to close sock");
   return ret;
}

client code:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
   struct sockaddr_in addr;     
   struct hostent *server; 
   int sock = 0;

   // resolve server name
   if (!(server = gethostbyname("127.0.0.1"))) perror("failed to resolve host");
   else {
      // prepare the server address
      memset((char *) &addr, 0, sizeof(addr));
      addr.sin_family = AF_INET;
      bcopy((char *)server->h_addr, (char *)&addr.sin_addr.s_addr, server->h_length);
      addr.sin_port = htons(45454);

      // create a socket and connect to the server
      if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) perror("failed to create client socket");
      else {
         if (connect(sock,(struct sockaddr *) &addr,sizeof(addr)) < 0) perror("failed to connect to server");
         else {
            printf("Connection is established will sleep now\n");
            sleep(3);
            printf("done sleeping\n");
            close(sock);
            printf("socket is closed\n");
         }
      }
   }
   return 0;
}

In the present example the second call to getsockopts() in the server fails resulting in perror("3") to be called. If I however comment this line out as well as the last call to setsockopts() the first call to getsockopts() fails (which previously seemed to work).

Any insights to where I go wrong are appreciated

Answer

abligh picture abligh · Jan 30, 2015

You are not initialising ntsnd and ntrcv to the size of the buffer available to getsockopt. Therefore if the random value there greater or equal to the size needed, the call will succeed. Else it will fail with EINVAL. From the man page:

The arguments optval and optlen are used to access option values for setsockopt(). For getsockopt() they identify a buffer in which the value for the requested option(s) are to be returned. For getsockopt(), optlen is a value-result argument, initially containing the size of the buffer pointed to by optval, and modified on return to indicate the actual size of the value returned. If no option value is to be supplied or returned, optval may be NULL.

To fix this intialize them both to sizeof(struct timeval).