Skip to content

Commit

Permalink
Merge pull request #998 from dorssel/firewall_check
Browse files Browse the repository at this point in the history
Improve firewall check
  • Loading branch information
dorssel committed Jul 27, 2024
2 parents 35a7c3b + b434e69 commit 8a33af1
Showing 1 changed file with 54 additions and 29 deletions.
83 changes: 54 additions & 29 deletions Usbipd/Wsl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ async Task CaptureBinary(Stream stream)
return new(process.ExitCode, stdout, stderr, memoryStream);
}

enum FirewallCheckResult
{
Unknown,
Pass,
Fail,
}

/// <summary>
/// BusId has already been checked, and the server is running.
/// </summary>
Expand Down Expand Up @@ -491,49 +498,67 @@ public static async Task<ExitCode> Attach(BusId busId, bool autoAttach, string?
console.ReportInfo($"Using IP address {hostAddress} to reach the host.");

// Heuristic firewall check
//
// The current timeout is two seconds.
// This used to be one second, but some users got false positives due to WSL being slow to start the command.
//
// With minimal requirements (bash only) try to connect from WSL to our server.
// If the process does not terminate within the timeout, then most likely a third party firewall is blocking connections (DENY).
// If the process terminates within the timeout, then there are several options:
// - The connection worked (pass).
// - A firewall is refusing connections (DROP).
// This is detectable, as the error will be something like:
// bash: connect: Connection refused
// bash: line 1: /dev/tcp/<host-address>/3240: Connection refused
// - bash is not available (silent pass)
// - the bash version does not support the /dev/tcp syntax (silent pass)
// - other reasons (silent pass)
// We will simply look for the word "refused". If it isn't there, then the test will be ignored (silent pass).
//
{
// The current timeout is two seconds.
// This used to be one second, but some users got false results due to WSL being slow to start the command.
using var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(2));
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
var pass = true; // NOTE: The default is (silent) pass, just in case the test doesn't work.
FirewallCheckResult result;
try
{
// With minimal requirements (bash only) try to connect from WSL to our server.
var pingResult = await RunWslAsync((distribution, "/"), null, false, linkedTokenSource.Token, "bash", "-c", $"echo < /dev/tcp/{hostAddress}/{Interop.UsbIp.USBIP_PORT}");
if (pingResult.StandardError.Contains("refused"))
{
pass = false;
// If the output contains "refused", then the test was executed and failed, irrespective of the exit code.
result = FirewallCheckResult.Fail;
}
else if (pingResult.ExitCode == 0)
{
// The test was executed, and returned within the timeout, and the connection was not actively refused (see above).
result = FirewallCheckResult.Pass;
}
else
{
// The test was not executed properly (bash unavailable, /dev/tcp not supported, etc.).
result = FirewallCheckResult.Unknown;
}
}
catch (OperationCanceledException) when (timeoutTokenSource.IsCancellationRequested)
{
// Timeout, probably a firewall dropping the connection request.
pass = false;
// Timeout, probably a firewall dropping the connection request (i.e., not actively refused (DENY), but DROP).
result = FirewallCheckResult.Fail;
}
if (!pass)
switch (result)
{
if (GetPossibleBlockReason() is string blockReason)
{
// We found a possible reason.
console.ReportWarning(blockReason);
}
// In any case, it isn't working...
console.ReportWarning($"A firewall may be blocking the connection; ensure TCP port {Interop.UsbIp.USBIP_PORT} is allowed.");
case FirewallCheckResult.Unknown:
default:
{
console.ReportInfo($"Firewall check not possible with this distribution (no bash, or wrong version of bash).");
// Try to detect any (domain) policy blockers.
if (GetPossibleBlockReason() is string blockReason)
{
// We found a possible blocker.
console.ReportWarning(blockReason);
}
}
break;

case FirewallCheckResult.Fail:
{
if (GetPossibleBlockReason() is string blockReason)
{
// We found a possible reason.
console.ReportWarning(blockReason);
}
// In any case, it isn't working...
console.ReportWarning($"A firewall appears to be blocking the connection; ensure TCP port {Interop.UsbIp.USBIP_PORT} is allowed.");
}
break;

case FirewallCheckResult.Pass:
// All is well.
break;
}
}

Expand Down

0 comments on commit 8a33af1

Please sign in to comment.