feat: add maui port check command#200
Conversation
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
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
…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>
…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>
There was a problem hiding this comment.
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 checkcommand 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 vialsof/ss/netstatwithIPGlobalPropertiesfallback. - 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. |
Expert Code Review — PR #200Methodology: 3 independent reviewers with adversarial consensus 3 findings posted as inline comments (1 critical, 2 minor):
Finding #1 is the most actionable: after exhausting retry attempts in Discarded findings (single-reviewer only, did not reach consensus)
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).
|
There was a problem hiding this comment.
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
|
@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 ( 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:
If you need me to access, download, or install something from one of these locations, you can either:
|
…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>
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>
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
Changes
Closes #197