Querying UDP Ports with PowerShell

It was brought to my attention earlier in the week that my Test-Port script had a fairly big bug involving checking the UDP port to determine whether it was open or not. Initially, the function would always come back with a True statement giving a false positive that the port was open, when in fact it was not. In the case of the bug report, the system in question was actually turned off, making it even more of a false positive. The reason behind this is that sending out a message or request via UDP does not guarantee that a response will be given back, unlike TCP where you will know pretty quickly if the port is open or closed. More information here, here and here.

So with that, I set off to find a good way of querying for an open UDP port.  I immediately went to the MSDN page for the System.Net.Sockets.UDPClient to see what methods were available to me. I also took note of the example that was given for making a UDP connection and receiving a message from that port. Since this was written in C#, I knew I could easily translate this into PowerShell (and in fact I left a community contribution on the page).

There are some gotchas  here that I will share with you that I came across while working on this. I can tell you that one of these caused me some pain as it made the executed code hang forever.

The first thing we need to do is create the UDPClient object. Using a constructor, I am opening the local UDP port on my laptop using port 11000:

$udpobject = new-Object system.Net.Sockets.Udpclient(11000)

Now that I have my object created, I can now proceed to set up my message string that will be sent along with actually sending out the data. The trick here with sending out a message via the UDPclient is that it must be in bytes. So while I supply a string message to send, it must be converted into a byte format.  This is done by using the System.Text.ASCIIEncoding class.  For this message, I will just send a date string to the remote server.

$a = new-object system.text.asciiencoding
$byte = $a.GetBytes("$(Get-Date)")

Capture

Ok, now lets create the “connection” to the remote server using the Connect() method. This method requires us to supply a hostname and port to connect on. I say “connection” because even if the port isn’t open, it will still act like a connection was made. You will only receive an error if the host does not exist, or is not on the network.

$udpobject.Connect("dc1",11000)

Now lets send the message to the remote host. Again, just like making the connection, we will not receive any sort of response if the send was successful or not. By doing this, we are forcing some sort of response by the port, either it will receive the data, or we will receive an error stating the port was closed.

[void]$udpobject.Send($byte,$byte.length)

After sending the data, we now need to setup the receiving portion of the code. The Receive() method requires the System.Net.IPEndpoint class to be supplied. That can be created using the following code by declaring that we are looking for data sent to us from any source.

#IPEndPoint object will allow us to read datagrams sent from any source.
$remoteendpoint = New-Object system.net.ipendpoint([system.net.ipaddress]::Any,0)

The 0 stands for any port and [System.Net.IPAddress]::Any means from any host.

Now that I have created an endpoint, lets now set the udp object to wait until a response is given back from the host which we sent the data to. We will also read the response back from the remote host.

 #Blocks until a message returns on this socket from a remote host.
$receivebytes = $udpobject.Receive([ref]$remoteendpoint)

#Convert returned data into string format
[string]$returndata = $a.GetString($receivebytes)

#Uses the IPEndPoint object to show that the host responded.
Write-Host "This is the message you received: $($returndata.ToString())"
Write-Host "This message was sent from: $($remoteendpoint.address.ToString()) on their port number: $($remoteendpoint.Port.ToString())"

Let’s assume that the port is closed on the remote host. Running the first line of the code will produce an error such as the one below.

Capture

Luckily, this error can be caught using Try/Catch so you can easily write to a log or anything else if this happens. Ok, so we know how to tell if a UDP port is closed on a remote machine, but what is going to happen if that port is open?  Let’s find out.  I am going to open a UDP port on my server, DC1 and then we will run the same code to see what happens.

First, lets open up that port on DC1:

Capture

Perfect, now lets re-run the same code and see what happens:

Capture

Cool, no errors and now we are just waiting for a response back from the remote server.

10 minutes later… (crickets chirping)

Capture

Hmmm… nothing at all being returned. You might think that the code is hung or something else is going on.  And you are right about something else going on.  Remember what I said earlier about how UDP works? It will not return anything to use even if you send data to that port. So in effort, this wait will basically go on forever.

Here is a gotcha that I was telling you about.  One way to resolve this is by setting a timeout on the receiving of data. To do this, you need to look at the properties of the $udpobject object we created:

PS C:\Users\boe> $udpobject | Format-List *

Client              : System.Net.Sockets.Socket
Available           : 0
Ttl                 : 64
DontFragment        : False
MulticastLoopback   : True
EnableBroadcast     : False
ExclusiveAddressUse : False

Take note of the client property which consists of the System.Net.Sockets.Socket class. Within this class is where we need to make a slight adjustment to the receiving timeout. Now we will expand out the Client property to find out where we need to make that change.

PS C:\Users\boe> $udpobject.client

Available           : 0
LocalEndPoint       : 0.0.0.0:11000
RemoteEndPoint      :
Handle              : 1364
Blocking            : True
UseOnlyOverlappedIO : False
Connected           : False
AddressFamily       : InterNetwork
SocketType          : Dgram
ProtocolType        : Udp
IsBound             : True
ExclusiveAddressUse : False
ReceiveBufferSize   : 8192
SendBufferSize      : 8192
ReceiveTimeout      : 0
SendTimeout         : 0
LingerState         :
NoDelay             :
Ttl                 : 64
DontFragment        : False
MulticastLoopback   : True
EnableBroadcast     : False

And there it is, the ReceiveTimeout property. Currently, it is set to 0, meaning that this will never timeout.  Keep in mind that the value you supply will be in milliseconds. I will go ahead and set this to 10000 milliseconds and now we will re-run the code again to see what happens.

Capture

Now, after 10 seconds, we get the error stating:

Exception calling “Receive” with “1” argument(s): “A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond”

Setting the timeout now allows the code to error out when the port is open so it does not wait forever for a response back. Plus, this can be caught with a Try/Catch statement so we can parse the error and show that the port is open on the system. One more gotcha is that if the server is powered down, but still in DNS, then the error will still occur instead of automatically reporting an error. So keep that in mind. In fact, performing a ping test (or Test-Connection)  prior to making the port check would be recommended.

Sending and Receiving a Message

Ok, now for something fun.  I am going to go onto DC1 and send a response to my laptop on the same port. So instead of a timeout, you will see the message and the ip and port that the message responded back on.

Lets start it off by opening port 11000 on DC1 and connecting to my laptop.

$udp = new-object System.Net.Sockets.Udpclient(11000)
$udp.Connect("boe-laptop",11000)

Capture

Now we create our UDP object here and wait for our message to be sent from DC1. For this I will not specify a timeout as I need to run some code on DC1. Most of the code will not run until it has received a message from DC1.

$udpobject = new-Object system.Net.Sockets.Udpclient(11000)
$remoteendpoint = New-Object system.net.ipendpoint([system.net.ipaddress]::Any,0)
$receivebytes = $udpobject.Receive([ref]$remoteendpoint)
[string]$returndata = $a.GetString($receivebytes)
Write-Host -fore green "This is the message you received: $($returndata.ToString())"
Write-Host -fore green "This message was sent from: $($remoteendpoint.address.ToString()) on their port number: $($remoteendpoint.Port.ToString())"

Capture

Now the command is waiting for a message from DC1. Lets go back to DC1 and create a message from a string and convert it into bytes. Once converted to bytes, we can then send the message to the waiting laptop.

$a = New-Object system.text.asciiencoding
$byte = $a.GetBytes("This message sent from DC1 to boe-laptop")
$udp.Send($byte,$byte.length)

If you have both windows available to view. You will notice that as soon as you perform the Send() method, the waiting PowerShell console will immediately process the message and display the results.

Capture

Pretty cool, isn’t it? You could go back and forth between these to systems and send messages if you wanted to.

That wraps it up for my post on working with UDP ports in PowerShell. As you can see, it is pretty easy to find out if a UDP port is actually open as long as you have a timeout specified in the UDPClient.Client properties. Once you do that, just some simple parsing of the error can determine if the port was open or closed.

Code

I am going to reference my Test-Port function I wrote a while back that is now updated with the UDP code bug fix.

Script Repository

PoshCode

function Test-Port{  
<#    
.SYNOPSIS    
    Tests port on computer.  
    
.DESCRIPTION  
    Tests port on computer. 
     
.PARAMETER computer  
    Name of server to test the port connection on.
      
.PARAMETER port  
    Port to test 
       
.PARAMETER tcp  
    Use tcp port 
      
.PARAMETER udp  
    Use udp port  
     
.PARAMETER UDPTimeOut 
    Sets a timeout for UDP port query. (In milliseconds, Default is 1000)  
      
.PARAMETER TCPTimeOut 
    Sets a timeout for TCP port query. (In milliseconds, Default is 1000)
                 
.NOTES    
    Name: Test-Port.ps1  
    Author: Boe Prox  
    DateCreated: 18Aug2010   
    List of Ports: http://www.iana.org/assignments/port-numbers  
      
    To Do:  
        Add capability to run background jobs for each host to shorten the time to scan.         
.LINK    
    https://boeprox.wordpress.org 
     
.EXAMPLE    
    Test-Port -computer 'server' -port 80  
    Checks port 80 on server 'server' to see if it is listening  
    
.EXAMPLE    
    'server' | Test-Port -port 80  
    Checks port 80 on server 'server' to see if it is listening 
      
.EXAMPLE    
    Test-Port -computer @("server1","server2") -port 80  
    Checks port 80 on server1 and server2 to see if it is listening  
    
.EXAMPLE
    Test-Port -comp dc1 -port 17 -udp -UDPtimeout 10000
    
    Server   : dc1
    Port     : 17
    TypePort : UDP
    Open     : True
    Notes    : "My spelling is Wobbly.  It's good spelling but it Wobbles, and the letters
            get in the wrong places." A. A. Milne (1882-1958)
    
    Description
    -----------
    Queries port 17 (qotd) on the UDP port and returns whether port is open or not
       
.EXAMPLE    
    @("server1","server2") | Test-Port -port 80  
    Checks port 80 on server1 and server2 to see if it is listening  
      
.EXAMPLE    
    (Get-Content hosts.txt) | Test-Port -port 80  
    Checks port 80 on servers in host file to see if it is listening 
     
.EXAMPLE    
    Test-Port -computer (Get-Content hosts.txt) -port 80  
    Checks port 80 on servers in host file to see if it is listening 
        
.EXAMPLE    
    Test-Port -computer (Get-Content hosts.txt) -port @(1..59)  
    Checks a range of ports from 1-59 on all servers in the hosts.txt file      
            
#>   
[cmdletbinding(  
    DefaultParameterSetName = '',  
    ConfirmImpact = 'low'  
)]  
    Param(  
        [Parameter(  
            Mandatory = $True,  
            Position = 0,  
            ParameterSetName = '',  
            ValueFromPipeline = $True)]  
            [array]$computer,  
        [Parameter(  
            Position = 1,  
            Mandatory = $True,  
            ParameterSetName = '')]  
            [array]$port,  
        [Parameter(  
            Mandatory = $False,  
            ParameterSetName = '')]  
            [int]$TCPtimeout=1000,  
        [Parameter(  
            Mandatory = $False,  
            ParameterSetName = '')]  
            [int]$UDPtimeout=1000,             
        [Parameter(  
            Mandatory = $False,  
            ParameterSetName = '')]  
            [switch]$TCP,  
        [Parameter(  
            Mandatory = $False,  
            ParameterSetName = '')]  
            [switch]$UDP                                    
        )  
    Begin {  
        If (!$tcp -AND !$udp) {$tcp = $True}  
        #Typically you never do this, but in this case I felt it was for the benefit of the function  
        #as any errors will be noted in the output of the report          
        $ErrorActionPreference = "SilentlyContinue"  
        $report = @()  
    }  
    Process {     
        ForEach ($c in $computer) {  
            ForEach ($p in $port) {  
                If ($tcp) {    
                    #Create temporary holder   
                    $temp = "" | Select Server, Port, TypePort, Open, Notes  
                    #Create object for connecting to port on computer  
                    $tcpobject = new-Object system.Net.Sockets.TcpClient  
                    #Connect to remote machine's port                
                    $connect = $tcpobject.BeginConnect($c,$p,$null,$null)  
                    #Configure a timeout before quitting  
                    $wait = $connect.AsyncWaitHandle.WaitOne($TCPtimeout,$false)  
                    #If timeout  
                    If(!$wait) {  
                        #Close connection  
                        $tcpobject.Close()  
                        Write-Verbose "Connection Timeout"  
                        #Build report  
                        $temp.Server = $c  
                        $temp.Port = $p  
                        $temp.TypePort = "TCP"  
                        $temp.Open = "False"  
                        $temp.Notes = "Connection to Port Timed Out"  
                    } Else {  
                        $error.Clear()  
                        $tcpobject.EndConnect($connect) | out-Null  
                        #If error  
                        If($error[0]){  
                            #Begin making error more readable in report  
                            [string]$string = ($error[0].exception).message  
                            $message = (($string.split(":")[1]).replace('"',"")).TrimStart()  
                            $failed = $true  
                        }  
                        #Close connection      
                        $tcpobject.Close()  
                        #If unable to query port to due failure  
                        If($failed){  
                            #Build report  
                            $temp.Server = $c  
                            $temp.Port = $p  
                            $temp.TypePort = "TCP"  
                            $temp.Open = "False"  
                            $temp.Notes = "$message"  
                        } Else{  
                            #Build report  
                            $temp.Server = $c  
                            $temp.Port = $p  
                            $temp.TypePort = "TCP"  
                            $temp.Open = "True"    
                            $temp.Notes = ""  
                        }  
                    }     
                    #Reset failed value  
                    $failed = $Null      
                    #Merge temp array with report              
                    $report += $temp  
                }      
                If ($udp) {  
                    #Create temporary holder   
                    $temp = "" | Select Server, Port, TypePort, Open, Notes                                     
                    #Create object for connecting to port on computer  
                    $udpobject = new-Object system.Net.Sockets.Udpclient
                    #Set a timeout on receiving message 
                    $udpobject.client.ReceiveTimeout = $UDPTimeout 
                    #Connect to remote machine's port                
                    Write-Verbose "Making UDP connection to remote server" 
                    $udpobject.Connect("$c",$p) 
                    #Sends a message to the host to which you have connected. 
                    Write-Verbose "Sending message to remote host" 
                    $a = new-object system.text.asciiencoding 
                    $byte = $a.GetBytes("$(Get-Date)") 
                    [void]$udpobject.Send($byte,$byte.length) 
                    #IPEndPoint object will allow us to read datagrams sent from any source.  
                    Write-Verbose "Creating remote endpoint" 
                    $remoteendpoint = New-Object system.net.ipendpoint([system.net.ipaddress]::Any,0) 
                    Try { 
                        #Blocks until a message returns on this socket from a remote host. 
                        Write-Verbose "Waiting for message return" 
                        $receivebytes = $udpobject.Receive([ref]$remoteendpoint) 
                        [string]$returndata = $a.GetString($receivebytes)
                        If ($returndata) {
                           Write-Verbose "Connection Successful"  
                            #Build report  
                            $temp.Server = $c  
                            $temp.Port = $p  
                            $temp.TypePort = "UDP"  
                            $temp.Open = "True"  
                            $temp.Notes = $returndata   
                            $udpobject.close()   
                        }                       
                    } Catch { 
                        If ($Error[0].ToString() -match "\bRespond after a period of time\b") { 
                            #Close connection  
                            $udpobject.Close()  
                            #Make sure that the host is online and not a false positive that it is open 
                            If (Test-Connection -comp $c -count 1 -quiet) { 
                                Write-Verbose "Connection Open"  
                                #Build report  
                                $temp.Server = $c  
                                $temp.Port = $p  
                                $temp.TypePort = "UDP"  
                                $temp.Open = "True"  
                                $temp.Notes = "" 
                            } Else { 
                                <# 
                                It is possible that the host is not online or that the host is online,  
                                but ICMP is blocked by a firewall and this port is actually open. 
                                #> 
                                Write-Verbose "Host maybe unavailable"  
                                #Build report  
                                $temp.Server = $c  
                                $temp.Port = $p  
                                $temp.TypePort = "UDP"  
                                $temp.Open = "False"  
                                $temp.Notes = "Unable to verify if port is open or if host is unavailable."                                 
                            }                         
                        } ElseIf ($Error[0].ToString() -match "forcibly closed by the remote host" ) { 
                            #Close connection  
                            $udpobject.Close()  
                            Write-Verbose "Connection Timeout"  
                            #Build report  
                            $temp.Server = $c  
                            $temp.Port = $p  
                            $temp.TypePort = "UDP"  
                            $temp.Open = "False"  
                            $temp.Notes = "Connection to Port Timed Out"                         
                        } Else {                      
                            $udpobject.close() 
                        } 
                    }     
                    #Merge temp array with report              
                    $report += $temp  
                }                                  
            }  
        }                  
    }  
    End {  
        #Generate Report  
        $report 
    }
}
This entry was posted in powershell, scripts and tagged , , , . Bookmark the permalink.

6 Responses to Querying UDP Ports with PowerShell

  1. MD says:

    Sorry I don’t quite get it. What’s forcing the remote UDP port to respond? Depending on the service associated with that port, couldn’t the remote server just refuse to respond to whatever UDP packets you send it (ie, it only responds to packets which make sense to the specific application), eventually triggering the timeout, at which point you don’t really know if the port is open or closed?

  2. Carl Galt says:

    Try Firebind. http://www.firebind.com
    It’s a turnkey service that launches a Java based app on your client machine and communicates back and forth with its server to send UDP packets over any of the 65535 UDP or TCP ports.
    It’s the only web based UDP port tester out there that can do this.
    You can run a test in seconds with only a few key clicks. They also have some predefined tests like League of Legends, World of Warcraft, etc.

  3. Pingback: Querying UDP Ports with PowerShell: Part 2 | Learn Powershell | Achieve More

  4. Boe Prox says:

    Hi Eric,
    Thanks for the comment and bug feedback. I will investigate this bug and find a resolution for it. Can you provide a little more information? Are you using the windows firewall to block the port or something else? If possible, can you supply the output from both portqry.exe and the function? I won’t need the computer name from either output to keep the data sanitized.

    Boe

    • I’ll try to get you the output on Monday. We’ve got a corporate firewall with different VLANs for secure and normal servers. Appropriate ACLs have been opened for the SQL ports, but sometimes they forget UDP 1434 which is SQL’s Browser service.

  5. Boe, I’m still hitting a bug where your function always returns true for an UDP port. Portqry shows filtered for the port, which is correct since a firewall blocks access.

    Thanks for the script though.

    –Eric

Leave a comment