DEV Community

Nizar
Nizar

Posted on • Edited on

Auto Pi Finder or Any Device Really

The aim of this is to demonstrate how to automatically find a Pi on a network using an Android application.

To understand what is needed, we should discuss ARP tables and a fast segment of ip addresses, because why not.

What's an ARP Table ?

Well, ARP stands for Address Resolution Protocol. It links network addresses to physical addresses, in other words, links every ip Address to a mac Address.

That happens when two devices interact on a network, during that, the mac address of each device is shared with the other under a specific ip address and linking it accordingly.

But of course, storing all those interactions can clog up a device's memory; therefore, the ARP table is cleared from time to time.
But I still didn't explain what an ARP table; it is just a file containing each ip address and the mac address accompanying it.

ARP table get cleared as we said, but this differs between devices and how much they can handle. Android phones being, well, android phones, the table is cleared really frequently ( gonna approximate 5 mins ). And not having nmap doesn't help in listing the devices on the same network.

Moreover, who wouldn't like to check what devices are connected onto one's internet on one's phone.
Well, I don't know about you, but it was crucial to start the project I'm working on.

Fast Introduction to IP Addresses

Well, there are multiple classes of ip addresses that have multiple numbers of hosts. Let's say ip addresses are of the format a.b.c.d. Well, a specifies the class.

For a ranging between [1, 126], the hosts are about 6 million. (class a)
Ignore for when a = 127, it is the local network address.
For a ranging between [128, 191], the hosts are about 300 thousand. (class b)
For a ranging between [192, 223], the hosts are actually 253. (class c)

Now the latter is called class c. There are 2 others, but they aren't used for private ip addresses.

Now, why are the hosts like that ? Well, b, c, and d vary between being constant and alternating for each.

For class a, b, c, and d are varying. They vary between 0 and 255; however, there are some exceptions for d.
For class b, c and d are varying.
For class c, d is only varying.

So let's start!

Note that this only works for private ips networks of class c ( 192 - 223 : first part of the ip address )

But what's the fastest form of interaction between two devices and the easiest, well, pinging is!
Setting out the process, it would go from checking online hosts, to pinging them, then collecting the ARP table.

Checking online hosts

To check for online hosts, I worked on pinging all the hosts available on a network.
This is rather time consuming, but I couldn't find any alternative to this. Pinging the broadcasting ip of a network isn't faster too. Therefore, I had to limit it to a class C private ip address network.

You can do that using Java, ping all the ip addresses from 1 to 254.
I instead chose to do all the stuff using Android Shell.

We need to check the available hosts and then reping them, the reason is explain in the next step. To do that, we need to save which ones gave us a response.
That was done using the following terminal command.

((ping -c 1 192.168.1.etc) > /dev/null) && (echo "up") || (echo "down")
Enter fullscreen mode Exit fullscreen mode

But wait on me, Android Shell has no bash automatically, so this command won't work. Thus, you have to include the sh tag with it.
Now to make it more efficient, I went ahead and made it almost Async. That way, all the devices that will give me a response give it at the same time.

Due to async, my counter bugged when two responses returned at the exact exact same time. To avoid that, I made a counter that deals with even and odd numbers just so that consecutive responses are never likely to reduce the counter at the same exact time and thus reducing it only once. ( By calling the counter at the same exact time I'm accessing the same value the variable had twice, and thus not reducing it twice, but only once ).

/*
 Setting a Counter to keep track
 Of the stuff that have finished
 In order to prevent Async Calling for
 Counter, had to delay it without actually
 Delaying it, so divided the Counting
*/
class Counter {
    int i = 0, j = 0;
    public Counter(int m) {
        if (m % 2 > 0) {
            this.i = (int) Math.floor((double) m / 2) + 1;
            this.j = (int) Math.floor((double) m / 2);
        } else {
            this.i = (int) Math.floor((double) m / 2) + 1;
            this.j = (int) Math.floor((double) m / 2);
        }
    }
    public void resize(int i) {
        this.i = i;
    }
}
Enter fullscreen mode Exit fullscreen mode

I kept having a bug with one host not returning at all, knowing that 255 would not return in time, I looped from 1 to 255 and proceeded to the next function at 254 responses.

// This is a counter to keep track of what is done pinging
final Counter pingerCounter;
/*
 Asyncing ( the easy way ) Pinging each IP Address in order to
 Collect active ones with their Mac Address in ARP Cache Tables
 When done pinging, ARP runs to get the IP Address
 */
final Thread pinger;
pingerCounter = new Counter(254);
class Pinger implements Runnable {
    int y = 0, tempY = 0;

    Pinger (int y) {
        this.y = y;
    }

    public void run() {
        StringBuffer output = new StringBuffer();
        Process ping;
        try {
            /*
             Commands there cause ADB Shell on Android
             Doesn't support god damn inline operands
             So had to do it with Bash
             */
            String[] command = { "sh", "-c", "((ping -c 1 " + ipString + this.y + ") > /dev/null) && (echo 'up') || (echo 'down')" };
            ping = Runtime.getRuntime().exec(command);

            /*
             Setting TempY to store the valid Y
             Instead of storing the one after it
             */
            this.tempY = this.y;

            /*
             Here we're making sure that we end
             Up running all pings almost async
             */
            this.y -= 1;
            if (this.y > 0) new Thread(new Pinger(this.y)).start();

            ping.waitFor();

            // Log.i("PiFinder", "Checked host on " + ipString + this.tempY);
            // Log.i("PiFinder", (pingerCounter.i - 1) + " hosts to be check");

            // Grabbing output to know whether the address is up
            // So that we can easily read it into ARP Tables
            InputStream inStream = ping.getInputStream();
            // Needed to observe errors, thus this is here
            // InputStream errorStream = ping.getErrorStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inStream));
            String line = "";
            StringBuilder response = new StringBuilder();

            while ((line = bufferedReader.readLine()) != null) {
                response.append(line);

                if (line.equals("up")) {
                    Log.i("PiFinder", "Available host on " + ipString + this.tempY);
                    pingables.add(ipString + this.tempY);
                }
            }

            /*
             Here we're checking for how many pings
             Were completed, so that we proceed
             In order to prevent Async Calling for
             Counter, had to delay it without actually
             Delaying it, so divided the Counting
             */
            if ((this.tempY % 2) == 1)
                pingerCounter.i--;
            else pingerCounter.j--;
            if ((pingerCounter.i + pingerCounter.j) == 0)
                new Thread(new Pingable(pingables.size())).start();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
pinger = new Thread(new Pinger(255));
pinger.start();
Enter fullscreen mode Exit fullscreen mode

Pinging Online Hosts

When I said I was gonna talk about ARP tables, I didn't mention everything.
There's a surprise I had to mention here, which is, that ARP tables have a specific number of lines sadly.
So pinging all the hosts would get some forgotten and buried under their brothers. We don't want that! So, we have to reping them.

Now note that, I assumed that there won't be more than 90 online hosts on a network of a person using my application, so I didn't go for measures to deal with more than 93 hosts.
Why 93 ? Because, after collecting the length of ARP tables of the android devices in my vicinity, I ended up with 93 on all of them. So, I assumed that ARP tables are gonna have a maximum of 93 devices.

So repinging them is similar to the process before using the -c1. But no need for the sh, even though I included it.

// List of Pingable Addresses or Online Hosts
final ArrayList<String> pingables = new ArrayList<String>();

// This is a counter to keep track of what is done pinging
final Counter pingableCounter = new Counter(0);
/*
 Repinging the Available Hosts to refresh
 the ARP Table Cache to make sure it's a Pi
 */
class Pingable implements Runnable {
    int i = 0, tempI = 0;

    Pingable(int m) {
        if (m == pingables.size()) {
            this.i = m - 1;
            pingableCounter.resize(m - 1);
        } else this.i = m;
    }

    public void run() {
        try {
            if (pingables.size() == 0)
                arp.start();
            else {
                String[] command = { "sh", "-c", "ping -c 1 " + pingables.get(this.i) };
                Process ping = Runtime.getRuntime().exec(command);

                // Setting TempY to store the valid I
                // Instead of storing the one after it
                this.tempI = this.i;

                // Here we're making sure that we end
                // Up running all pings almost async
                this.i -= 1;
                if (this.i > -1) new Thread(new Pingable(this.i)).start();

                Log.i("PiFinder", "Pinged host on " + pingables.get(this.tempI));

                ping.waitFor();

                /*
                 Here we're checking for how many pings
                 Were completed, so that we proceed
                 In order to prevent Async Calling for
                 Counter, had to delay it without actually
                 Delaying it, so divided the Counting
                 */
                if ((this.tempI % 2) == 0)
                    pingableCounter.i--;
                else pingableCounter.j--;
                if ((pingableCounter.i + pingableCounter.j) == 0)
                    arp.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Collecting ARP table

There's a specific file in the Android root folders where the table is cached.
The file is in /proc/net/arp.

Well, in my case, I wanted to find a specific device in that ARP table. I was searching for a Raspberry Pi. Oh well, damn it, I guess now you know I'm working on something for the Raspberry Pi.

Anyway, I added some operands to use grep and search for the specific mac address a raspberry pi starts with, the famous b8:27:eb.

Thus what I did was, the following.

/*
 After getting the online hosts use ARP to get mac addresses
 Knowing that Pis start with B8:27:EB, group them accordingly
 This runs after the bottom thread pinger runs
 */
class ARP implements Runnable {
    public void run() {

        try {
            /*
             Commands there cause ADB Shell on Android
             Doesn't support god damn inline operands
             So had to do it with Bash
             */
            String[] command = { "sh", "-c", "arp -vn | grep -i ' b8:27:eb'" };
            Process arpa = Runtime.getRuntime().exec(command);

            arpa.waitFor();

            Log.i("PiFinder", "Collected ARP Table");

            // Read out available Pi addresses
            InputStream inStream = arpa.getInputStream();
            // Needed to observe errors, thus this is here
            // InputStream errorStream = ping.getErrorStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inStream));
            String line = "";
            StringBuilder response = new StringBuilder();

            ArrayList<ArrayList<String>> pis = new ArrayList<ArrayList<String>>();

            /*
             Since each line is an address, then
             Execute on each line read by buffer
             Then split to get the necessary fields
             */
            while ((line = bufferedReader.readLine()) != null) {
                response.append(line);

                String[] tempLine = line.split("\\s+");

                ArrayList<String> pi = new ArrayList<String>();
                pi.add(tempLine[1].substring(1, tempLine[1].length() - 1));
                pi.add(tempLine[3]);

                pis.add(pi);
            }
                if (pis.size() == 0) {
                    Log.i("PiFinder", "No Pis found");
                    // Use a callback function here (negative)
                } else { // Use a callback function here (positive) }
        } catch (IOException e){
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
final Thread arp = new Thread(new ARP());
Enter fullscreen mode Exit fullscreen mode

Now if you don't wish to collect any specific something, you can read the file with Java. You can also go for removing the part after arp -vn and then change the collection of each line to be specific device with the mac and ip address.

But what about the ip field we're missing ?!

I'm sorry about this, I forgot about it...
Here it is.

WifiManager manager = (WifiManager) getApplicationContext().getSystemService(getApplicationContext().WIFI_SERVICE);
WifiInfo info = manager.getConnectionInfo();

long infoIp = info.getIpAddress();

String address = (infoIp & 0xFF) +
        "." + ((infoIp >> 8) & 0xFF) +
        "." + ((infoIp >> 16) & 0xFF) +
        "." + ((infoIp >> 24) & 0xFF);

String[] ip = address.split("\\.");
ipAddress = new String[]{ ip[0], ip[1], ip[2] };

for (int i = 0; i < ipAddress.length; i++)
    ipString = ipString + ipAddress[i] + ".";
Enter fullscreen mode Exit fullscreen mode

And declare those globally...

String[] ipAddress;
String ipString = "";
Enter fullscreen mode Exit fullscreen mode

Well, this was my journey. I hope my tutorial was clear and not abstract.
I've tested the code and pretty sure it works.
For them all in one place, use this.

import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    String[] ipAddress;
    String ipString = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_process);

        findMePi();
    }

    private void findMePi() {
        WifiManager manager = (WifiManager) getApplicationContext().getSystemService(getApplicationContext().WIFI_SERVICE);
        WifiInfo info = manager.getConnectionInfo();

        long infoIp = info.getIpAddress();

        String address = (infoIp & 0xFF) +
                "." + ((infoIp >> 8) & 0xFF) +
                "." + ((infoIp >> 16) & 0xFF) +
                "." + ((infoIp >> 24) & 0xFF);

        String[] ip = address.split("\\.");
        ipAddress = new String[]{ ip[0], ip[1], ip[2] };

        for (int i = 0; i < ipAddress.length; i++)
            ipString = ipString + ipAddress[i] + ".";

        class Counter {
            int i = 0, j = 0;
            public Counter(int m) {
                if (m % 2 > 0) {
                    this.i = (int) Math.floor((double) m / 2) + 1;
                    this.j = (int) Math.floor((double) m / 2);
                } else {
                    this.i = (int) Math.floor((double) m / 2) + 1;
                    this.j = (int) Math.floor((double) m / 2);
                }
            }
            public void resize(int i) {
                this.i = i;
            }
        }

        class ARP implements Runnable {
            public void run() {

                try {
                    String[] command = { "sh", "-c", "arp -vn | grep -i ' b8:27:eb'" };
                    Process arpa = Runtime.getRuntime().exec(command);

                    arpa.waitFor();

                    Log.i("PiFinder", "Collected ARP Table");

                    // Read out available Pi addresses
                    InputStream inStream = arpa.getInputStream();
                    // Needed to observe errors, thus this is here
                    // InputStream errorStream = ping.getErrorStream();
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inStream));
                    String line = "";
                    StringBuilder response = new StringBuilder();

                    ArrayList<ArrayList<String>> pis = new ArrayList<ArrayList<String>>();

                    while ((line = bufferedReader.readLine()) != null) {
                        response.append(line);

                        String[] tempLine = line.split("\\s+");

                        ArrayList<String> pi = new ArrayList<String>();
                        pi.add(tempLine[1].substring(1, tempLine[1].length() - 1));
                        pi.add(tempLine[3]);

                        pis.add(pi);
                    }
                    if (pis.size() == 0) {
                        Log.i("PiFinder", "No Pis found");
                        // Use Callback Here ( Negative )
                    } else {
                        // Use Callback Here ( Positive )
                    }
                } catch (IOException e){
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        final Thread arp = new Thread(new ARP());

        final ArrayList<String> pingables = new ArrayList<String>();

        final Counter pingableCounter = new Counter(0);

        class Pingable implements Runnable {
            int i = 0, tempI = 0;

            Pingable(int m) {
                if (m == pingables.size()) {
                    this.i = m - 1;
                    pingableCounter.resize(m - 1);
                } else this.i = m;
            }

            public void run() {
                try {
                    if (pingables.size() == 0)
                        arp.start();
                    else {
                        String[] command = { "sh", "-c", "ping -c 1 " + pingables.get(this.i) };
                        Process ping = Runtime.getRuntime().exec(command);

                        this.tempI = this.i;

                        this.i -= 1;
                        if (this.i > -1) new Thread(new Pingable(this.i)).start();

                        Log.i("PiFinder", "Pinged host on " + pingables.get(this.tempI));

                        ping.waitFor();

                        if ((this.tempI % 2) == 0)
                            pingableCounter.i--;
                        else pingableCounter.j--;
                        if ((pingableCounter.i + pingableCounter.j) == 0)
                            arp.start();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        final Counter pingerCounter;

        final Thread pinger;
        pingerCounter = new Counter(254);
        class Pinger implements Runnable {
            int y = 0, tempY = 0;

            Pinger (int y) {
                this.y = y;
            }

            public void run() {
                StringBuffer output = new StringBuffer();
                Process ping;
                try {
                    String[] command = { "sh", "-c", "((ping -c 1 " + ipString + this.y + ") > /dev/null) && (echo 'up') || (echo 'down')" };
                    ping = Runtime.getRuntime().exec(command);

                    this.tempY = this.y;

                    this.y -= 1;
                    if (this.y > 0) new Thread(new Pinger(this.y)).start();

                    ping.waitFor();
                    InputStream inStream = ping.getInputStream();
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inStream));
                    String line = "";
                    StringBuilder response = new StringBuilder();

                    while ((line = bufferedReader.readLine()) != null) {
                        response.append(line);

                        if (line.toString().equals("up")) {
                            Log.i("PiFinder", "Available host on " + ipString + this.tempY);
                            pingables.add(ipString + this.tempY);
                        }
                    }

                    if ((this.tempY % 2) == 1)
                        pingerCounter.i--;
                    else pingerCounter.j--;
                    if ((pingerCounter.i + pingerCounter.j) == 0)
                        new Thread(new Pingable(pingables.size())).start();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        pinger = new Thread(new Pinger(255));
        pinger.start();
    }
}

Enter fullscreen mode Exit fullscreen mode

I hope my code isn't that bad.

Top comments (0)