It's summer, and that means the attic gets hot. I'm looking to get some
kind of easy way to check the attic temperature from afar, so that I can
keep tabs on it, and so that I can adjust the fans and the windows
appropriately. This is really basic telemetry, a task that has been
done many times before, using nearly every network protocol ever invented.
I decided for 2021 that I wanted to do this monitoring with
some old-fashioned protocols, so the query tool of choice is
"finger". I found a very nice modern implementation called
finger2020 from
Michael Lazar (mozz), which is designed for single-user systems.
This runs under systemd, which makes it easy to install.
A simple crontab pulls temperature data from wherever it can
find it, which on a Linux system could be lots of things. From
past experience I have found that simply tracking the /sys/class/thermal
values from the kernel give you CPU temperature for whatever
computer you're running on, and that if your computer is hot,
the rest of your space is probably hot too. As a bonus it's easy
to grab with no additional external sensors needed.
I'm running Tailscale, which means there's a trustable inside
network on tailscale0
, and an untrusted outside network on
ethernet or wifi or whatever interfaces are exposed. This led
to the question: can you deliver different content to the world
depending on which interface the traffic comes in on, without
putting any of that logic into the program serving up the data?
After some spelunking through the systemd documentation and not
a lot of searchable examples from the net, I came up with either
a brilliant hack or an awful kludge to make this work. systemd
has a BindToDevice
directive in its socket files that lets you
associate a socket with an interface (not just its address,
which could change, but the interface itself). So I took
the unit files from finger2020, replicated them three ways,
and for each one added an appropriate BindToDevice=tailscale0
or whatever interface I had and then created a service file
that pointed to that socket.
The slightly weird part is that if you are on a machine querying
itself, even if you specify an IP address of an external interface,
you'll get packets on the loopback (lo) interface. So one socket
handles loopback, one handles eth0 (for queries on a public network),
and one handles tailscale0 (for queries on the private VPN).
A more elegant approach if you want to change your server
is to look up the address from Tailscale, using the function
tailscale.WhoIs. An example of this logic is in the
hello service run by Tailscale
which switches on the results of the WhoIs call and
lets you further serve individual content because you
know not just the interface the user is coming in on
but the user name! So very handy if you have more than
one user on your network.
The BindToDevice approach should work for any VPN you
are using, and it should also let you do deliver services
that are reachable only from private addresses and that
don't ever show up on the public Internet, even if the
code you are running is ignorant of that distinction.
Brilliant hack or awful kludge? Hard to say yet.