Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#

PortQry Implementation using TcpClient, Socket, and Exception Handling

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
3 Jun 2010CPOL3 min read 25.5K   22   2
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:

C#
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.

C#
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.

C#
    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;
        }
    }
}
In the event that ICMP actually responds, signifying the TCP Port isn't filtered with a firewall, Poll(100, SelectMode.SelectError) throws the SocketException associated with the received ICMP message.
C#
         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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralI Have some clarifications ... Pin
Emilio Garavaglia3-Jun-10 21:29
Emilio Garavaglia3-Jun-10 21:29 
AnswerRe: I Have some clarifications ... Pin
C. M. Stephan9-Apr-12 17:57
C. M. Stephan9-Apr-12 17:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.