Winix.NetCat
0.4.0
Prefix Reserved
dotnet tool install --global Winix.NetCat --version 0.4.0
dotnet new tool-manifest
dotnet tool install --local Winix.NetCat --version 0.4.0
#tool dotnet:?package=Winix.NetCat&version=0.4.0
nuke :add-package Winix.NetCat --version 0.4.0
nc
Cross-platform netcat replacement — TCP/UDP send/receive, port checks, TLS clients.
Built for the developer/sysadmin on Windows who's tired of typing Test-NetConnection. Familiar nc muscle memory works (-z, -l, -u, -w, -4, -6), but long-form flags are first-class.
Install
Scoop (Windows)
scoop bucket add winix https://github.com/Yortw/winix
scoop install winix/nc
Winget (Windows, stable releases)
winget install Winix.NetCat
.NET Tool (cross-platform)
dotnet tool install -g Winix.NetCat
Direct Download
Download native binaries from GitHub Releases.
Usage
nc [options] HOST PORT # connect (default)
nc --listen [options] PORT # listen for one connection
nc --check [options] HOST PORT # check port(s)
Examples
# Quick port check
nc -z target.com 443
# Multiple ports
nc -z target.com 80,443,5432
# Range check with JSON
nc -z target.com 1-1024 --json
# TCP send/receive
echo "GET / HTTP/1.0" | nc target.com 80
# TLS client
nc --tls api.example.com 443
# UDP send
nc -u dnsserver 53 < query.bin
# Listen for one connection
nc -l 8080
# File transfer
nc target.com 80 < request.bin > response.bin
Options
| Long | Short | Description |
|---|---|---|
--listen |
-l |
Listen for one inbound connection |
--check |
-z |
Check whether port(s) are open |
--udp |
-u |
Use UDP (default is TCP) |
--tls |
(also --ssl) |
Wrap TCP connection in TLS (client only) |
--insecure |
Skip TLS certificate validation | |
--ipv4 |
-4 |
Force IPv4 |
--ipv6 |
-6 |
Force IPv6 |
--timeout SEC |
-w SEC |
Connect/idle timeout |
--bind ADDR |
Listener bind interface | |
--no-shutdown |
Don't half-close on stdin EOF | |
--verbose |
-v |
Show closed/timeout ports too in check mode |
--json |
Emit JSON summary to stderr | |
--describe |
Agent metadata as JSON | |
--color / --no-color |
Colour control | |
--help / --version |
Standard |
Port Range Syntax
The --check mode accepts single ports, ranges, lists, or mixed specifiers:
nc -z host 80 # single
nc -z host 80-1000 # range (inclusive)
nc -z host 80,443,8080 # list
nc -z host 80-100,443,8080-8090 # mixed
Stdout prints one line per open port (suitable for piping). Closed/timeout/error ports go to stderr only under --verbose.
Half-close on stdin EOF
When stdin reaches EOF (e.g. echo request | nc host 80 or nc host 80 < file.bin), nc calls Socket.Shutdown(Send) so the peer sees end-of-stream on its read side. Without this, request/response protocols like HTTP hang because the server waits for more request bytes before sending a response.
Use --no-shutdown only for the rare line-oriented protocols that treat half-close as a full connection close.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Connection refused / unreachable / closed port / TLS failure |
| 2 | Timeout |
| 125 | Usage error |
| 126 | Permission denied (privileged port) |
| 130 | Interrupted (Ctrl-C) |
Colour
- Status messages on stderr use colour: green = open, red = closed/error, yellow = timeout/warning.
- Bytes relayed on stdout are never coloured.
- Respects
NO_COLORand--color/--no-color.
Part of Winix
nc is part of the Winix CLI toolkit.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
## [0.3.0] - 2026-04-24
### Added
- `--ipv4` / `--ipv6` flags now actually honoured in TCP Connect and
`--check` modes. Previously the flags were advertised in `--help` /
`--describe` but the resolver ran dual-stack regardless.
- `--check --json` now suppresses per-port text on stdout so downstream
parsers get a single-stream JSON envelope.
- Check-mode non-verbose stderr summary now covers both all-error AND
all-timeout cases — `nc -z blackhole 80,443` no longer exits 2 with
empty stdout and empty stderr.
- `nc --check` emits a JSON summary where `exit_reason` distinguishes
`all_failed` from `some_failed` when probes return a mix of errors and
successes.
- `nc --listen -w N` now writes a stderr note when accept times out, so
the user sees WHY the tool exited with 2 rather than a silent exit.
- `nc -u host port -w N` now writes a stderr warning when no UDP response
arrives within the timeout (BSD nc precedent is exit 0; the warning
makes the empty-output case intentional rather than ambiguous).
- JSON error envelope emitted from the safety-net catches when `--json`
is set. Automation no longer gets a bare stderr crash line on
unexpected exceptions.
- New JSON field `error` advertised in `--describe` (present only on the
catch-all envelope).
- New `exit_reason` enum values: `pump_failed` (emitted when the bidirectional
relay hits an unexpected exception class — ObjectDisposedException or
InvalidOperationException from a racy half-close — instead of escaping to
the safety-net), `accept_failed` (listener-side parity for non-SocketException
accept failures), `unexpected_error` (emitted on the Main safety-net's JSON
envelope when `--json` is set and an exception escaped all per-site catches),
and `connect_failed` (TCP/UDP client-side parity for non-SocketException
connect failures).
### Changed
- TLS handshake timeout is now scoped to the handshake itself rather than
sharing the TCP connect deadline. A hanging TLS server with `-w N` set
now times out at N seconds instead of blocking indefinitely.
- TLS close uses `SslStream.ShutdownAsync` (`close_notify`) before the
TCP write-half shutdown, so strict peers don't log truncation alerts.
- `--bind` now rejects unparseable IPs at parse time (was silently
falling through to `IPAddress.Any`, defeating the security intent).
- `--bind` now rejects AF mismatch with `--ipv4` / `--ipv6` — e.g.
`nc -l --ipv6 --bind 127.0.0.1 8080` is a usage error rather than
silently ignoring `--ipv6`.
- `--bind` rejects hostnames — IP literals only (matches BSD `nc -s`).
- Check-mode port-spec parse failures (`nc -z host invalid`) now return
a clean usage error instead of a stack trace.
### Fixed
- Non-SocketException / non-OperationCanceledException exception types
from ConnectAsync / UdpClient.Connect / stdin ReadAsync / stdout
WriteAsync no longer escape to the safety-net as "unexpected error"
exit 126. Each path now classifies its failures to a real exit code
(1 socket_error / 1 io_error / 0 stdout_closed / 1 connect_failed)
and returns a proper JSON envelope.
- Relay pump now observes both send and receive task outcomes via a
linked CTS + try/finally. A send-side exception no longer leaves
the receive task unobserved, and both sides' exceptions are surfaced
via `ExceptionDispatchInfo` with preserved stack traces.
- Downstream pipe closure during receive (e.g. `nc host 80 | head -c 10`)
now exits 0 with `exit_reason=stdout_closed`, matching BSD nc's
SIGPIPE behaviour. Previously mis-classified as `socket_error`.
- `nc -z bad-hostname` no longer aborts the whole scan via Task.WhenAll.
Per-port errors are now bucketed as Error so the remaining probes run.
- Ctrl-C during UDP Connect send now returns RunResult(130) with partial
byte counts + duration, so `--json` consumers see a complete envelope.
- NetCatListener UDP path now has user-cancel (Ctrl-C) parity with the
TCP path — Ctrl-C during `nc -l -u 53` no longer bypasses the JSON
envelope.
- `RelayPump.ShouldShutdownSend` now reflects actual state — clears
when the half-close callback fails instead of staying true.
- `onSendComplete` callbacks in NetCatClient and NetCatListener catch
broadly so a cosmetic half-close failure (racy
InvalidOperationException, SslStream IOException) cannot mask a
successful pump outcome.
- TCP accept `SocketException` (Linux interface flap, fd limit) now
mapped to exit 1 rather than escaping as "unexpected error".
- User-cancel OCE during TLS handshake rethrown so Main's 130 arm fires
instead of mis-labelling as `tls_failed` / exit 1.
- `--version` output no longer carries the `+gitsha` SourceLink suffix.
Users see plain `nc 0.3.0`, matching the suite-wide convention.
See full changelog at https://github.com/Yortw/winix/blob/main/src/nc/CHANGELOG.md