-1

For a project I'm building a "super-server" like inetd. It is supposed to read a set of ports and commands from a config file, and for each port spin up a listener socket. It should then use select() to determine when one or more of these sockets is ready to read from. When select finds a socket, it should use accept() to connect to this socket, and then fork() a child process in which the command will be executed. Unfortunately, select is always either timing out or failing when I try to call "nc -l localhost 12345" to test it (with '12345 echo "hello world"' in the config.txt file).

Can you spot anything I might be doing wrong? Thanks in advance! I've been going crazy trying to figure this out!

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>

#include <iostream>
#include <sstream>
#include <fstream>
#include <map>

using namespace std;

map<int,string> parse_config_file() {
    string line;
    ifstream file;
    stringstream ss;
    int port;
    string command;
    map<int,string> port_to_command;

    file.open("config.txt");

    while (getline(file,line)) {
        ss = stringstream(line);
        ss >> port;
        getline(ss,command);
        port_to_command[port] = command;
    }

    file.close();

    return port_to_command;
}

void handle_client(int socket, string command) {
    dup2(socket, STDIN_FILENO);
    dup2(socket, STDOUT_FILENO);
    execl("/bin/sh", "/bin/sh", "-c", command.c_str(), NULL);
}

int main(int argc, const char * argv[]) {

    int rc;
    int readyfd;
    int peerfd;
    int maxfd = 0;
    int port;

    pid_t child_pid;
    fd_set readfds;

    struct timeval tv;
    tv.tv_sec = 10;
    tv.tv_usec = 0;

    struct sockaddr* server_address;
    socklen_t server_address_length = sizeof(server_address);
    struct sockaddr client_address;
    socklen_t client_address_length = sizeof(client_address);

    map<int,string> port_to_command = parse_config_file();
    map<int,string>::iterator pcitr;
    map<int,int> socket_to_port;
    map<int,int>::iterator spitr;


    // Create, bind, and listen on the sockets:
    for (pcitr = port_to_command.begin(); pcitr != port_to_command.end(); pcitr++) {

        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0) {
            cerr << "ERROR opening socket";
            exit(EXIT_FAILURE);
        }

        port = pcitr->first;
        struct sockaddr_in server_address_internet;
        bzero((char *) &server_address_internet, sizeof(server_address_internet));
        server_address_internet.sin_family = AF_INET;
        server_address_internet.sin_addr.s_addr = INADDR_ANY;
        server_address_internet.sin_port = htons(port);
        server_address = (struct sockaddr *)&server_address_internet;

        bind(sockfd, server_address, server_address_length);

        rc = listen(sockfd, 10);
        if (rc < 0) {
            cerr << "listen() failed";
            exit(EXIT_FAILURE);
        }

        socket_to_port[sockfd] = pcitr->first;

        if (sockfd > maxfd) {
            maxfd = sockfd;
        }
    }

    // Server Loop
    while (true) {

        // Rebuild the FD set:
        FD_ZERO(&readfds);
        for (spitr = socket_to_port.begin(); spitr != socket_to_port.end(); spitr++) {
            FD_SET(spitr->first, &readfds);
        }

        // Select
        rc = select(maxfd + 1, &readfds, NULL, NULL, &tv);
        if (rc == 0) {
            // Timeout
            continue;
        } else if (rc < 0) {
            cerr << "select failed" << endl;
            exit(EXIT_FAILURE);
        }

        // Find the socket that is ready to be read:
        readyfd = -1;
        for (spitr = socket_to_port.begin(); spitr != socket_to_port.end(); spitr++) {
            if (FD_ISSET(spitr->first, &readfds)) {
                readyfd = spitr->first;
                break;
            }
        }

        // Accept
        peerfd = accept(readyfd, &client_address, &client_address_length);
        if (peerfd < 0) {
            cerr << "accept failed" << endl;
            exit(EXIT_FAILURE);
        }

        // Fork to handle request:
        child_pid = fork();
        if (child_pid == 0) {
            port = ((struct sockaddr_in*)&client_address)->sin_port;
            handle_client(peerfd, port_to_command[port]);
            close(peerfd);
            exit(EXIT_SUCCESS);
        } else {
            close(peerfd);
        }
    }

    return 0;
}
too honest for this site
  • 12,050
  • 4
  • 30
  • 52
Logan Shire
  • 5,013
  • 4
  • 29
  • 37

1 Answers1

1

Well, I did spot a few things you were doing wrong.

  1. using namespace std; -- that part is obviously wrong.

  2. parse_config_file() does not validate and check the syntax of the configuration file. A typo, or a misplaced character would result in operator>> failing, which will not be detected. So, a command's port would either be random, uninitialized, or a copy of the previous command's port.

  3. And, finally we come to this:

    struct sockaddr* server_address;
    socklen_t server_address_length = sizeof(server_address);
    

Pop quiz: what is sizeof(struct sockaddr *)? Well, it's a pointer, so it's going to be either 4 or 8 bytes, here.

 bind(sockfd, server_address, server_address_length);

I'm fairly certain that struct sockaddr_in is larger than that. A quick check confirms that it's 16 bytes long. You were passing either 4 or 8 bytes, as the size of a 16-byte structure.

You had a two-fer here. Getting the size wrong, and failing to check the error code returned by bind(), so you remained completely unaware that the system call was always failing.

You can't assume that a system call will always succeed. Whether it's bind(), socket(), connect(), or accept(). Every system call can fail. Always check the return value from every system call. It might be tedious, or boring, to check the return value of a system call, but it must be done. If you did that, you would've caught the initial bug, with the wrong sizeof().

Community
  • 1
  • 1
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148