Search code examples
powershelltcptcpclient

powershell TCPclient timeout per connection try


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?


Solution

  • 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.

    Note, this function requires .NET Framework 4.5+ if running Windows PowerShell / .NET Core 1.0+ if running PowerShell Core.

    using namespace System.Diagnostics
    using namespace System.Collections.Generic
    using namespace System.Net.Sockets
    using namespace System.Threading.Tasks
    
    function Test-TCPConnectionAsync {
        [cmdletbinding()]
        param(
            [parameter(Mandatory, Valuefrompipeline, ValueFromPipelineByPropertyName)]
            [alias('ComputerName', 'HostName', 'Host')]
            [string[]] $Target,
    
            [parameter(Mandatory, ValueFromPipelineByPropertyName)]
            [ValidateRange(1, 65535)]
            [int[]] $Port,
    
            [parameter()]
            [ValidateRange(5, [int]::MaxValue)]
            [int] $TimeOut = 5, # In seconds!
    
            [parameter()]
            [switch] $IPv6
        )
    
        begin {
            $timer   = [Stopwatch]::StartNew()
            $queue   = [List[hashtable]]::new()
            $TimeOut = [timespan]::FromSeconds($TimeOut).TotalMilliseconds
            if($IPv6.IsPresent) {
                $newTcp = { [TCPClient]::new([AddressFamily]::InterNetworkV6) }
                return
    
            }
            $newTcp = { [TCPClient]::new() }
        }
        process {
            foreach($item in $Target) {
                foreach($i in $Port) {
                    $tcp = & $newTcp
                    $queue.Add(@{
                        Instance = $tcp
                        Task     = $tcp.ConnectAsync($item, $i)
                        Output   = [ordered]@{
                            Source       = $env:COMPUTERNAME
                            Destination  = $item
                            Port         = $i
                        }
                    })
                }
            }
        }
        end {
            while($queue -and $timer.ElapsedMilliseconds -le $timeout) {
                try {
                    $id = [Task]::WaitAny($queue.Task, 200)
                    if($id -eq -1) {
                        continue
                    }
                    $instance, $task, $output = $queue[$id]['Instance', 'Task', 'Output']
                    if($instance) {
                        $instance.Dispose()
                    }
                    $output['Success'] = $task.Status -eq [TaskStatus]::RanToCompletion
                    $queue.RemoveAt($id)
                    [pscustomobject] $output
                }
                catch {
                    $PSCmdlet.WriteError($_)
                }
            }
    
            foreach($item in $queue) {
                try {
                    $instance, $task, $output = $item['Instance', 'Task', 'Output']
                    $output['Success'] = $task.Status -eq [TaskStatus]::RanToCompletion
                    if($instance) {
                        $instance.Dispose()
                    }
                    [pscustomobject] $output
                }
                catch {
                    $PSCmdlet.WriteError($_)
                }
            }
        }
    }
    

    If you want to implement a parallel scan for multiple hosts, you can find an example using Runspaces on my GitHub.


    Examples

    'google.com', 'stackoverflow.com' | Test-TCPConnectionAsync 80, 443, 8080, 389, 636
    
    @'
    Target,Port
    google.com,80
    google.com,443
    google.com,8080
    google.com,389
    google.com,636
    cisco.com,80
    cisco.com,443
    cisco.com,8080
    cisco.com,389
    cisco.com,636
    amazon.com,80
    amazon.com,443
    amazon.com,8080
    amazon.com,389
    amazon.com,636
    '@ | ConvertFrom-Csv | Test-TCPConnectionAsync