我试图在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]$
答案 0 :(得分:4)
select
报告某些内容为“可读”或“可写”的事实仅表示read
/ write
的尝试不会阻止。它并不意味着read
/ write
的结果将会是什么。
注意:同样,您需要区分connect
返回EINPROGRESS
(并在此情况下为可写性进行轮询)与其他失败的情况。 (虽然也许您可以使用TCP_KEEPCNT
或TCP_USER_TIMEOUT
代替?但如果您使用getaddrinfo
就像您应该使用的那样并且尝试所有这些地址可能一次,但每次都有延迟)
此外,您应该认真考虑使用其中一个较新的替代方案,至少poll
(即POSIX),如果不是某些系统特定的,例如epoll
或kqueue
。
答案 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,并在假设它们打开之前实际连接到端口。