使用select()来超时套接字

时间:2016-05-22 03:21:59

标签: c sockets select

我试图在C中制作一个简单的port scanner,并最终处于最后阶段。我写了一个简单的函数来连接到端口上的服务器,如果端口关闭,它应该超时。我有以下测试用例:

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(int argc, char **argv) {
    if(argc != 3) {
        fprintf(stderr, "idiot.\n");
    }

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    int flags = fcntl(sock, F_GETFL, 0);

    flags |= O_NONBLOCK;
    fcntl(sock, F_SETFL, flags);

    if(sock < 0) {
        perror("socket()");
    }

    struct hostent *server = gethostbyname(argv[1]);

    if (server == NULL) {
        perror("gethostbyname()");
    }

    struct sockaddr_in server_addr;
    bzero((char*)&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    bcopy(server->h_addr, (char*)&server_addr.sin_addr.s_addr, server->h_length);
    server_addr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("connecting()");
    }

    fd_set rfds;
    struct timeval tv;
    int retval;

    FD_ZERO(&rfds);
    FD_SET(sock, &rfds);

    tv.tv_sec = 3;
    tv.tv_usec = 0;
    retval = select(sock+1, &rfds, NULL, NULL, &tv);

    if (retval == -1)
        perror("select()");
    else if (retval)
        printf("port is open.\n");
    else
        printf("port is closed.\n");
    return 0;
}

问题是,我传递的任何端口都被报告为打开。例如:

[nchambers@shell:~/SpinCloud/Chevron] [devel]$ ./testcase localhost 22 # sshd actually runs on this machine, so its correct
connecting(): Operation now in progress
port is open.
[nchambers@shell:~/SpinCloud/Chevron] [devel]$ telnet localhost 22
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
SSH-2.0-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.6
^]

telnet> quit
Connection closed.
[nchambers@shell:~/SpinCloud/Chevron] [devel]$ ./testcase localhost 80 # but no webserver
connecting(): Operation now in progress
port is open.
[nchambers@shell:~/SpinCloud/Chevron] [devel]$ telnet localhost 80
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused
[nchambers@shell:~/SpinCloud/Chevron] [devel]$

2 个答案:

答案 0 :(得分:4)

select报告某些内容为“可读”或“可写”的事实仅表示read / write的尝试不会阻止。它并不意味着read / write结果将会是什么。

注意:同样,您需要区分connect返回EINPROGRESS(并在此情况下为可写性进行轮询)与其他失败的情况。 (虽然也许您可以使用TCP_KEEPCNTTCP_USER_TIMEOUT代替?但如果您使用getaddrinfo就像您应该使用的那样并且尝试所有这些地址可能一次,但每次都有延迟)

此外,您应该认真考虑使用其中一个较新的替代方案,至少poll(即POSIX),如果不是某些系统特定的,例如epollkqueue

答案 1 :(得分:2)

您正在打开非阻止请求。当您调用connect()时,它会返回EINPROGRESS或“正在进行操作”,如manpage for connect()中所述:

          The socket is nonblocking and the connection cannot be
          completed immediately.  It is possible to select(2) or poll(2)
          for completion by selecting the socket for writing.  After
          select(2) indicates writability, use getsockopt(2) to read the
          SO_ERROR option at level SOL_SOCKET to determine whether
          connect() completed successfully (SO_ERROR is zero) or
          unsuccessfully (SO_ERROR is one of the usual error codes
          listed here, explaining the reason for the failure).

您应该检查connect()上的errno值。此外,非负select返回并不一定意味着端口在您正在测试的服务器上打开。

顺便说一下,您发布的代码通常不能很好地处理错误。从第一个if(argc != 3) {开始,您不会返回,只是打印一条消息。如果您在没有三个输入的情况下运行代码,它将显示错误,但随后继续执行(这可能最终会出现seg错误)。

我建议您花更多时间研究同步与异步i / o,并在假设它们打开之前实际连接到端口。