I am making a network-connected device (using Raspberry Pi 4 inside), and I would like a way for these devices to discover each other (when on the same local subnet). I am vaguely aware of protocols like SSDP / UPnP which can help with things like this, but the complexity and security concerns lead me to believe that that is not the best option.
Update: The feedback I have gotten so far suggests that mDNS & DNS-SD, implemented in existing software like avahi
, are far preferable tools for this job. In the process of learning about these, I am finding the following links helpful (if you have more please add them in an answer or comment!):
All of the devices that I am making are running an HTTPS server (nginx) listening on tcp/443 and configured with a (legitimate) certificate valid for hostnames like www.example.com
and *.example.com
(where example.com is replaced with my own domain name).
The way I am currently trying to support this peer discovery feature is like this:
- User connects to web interface of one device (e.g. by knowing the IP a priori)
- User triggers "discover peers" feature (e.g. HTTP/WebSockets request)
- Device uses
nmap
's ssl-cert
script to scan to identify peers like this:
nmap --script ssl-cert --script-args=tls.servername=www.example.com <local network>
- Output is collected, parsed, and filtered then resulting IP address list is displayed to the user.
First off, are there alternative ways of doing this that might be "better" (less "invasive" / noisy, more reliable, etc.)?
Secondly, the output from the nmap command listed in step 3 is hard to parse. It looks something like this (targeting known test server here):
# nmap -p 443 --script ssl-cert --script-args=tls.servername=www.example.com 192.168.1.40
Starting Nmap 7.80 ( https://nmap.org ) at 2023-03-08 13:20 CST
Nmap scan report for 192.168.1.40
Host is up (0.00017s latency).
PORT STATE SERVICE
443/tcp open https
| ssl-cert: Subject: commonName=*.example.com
| Subject Alternative Name: DNS:*.example.com, DNS:example.com
| Issuer: commonName=...
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2022-06-08T00:00:00
| Not valid after: 2023-07-09T23:59:59
| MD5: ...
|_SHA-1: ...
Nmap done: 1 IP address (1 host up) scanned in 0.90 seconds
If I enable -oG
for greppable output it seems that the certificate information gets omitted. (If there is a way to change that I would like to know about it.) So instead I tried piping to grep with the "lines before" option like nmap ...|grep -B 5 "ssl-cert: Subject: commonName=\*.example.com"|
and then grepping again and using awk to select only the hostname/IP address. The result is like this:
#!/bin/bash
NMAP=/usr/bin/nmap
HOSTNAME="www.example.com"
TCP_PORT_FOR_HTTPS=443
DESIRED_COMMON_NAME="\*.example.com"
TARGET="192.168.1.30-100"
# Attempt TLS handshake with each host specified by TARGET, using SNI to request HOSTNAME
# Filter results for hosts with certificates matching DESIRED_COMMON_NAME
RESULTS=$($NMAP -p $TCP_PORT_FOR_HTTPS --script ssl-cert --script-args=tls.servername=$HOSTNAME $TARGET \
|grep -B 5 "Subject: commonName=$DESIRED_COMMON_NAME" \
|grep "Nmap scan report for" \
|awk '{split($0, array, "Nmap scan report for "); print "\x27"array[2]"\x27"}' \
)
# Output as JSON array: "'x.x.x.x' 'y.y.y.y'" --> "['x.x.x.x','y.y.y.y']"
echo "[$(echo -n $RESULTS|tr ' ' ,)]"
# Example output: ['192.168.1.37','192.168.1.43','192.168.1.40']
Since I was just trying to get this to work I suspect there is a better / more elegant approach to this out there. Would anyone be willing to provide feedback, suggestions, etc.?