PortQry Implementation using TcpClient, Socket, and Exception Handling





5.00/5 (1 vote)
Query an endpoint service via TCP and determine if it is available for connection, refusing connectivity, or filtered by a firewall.
Introduction
As previously noted in an article posted by Razan Paul, there is a rather lengthy timeout in WinSock before a synchronous Connect using System.Net.Sockets.TcpClient
fails. This can lead to rather lengthy waits (up to 30 seconds) if you attempt to connect to a service that is not available due to a filtering mechanism in your path (firewall).
I could have never gotten this far without first reading over how Razan had done this. Many thanks to him for this article.
Background
Recently, I've been working on several projects that require knowing whether a service is available. Most of us have written Ping applications that track if a device is up, but in this instance, I needed to make sure the port was responding to a TCP Socket before calling some other functions that do various and lengthy subroutines before returning their result. To get past this, I had chosen to use the asynchronous BeginConnect
function provided in the Sockets.TcpClient
class and just threaded out the processes to manage the long wait times that could occur. This turned out to be rather ugly code-wise, and not very efficient, resulting in a rather ridiculous amount of exception catching.
To resolve the issue, I wrote a rather small static
function that interacts similar to the PortQry or NMap tools. We simply attempt to connect using the BeginConnect
Subroutine, and then wait until our timeout has occurred at which we assume the port is no longer responding and mark it filtered. Not only is this extremely responsive, but it threads really well when used in a BackgroundWorker
.
Using the Code
The following has the basic implementation for the function:
public static SocketResponse SocketPoll(string Node /*127.0.0.1*/,
int tcpPort /*135*/,
int tcpTimeout /*2500*/)
{
if (Node == null || Node.Trim() == "") Node = "127.0.0.1";
if (tcpPort < 0) tcpPort = 135;
if (tcpTimeout < 0) tcpPort = 2500;
IPAddress ipNode = DetermineIP(Node);
TcpClient tSocket = new TcpClient();
DateTime endTime = DateTime.Now.AddMilliseconds(tcpTimeout);
IAsyncResult ar = tSocket.BeginConnect(ipNode, tcpPort, null, tSocket);
Notice the null
in the previous bit of code. I chose to NOT utilize the CallBack
as I'm not concerned with any cleanup until we get back to the primary bit of code. We're really treating this as a synchronous function, and terminating the request if the function doesn't complete in the time we have allotted.
Should you want to do something with the open connection once it's completed, you could write a Callback Function that accepts an AsyncCallback
, does something with the IAsyncResult
and returns to the calling function. I started with this method, and realized how incredible of a task reimplementing the IAsyncResult
interface and AsyncCallback
delegates were going to be.
while(DateTime.Now < endTime)
{
if (ar.IsCompleted)
{
Socket s = tSocket.Client;
try
{
if (!s.Poll(100, SelectMode.SelectError))
{
return SocketResponse.LISTENING;
}
s.EndConnect(ar);
tSocket.Close();
}
TcpClient.Client
exposes the Socket
class that TcpClient
was built on top of, allowing us to use Socket.Poll
to get the SocketError
(ICMP response) should the attempt fail.
In the event that we connect successfully, poll fails, and we return the LISTENING SocketResponse
.
catch (System.Net.Sockets.SocketException SocketEx)
{
switch ((SocketError)Enum.ToObject(typeof(SocketError),
SocketEx.ErrorCode))
{
case SocketError.ConnectionRefused:
return SocketResponse.NOT_LISTENING;
case SocketError.HostUnreachable:
case SocketError.HostDown:
case SocketError.NetworkUnreachable:
return SocketResponse.UNREACHABLE;
default: //Else...
return SocketResponse.OTHER;
}
}
}
Poll(100, SelectMode.SelectError)
throws the SocketException
associated with the received ICMP message. Thread.Sleep(10);
}
//If connection times out without a ICMP message response...
return SocketResponse.FILTERED;
}
So, finally if a TCP connection hasn't been established or an exception hasn't been thrown due to receipt of an ICMP message indicating a failed connection, the thread sleeps for 10 milliseconds allowing other threads to execute in the mean time, and we start over until the timeout is exceeded.
At the point the timeout is exceeded, we simply return the FILTERED SocketException
back to the calling function.
Points of Interest
Raw Sockets are no longer supported post Windows XP Service Pack 3. Wonderful.
History
- 4th June, 2010: Initial post