Skip to content

feat: add maui port check command#200

Open
rmarinho wants to merge 9 commits into
mainfrom
cli/port-check
Open

feat: add maui port check command#200
rmarinho wants to merge 9 commits into
mainfrom
cli/port-check

Conversation

@rmarinho
Copy link
Copy Markdown
Member

Implements \maui port check \ from issue #197.

Summary

Cross-platform TCP port diagnostic tool that identifies which process holds a given port.

\
$ maui port check 8080
Port 8080 is in use:
PID 12345 (dotnet) 0.0.0.0 [ipv4]

$ maui port check 8080 --json
{ "port": 8080, "in_use": true, "listeners": [...] }
\\

Exit codes

  • 0 — port is free
  • 1 — port is in use
  • 2 — error

Changes

  • \Providers/Port/IPortInspector.cs\ — abstraction
  • \Providers/Port/WindowsPortInspector.cs\ — P/Invoke \GetExtendedTcpTable\ (IPv4 + IPv6)
  • \Providers/Port/UnixPortInspector.cs\ — lsof → ss → netstat → \IPGlobalProperties\
  • \Models/PortCheckResult.cs\ — JSON output model
  • \Commands/PortCommands.cs\ — CLI command
  • \Tests/PortCheckTests.cs\ — unit tests
  • \ErrorCodes.cs\ — E1008 PortEnumerationFailed
  • \MauiCliJsonContext.cs\ — AOT type registration
  • \Program.cs\ — command registration

Closes #197

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Code Review: 7 findings posted inline (2 critical, 4 moderate, 1 minor). See the summary comment for methodology and details.

Generated by Expert Code Review (auto) for issue #200 · ● 11.9M

Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/WindowsPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/UnixPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli.UnitTests/PortCheckTests.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/UnixPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/WindowsPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/WindowsPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/UnixPortInspector.cs Outdated
rmarinho added a commit that referenced this pull request Apr 30, 2026
…IPv6, leaks)

Resolves the round-2 review findings on PR #200:

CRITICAL fixes
- TCP_TABLE_OWNER_PID_LISTENER must be 3 (Win32 MIB_TCP_TABLE_CLASS).
  Value 6 is OWNER_MODULE_LISTENER which returns ~160-byte rows; the code
  iterated with 24-byte MibTcpRowOwnerPid stride and produced corrupted
  PIDs/ports/addresses on Windows. Now returns valid data.
- RunCommand on Unix now drains stderr asynchronously (BeginErrorReadLine)
  so a child writing >64KB to stderr (e.g. lsof permission-denied messages
  on a busy host) cannot deadlock. Output is also drained async; on
  WaitForExit timeout the child tree is killed.

MODERATE fixes
- IPv6 wildcard '*' from lsof is now mapped to '::' instead of '0.0.0.0'
  when the TYPE column reports IPv6, so address+family are consistent.
- Same fix for ss output: '[::]:port' and '*' under bracketed IPv6 hint
  now report '::'.
- WindowsPortInspector.GetProcessName now disposes the Process handle
  (using var) instead of leaking SafeProcessHandle.
- GetListenersForFamily retries on ERROR_INSUFFICIENT_BUFFER (122) up to
  4 times, doubling buffer size, instead of silently reporting an empty
  list when the table grows between sizing and data calls.

Tests
- Updated ParseLsofOutput_IPv6_ReturnsIpv6Family to also assert
  Address == '::' (locks the fix).
- Added ParseSsOutput_IPv6Wildcard_ReturnsIpv6Address.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rmarinho added a commit that referenced this pull request May 4, 2026
…IPv6, leaks)

Resolves the round-2 review findings on PR #200:

CRITICAL fixes
- TCP_TABLE_OWNER_PID_LISTENER must be 3 (Win32 MIB_TCP_TABLE_CLASS).
  Value 6 is OWNER_MODULE_LISTENER which returns ~160-byte rows; the code
  iterated with 24-byte MibTcpRowOwnerPid stride and produced corrupted
  PIDs/ports/addresses on Windows. Now returns valid data.
- RunCommand on Unix now drains stderr asynchronously (BeginErrorReadLine)
  so a child writing >64KB to stderr (e.g. lsof permission-denied messages
  on a busy host) cannot deadlock. Output is also drained async; on
  WaitForExit timeout the child tree is killed.

MODERATE fixes
- IPv6 wildcard '*' from lsof is now mapped to '::' instead of '0.0.0.0'
  when the TYPE column reports IPv6, so address+family are consistent.
- Same fix for ss output: '[::]:port' and '*' under bracketed IPv6 hint
  now report '::'.
- WindowsPortInspector.GetProcessName now disposes the Process handle
  (using var) instead of leaking SafeProcessHandle.
- GetListenersForFamily retries on ERROR_INSUFFICIENT_BUFFER (122) up to
  4 times, doubling buffer size, instead of silently reporting an empty
  list when the table grows between sizing and data calls.

Tests
- Updated ParseLsofOutput_IPv6_ReturnsIpv6Family to also assert
  Address == '::' (locks the fix).
- Added ParseSsOutput_IPv6Wildcard_ReturnsIpv6Address.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rmarinho rmarinho marked this pull request as ready for review May 4, 2026 19:34
Copilot AI review requested due to automatic review settings May 4, 2026 19:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new cross-platform maui port check <port> diagnostic command to report whether a TCP port is currently in use and (when possible) which process is listening, with both human-readable and --json output.

Changes:

  • Added maui port check command with exit codes (0 free / 1 in use / 2 error) and JSON output model types.
  • Implemented port inspection providers: Windows via GetExtendedTcpTable (IPv4/IPv6) and Unix via lsof/ss/netstat with IPGlobalProperties fallback.
  • Added unit tests for JSON shape and Unix parsing helpers, and registered new JSON source-gen types + error code E1008.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Cli/Microsoft.Maui.Cli/Providers/Port/WindowsPortInspector.cs Windows TCP listener enumeration via GetExtendedTcpTable (IPv4/IPv6).
src/Cli/Microsoft.Maui.Cli/Providers/Port/UnixPortInspector.cs Unix TCP listener enumeration using CLI tools with managed fallback and output parsers.
src/Cli/Microsoft.Maui.Cli/Providers/Port/PortListenerInfo.cs Internal listener DTO used by inspectors.
src/Cli/Microsoft.Maui.Cli/Providers/Port/PortInspectorHelpers.cs Shared helper for port decoding from Windows network-order DWORD.
src/Cli/Microsoft.Maui.Cli/Providers/Port/IPortInspector.cs Inspector abstraction for platform implementations.
src/Cli/Microsoft.Maui.Cli/Commands/PortCommands.cs CLI command implementation for maui port check.
src/Cli/Microsoft.Maui.Cli/Models/PortCheckResult.cs Public JSON output models for port check results.
src/Cli/Microsoft.Maui.Cli/Output/MauiCliJsonContext.cs Added source-gen registrations for new result types.
src/Cli/Microsoft.Maui.Cli/Errors/ErrorCodes.cs Added E1008 (PortEnumerationFailed).
src/Cli/Microsoft.Maui.Cli/Program.cs Registered the new port command group at the root.
src/Cli/Microsoft.Maui.Cli.UnitTests/PortCheckTests.cs Unit tests for JSON shape and Unix output parsers + port decoding helper.

Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/UnixPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Commands/PortCommands.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/WindowsPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/WindowsPortInspector.cs Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

Expert Code Review — PR #200

Methodology: 3 independent reviewers with adversarial consensus

3 findings posted as inline comments (1 critical, 2 minor):

# Severity Consensus File Line(s)
1 🔴 Critical 3/3 WindowsPortInspector.cs 76–86
2 🟢 Minor 2/3 WindowsPortInspector.cs 79
3 🟢 Minor 2/3 (after follow-up) UnixPortInspector.cs 172–174

Finding #1 is the most actionable: after exhausting retry attempts in GetListenersForFamily, the buffer is read without confirming GetExtendedTcpTable succeeded — risking a crash or corrupted results on busy Windows systems.

Discarded findings (single-reviewer only, did not reach consensus)
  • ParseSsOutput false-positive column matching (1/3 — both follow-ups disagreed; ss -tlnpH scopes to listeners, peer column is always *)
  • ParseLsofOutput fused (LISTEN) token (1/3 — both follow-ups disagreed; lsof -nP always separates state with whitespace)
  • PATH-based command hijack in RunCommand (1/3 — no follow-up, below cap)
  • dwState not checked on Windows TCP rows (1/3 — no follow-up, below cap)
  • TryLsof returns null on free ports causing unnecessary fallback (1/3 — no follow-up, below cap)
  • Exit code 1 not documented in --help text (1/3 — no follow-up, below cap)
  • GetPortFromNetworkDword upper 16 bits not asserted (1/3 — no follow-up, below cap)

CI status: ✅ Build passing on macOS and Windows. CLA check passed.

Test coverage: PR includes 170 lines of unit tests covering JSON serialization, all 3 Unix parser methods (lsof, ss, netstat), port filtering, IPv4/IPv6 handling, and the network byte-order conversion helper. Good coverage of the parsing logic. No integration tests for the Windows P/Invoke path (expected — requires real Windows).

Generated by Expert Code Review · 3 independent reviewers with adversarial consensus

Generated by Expert Code Review (auto) for issue #200 · ● 9.9M ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Code Review: 3 findings posted inline (1 critical, 2 minor). See the lean summary comment for methodology and details.

Generated by Expert Code Review (auto) for issue #200 · ● 9.9M

Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/WindowsPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/WindowsPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/UnixPortInspector.cs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 9 comments.

Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/WindowsPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Commands/PortCommands.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/UnixPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Commands/PortCommands.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/UnixPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/WindowsPortInspector.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Providers/Port/PortInspectorHelpers.cs
Comment thread src/Cli/Microsoft.Maui.Cli.UnitTests/PortCheckTests.cs Outdated
Comment thread src/Cli/Microsoft.Maui.Cli/Models/PortCheckResult.cs
@jfversluis
Copy link
Copy Markdown
Member

@copilot formatting of this code looks wrong, everything is stuck to the left. Please make it inline with the rest of the codebase.

Copy link
Copy Markdown
Contributor

Copilot AI commented May 5, 2026

@copilot formatting of this code looks wrong, everything is stuck to the left. Please make it inline with the rest of the codebase.

Fixed in 907175a — all six new files (PortCommands.cs, UnixPortInspector.cs, WindowsPortInspector.cs, PortCheckResult.cs, PortCheckTests.cs, IPortInspector.cs) have been reformatted with tab indentation to match the rest of the codebase.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • c50vsblobprodcus330.vsblob.vsassets.io
    • Triggering command: /usr/bin/dotnet dotnet test src/Cli/Microsoft.Maui.Cli.UnitTests/ --filter PortCheck -v minimal (dns block)
    • Triggering command: /usr/bin/dotnet dotnet build src/Cli/Cli.slnf --no-restore (dns block)
  • pc2vsblobprodcus360.vsblob.vsassets.io
    • Triggering command: /usr/bin/dotnet dotnet test src/Cli/Microsoft.Maui.Cli.UnitTests/ --filter PortCheck -v minimal (dns block)
    • Triggering command: /usr/bin/dotnet dotnet build src/Cli/Cli.slnf --no-restore (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from jfversluis May 5, 2026 18:37
rmarinho added a commit that referenced this pull request May 6, 2026
…IPv6, leaks)

Resolves the round-2 review findings on PR #200:

CRITICAL fixes
- TCP_TABLE_OWNER_PID_LISTENER must be 3 (Win32 MIB_TCP_TABLE_CLASS).
  Value 6 is OWNER_MODULE_LISTENER which returns ~160-byte rows; the code
  iterated with 24-byte MibTcpRowOwnerPid stride and produced corrupted
  PIDs/ports/addresses on Windows. Now returns valid data.
- RunCommand on Unix now drains stderr asynchronously (BeginErrorReadLine)
  so a child writing >64KB to stderr (e.g. lsof permission-denied messages
  on a busy host) cannot deadlock. Output is also drained async; on
  WaitForExit timeout the child tree is killed.

MODERATE fixes
- IPv6 wildcard '*' from lsof is now mapped to '::' instead of '0.0.0.0'
  when the TYPE column reports IPv6, so address+family are consistent.
- Same fix for ss output: '[::]:port' and '*' under bracketed IPv6 hint
  now report '::'.
- WindowsPortInspector.GetProcessName now disposes the Process handle
  (using var) instead of leaking SafeProcessHandle.
- GetListenersForFamily retries on ERROR_INSUFFICIENT_BUFFER (122) up to
  4 times, doubling buffer size, instead of silently reporting an empty
  list when the table grows between sizing and data calls.

Tests
- Updated ParseLsofOutput_IPv6_ReturnsIpv6Family to also assert
  Address == '::' (locks the fix).
- Added ParseSsOutput_IPv6Wildcard_ReturnsIpv6Address.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rmarinho and others added 6 commits May 7, 2026 02:04
Implements cross-platform TCP port diagnostic tool that reports which
process holds a given port. Closes #197.

## Changes

### New files
- Providers/Port/IPortInspector.cs - abstraction
- Providers/Port/PortListenerInfo.cs - internal data model
- Providers/Port/WindowsPortInspector.cs - P/Invoke GetExtendedTcpTable
- Providers/Port/UnixPortInspector.cs - lsof/ss/netstat fallback chain
- Models/PortCheckResult.cs - JSON output models (snake_case)
- Commands/PortCommands.cs - maui port check <port> command
- Tests/PortCheckTests.cs - unit tests

### Modified files
- ErrorCodes.cs - add E1008 PortEnumerationFailed
- MauiCliJsonContext.cs - register PortCheckResult, PortListenerResult
- Program.cs - wire in PortCommands.Create()

## Exit codes
- 0: port is free
- 1: port is in use
- 2: error (invalid arg, enumeration failure)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Register ErrorCodes.E1008, MauiCliJsonContext types, and PortCommands.Create()
into the CLI host.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PortCheckTests.cs called platform-restricted parser methods directly,
triggering CA1416 errors on macOS CI builds:

- UnixPortInspector class had [SupportedOSPlatform(macos|linux)] but its
  parser methods (ParseLsofOutput/ParseSsOutput/ParseNetstatOutput) are
  pure string parsers with no OS-specific calls. Removed the class-level
  attributes; the actual command invocations (lsof/ss/netstat) work via
  Process.Start which is not platform-restricted.

- WindowsPortInspector keeps its class-level attribute (P/Invoke to
  iphlpapi.dll is genuinely Windows-only). Pure-logic helper
  GetPortFromNetworkDword extracted to a new non-restricted
  PortInspectorHelpers static class so tests can call it on any OS.

- Fixed pre-existing wrong test data for the byte-swap test: 8080 and
  443 inputs needed to be byte-swapped (0x901F, 0xBB01) like 80 was.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…IPv6, leaks)

Resolves the round-2 review findings on PR #200:

CRITICAL fixes
- TCP_TABLE_OWNER_PID_LISTENER must be 3 (Win32 MIB_TCP_TABLE_CLASS).
  Value 6 is OWNER_MODULE_LISTENER which returns ~160-byte rows; the code
  iterated with 24-byte MibTcpRowOwnerPid stride and produced corrupted
  PIDs/ports/addresses on Windows. Now returns valid data.
- RunCommand on Unix now drains stderr asynchronously (BeginErrorReadLine)
  so a child writing >64KB to stderr (e.g. lsof permission-denied messages
  on a busy host) cannot deadlock. Output is also drained async; on
  WaitForExit timeout the child tree is killed.

MODERATE fixes
- IPv6 wildcard '*' from lsof is now mapped to '::' instead of '0.0.0.0'
  when the TYPE column reports IPv6, so address+family are consistent.
- Same fix for ss output: '[::]:port' and '*' under bracketed IPv6 hint
  now report '::'.
- WindowsPortInspector.GetProcessName now disposes the Process handle
  (using var) instead of leaking SafeProcessHandle.
- GetListenersForFamily retries on ERROR_INSUFFICIENT_BUFFER (122) up to
  4 times, doubling buffer size, instead of silently reporting an empty
  list when the table grows between sizing and data calls.

Tests
- Updated ParseLsofOutput_IPv6_ReturnsIpv6Family to also assert
  Address == '::' (locks the fix).
- Added ParseSsOutput_IPv6Wildcard_ReturnsIpv6Address.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d, improve PID display

- Add ret != 0 guard after retry loop to prevent reading invalid buffer data
- Add uint overflow guard on bufLen before AllocHGlobal
- Map netstat tcp6/tcp46 wildcard to :: instead of 0.0.0.0
- Show 'unknown process' instead of 'PID 0 ()' when PID unavailable

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All port check files were using no indentation. Reformatted to use
tab-based indentation consistent with the rest of the CLI codebase.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DevFlow skills: prefer unified maui CLI over standalone tools + improve JSON/error guidance

4 participants