Skip to main content

Dual-mode (CLI + PyQt6) async TCP connect scanner

Project description

netscanqt

A fast, async TCP connect scanner with one engine and two front ends: a streaming command-line tool and an optional PyQt6 GUI. It scans, optionally discovers live hosts first, and optionally fingerprints the services it finds against the Recog database.

It does not use raw sockets — no root, no libpcap, no CAP_NET_RAW. It runs as an ordinary user on a headless jump box, which is exactly where it's meant to live. The scanning core is pure standard library; the GUI, the prettier CLI output, and the network features (fingerprint corpus download) are all opt-in, so a plain pip install pulls in nothing you don't need.

netscanqt is a reachability scanner with service identification, not an nmap replacement. It tells you which TCP ports answer and makes a best effort to name the service behind them. It does not do SYN/stealth scans, UDP, OS fingerprinting, or NSE-style scripting. If you need those, run nmap.

netscanqt GUI: a /24 scan with discovery and service fingerprinting, showing open ports with product and version columns


Install

# CLI + engine — zero non-stdlib dependencies
pip install netscanqt

# add extras as needed
pip install netscanqt[rich]      # prettier CLI tables + progress bars
pip install netscanqt[gui]       # the PyQt6 desktop GUI
pip install netscanqt[gui,rich]  # everything

From source, with an editable install for development:

git clone https://github.com/scottpeterman/netscanqt
cd netscanqt
pip install -e .[gui,rich]

Requires Python 3.10+.

Launching:

netscanqt ...                 # CLI console script
python -m netscanqt ...       # module form, same CLI
netscanqt-gui                 # GUI (prints an install hint without the [gui] extra)
netscanqt-fetch-fingerprints  # download the Recog corpus (see Fingerprinting)

Quick start

# scan the well-known ports across a /24
netscanqt 10.0.0.0/24

# discover live hosts first, then full-scan only those
netscanqt 10.0.0.0/24 -d

# discover, scan, and identify the services on open ports
netscanqt 10.0.0.0/24 -d -F

netscanqt discovering and scanning a /24 from the command line, with a live phased progress bar


CLI guide

Synopsis

netscanqt [options] TARGET [TARGET ...]

Targets

One or more hosts, IP addresses, or CIDR blocks, space-separated. CIDR blocks expand to their usable host addresses; bare IPs and hostnames pass through to the resolver.

netscanqt 10.0.0.0/24 192.168.1.1 host.example.com

Ports (-p / --ports)

Form Meaning
22,80,443 an explicit list
1-1024 an inclusive range
22,8000-8100 lists and ranges combined
all (or -) every port, 1–65535

Default: 1-1024.

Options

Flag Default Description
-p, --ports 1-1024 ports to scan (see forms above)
-c, --concurrency 500 number of simultaneous connects
-t, --timeout 1.0 per-connect timeout, in seconds
-r, --retries 1 extra attempts on timeout before a port is marked filtered
--filtered off also report filtered ports, not just open ones
-d, --discover off liveness-sweep the range first; full-scan only hosts that answer
--discovery-timeout 0.5 per-probe timeout for the liveness pass
-F, --fingerprint off identify services on open ports (banner grab + Recog match)
--recog-dir path to a Recog xml/ directory (overrides cache and env)
--fingerprint-timeout 2.0 per-service timeout for the fingerprint pass
-o, --output text output format: text, json, or csv
-q, --quiet off suppress the progress line on stderr
--no-color off force plain text even if rich is installed
--version print version and exit

Discovery (-d) — and why it matters

On a sparse range, most addresses are dark, and without discovery every port on every dead host is probed until it times out — a /24 × 1-1024 is ~260,000 connects, almost all waiting on silence.

With -d, netscanqt first sweeps a small set of liveness ports (22, 80, 443, 445, 3389) with a short timeout, keeps only the hosts that respond, then full-scans just those. A host counts as alive if any probe is open or closed — a closed port (an immediate RST) proves a host is there just as well as an open one; only silence means dead. On a typical sparse /24 that turns hundreds of thousands of probes into a few thousand, and minutes into seconds. Use it whenever you're scanning a range rather than a known host list.

Tuning for a fast, reliable LAN

netscanqt 10.0.0.0/24 -d -t 0.4 -r 0

-t 0.4 shortens the wait on non-responders; -r 0 drops the retry so each dead probe costs one timeout instead of two.

File descriptors. --concurrency is bounded by your open-file limit (ulimit -n, often 1024). Push past it and the OS refuses sockets; netscanqt treats that refusal like an unreachable port, so you'd get wrong results, not an error. If you raise concurrency, raise the fd limit with it.


Fingerprinting (-F)

-F adds a second pass over the open ports: it connects, grabs an identifying string (an HTTP Server header, a self-announced banner), and matches it against the Recog fingerprint database to report a product, version, and CPE. The scan stays a clean reachability layer; fingerprinting is a separate stage layered on top.

Getting the corpus

netscanqt ships only a tiny built-in sample (a handful of fingerprints), so out of the box -F identifies very little. Download the full corpus once:

netscanqt-fetch-fingerprints                 # into ~/.cache/netscanqt/recog
netscanqt-fetch-fingerprints --ref v3.1.4    # pin a tag for a reproducible set
netscanqt-fetch-fingerprints --dest ./recog  # somewhere explicit

This pulls ~50 XML files (~3 MB) as a single tarball — no GitHub API, so no rate limiting. Run it once from a box with internet; every scan afterward loads the corpus locally and offline, which is what keeps -F usable on airgapped jump boxes. The full corpus is ~4,300 fingerprints.

Where fingerprints are loaded from

Resolved in this order, first match wins:

  1. --recog-dir PATH
  2. $NETSCANQT_RECOG_DIR
  3. the downloaded cache (~/.cache/netscanqt/recog)
  4. the built-in sample

So once you've run the fetch command, both the CLI and GUI pick up the full corpus with no further configuration.

What it can and can't identify

Self-announcing services are covered: HTTP/HTTPS (Apache, IIS, nginx), IPP/CUPS, SSH, and the FTP/SMTP/POP/IMAP greeters. Silent or client-speaks-first services (PostgreSQL, MSSQL, raw 9100) stay unidentified because they need a real protocol exchange to elicit a response. When no fingerprint matches, the raw banner is shown instead of a blank — so you always see what the service actually said.


Output formats (-o)

text (default) renders a table — a rich table with colored states and a phased progress bar if the [rich] extra is installed and you're on a terminal, or aligned plain text otherwise (also used automatically when piping):

Host         Port   State   Service   Product            Version    Latency
10.0.0.27      22   open    ssh       OpenBSD OpenSSH     8.9p1       2.7 ms
10.0.0.55     631   open    ipp       nginx                          54.9 ms
10.0.0.1      443   open    https     Xfinity Broadband ...         153.6 ms

netscanqt rich CLI output: colored port states and a fingerprinted results table

json emits one array after the scan completes; csv writes a header plus rows. With -F, both include product, version, cpe, os, and the raw banner.

# discover + fingerprint a /24, pull the SSH hosts out with jq
netscanqt 10.0.0.0/24 -d -F -o json -q | jq '.[] | select(.port == 22)'

# CSV inventory with service identification
netscanqt 10.0.0.0/24 -d -F -o csv -q > inventory.csv

Exit codes

Code Meaning
0 completed
2 bad input, fingerprint load failure, or GUI launched without [gui]
130 interrupted (Ctrl-C) — sockets are closed cleanly on the way out

GUI

netscanqt-gui

Enter targets and ports, tick Discover live hosts first and/or Fingerprint services, and hit Scan. Results stream into the table — with Product and Version columns when fingerprinting — and the progress bar relabels itself across the discovery, scan, and fingerprint phases. Stop cancels cleanly. It's the same engine and the same fingerprint corpus as the CLI; the window is just another way to build a scan configuration.

netscanqt GUI mid-scan, the progress bar relabeling across the discovery, scan, and fingerprint phases

The corpus the GUI matches against is the same one the CLI resolves, and it can be inspected and downloaded from the window — the count shown is exactly what -F will match against.

netscanqt GUI corpus panel: inspect the loaded fingerprint count and download or refresh the Recog database


Using the engine as a library

The scanner and the fingerprinter are importable on their own; the CLI and GUI are thin drivers over them. The engine yields structured events and never prints:

import asyncio
from netscanqt import scan, ScanConfig, ScanResult, ScanProgress
from netscanqt import enrich, load_recog

async def main():
    config = ScanConfig.from_specs(["10.0.0.0/24"], ports="22,80,443", discover=True)
    opens = [e async for e in scan(config) if isinstance(e, ScanResult)]

    recog = load_recog()  # downloaded cache, env, or built-in sample
    async for er in enrich(opens, recog):
        product = er.fingerprint.get("service.product") if er.fingerprint else er.banner
        print(f"{er.result.host}:{er.result.port}  {product}")

asyncio.run(main())

scan() and enrich() are async generators. Stop iterating, break, or aclose() and in-flight probes are cancelled and their sockets closed — cancellation is part of the contract, which is why both front ends get a clean Stop for free.


How it works

One engine, two drivers. engine.py knows how to scan and nothing else — no formatting, no argparse, no Qt. Both shells exist only to construct a ScanConfig and consume the events scan() yields. A CLI flag and a GUI checkbox are the same thing: a field on the config.

Concurrency, not parallelism. Connect-scanning is I/O-bound — every worker waits on the network, not the CPU — so the engine uses a pool of asyncio workers over a single thread. Threads or multiprocessing would buy nothing; the only way to go faster is to stop waiting on dead hosts, which is what discovery does.

Layered passes. Discovery, the scan, and fingerprinting are distinct passes that compose: each consumes the previous one's output rather than complicating it. Fingerprint matching is a pure function, so it's the one piece that could move to a process pool if service-identification volume ever made regex matching a CPU bottleneck.


Notes

Scan only hosts and networks you own or are explicitly authorized to scan. Unauthorized port scanning may be illegal where you are.

The Recog fingerprint database is © Rapid7 and contributors, licensed under BSD-2-Clause; it is downloaded at the user's request and is not redistributed with netscanqt.

License

GPL-3.0-or-later.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

netscanqt-0.1.0.tar.gz (26.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

netscanqt-0.1.0-py3-none-any.whl (32.1 kB view details)

Uploaded Python 3

File details

Details for the file netscanqt-0.1.0.tar.gz.

File metadata

  • Download URL: netscanqt-0.1.0.tar.gz
  • Upload date:
  • Size: 26.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for netscanqt-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5d6d19799226ee076a9526e682bc8360226abcf59bccc0d7d5debf9640e1aebd
MD5 38db051cd05ff2e14a04476b11346218
BLAKE2b-256 bba978cc9664459cc8a1bb8d0a020fe621374bfde011eb0b41cd93af048d5f2b

See more details on using hashes here.

File details

Details for the file netscanqt-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: netscanqt-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 32.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for netscanqt-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c95aca4c8ed8765566ebe9d16d60d16f43f68a8c355b98daaf98095b7635c102
MD5 a132fa572f305f6273b05a269b32a83a
BLAKE2b-256 725679687b62f4256af2def05a5cc342d17db425e7a65661b39df5e38515163a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page