8

I have an app which pings IP or IP range. The problem is that when hosts are closed it takes longer to ping than they are open. When host is closed the time to ping is about 1-2 seconds.

How could I make it faster when hosts are closed?

This is my code:

using System;
using System.Text;
using System.Windows.Forms;
using System.Net.NetworkInformation;

namespace Range_Pinger
{
    public partial class PingIPRange : Form
    {
        uint startIP, endIP, currentIP;
        int count = 0;
        int open = 0;
        int closed = 0;

        public PingIPRange()
        {
            InitializeComponent();

            tmrPingInterval.Tick += new EventHandler(tmrPingInterval_Tick);
        }

        void tmrPingInterval_Tick(object sender, EventArgs e)
        {
            if (txtTo.Text == string.Empty) Ping(ip2str(startIP));
            else
            {
                if (currentIP >= endIP) tmrPingInterval.Stop();
                Ping(ip2str(currentIP));
                currentIP++;
            }

            count++;

            tsslPingCount.Text = "Total number of pings: " + count.ToString() + 
                " Open IPs: " + open.ToString() + " Closed IPs: " + closed.ToString();
        }

        static uint str2ip(string ip)
        {
            string[] numbers = ip.Split('.');

            uint x1 = (uint)(Convert.ToByte(numbers[0]) << 24);
            uint x2 = (uint)(Convert.ToByte(numbers[1]) << 16);
            uint x3 = (uint)(Convert.ToByte(numbers[2]) << 8);
            uint x4 = (uint)(Convert.ToByte(numbers[3]));

            return x1 + x2 + x3 + x4;
        }

        static string ip2str(uint ip)
        {
            string s1 = ((ip & 0xff000000) >> 24).ToString() + ".";
            string s2 = ((ip & 0x00ff0000) >> 16).ToString() + ".";
            string s3 = ((ip & 0x0000ff00) >> 8).ToString() + ".";
            string s4 = (ip & 0x000000ff).ToString();

            return s1 + s2 + s3 + s4;
        }


        private void btnPing_Click(object sender, EventArgs e)
        {
            txtDisplay.Text = string.Empty;
            tsslPingCount.Text = string.Empty; 
            count = 0;
            open = 0;
            closed = 0;
            tmrPingInterval.Interval = int.Parse(nudInterval.Value.ToString());

            try
            {
                startIP = str2ip(txtFrom.Text);
                if (txtTo.Text != string.Empty) endIP = str2ip(txtTo.Text);
                currentIP = startIP;
                tmrPingInterval.Start();
            }
            catch
            {
                MessageBox.Show("Invalid input. It must be something like: 255.255.255.255");
            }
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            tmrPingInterval.Stop();
        }

        private void Ping(string address)
        {
            Ping pingSender = new Ping();
            PingOptions options = new PingOptions();
            options.DontFragment = true;
            string data = "01234567890123456789012345678901";
            byte[] buffer = Encoding.ASCII.GetBytes(data);
            int timeout = 120;
            try
            {
                PingReply reply = pingSender.Send(address, timeout, buffer, options) ;
                if (reply.Status == IPStatus.Success)
                {
                    open++;
                    txtDisplay.AppendText("Host " + address + " is open." + Environment.NewLine);
                }
                else
                {
                    closed++;
                    txtDisplay.AppendText("Host " + address + " is closed." + Environment.NewLine);
                }
            }
            catch (Exception ex)
            {
                txtDisplay.SelectedText += Environment.NewLine + ex.Message;
            }
        }

        private void tsmiExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    }
}

This is what I have now:

    [DllImport("iphlpapi.dll", ExactSpelling = true)]
    public static extern int SendARP(IPAddress DestIP, int SrcIP, byte[] pMacAddr, ref uint PhyAddrLen);

    private void Ping(IPAddress address)
    {
        byte[] macAddr = new byte[6];
        uint macAddrLen = (uint)macAddr.Length;

        if (SendARP(address, 0, macAddr, ref macAddrLen) == 0)
        {
            txtDisplay.AppendText("Host " + address + " is open." + Environment.NewLine);
        }
        else txtDisplay.AppendText("Host " + address + " is closed." + Environment.NewLine);
    }
HelpNeeder
  • 6,383
  • 24
  • 91
  • 155
  • 7
    What is a closed host? Also why not just make the timeout like 2 seconds instead of 120? – leppie Jan 19 '12 at 10:07
  • @leppie, by saying closed host I meant that IP doesn't respond to ping or is down. Changing timeout doesn't change anything. I have tried. – HelpNeeder Jan 19 '12 at 10:09
  • What did you change the timeout to? – leppie Jan 19 '12 at 10:10
  • Also, what is the tick interval? Too many variables here :( – leppie Jan 19 '12 at 10:11
  • @leppie, I have tried `int timeout = 1;` , and it seems like there's little to no difference when it's set to 120. Interval I'm trying at 1 millisecond. lol – HelpNeeder Jan 19 '12 at 10:12
  • The timer interval should not be less than 10ms else the rest of the code could possible never catch up and resources will be exhausted, etc. – leppie Jan 19 '12 at 10:14
  • @leppie, yes and no. I want to ping fast. This code doesn't seem to consume much memory/CPU. Especially when hosts are unreachable. – HelpNeeder Jan 19 '12 at 10:17
  • Ok the code you have now is almost correct. Try replacing the if with -> if (SendARP((int)address.Address, 0, macAddr, ref macAddrLen) == 0) – SanBen Jan 19 '12 at 19:41
  • @R34lthing, changing code to your specifications doesn't work. It compiles but when I ping any IP it keeps showing that every IP is down. – HelpNeeder Jan 21 '12 at 02:33

5 Answers5

10

You shouldn't reduce the timeout. Try to send multiple pings at once async.

var ping = new Ping();
ping.PingCompleted += (sender, eventArgs) =>
{
    // eventArgs.Reply.Address
    // eventArgs.Reply.Status
};
ping.SendAsync(ip, etc.);
Marc Messing
  • 344
  • 1
  • 5
7

Your address is a string. Thus it will go via DNS first to see if this is possibly a hostname (even if it is an IP address).

I suggest you use the overload taking an IPAddress instead.

leppie
  • 115,091
  • 17
  • 196
  • 297
4

I created a live host scanner not too long ago. It uses ARP to check if a computer is online. An ARP request is much faster than if you'd ping a host. Here's the code I used to check if a Host is available:

//You'll need this pinvoke signature as it is not part of the .Net framework
[DllImport("iphlpapi.dll", ExactSpelling = true)]
public static extern int SendARP(int DestIP, int SrcIP, 
                                 byte[] pMacAddr, ref uint PhyAddrLen);

//These vars are needed, if the the request was a success 
//the MAC address of the host is returned in macAddr
private byte[] macAddr = new byte[6];
private uint macAddrLen;

//Here you can put the IP that should be checked
private IPAddress Destination = IPAddress.Parse("127.0.0.1");

//Send Request and check if the host is there
if (SendARP((int)Destination.Address, 0, macAddr, ref macAddrLen) == 0)
{
    //SUCCESS! Igor it's alive!
}

If you're interested Nmap also uses this technique to scan for available hosts.

ARP scan puts Nmap and its optimized algorithms in charge of ARP requests. And if it gets a response back, Nmap doesn't even need to worry about the IP-based ping packets since it already knows the host is up. This makes ARP scan much faster and more reliable than IP-based scans. So it is done by default when scanning ethernet hosts that Nmap detects are on a local ethernet network. Even if different ping types (such as -PE or -PS) are specified, Nmap uses ARP instead for any of the targets which are on the same LAN.

EDIT:

This only works within the current subnet! As long as there is no router between the requesting machine and the target it should work fine.

ARP is a non-routable protocol, and can therefore only be used between systems on the same Ethernet network. [...] arp-scan can be used to discover IP hosts on the local network. It can discover all hosts, including those that block all IP traffic such as firewalls and systems with ingress filters. - Excerpt from NTA-Monitor wiki

For more information on the SendARP function you can check the pinvoke.net documentation.

SanBen
  • 2,581
  • 26
  • 35
3

You need to redesign your application to use multithreading -> tasks. Issue a task for each ping, and when you receive a response from a given host fire an event and update the UI. Changing socket timeout will only help you to reduce the timeout from outrageous to insufferable.

OSH
  • 2,847
  • 3
  • 25
  • 46
2

Not sure if this is any help (see final post on the thread), it seems an almost identical problem. What you're butting up against there is the protocol stack's timeout. You can get around it if you use socket to connect as you'll have more control.

Twirrim
  • 406
  • 8
  • 16