summaryrefslogtreecommitdiffhomepage
path: root/networking/pscan.c
blob: 72ed8cdb5c36139de3253b6cca42088274a7f758 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/*
 * Pscan is a mini port scanner implementation for busybox
 *
 * Copyright 2007 Tito Ragusa <farmatito@tiscali.it>
 *
 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
 */

//usage:#define pscan_trivial_usage
//usage:       "[-cb] [-p MIN_PORT] [-P MAX_PORT] [-t TIMEOUT] [-T MIN_RTT] HOST"
//usage:#define pscan_full_usage "\n\n"
//usage:       "Scan a host, print all open ports\n"
//usage:     "\n	-c	Show closed ports too"
//usage:     "\n	-b	Show blocked ports too"
//usage:     "\n	-p	Scan from this port (default 1)"
//usage:     "\n	-P	Scan up to this port (default 1024)"
//usage:     "\n	-t	Timeout (default 5000 ms)"
//usage:     "\n	-T	Minimum rtt (default 5 ms, increase for congested hosts)"

#include "libbb.h"

/* debugging */
#ifdef DEBUG_PSCAN
#define DMSG(...) bb_error_msg(__VA_ARGS__)
#define DERR(...) bb_perror_msg(__VA_ARGS__)
#else
#define DMSG(...) ((void)0)
#define DERR(...) ((void)0)
#endif

static const char *port_name(unsigned port)
{
	struct servent *server;

	server = getservbyport(htons(port), NULL);
	if (server)
		return server->s_name;
	return "unknown";
}

/* We don't expect to see 1000+ seconds delay, unsigned is enough */
#define MONOTONIC_US() ((unsigned)monotonic_us())

int pscan_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int pscan_main(int argc UNUSED_PARAM, char **argv)
{
	const char *opt_max_port = "1024";      /* -P: default max port */
	const char *opt_min_port = "1";         /* -p: default min port */
	const char *opt_timeout = "5000";       /* -t: default timeout in msec */
	/* We estimate rtt and wait rtt*4 before concluding that port is
	 * totally blocked. min rtt of 5 ms may be too low if you are
	 * scanning an Internet host behind saturated/traffic shaped link.
	 * Rule of thumb: with min_rtt of N msec, scanning 1000 ports
	 * will take N seconds at absolute minimum */
	const char *opt_min_rtt = "5";          /* -T: default min rtt in msec */
	const char *result_str;
	len_and_sockaddr *lsap;
	int s;
	unsigned opt;
	unsigned port, max_port, nports;
	unsigned closed_ports = 0;
	unsigned open_ports = 0;
	/* all in usec */
	unsigned timeout;
	unsigned min_rtt;
	unsigned rtt_4;
	unsigned start, diff;

	opt_complementary = "=1"; /* exactly one non-option */
	opt = getopt32(argv, "cbp:P:t:T:", &opt_min_port, &opt_max_port, &opt_timeout, &opt_min_rtt);
	argv += optind;
	max_port = xatou_range(opt_max_port, 1, 65535);
	port = xatou_range(opt_min_port, 1, max_port);
	nports = max_port - port + 1;
	min_rtt = xatou_range(opt_min_rtt, 1, INT_MAX/1000 / 4) * 1000;
	timeout = xatou_range(opt_timeout, 1, INT_MAX/1000 / 4) * 1000;
	/* Initial rtt is BIG: */
	rtt_4 = timeout;

	DMSG("min_rtt %u timeout %u", min_rtt, timeout);

	lsap = xhost2sockaddr(*argv, port);
	printf("Scanning %s ports %u to %u\n Port\tProto\tState\tService\n",
			*argv, port, max_port);

	for (; port <= max_port; port++) {
		DMSG("rtt %u", rtt_4);

		/* The SOCK_STREAM socket type is implemented on the TCP/IP protocol. */
		set_nport(&lsap->u.sa, htons(port));
		s = xsocket(lsap->u.sa.sa_family, SOCK_STREAM, 0);
		/* We need unblocking socket so we don't need to wait for ETIMEOUT. */
		/* Nonblocking connect typically "fails" with errno == EINPROGRESS */
		ndelay_on(s);

		DMSG("connect to port %u", port);
		result_str = NULL;
		start = MONOTONIC_US();
		if (connect(s, &lsap->u.sa, lsap->len) == 0) {
			/* Unlikely, for me even localhost fails :) */
			DMSG("connect succeeded");
			goto open;
		}
		/* Check for untypical errors... */
		if (errno != EAGAIN && errno != EINPROGRESS
		 && errno != ECONNREFUSED
		) {
			bb_perror_nomsg_and_die();
		}

		diff = 0;
		while (1) {
			if (errno == ECONNREFUSED) {
				if (opt & 1) /* -c: show closed too */
					result_str = "closed";
				closed_ports++;
				break;
			}
			DERR("port %u errno %d @%u", port, errno, diff);

			if (diff > rtt_4) {
				if (opt & 2) /* -b: show blocked too */
					result_str = "blocked";
				break;
			}
			/* Can sleep (much) longer than specified delay.
			 * We check rtt BEFORE we usleep, otherwise
			 * on localhost we'll have no writes done (!)
			 * before we exceed (rather small) rtt */
			usleep(rtt_4/8);
 open:
			diff = MONOTONIC_US() - start;
			DMSG("write to port %u @%u", port, diff - start);
			if (write(s, " ", 1) >= 0) { /* We were able to write to the socket */
				open_ports++;
				result_str = "open";
				break;
			}
		}
		DMSG("out of loop @%u", diff);
		if (result_str)
			printf("%5u" "\t" "tcp" "\t" "%s" "\t" "%s" "\n",
					port, result_str, port_name(port));

		/* Estimate new rtt - we don't want to wait entire timeout
		 * for each port. *4 allows for rise in net delay.
		 * We increase rtt quickly (rtt_4*4), decrease slowly
		 * (diff is at least rtt_4/8, *4 == rtt_4/2)
		 * because we don't want to accidentally miss ports. */
		rtt_4 = diff * 4;
		if (rtt_4 < min_rtt)
			rtt_4 = min_rtt;
		if (rtt_4 > timeout)
			rtt_4 = timeout;
		/* Clean up */
		close(s);
	}
	if (ENABLE_FEATURE_CLEAN_UP) free(lsap);

	printf("%u closed, %u open, %u timed out (or blocked) ports\n",
					closed_ports,
					open_ports,
					nports - (closed_ports + open_ports));
	return EXIT_SUCCESS;
}