- C++ 91.6%
- CMake 4.3%
- C 2.1%
- NSIS 1.3%
- Python 0.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
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
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 |
||
| .forgejo/workflows | ||
| docs/screenshots | ||
| licenses | ||
| resources | ||
| src | ||
| tools | ||
| .gitattributes | ||
| .gitignore | ||
| ARCHITECTURE.md | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| CMakeLists.txt | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| README.md | ||
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. Nosudo, nosetcap, 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 isFiltered. A host that produced nothing at all isDead. 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, not0. - 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
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.
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.
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.


