Cross-platform network scanner that does not need root
  • C++ 91.6%
  • CMake 4.3%
  • C 2.1%
  • NSIS 1.3%
  • Python 0.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
David Simmons 4d48449569
All checks were successful
Build / verify-network-macos (push) Has been skipped
Build / verify-network-windows (push) Has been skipped
Build / build-linux (push) Successful in 48s
Build / build-macos (push) Successful in 1m18s
Build / build-linux-rpm (push) Successful in 1m33s
Build / build-windows (push) Successful in 2m32s
Build / build-linux-appimage (push) Successful in 2m44s
Build / release (push) Successful in 19s
Release 0.5.1: macOS local-network access, and screenshots
Patch rather than minor — a fix plus documentation, no new capability.

The macOS half of this cannot be verified by CI at all: a CI-run binary
gets no TCC grant and the CLI is not a bundle, so NSLocalNetworkUsage-
Description only does anything in an installed, launched .app. Releasing
is the only way to find out whether macOS prompts and whether the MAC
column then fills — which is also what finally puts the BSD parsing
rewrite in front of real data.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CKCoEkbH2YJaAH1X88UAtZ
2026-09-04 23:25:48 -04:00
.forgejo/workflows CI: print the macOS system's own ARP/NDP tables alongside ours 2026-09-04 19:25:58 -04:00
docs/screenshots Add README screenshots, and reference them from the AppStream metadata 2026-09-04 18:07:19 -04:00
licenses Initial commit: Mildly Irritated IP Scanner 0.1.0 2026-09-04 12:33:17 -04:00
resources Release 0.5.1: macOS local-network access, and screenshots 2026-09-04 23:25:48 -04:00
src macOS: request local-network access, and stop calling this a code bug 2026-09-04 19:30:13 -04:00
tools Package for all four targets: RPM, AppImage, Windows and macOS 2026-09-04 13:24:52 -04:00
.gitattributes Initial commit: Mildly Irritated IP Scanner 0.1.0 2026-09-04 12:33:17 -04:00
.gitignore Initial commit: Mildly Irritated IP Scanner 0.1.0 2026-09-04 12:33:17 -04:00
ARCHITECTURE.md docs: record three-platform CI coverage and the Windows RST finding 2026-09-04 13:05:39 -04:00
CHANGELOG.md Release 0.5.1: macOS local-network access, and screenshots 2026-09-04 23:25:48 -04:00
CLAUDE.md Initial commit: Mildly Irritated IP Scanner 0.1.0 2026-09-04 12:33:17 -04:00
CMakeLists.txt Release 0.5.1: macOS local-network access, and screenshots 2026-09-04 23:25:48 -04:00
CONTRIBUTING.md macOS: request local-network access, and stop calling this a code bug 2026-09-04 19:30:13 -04:00
LICENSE Initial commit: Mildly Irritated IP Scanner 0.1.0 2026-09-04 12:33:17 -04:00
README.md Release 0.5.1: macOS local-network access, and screenshots 2026-09-04 23:25:48 -04:00

Mildly Irritated IP Scanner

A cross-platform network scanner that finds what's on your network without asking for root.

Sweep an IPv4 range and get back which hosts answered, how fast, what they're called, what hardware they are, and which of your chosen ports are open. Export it to CSV, JSON or XML.

Current version: 0.5.1 · GPL-3.0-or-later · Qt 6


Why another IP scanner

Because the good ones make you choose between "needs administrator" and "misses half your hosts", and that is a false choice.

  • Runs unprivileged, everywhere. Host discovery uses the unprivileged ICMP interface each OS actually provides — Linux ping sockets, macOS datagram ICMP, Windows IcmpSendEcho2 — instead of raw sockets. No sudo, no setcap, no UAC prompt. When ICMP genuinely isn't available it falls back to TCP connect probes and tells you it did, rather than silently returning a shorter list.
  • It distinguishes "quiet" from "absent". A host that ignores ping but answers TCP is reported as Alive (TCP). A host that got an ICMP error back is Filtered. A host that produced nothing at all is Dead. Most scanners collapse these into one bit and lose the only part that was diagnostic.
  • Sub-millisecond timings. RTT is measured and stored in microseconds, so a LAN host reads 0.14, not 0.
  • It asks devices their own name. Most things on a LAN have no reverse-DNS record, so most scanners leave the Hostname column blank for them. miscan follows up with an mDNS query and a NetBIOS node status request — the same thing that makes a Sonos speaker or a Windows box identify itself. Measured on a real /24: 35% of hosts named by DNS alone, 69% with these added.
  • It knows what a randomised MAC is. Modern phones rotate their MAC addresses; instead of an unexplained blank Vendor column you get (randomized / locally administered).
  • The rate limit is real. The concurrency setting is a hard global ceiling on probes in flight, enforced across host discovery and port scanning together — not a per-stage suggestion that quietly multiplies.

Screenshots

The main window after a scan

A finished scan. Alive answered ICMP; Alive (TCP) ignored ping but answered a connection; Filtered got an ICMP error back, so something is blocking it rather than absent. Names come from reverse DNS where it exists and from mDNS or NetBIOS where it doesn't — printer.local and DESK-WIN11 here — and hovering the column tells you which. A MAC with no registered vendor is called out as randomised rather than left blank.

Right-click menu on a result

The right-click menu is built from what was actually found on that host: a browser action only when a web port answered, SSH only when 22 is open, Wake-on-LAN only when there's a MAC.

Preferences

A light theme ships alongside the default dark one — see main-window-light.png and preferences-light.png.

(These show a synthetic result set on RFC 5737 documentation addresses. The MAC addresses pair real IEEE vendor prefixes — public registry data naming a manufacturer, not a device — with invented device portions, so the vendor column shows what it genuinely does without publishing anyone's hardware.)

Installing

Download from the releases page.

Linux

sudo apt install ./miscan_0.5.1_amd64.deb        # Debian, Ubuntu
sudo dnf install ./miscan-0.5.1-1.x86_64.rpm     # Fedora, RHEL

Or take the .AppImage, which needs no installation and no root:

chmod +x miscan-0.5.1-x86_64.AppImage
./miscan-0.5.1-x86_64.AppImage

The AppImage needs glibc 2.38 or newer — measured across every binary inside it, not assumed from the build container's version. That covers Ubuntu 24.04+, Debian 13+, Fedora 39+ and equivalents. Older systems should use the .deb or .rpm.

Windows

Take either the installer (miscan-0.5.1-windows-x64-setup.exe) or the portable zip, which needs no installation — unpack it and run miscan.exe.

The installer is unsigned. There is no code-signing certificate for this project, so SmartScreen will show "Windows protected your PC" on first run. "More info" → "Run anyway" if you trust the source. This is stated here rather than left as a surprise; it will stop happening if the project ever gets a certificate.

macOS

miscan-0.5.1-macos-arm64.dmg, which contains MIIP Scanner.app — drag it to Applications. Apple Silicon only: the build machine is arm64 and nothing here produces a universal binary, so it will not run on an Intel Mac.

It is unsigned and un-notarised, so Gatekeeper will refuse it outright on first open. Either right-click the app and choose Open (which offers an override the double-click path does not), or clear the quarantine attribute:

xattr -dr com.apple.quarantine "/Applications/MIIP Scanner.app"

That is the honest state of things, not an oversight: signing requires a paid Apple Developer account, and notarisation requires that plus an upload step neither of which this project has yet.

Building from source

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --target miscan -j$(nproc)
./build/miscan

Needs Qt 6 (Widgets, Network, Concurrent, Svg, Test) and a C++17 compiler. See CONTRIBUTING.md for the full dependency list and the test suite.

Using it

Type a range and press Start (or Ctrl+Return). Accepted formats:

You type You get
192.168.1.10 one address
192.168.1.0/24 a CIDR block
192.168.1.10-192.168.1.20 an explicit range
192.168.1.10-20 the same, shorthand final octet
192.168.1.* a wildcard octet
192.168.*.* multiple wildcards
192.168.1.0/24, 10.0.0.1-50 several targets at once

Tools → Scan This Machine's Subnet fills in the subnet you're plugged into. It fills in the field — it does not start a scan. Nothing in this app ever probes anything you didn't ask it to.

Columns

Column What it means
State Alive (answered ICMP), Alive (TCP) (ignored ping, answered a connection), Filtered (an ICMP error came back — blocked, not absent), Dead (nothing at all)
RTT (ms) Probe round trip, two decimals
Hostname The host's name, and hovering tells you where it came from. Reverse DNS first; for the many devices with no PTR record, miscan asks the host itself over mDNS and NetBIOS. On a real /24 that took naming from 35% of hosts to 69% — the printers, phones and speakers are exactly the ones DNS doesn't know about
MAC From your own ARP table. Only ever available for hosts on a directly attached subnet — anything past a router is reached via the router's MAC, so a blank here is the correct answer, not a failure. On macOS 15+ see the note below
Vendor IEEE OUI registration for that MAC
Open Ports Which of your configured ports answered. Hover for the full open/closed/filtered breakdown

Exporting

Scan → Export Results writes CSV, JSON or XML. Every format carries the range, timestamp, duration and transport the scan actually used, so the file is still meaningful to someone reading it months later.

CSV cells that begin with =, +, - or @ are prefixed with an apostrophe. This is deliberate: the Hostname column is a PTR record, and a PTR record is set by the administrator of the host you scanned — which makes it attacker-controlled input that would otherwise execute as a formula when the file is opened in a spreadsheet.

Command line

Run a scan without a GUI. Needs no display, so it works over SSH, in a container, and in a cron job.

miscan --scan 192.168.1.0/24
miscan --scan 10.0.0.0/24 --format json --output hosts.json
miscan --scan 192.168.1.1-50 --ports 22,443 --no-mac --quiet

Results go to stdout, progress to stderr, so redirecting stdout gives clean parseable output. Exit codes: 0 scan completed, 1 bad arguments, 2 the scan could not run. miscan --help lists every option.

Bad input is rejected rather than clamped — a script should not carry on believing a setting took effect when it did not.

Right-click on a result

The results table has a context menu built from what was actually found on that host: open it in a browser if a web port answered, SSH to it if 22 is open, copy its address, hostname or MAC, send it a Wake-on-LAN magic packet, or rescan just that host. Actions that cannot apply are disabled rather than hidden — a greyed-out Copy MAC Address tells you the host has no ARP entry, which is itself information.

Wake-on-LAN needs a MAC, which means it only offers itself for hosts on a directly attached subnet — the same hosts a broadcast magic packet can reach. Nothing can confirm a machine actually woke, and the app says so rather than claiming success.

Updates

Help ▸ Check for Updates asks the release server whether a newer version exists. There is also a once-a-day check at startup, on by default, which you can turn off in Preferences ▸ Updates.

What that request contains, precisely: an unauthenticated GET to a public releases endpoint, with nothing about you or your install attached — no version number, no identifier, no configuration, and no scan results ever. Nothing is downloaded or installed automatically either; you are shown the release page and you decide.

The startup check is deliberately quiet. It tells you only when there is genuinely a newer release, and says so in the status bar rather than interrupting you with a dialog. It never reports its own success, and it never complains about being unable to reach the server — plenty of machines have no route to it, and that is not something you asked about.

Scope, honestly stated

IPv4 only. This is not an oversight. The smallest normally-allocated IPv6 subnet is a /64 — eighteen quintillion addresses — so "sweep this range" is not a thing that finishes. IPv6 host discovery is a genuinely different problem and belongs in its own feature rather than behind a parser that would accept a target it can never complete.

Connect scans, not SYN scans. A half-open SYN scan is faster and quieter, but it needs CAP_NET_RAW. Given the choice between "quieter" and "no privileges", this project picks no privileges every time.

Platform coverage. All three platforms build and run the full test suite in CI, on self-hosted runners:

Platform Built with Tests Ships
Linux (Ubuntu 26.04) Qt 6.11 all 5 suites .deb
Linux (Fedora 43 container) Qt 6 all 5 suites .rpm
Linux (Ubuntu 24.04 container) Qt 6.4 all 5 suites .AppImage
Windows 11 (MSVC 2022) Qt 6.8.2 all 5 suites .exe installer, portable .zip
macOS 27 (arm64) Homebrew Qt 6.11 all 5 suites .dmg (unsigned)

Each packaging job also verifies the artifact it produced actually runs standalone — the Windows tree with the Qt prefix stripped from PATH, the macOS bundle checked for any remaining link against Homebrew Qt, and the AppImage by extracting and running it. Those are the only checks that exercise the shipped binary rather than the build-tree one.

Licence

GPL-3.0-or-later. See LICENSE, and licenses/THIRD-PARTY-NOTICES.md for Qt and the bundled IEEE OUI registry.