port-scanner

a TCP port scanner written in C


port-scanner / scanner.c

scanner.c (3.8K) raw | download
1#define _POSIX_C_SOURCE 200809L
2#include <sys/socket.h>
3#include <sys/select.h>
4#include <netinet/in.h>
5#include <netdb.h>
6#include <fcntl.h>
7#include <unistd.h>
8#include <errno.h>
9#include <stdint.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <time.h>
14
15static int
16parse_port(const char *text)
17{
18 long port;
19 if (!*text || strspn(text, "0123456789") != strlen(text)) return -1;
20 errno = 0;
21 port = strtol(text, NULL, 10);
22 if (errno || port < 1 || port > 65535) return -1;
23 return (int)port;
24}
25
26static int64_t
27now_ms(void)
28{
29 struct timespec ts;
30 if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) return -1;
31 return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
32}
33
34/* Return 1 for open, 0 for refused/unreachable/timed out, or -1 for failure. */
35static int
36connect_port(const struct sockaddr_in *address)
37{
38 int fd = socket(AF_INET, SOCK_STREAM, 0);
39 int result = -1, error, saved;
40 int64_t deadline;
41 if (fd < 0) return -1;
42 if (fd >= FD_SETSIZE) { errno = EMFILE; goto done; }
43 do {
44 error = fcntl(fd, F_SETFL, O_NONBLOCK);
45 } while (error < 0 && errno == EINTR);
46 if (error < 0) goto done;
47 if (connect(fd, (const struct sockaddr *)address, sizeof(*address)) == 0) {
48 result = 1;
49 goto done;
50 }
51 if (errno != EINPROGRESS && errno != EINTR && errno != EALREADY)
52 goto failed;
53 deadline = now_ms();
54 if (deadline < 0) goto done;
55 deadline += 1000;
56 for (;;) {
57 fd_set writable;
58 struct timeval timeout;
59 socklen_t size = sizeof(error);
60 int64_t now = now_ms(), remaining;
61 if (now < 0) goto done;
62 remaining = deadline - now;
63 if (remaining <= 0) { result = 0; goto done; }
64 FD_ZERO(&writable);
65 FD_SET(fd, &writable);
66 timeout.tv_sec = (time_t)(remaining / 1000);
67 timeout.tv_usec = (suseconds_t)((remaining % 1000) * 1000);
68 error = select(fd + 1, NULL, &writable, NULL, &timeout);
69 if (error < 0 && errno == EINTR) continue;
70 if (error < 0) goto done;
71 if (error == 0) { result = 0; goto done; }
72 do {
73 saved = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &size);
74 } while (saved < 0 && errno == EINTR);
75 if (saved < 0) goto done;
76 if (error == 0) { result = 1; goto done; }
77 errno = error;
78 break;
79 }
80failed:
81 switch (errno) {
82 case ECONNREFUSED:
83 case ECONNRESET:
84 case ETIMEDOUT:
85 case EHOSTUNREACH:
86 case ENETUNREACH:
87 result = 0;
88 break;
89 }
90done:
91 saved = errno;
92 close(fd);
93 errno = saved;
94 return result;
95}
96
97int
98main(int argc, char **argv)
99{
100 struct addrinfo hints = {0}, *addresses;
101 struct sockaddr_in address;
102 int first, last, port, result;
103 if (argc != 4) {
104 fprintf(stderr, "usage: %s host start-port end-port\n", argv[0]);
105 return 1;
106 }
107 first = parse_port(argv[2]);
108 last = parse_port(argv[3]);
109 if (first < 0 || last < 0 || first > last) {
110 fprintf(stderr, "invalid port range: %s %s\n", argv[2], argv[3]);
111 return 1;
112 }
113 hints.ai_family = AF_INET;
114 hints.ai_socktype = SOCK_STREAM;
115 result = getaddrinfo(argv[1], NULL, &hints, &addresses);
116 if (result != 0) {
117 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result));
118 return 1;
119 }
120 address = *(const struct sockaddr_in *)addresses->ai_addr;
121 freeaddrinfo(addresses);
122 for (port = first; port <= last; ++port) {
123 address.sin_port = htons((unsigned short)port);
124 result = connect_port(&address);
125 if (result < 0) {
126 fprintf(stderr, "port %d: %s\n", port, strerror(errno));
127 return 1;
128 }
129 if (result && printf("%d open\n", port) < 0) {
130 perror("stdout");
131 return 1;
132 }
133 }
134 if (fflush(stdout) == EOF) { perror("stdout"); return 1; }
135 return 0;
136}