user1389840
user1389840

Reputation: 679

Client socket connect() succeeds after server accept() times out

I'm working on a c++ class that acts as a high-level wrapper around sockets in linux. While testing it, I purposely made the server's accept() call time out by having the client application sleep for a few seconds before calling connect().

However, after the server times out, the client application is still able to call connect() and send data without detecting an error. This is obviously a problem, because the server is not receiving the data, so the client should know the connection failed.

Here is my code. The server app calls Socket::accept_connection() and the client app sleeps and then calls Socket::connect_to().

// Accept a connection on the server side with a timeout
Socket *Socket::accept_connection(double timeout) {
    Socket *new_connection = NULL;

    socklen_t sin_size;
    struct sockaddr_storage client_address;  // Client's address
    struct sockaddr_in client_port_address;  // Client's port
    char s[INET6_ADDRSTRLEN];
    sin_size = sizeof client_address;

    fd_set rfds;
    struct timeval timeout_structure;

    timeout_structure.tv_sec = (long)(timeout);
    timeout_structure.tv_usec = int((timeout - timeout_structure.tv_sec) * 1e6);
    struct timeval *timeout_ptr = NULL;
    if(timeout > 0)
        timeout_ptr = &timeout_structure;

    // Loop until the timeout has been reached
    while(true) {
        FD_ZERO(&rfds);
        FD_SET(socket_desc, &rfds);
        if(select(socket_desc + 1, &rfds, NULL, NULL, timeout_ptr) > 0) {
            int client_sock = accept(socket_desc, (struct sockaddr *)&client_address, &sin_size);

            if(client_sock == -1) {
                // Failed to connect
                connected = false;
                continue;

            } else {

                // Connected 
                inet_ntop(client_address.ss_family, get_in_addr((struct sockaddr *)&client_address), s, sizeof s);
                getpeername(client_sock, (struct sockaddr*)&client_port_address, &sin_size);
                int client_port = ntohs(client_port_address.sin_port);
                // ...
            }
        } else {
            // Timed out
            connected = false;
            std::cout << "accept() timed out\n";
            break;
        }
    }
    return new_connection;
}

// Connect to the given ip address and port
bool Socket::connect_to(std::string server_ip, int server_port, double timeout) {
    connected = false;
    // Create the socket and allocate memory for reading in data
    struct addrinfo hints, *servinfo, *p;
    int rv;
    char s[INET6_ADDRSTRLEN];

    struct timeval timeout_structure;
    timeout_structure.tv_sec = (long)(timeout);
    timeout_structure.tv_usec = int((timeout - timeout_structure.tv_sec) * 1e6);
    struct timeval *timeout_ptr = NULL;
    if(timeout > 0)
        timeout_ptr = &timeout_structure;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    if ((rv = getaddrinfo(server_ip.c_str(), std::to_string(server_port).c_str(), &hints, &servinfo)) != 0) {
        fprintf(stderr, "Socket error: connect_to, getaddrinfo: %s\n", gai_strerror(rv));
        throw;
    }
    // loop through all the results and connect to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((socket_desc = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            perror("Socket error: connect_to, socket");
            continue;
        }

        int flags = 0, error = 0, ret = 0;
        fd_set  rset, wset;
        socklen_t   len = sizeof(error);

        //clear out descriptor sets for select
        //add socket to the descriptor sets
        FD_ZERO(&rset);
        FD_SET(socket_desc, &rset);
        wset = rset;    //structure assignment ok
        //set socket nonblocking flag
        if((flags = fcntl(socket_desc, F_GETFL, 0)) < 0)
            continue;
        if(fcntl(socket_desc, F_SETFL, flags | O_NONBLOCK) < 0)
            continue;
        //initiate non-blocking connect
        if(ret = connect(socket_desc, p->ai_addr, p->ai_addrlen) == -1) {
            if (errno != EINPROGRESS) {
                close(socket_desc);
                perror("Socket error: connect_to, could not connect");
                continue;
            }
        }
        if(ret != 0) {    // If connect did not succeed right away
            // We are waiting for connect to complete now
            if((ret = select(socket_desc + 1, NULL, &wset, NULL, timeout_ptr)) < 0)
                return false;
            if(ret == 0){   //we had a timeout
                errno = ETIMEDOUT;
                return false;
            }
            //we had a positive return so a descriptor is ready
            if(FD_ISSET(socket_desc, &rset) || FD_ISSET(socket_desc, &wset)){
                if(getsockopt(socket_desc, SOL_SOCKET, SO_ERROR, &error, &len) < 0 || error != 0)
                    return false;
            } else
                return false;

            if(error){  //check if we had a socket error
                errno = error;
                return false;
            }
        }
        //put socket back in blocking mode
        if(fcntl(socket_desc, F_SETFL, flags) < 0)
            return false;
        break;
    }
    if(p == NULL) {
        fprintf(stderr, "Socket error: connect_to, failed to connect\n");
        socket_desc = 0;
        return false;
    }
    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s);
    freeaddrinfo(servinfo); // all done with this structure
    connected = true;

    return connected;
}

Upvotes: 1

Views: 1394

Answers (1)

user207421
user207421

Reputation: 310860

This is normal, not a problem.

  1. TCP maintains a listen backlog queue into which connections are placed that have been completed by the TCP stack but not yet accepted by the application.
  2. TCP maintains a socket receive buffer per socket into which data is placed that has arrived from the peer and not yet been read by the application.

the client should know the connection failed.

It didn't fail. The server can accept it and read the data.

Upvotes: 2

Related Questions