I am trying to create a timeout for a socket in powershell and I have encountered a problem that I do not know how to solve even after many hours of Google searches.
$tcp = New-Object System.Net.Sockets.TcpClient
$tcp.Connect('127.0.0.0',"port1")
$tcp.Connect('127.0.0.0',"port2")
$tcp.Connect('127.0.0.0',"port3")
When the server side listens to the first port in the series, the client side usually receives the request after one or two seconds. But if the server side listens to the third port in the series - it will take the client 20 seconds of attempt to connect the first port, then another 20 seconds of attempt to connect to the second port, then when it tries to connect to the third port the server listens to - it connects directly.
As I noted at the beginning of the post, I searched for a long time for an answer to this on Google, I tried many attempts myself as well. I would greatly appreciate your help.
How can I change the wait time for each TCP connection attempt?
CodePudding user response:
Here is an example of how you can implement the .ConnectAsync(..)
Method from the TcpClient
Class, which would allow to test async multiple TCP ports and with a specific TimeOut defined as argument.
If you're wondering why $tasks[0..62].TCP.Task.AsyncWaitHandle
is required in this function, you can refer to Remarks from WaitHandle.WaitAny
Method:
The maximum number of the wait handles is 64, and 63 if the current thread is in STA state.
Note, this function requires .NET Framework 4.5 if running Windows PowerShell / .NET Core 1.0 if running PowerShell Core.
using namespace System.Threading
using namespace System.Collections.Generic
using namespace System.Diagnostics
using namespace System.Net.Sockets
function Test-TCPConnection {
[cmdletbinding()]
param(
[parameter(Mandatory, Valuefrompipeline)]
[string] $Name,
[parameter(Mandatory, Position = 1)]
[ValidateRange(1, 65535)]
[int[]] $Port,
# 1 second minimum, reasonable for TCP connection
[ValidateRange(1000, [int]::MaxValue)]
[int] $TimeOut = 1200
)
begin {
$timer = [Stopwatch]::StartNew()
$tasks = [List[Object]]::new()
}
process {
foreach($i in $Port) {
$tcp = [TCPClient]::new()
$task = [ordered]@{
Source = $env:COMPUTERNAME
Destionation = $Name
Port = $i
TCP = [ordered]@{
Instance = $tcp
Task = $tcp.ConnectAsync($Name, $i)
}
}
$tasks.Add($task)
}
}
end {
do {
$id = [WaitHandle]::WaitAny($tasks[0..62].TCP.Task.AsyncWaitHandle, 200)
if($id -eq [WaitHandle]::WaitTimeout) {
continue
}
$thisTask = $tasks[$id]
$instance, $task = $thisTask.TCP[0, 1]
$thisTask['Success'] = $task.Status -eq 'RanToCompletion'
$instance.foreach('Dispose') # Avoid any throws here
$thisTask.Remove('TCP')
[pscustomobject] $thisTask
$tasks.RemoveAt($id)
} while($tasks -and $timer.ElapsedMilliseconds -le $timeout)
# clear the remaining tasks, if any
foreach($task in $tasks) {
$task.TCP.Instance.foreach('Dispose')
$task.Remove('TCP')
$task['Success'] = $false
[pscustomobject] $task
}
}
}
[int[]] $PortsToScan = 80, 443, 125, 8080
'google.com', 'cisco.com', 'amazon.com' | Test-TCPConnection $PortsToScan
If you want to implement a parallel scan for multiple hosts, you can find an example using Runspaces on my GitHub.