Workaround: Running nmap presence tracker in container, unprivileged

Not even sure this qualifies as a “project!”
It’s just my workaround for being able to run HA in a container, while using nmap for presence detection.

As you may or may not know, containers run in a different network than their host and this means they cannot sniff the host’s network. As a result, nmap doesn’t return anything of interest.
The suggested fix being to run the container in the host’s network, which is not always desirable. For instance, it is implied that you will be running the HA container as root in order to retrieve MAC addresses.

Here is my approach:

  • install nmap on both the host and inside the container
  • create a cronjob on the host that runs the actual nmap command every minute
  • store the XML output inside a temporary file and copy it to the container
  • modify the container’s nmap command to output the content of that file when queried by HA

Pretty simple, isn’t it?

So, why install nmap in the container, rather than letting the tracker code do this all by itself? This is to avoid your modified nmap script being clobbered when the tracker code runs and doesn’t find nmap already installed.

So, this leaves us with… in the container, your /usr/bin/nmap content:

#!/bin/bash

if [ "$1" == "-V" ]; then
  echo ""
  echo "Nmap version 7.60 ( https://nmap.org )"
  echo "Platform: x86_64-pc-linux-gnu"
  echo "Compiled with: liblua-5.3.3 openssl-1.1.0g nmap-libssh2-1.8.0 libz-1.2.8 libpcre-8.39 libpcap-1.8.1 nmap-libdnet-1.12 ipv6"
  echo "Compiled without:"
  echo "Available nsock engines: epoll poll select"
else
  OUTPUT=/tmp/nmap-out.xml
  LASTCHECK=/tmp/nmap-check
  [ -f "$LASTCHECK" ] && {
    lastupdate="$(cat $LASTCHECK)"
  } || {
    lastupdate=0
  }
  [ -f "$OUTPUT" ] && {
    nmaptime="$(stat -c %Y $OUTPUT)"
  } || {
    nmaptime=0
  }
  [ "$lastupdate" != "$nmaptime" ] && {
    echo -n $nmaptime > $LASTCHECK
    # Yes there is still a small potential for a race condition. I know.
    cp $OUTPUT $OUTPUT.wrk
    # None of this is mandatory.
    # I'm just making sure the file is well formed.
    # Feel free to comment out if you are not interested.
    # BEGIN CHECK
    [ "$(which xmllint)" == "" ] && {
      apt-get install -y libxml2-utils
    }
    [ "$(xmllint $OUTPUT.wrk)" ] || {
      exit 0
    }
    # END CHECK
  }
  cat $OUTPUT.wrk
fi

On the host, your nmap run script (e.g. '/opt/scripts/run_nmap_device_tracker.sh`)

sudo /usr/bin/nmap -oX - 192.168.1.1/24 -F --host-timeout 5s > /tmp/nmap-out.xml && \
docker cp /tmp/nmap-out.xml homeassistant_main_1://tmp/

Do not forget to update your network information. You may be using a completely different subnet (I am, in fact, using 192.168.0.1/23)
And, of course, also use your own container’s name.

Finally, your cronjob:

crontab -e
* * * * * /opt/scripts/run_nmap_device_tracker.sh

That’s it. Told you there wasn’t much to it!

Hey, I just want to check: are you still using this? how has it held up over the years?

also, just to verify, you overwrite nmap in /usr/bin/ with the bash script right? you’re not creating an alias for /user/bin/nmap or anything similar?