From b434e691600673396754ed6d27b829ef8241fbe4 Mon Sep 17 00:00:00 2001 From: Frans van Dorsselaer <17404029+dorssel@users.noreply.github.com> Date: Sat, 20 Jul 2024 01:44:19 +0200 Subject: [PATCH] Improve firewall check --- Usbipd/Wsl.cs | 83 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/Usbipd/Wsl.cs b/Usbipd/Wsl.cs index 27f52ca..9729031 100644 --- a/Usbipd/Wsl.cs +++ b/Usbipd/Wsl.cs @@ -178,6 +178,13 @@ async Task CaptureBinary(Stream stream) return new(process.ExitCode, stdout, stderr, memoryStream); } + enum FirewallCheckResult + { + Unknown, + Pass, + Fail, + } + /// /// BusId has already been checked, and the server is running. /// @@ -491,49 +498,67 @@ public static async Task 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//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; } }