Click here to Skip to main content
15,887,214 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.6K   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 
I made the article "public" since it is an interesting part of a chain, but I don't agree with some of your points.

In particular, the use of "application level timeouts" is one of the practice often used by developer to circumvent the TCP functionality to "speed i t up".
This practices, if used extensively and differently (because each has its own) across different applications and servers, are what makes network management a nightmare.

When dealing with communication protocols you're not alone: there is a world outside that -because you're declaring to use a certain "protocol", expect from you a well defined "behavior".
TCP timeouts are there because all machine in the network will behave the same.

Networks can loose packets, and inside those 30secs. there are all the back-off algorithms of TCP to try to recover the error. And that 30 secs are also the time where network routing protocols and LAN spanning trees can find a new path in case of network disruption (and if the network is arbitrarily long, with an arbitrary number of subjects in the middle, like the Internet is, you can always find a "disruption" somewhere- it is just a matter of statistics).

If you arbitrarily decide that after 2 sec. you peer is died, you are in fact disabling all the network capability to recover from an internal network error. An are letting you peer (that may be have already seen your connection attempt) sitting with embryonic connection open for the next 3 minutes in case you will be yet alive.

If anyone behave like you, the remote sever will soon run out of ports.

Moral: don't circumvent network protocol functionality: they have their own motivation.
___

P.s. The article is public, but still unedited. Give a check to the format: there is a piece of text at the end that looks like a title ...

2 bugs found.
> recompile ...
65534 bugs found.
D'Oh! | :doh:


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.