port-scanner

A small TCP connect port scanner in Python

View the Project on GitHub RYzZzEN007/port-scanner

How it works

The TCP connect scan

Every TCP connection starts with a three-way handshake:

scanner                          target
   | -------- SYN ----------------> |    "I want to connect to port N"
   | <------- SYN/ACK ------------- |    "OK, port N is listening"
   | -------- ACK ----------------> |    "Connection established"

A connect scan simply asks the operating system to open a normal connection to each port, using the same connect() call a browser or an SSH client uses. The kernel does the handshake, and the scanner looks at how the attempt ended. If the connection was established, the scanner closes it again straight away (or reads a banner first when --banner is set).

Because it uses the normal socket API, a connect scan needs no special privileges. Its faster relative, the SYN scan, sends the SYN, reads the reply and never sends the final ACK. That requires building packets by hand with raw sockets, which needs root, so this scanner does not do it.

For each port the scanner creates a socket, sets a timeout, and calls connect_ex((ip, port)). connect_ex is the same as connect except that it returns an error number instead of raising an exception. The ports are spread over a pool of threads so that many attempts are in flight at once.

The three port states

Open. The target answers the SYN with SYN/ACK and the handshake completes. connect_ex returns 0. A program is listening on that port and accepting connections.

SYN ->      <- SYN/ACK      ACK ->        connect_ex returns 0

Closed. The target answers the SYN with a RST (reset) packet. The host is reachable, but no program is listening on that port. The kernel reports this as “connection refused”, so connect_ex returns errno.ECONNREFUSED. The answer arrives as fast as an open port’s would.

SYN ->      <- RST                        connect_ex returns ECONNREFUSED

Filtered. Nothing useful comes back. Usually a firewall is silently dropping the SYN, so the scanner waits until the timeout runs out. Sometimes a router sends back an ICMP “unreachable” message instead, which shows up as a different error such as EHOSTUNREACH. The scanner treats a timeout and every error other than ECONNREFUSED as filtered.

SYN ->      (silence)                     timeout, or some other error

Closed and filtered are kept apart because they mean different things. A closed port proves that the host is up and that the packet reached it. A filtered port proves neither: the port might be open behind a firewall, or the host might not exist at all. Filtered ports are also why scans of firewalled hosts are slow, because each one costs a full timeout.

One more state can appear, error. It means the scanner itself could not create a socket because it ran out of file descriptors. It says nothing about the target, so those ports are counted separately.

With --banner, the scanner reads the first bytes an open port sends. Some protocols speak first (SSH, FTP, SMTP), so the scanner only has to read. HTTP servers wait for a request, so on HTTP ports the scanner first sends HEAD / HTTP/1.0 and then reads the status line and the Server header. TLS ports such as 443 are skipped, because without a TLS handshake they return nothing readable.

Limitations