scvalex.net

25. Where's my Pi?

When I powered on my new Raspberry Pi the other day, I realized I had a problem: even though we were both connected to the same WiFi network, I had no idea what its address was.

My anticlimatic solution was to connect it to a screen, but clearly this wouldn’t always be an option. No, we want to be able to locate a machine just by virtue of it being on the same network as us. The obvious idea is to have it broadcast some identifiable network packet periodically; then, we could wait for the packet, and, once we receive it, work our way back to the machine.

Let’s see if it works with UDP. On the local machine, we start netcat(1) listening on UDP port \(23456\):

% nc -ulvn -p 23456
listening on [any] 23456 ...

On the Raspberry Pi, we have netcat broadcast its hostname, lily, to everybody on the above UDP port:

% echo "$(hostname)" | nc.traditional -ub -w 1 255.255.255.255 23456

Checking back on the local machine, we find lily’s address, 192.168.0.6:

listening on [any] 23456 ...
connect to [192.168.0.2] from (UNKNOWN) [192.168.0.6] 50028
lily

So, the idea works. But we’d like to get just the source address out of all that, and netcat’s output is a bit hard to work with. Instead of trying to process it, let’s write whereis.py, a Python script just for this task:

#!/usr/bin/python

from __future__ import print_function
import socket, sys

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("USAGE: %s <id>" % (sys.argv[0],))
        exit(1)
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(("0.0.0.0", 23456))
    while True:
        (data, (addr, port)) = sock.recvfrom(1024)
        if data.decode().strip() == sys.argv[1]:
            break
    print(addr)

There’s nothing surprising about the above: we open a socket, bind it to a port, wait for packets, and exit only when the expected packet arrives. If we redo the experiment, we see:

% whereis.py lily
192.168.0.6

We still need to run the netcat broadcast on lily somehow. The simple solution is to add the command to its crontab(1) so that it runs every minute:

% crontab -l
* * * * * echo "$(hostname)" | nc.traditional -ub -w 1 255.255.255.255 23456

As an example, we can now ssh into lily with:

scvalex@alita ~ % ssh $(whereis.py lily)
scvalex@lily ~ %

To recap, we wanted a way to find the address of a machine on the same network as us. We wrote a Python script that waits for a UDP packet with a certain content, and prints the source address of the packet. We then configured the remote machine to broadcast that packet using netcat and cron.


We’ve already mentioned netcat’s unwieldy output, but a bigger problem is that there’s no standard netcat. The above commands work with the traditional netcat (which is nc on Gentoo, and nc.traditional on Debian); the flags for other netcat variants are probably different.


Finally, note that our solution is basically a subset of Zeroconf. If you don’t want to get your hands dirty, or need more functionality, consider setting up Avahi.