Friday, March 19, 2010

Check if remote port is opened

Here's something very simple with which you can check a port status(opened/closed) on remote host.
Add WinSock to uses clause
function ResolveAddress(HostName: String; out Address: DWORD): Boolean;
var  lpHost:        PHostEnt;
begin
  // Set default address
  Address := DWORD(INADDR_NONE);
  try
    // Check host name length
    if (Length(HostName) > 0) then begin
      // Try converting the hostname
      Address := inet_addr(PChar(HostName));
      // Check address
      if (DWORD(Address) = DWORD(INADDR_NONE)) then begin
        // Attempt to get host by name
        lpHost := gethostbyname(PChar(HostName));
        // Check host ent structure for valid ip address
        if Assigned(lpHost) and Assigned(lpHost^.h_addr_list^) then
          // Get the address from the list
          Address := u_long(PLongInt(lpHost^.h_addr_list^)^);
      end;// if (DWORD(Address) = DWORD(INADDR_NONE)) then begin
    end;// if (Length(HostName) > 0) then begin
  finally
    // Check result address
    if (DWORD(Address) = DWORD(INADDR_NONE)) then
      // Invalid host specified
      Result:= False
    else
      // Converted correctly
      Result := True;
  end;// try ... finally
end;// function ResolveAddress(HostName: String; out Address: DWORD): Boolean;

function IsPortOpened(const Host: string; Port: Integer): Boolean;
const
  szSockAddr = SizeOf(TSockAddr);
var
  WinSocketData: TWSAData;
  Socket: TSocket;
  Address: TSockAddr;
  dwAddress: DWORD;
label
  lClean;
begin
  // initialize result
  Result := False;
  // create WinSocketData
  if WinSock.WSAStartup(MakeWord(1, 1), WinSocketData) = 0 then begin
    // set address family
    Address.sin_family := AF_INET;
    // try to translate Host to IP address
    if NOT ResolveAddress(Host, dwAddress) then
      // faild! go to lClean label
      goto lClean;
    // set the address
    Address.sin_addr.S_addr := dwAddress;
    // create a socket
    Socket := WinSock.Socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    // if faild to create socket
    if Socket = INVALID_SOCKET then
      // go to lClean label
      goto lClean;
    // set the port
    Address.sin_port := WinSock.htons(Port);
    // attempt remote connection to Host on Port
    if WinSock.Connect(Socket, Address, szSockAddr) = 0 then begin
      // if succeded return true
      Result := True;
      // close the socket
      WinSock.closesocket(Socket);
    end;// if WinSock.Connect(Socket, Address, szSockAddr) = 0 then begin
  end;// if WinSock.WSAStartup(MakeWord(1, 1), WinSocketData) = 0 then begin
  // label to which we jump to clean up 
  lClean:
    WinSock.WSACleanup;
end;// function IsPortOpened(const Host: string; Port: Integer): Boolean;

Create a new VCL application, add a button on the main form, double-click the button and paste this code:
if IsPortOpened('google.com', 80) then
  ShowMessage('google has port 80 opened')
else
  ShowMessage('google has port 80 closed???');

14 comments:

  1. Hello Dorin.

    Can we set a TimeOut property on this function in order for it not to wait too much for a connection?
    Thanks.
    Bye.

    ReplyDelete
  2. @Sorin yes you can, try this
    function SetSocketTimeOut(const ASocket, ATimeOutRead, ATimeOutWrite: Integer): Boolean;
    begin
    // timeout is in milisecond
    // set the read(receive) timeout
    Result := WinSock.SetSockOpt(ASocket, SOL_SOCKET, SO_RCVTIMEO, @ATimeOutRead, SizeOf(ATimeOutRead)) <> SOCKET_ERROR;
    // set the send(write) timeout
    Result := Result and (WinSock.SetSockOpt(ASocket, SOL_SOCKET, SO_SNDTIMEO, @ATimeOutWrite, SizeOf(ATimeOutWrite)) <> SOCKET_ERROR);
    end;

    just before the "Connect" call add:
    SetSocketTimeOut(Socket, 100, 100);
    Note: that the values are in miliseconds, and I would use values between 500 and 1000, half of second and one second.

    Let me know if this works for you.

    ReplyDelete
  3. Thanks Dorin.

    It's kind of strange. Even though the function returns True, it seems that it has no effect. It is waiting like 20 seconds each time I run it, no matter what value I use for the timeout. :) I have no firewall active. Any idea why it does that? I've checked the connections with TCPView and I can see them staying there in SYN_SENT state, every time when the checked destination is down. When they're up, the function returns in an instant.

    ReplyDelete
  4. @Sorin you should use a thread if you want the application to be responsive, the main issue is with "GetHostByName" -- this takes quite some time to find the address... a better alternative is to import GetAddrInfo(here) which is recommended by Microsoft, they also say that GetHostByName is deprecated(here).

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Hello again, Dorin.

    In fact I’m not even using the “GetHostByName” function, because I’ve modified the code a little bit. Please see below the code I use.
    You can see that it makes no address lookups or anything similar. I am calling the function like this: IsPortOpened(352430272, 21);
    In this call, I’m trying to see if the port 21 is opened on the machine with the IP address ‘192.168.1.21’ (a machine from my LAN).
    If the port is opened, the function returns quickly but if it closed or machine is not reachable then it takes at least 20 seconds
    for it to return. Imagine what happens when I’m trying to check multiple ports. :)
    I’m really confused here, because I can’t figure out what might trigger this delay. I’ve also tried the code on another machine, with
    another version of Windows and it behaves the same way. Do you have any other ideas?
    Thanks for your help.

    function IsPortOpened(const Host: DWORD; Port: Integer): Boolean;
    const
    szSockAddr = SizeOf(TSockAddr);
    var
    WinSocketData: TWSAData;
    Socket: TSocket;
    Address: TSockAddr;
    begin
    Result := False;// initialize result
    if WinSock.WSAStartup(MakeWord(1, 1), WinSocketData) = 0 then // create WinSocketData
    begin
    Address.sin_family := AF_INET;// set address family
    Address.sin_addr.S_addr := Host;// set the address
    Socket := WinSock.Socket(AF_INET, SOCK_STREAM, IPPROTO_IP);// create a socket
    if Socket = INVALID_SOCKET then WinSock.WSACleanup // if failed to create socket
    else begin
    Address.sin_port := WinSock.htons(Port); // set the port
    SetSocketTimeOut(Socket, 300, 300); // set timeout
    // attempt remote connection to Host on Port
    if WinSock.Connect(Socket, Address, szSockAddr) = 0 then
    begin
    Result := True; // if succeeded return true
    WinSock.closesocket(Socket); // close the socket
    end;
    end;
    end;
    end;

    ReplyDelete
  7. @Sorin sockets are a bit harder to work with, however you still have a few options:
    a) use threads
    b) set the socket to non-blocking(msdn link)
    before calling the "connect()" method, define a local variable called "LMode: Integer;" and add the following lines
    LMode := 1; // a socket is non-blocking if the value of the third argument is different than zero
    ioctlsocket(Socket, FIONBIO, LMode);

    I'm sorry, I don't have too much time to investigate on this, however let me know if this reduces the timeout so I can update the post for others as well.

    Have fun!

    ReplyDelete
  8. Hi Dorin.

    I've tried to set the socket mode to non-blocking but that gave me erroneous results every time (when the tested ports were opened, or closed, no matter what). So don't update your post. :) Keep the blocking sockets in place because, this way at least you get a good result when the function returns.
    Regarding the idea with threads, that's a good one to which I might add the following:
    -we create the thread in which we do 2 things: start a timer and call the function to check the ports.
    -when the timer expires (after 1000-1500 milliseconds let's say) we kill the thread and return a false result
    This is the last idea that comes into my mind, regarding the timing out of the connections. What do you think?
    Thanks again for you time!

    ReplyDelete
  9. @Sorin a easier approach would be to create dynamically a TIdTCPClient(and set ConnectTimeout to whatever you choose) to check if port is opened, however if the target address is not online you will still get a freeze...

    ReplyDelete
  10. Hello Dorin.

    Windows Sockets world, is too strange for me. :) I've tried also to get a solution with Indy Sockets, as you said, but again, no matter what values I've used for ConnectTimeout property, the function still returns after 20 seconds when host or port is down. I've tried also the non-blocking ICS suite (from F.Piette) and it happened the same.
    I've went outside Delphi and created an AutoIt script, just to see if this is a Delphi problem or something, and discovered that it is not. The AutoIt procedure returned after 20 seconds when host/port down.
    So in Windows I can't programatically see, in less then 20 seconds if a host/port is down/closed. :)

    ReplyDelete
  11. @Sorin Hello, if the target address is available then you get a response(if you check for port state) fairly fast(depending on latency), however if it's unreachable, then Windows tries to resolve it with a default(built-in I believe) timeout value, you can however use a thread and a timer(with 1-3 sec. timeout) which will kill the thread if it's still running.

    ReplyDelete
  12. Hi Dorin.

    I was wondering how Windows port scanners do it. I remember that I've used some long time ago a port scanner, and it was able to scan hundreds of ports/second. And this while running on Windows. :)

    ReplyDelete
  13. Spaghetti programmer! Using a label, are you nuts!!???

    ReplyDelete
    Replies
    1. yeah, a little, I use everything that the language offers if it makes sense for me, you be my guest and refactor (:

      cheers!

      Delete

Blogroll(General programming and Delphi feeds)