views:

251

answers:

5

Question: I want to search the subnet for all computers in it. So I send a ping to all IP addresses in the subnet.

The problem is it works fine if I only scan 192.168.0.". But if I scan 192.168..*", then I get an "Out of memory" exception.

Why ? Do I have to limit the threads, or is the problem the memory consumed by new ping which doesn't get destructed once finished, or do I need to call gc.collect() ?

    static void Main(string[] args)
    { 
        string strFromIP = "192.168.0.1";
        string strToIP = "192.168.255.255";

        Oyster.Math.IntX omiFromIP = 0;
        Oyster.Math.IntX omiToIP = 0;
        IsValidIP(strFromIP, ref omiFromIP);
        IsValidIP(strToIP, ref omiToIP);
        for (Oyster.Math.IntX omiThisIP = omiFromIP; omiThisIP <= omiToIP; ++omiThisIP)
        {
            Console.WriteLine(IPn2IPv4(omiThisIP));
            System.Net.IPAddress sniIPaddress = System.Net.IPAddress.Parse(IPn2IPv4(omiThisIP));
            SendPingAsync(sniIPaddress);
        }

        Console.WriteLine(" --- Press any key to continue --- ");
        Console.ReadKey();
    } // Main


    // http://pberblog.com/post/2009/07/21/Multithreaded-ping-sweeping-in-VBnet.aspx
    // http://www.cyberciti.biz/faq/how-can-ipv6-address-used-with-webbrowser/#comments
    // http://www.kloth.net/services/iplocate.php
    // http://bytes.com/topic/php/answers/829679-convert-ipv4-ipv6
    // http://stackoverflow.com/questions/1434342/ping-class-sendasync-help
    public static void SendPingAsync(System.Net.IPAddress sniIPaddress)
    {
        int iTimeout = 5000;
        System.Net.NetworkInformation.Ping myPing = new System.Net.NetworkInformation.Ping();
        System.Net.NetworkInformation.PingOptions parmPing = new System.Net.NetworkInformation.PingOptions();

        System.Threading.AutoResetEvent waiter = new System.Threading.AutoResetEvent(false);
        myPing.PingCompleted += new System.Net.NetworkInformation.PingCompletedEventHandler(AsyncPingCompleted);
        string data = "ABC";
        byte[] dataBuffer = Encoding.ASCII.GetBytes(data);

        parmPing.DontFragment = true;
        parmPing.Ttl = 32;

        myPing.SendAsync(sniIPaddress, iTimeout, dataBuffer, parmPing, waiter);
        //waiter.WaitOne();
    }


    private static void AsyncPingCompleted(Object sender, System.Net.NetworkInformation.PingCompletedEventArgs e)
    {

        System.Net.NetworkInformation.PingReply reply = e.Reply;
        ((System.Threading.AutoResetEvent)e.UserState).Set();
        if (reply.Status == System.Net.NetworkInformation.IPStatus.Success)
        {
            Console.WriteLine("Address: {0}", reply.Address.ToString());
            Console.WriteLine("Roundtrip time: {0}", reply.RoundtripTime);
        }
    }
A: 

I guess the problem is that you are spawning roughly 63K ping requests near-simultaneously. Without further memory profiling it is hard to say which parts consume the memory. You are working with network resources, which probably are limited. Throttling the number of active pings will ease the use of local resources, and also network traffic.

Again I would look into the Task Parallel Library, the Parallel.For construct combined with the Task<T> should make it easy for you.

Note: for .Net 3.5 users, there is hope.

Peter Lillevold
That's framework 4. I'm on framework 2.0
Quandary
@Quandary - see my updated answer :)
Peter Lillevold
Upgrade? C# Express is free.
DeadMG
Peter has correctly identified the problem, 65,536 pings starting. Your app should take into account that all of the hosts may not respond. If you have a timeout of 5000 and none of the hosts respond...
dbasnett
A: 

First: Only start like 1000 pings the first time (in the loop in Main)

Second: Move the following parameters to Program class (member variables)

Oyster.Math.IntX omiFromIP = 0; 
Oyster.Math.IntX omiToIP = 0;
Oyster.Math.IntX omiCurrentIp = 0;
object syncLock = new object();

Third: In AsyncPingCompleted do something like this in the bottom:

public void AsyncPingCompleted (bla bla bla)
{
    //[..other code..]

    lock (syncLock) 
    {
        if (omiToIP < omiCurrentIp)
        {
           ++omiCurrentIp;
           System.Net.IPAddress sniIPaddress = System.Net.IPAddress.Parse(IPn2IPv4(omiCurrentIp)); 
           SendPingAsync(sniIPaddress); 
        }
    }
}

Update with complete code example

public class Example
{
    // Number of pings that can be pending at the same time
    private const int InitalRequests = 10000;

    // variables from your Main method
    private Oyster.Math.IntX _omiFromIP = 0;
    private Oyster.Math.IntX _omiToIP = 0;
    private Oyster.Math.IntX _omiCurrentIp = 0;

    // synchronoize so that two threads
    // cannot ping the same IP.
    private object _syncLock = new object();

    static void Main(string[] args)
    {
        string strFromIP = "192.168.0.1";
        string strToIP = "192.168.255.255";

        IsValidIP(strFromIP, ref _omiFromIP);
        IsValidIP(strToIP, ref _omiToIP);
        for (_omiCurrentIp = _omiFromIP; _omiCurrentIp <= _omiFromIP + InitalRequests; ++_omiCurrentIp)
        {
            Console.WriteLine(IPn2IPv4(_omiCurrentIp));
            System.Net.IPAddress sniIPaddress = System.Net.IPAddress.Parse(IPn2IPv4(_omiCurrentIp));
            SendPingAsync(sniIPaddress);
        }

        Console.WriteLine(" --- Press any key to continue --- ");
        Console.ReadKey();
    } // Main


    // http://pberblog.com/post/2009/07/21/Multithreaded-ping-sweeping-in-VBnet.aspx
    // http://www.cyberciti.biz/faq/how-can-ipv6-address-used-with-webbrowser/#comments
    // http://www.kloth.net/services/iplocate.php
    // http://bytes.com/topic/php/answers/829679-convert-ipv4-ipv6
    // http://stackoverflow.com/questions/1434342/ping-class-sendasync-help
    public void SendPingAsync(System.Net.IPAddress sniIPaddress)
    {
        int iTimeout = 5000;
        System.Net.NetworkInformation.Ping myPing = new System.Net.NetworkInformation.Ping();
        System.Net.NetworkInformation.PingOptions parmPing = new System.Net.NetworkInformation.PingOptions();

        System.Threading.AutoResetEvent waiter = new System.Threading.AutoResetEvent(false);
        myPing.PingCompleted += new System.Net.NetworkInformation.PingCompletedEventHandler(AsyncPingCompleted);
        string data = "ABC";
        byte[] dataBuffer = Encoding.ASCII.GetBytes(data);

        parmPing.DontFragment = true;
        parmPing.Ttl = 32;

        myPing.SendAsync(sniIPaddress, iTimeout, dataBuffer, parmPing, waiter);
        //waiter.WaitOne();
    }


    private void AsyncPingCompleted(Object sender, System.Net.NetworkInformation.PingCompletedEventArgs e)
    {

        System.Net.NetworkInformation.PingReply reply = e.Reply;
        ((System.Threading.AutoResetEvent)e.UserState).Set();
        if (reply.Status == System.Net.NetworkInformation.IPStatus.Success)
        {
            Console.WriteLine("Address: {0}", reply.Address.ToString());
            Console.WriteLine("Roundtrip time: {0}", reply.RoundtripTime);
        }


        // Keep starting those async pings until all ips have been invoked.
        lock (_syncLock)
        {
            if (_omiToIP < _omiCurrentIp)
            {
                ++_omiCurrentIp;
                System.Net.IPAddress sniIPaddress = System.Net.IPAddress.Parse(IPn2IPv4(_omiCurrentIp));
                SendPingAsync(sniIPaddress);
            }
        }
    }        
}
jgauffin
NO ! This is linearization/synchronous. You make a sync ping out of an async ping. It takes this variant about 255*255*5s = 325125 s = 5418 min = 90 hours = 5 days to find out how many computers are on the network... That number will have changed by then, and so will the IP numbers. I don't question that your variant probably 'works', but are you insane ?
Quandary
1000 first pings was just a number. It can be 40 000 or 5. The fact still remains, the current solution consumes to much memory. And I don't see how a completed async op invoking a new async op makes it synchronous???? Are YOU insane?
jgauffin
Was SendPingAsync(sniIPaddress); in the main loop before ? I don't remember it. And why would you start pings from outside the main loop ? I don't see why this should make it work... Much less I see why you stop after 10'000 addresses. That only goes to 192.168.39.*. My variant goes to 192.168.109.* until it crashes. Your variant simply quits before it reaches out of memory. And why do you need an object to lock? A simple bool would suffice. And why locking at all? There's no critical code in the ping completed section.
Quandary
It do not stop. Each time a ping of of the 10000 (or whatever initial value you decide to use) completes a new one is started until all addresses is being processed. If 50 pings complete at the same time, then 50 new pings are invoked. My method makes sure that it's always 10000 (or whatever number you decide to use) are being invkoed at the same time until there are no more pings to invoke
jgauffin
You need to read up on multithreading if you think that the lock is not nessacary.
jgauffin
A: 

pseudo-code

do

if pings_running > 100 then
sleep 100ms.
else
start ping
endif

loop while morepings
dbasnett
Mmmh, I did exactly this with 5000 instead of 100, it worked well for about one minute, then it crashes. But maybe I need to reduce from 5000 to somewhat less.
Quandary
+1  A: 

According to this thread, System.Net.NetworkInformation.Ping seems to allocate one thread per async request, and "ping-sweeping a class-B network creates 100's of threads and eventually results in an out-of-memory error."

The workaround that person used was to write their own implementation using raw sockets. You don't have to do that in F#, of course, but there are a number of advantages in doing so.

Joel Mueller
A: 

Finally... No ping requried at all...

http://www.codeproject.com/KB/cs/c__ip_scanner.aspx

All I needed to do is to make it thread-safe for debugging. Changing Add to:

void Add( string m )
{
    Invoke(new MethodInvoker(
        delegate
        {
            add.Items.Add(m);
        }));
    //add.Items.Add( m );
}

 

Private Sub Add(m As String)
    Invoke(New MethodInvoker(Function() Do
        add.Items.Add(m)
    End Function))
    'add.Items.Add(m);'
End Sub
Quandary