Network perfection
5 minute read
Shortly after moving to Lund I found out that internet was not included in the rent, and after ordering internet from an external provider I didn't get any router. This presented a great opportunity to create the perfect local area network.
Tools Used
- dnsmasq: For DNS and DHCP.
- cloudflared: For DNS over HTTPS.
- hostapd: For access point configuration.
- Monero miner: For mining Monero.
- netdata: For monitoring resource usage.
- TailScale: For VPN access to the LAN.
- Python: For handling mining broadcasts.
Method
Choosing the hardware
I had an old laptop with an ethernet port that I was going to use as a general server anyway, so I thought why not convert it to a router instead of buying a new. After all, there cannot be that much of a difference between a "real" routers network card, and a general computers network card. I had debian installed on the server, and as linux has great networking support for other reasons there were great premade tools for this.
Configuring DNS
I used dnsmasq for DNS and DHCP and configured it with the following options:
interface=NETWORK_CARD_HERE
cache-size=1024 # Cache 1024 DNS requests
dhcp-authoritative # Assign router as DHCP server
dnssec # Enable DNS response versification
dhcp-option=option:dns-server,10.10.10.10 # Assign router as DNS server
dhcp-option=option:domain-name,lan # Assign domain name for DHCP clients
# Disable DNS handling, as cloudflared handles it with DoH
no-resolv
server=127.0.0.1#5053
# Enable local DNS resolution
domain=lan
local=/lan/
There is no great way to configure a prime number DHCP network, but you can set the dhcp-range manually for each entry, which is what I did below.
# Configure IP space for DHCP to only use prime numbers
dhcp-range=10.1.1.1,10.1.1.1,255.0.0.0,infinite
dhcp-range=10.2.2.2,10.2.2.2,255.0.0.0,infinite
dhcp-range=10.3.3.3,10.3.3.3,255.0.0.0,infinite
dhcp-range=10.5.5.5,10.5.5.5,255.0.0.0,infinite
dhcp-range=10.7.7.7,10.7.7.7,255.0.0.0,infinite
dhcp-range=10.11.11.11,10.11.11.11,255.0.0.0,infinite
dhcp-range=10.13.13.13,10.13.13.13,255.0.0.0,infinite
dhcp-range=10.17.17.17,10.17.17.17,255.0.0.0,infinite
dhcp-range=10.19.19.19,10.19.19.19,255.0.0.0,infinite
dhcp-range=10.23.23.23,10.23.23.23,255.0.0.0,infinite
dhcp-range=10.29.29.29,10.29.29.29,255.0.0.0,infinite
dhcp-range=10.31.31.31,10.31.31.31,255.0.0.0,infinite
dhcp-range=10.37.37.37,10.37.37.37,255.0.0.0,infinite
dhcp-range=10.41.41.41,10.41.41.41,255.0.0.0,infinite
dhcp-range=10.43.43.43,10.43.43.43,255.0.0.0,infinite
dhcp-range=10.47.47.47,10.47.47.47,255.0.0.0,infinite
dhcp-range=10.53.53.53,10.53.53.53,255.0.0.0,infinite
dhcp-range=10.59.59.59,10.59.59.59,255.0.0.0,infinite
dhcp-range=10.61.61.61,10.61.61.61,255.0.0.0,infinite
dhcp-range=10.67.67.67,10.67.67.67,255.0.0.0,infinite
dhcp-range=10.71.71.71,10.71.71.71,255.0.0.0,infinite
dhcp-range=10.73.73.73,10.73.73.73,255.0.0.0,infinite
dhcp-range=10.79.79.79,10.79.79.79,255.0.0.0,infinite
dhcp-range=10.83.83.83,10.83.83.83,255.0.0.0,infinite
dhcp-range=10.89.89.89,10.89.89.89,255.0.0.0,infinite
dhcp-range=10.97.97.97,10.97.97.97,255.0.0.0,infinite
dhcp-range=10.101.101.101,10.101.101.101,255.0.0.0,24h
dhcp-range=10.103.103.103,10.103.103.103,255.0.0.0,24h
dhcp-range=10.107.107.107,10.107.107.107,255.0.0.0,24h
dhcp-range=10.109.109.109,10.109.109.109,255.0.0.0,24h
dhcp-range=10.113.113.113,10.113.113.113,255.0.0.0,24h
dhcp-range=10.127.127.127,10.127.127.127,255.0.0.0,24h
dhcp-range=10.131.131.131,10.131.131.131,255.0.0.0,24h
dhcp-range=10.137.137.137,10.137.137.137,255.0.0.0,24h
dhcp-range=10.139.139.139,10.139.139.139,255.0.0.0,24h
dhcp-range=10.149.149.149,10.149.149.149,255.0.0.0,24h
dhcp-range=10.151.151.151,10.151.151.151,255.0.0.0,24h
dhcp-range=10.157.157.157,10.157.157.157,255.0.0.0,24h
dhcp-range=10.163.163.163,10.163.163.163,255.0.0.0,24h
dhcp-range=10.167.167.167,10.167.167.167,255.0.0.0,24h
dhcp-range=10.173.173.173,10.173.173.173,255.0.0.0,24h
dhcp-range=10.179.179.179,10.179.179.179,255.0.0.0,24h
dhcp-range=10.181.181.181,10.181.181.181,255.0.0.0,24h
dhcp-range=10.191.191.191,10.191.191.191,255.0.0.0,24h
dhcp-range=10.193.193.193,10.193.193.193,255.0.0.0,24h
dhcp-range=10.197.197.197,10.197.197.197,255.0.0.0,24h
dhcp-range=10.199.199.199,10.199.199.199,255.0.0.0,24h
dhcp-range=10.211.211.211,10.211.211.211,255.0.0.0,24h
dhcp-range=10.223.223.223,10.223.223.223,255.0.0.0,24h
dhcp-range=10.227.227.227,10.227.227.227,255.0.0.0,24h
dhcp-range=10.229.229.229,10.229.229.229,255.0.0.0,24h
dhcp-range=10.233.233.233,10.233.233.233,255.0.0.0,24h
dhcp-range=10.239.239.239,10.239.239.239,255.0.0.0,24h
dhcp-range=10.241.241.241,10.241.241.241,255.0.0.0,24h
dhcp-range=10.251.251.251,10.251.251.251,255.0.0.0,24h
Apparently I included 1 there aswell. Not sure why, as its not prime, but I like 1 so I'll keep it anyway.
Cloudflared with DoH
I decided to use cloudflares tool cloudflared for DNS lookups locally on the router. I like this approach as all requests will be encrypted now, usually that is quite trubblesome to achieve on a network. I guess the downside is that this requires another request from dnsmasq to the cloudflared service, and also that DoH has some overhead (because of the handshakes and TCP stuff) which makes it slightly slower. In general I don't think this is much of a problem, especially as DNS responses are usually cached on the device or the router anyway.
Here is the config I used for cloudflared:
proxy-dns: true
proxy-dns-port: 5053
proxy-dns-address: 0.0.0.0
proxy-dns-upstream:
- https://1.1.1.1/dns-query
- https://1.0.0.1/dns-query
Setting up the AP
I chose not to use WPA 3 as I want crosscommunication, and the option of interception. Also, I trust the devices on LAN anyway.
Here is the config I used for hostapd (/etc/hostapd/hostapd.conf
):
interface=WIRELESS_INTERFACE
ssid=WIFI_NAME
hw_mode=g
channel=6
ieee80211n=1
wmm_enabled=1
ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40]
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=WIFI_PASSWORD
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
Setting up mining network
As I have controll over the router, it makes it easier to recognize when my phone joins/leaves the network. All device association/disassociation events are logged in /var/log/syslog
. Even if the device does not actively send a disassociation packet but still becomes idle, such as if the device runns out of battery, or simply leaved the LAN physically, the device will be flagged as disassociated after a few minutes. This is great, but also creates some false positives when the phone is simply inactive (mainly during night). For this reason the router will try to ping my phone aswell, to assure that it really isn't connected anymore.
After my phone (and persumably me aswell) has left the network, the router will broadcast to all other devices that its mining time over UDP. If any device misses the initial broadcast, the status will continue to be re-sent every 5 minutes. If any miner would boot after my devices have left, they can ask the router for the mining status. The router also sends Wake on WLAN (WoWLAN) packets to all preconfigured computers that should wake up and start mining when I leave. This primairly includes my main PC, which isn't turned on all the time because of fan noise.
All mining devices on the network (including the router) will then start a local instance of a monero miner, working tirelessly while I'm away from home.
I don't pay for electricity, and the fan noise does not bother me while I'm away from home. As a bonus my room is nice and warm when I come back from university in the winter. The network does not generate any remarkable revenue though, but in any case there is really no downside to this method.
Here is the code for the mining client:
import socket
import subprocess
from lib import query_miner_status
def udp_listener(port: int):
"""
Listen for UDP broadcasts on a specific port and execute or terminate mxrig based on the received message.
"""
# Create a UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Bind the socket to the port
sock.bind(("", port))
# Check with network if xmrig should be running, default to False if no response
active = query_miner_status("10.255.255.255") == "START"
print(f"Listening for UDP broadcasts on port {port}...")
while True:
# Receive data from the socket (up to 1024 bytes)
data, addr = sock.recvfrom(1024)
message = data.decode("utf-8")
# Check if the message is 'START'
if message == "START" and not active:
active = True
print(f"Received 'START' from {addr}. Launching xmrig...")
subprocess.Popen(["sudo", "xmrig", "--config=/root/.xmrig.json"])
elif message == "STOP" and active:
active = False
print(f"Received 'STOP' from {addr}. Terminating xmrig...")
subprocess.run(["sudo", "pkill", "-9", "xmrig"])
elif message == "STATUS":
print(
f"Received 'STATUS' from {addr}. xmrig is {'active' if active else 'inactive'}"
)
# Send the status back to the client
sock.sendto(bytes("START" if active else "STOP", "utf-8"), addr)
if __name__ == "__main__":
udp_listener(1337)
Here is the code for the mining server:
def main():
global current_mode, timer
run_subprocess(["tail", "-n0", "-f", "/var/log/syslog"])
logging.info("Remember to run this as su!")
associated_pattern = "IEEE 802.11: associated"
disassociated_pattern = "IEEE 802.11: disassociated"
try:
p = subprocess.Popen(
["tail", "-n0", "-f", "/var/log/syslog"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
for line in iter(p.stdout.readline, ""):
if associated_pattern in line and TARGET_DEVICE in line:
logging.info("Target connected, broadcasting kill signal...")
current_mode = MESSAGE.STOP
send_udp_broadcast(MESSAGE.STOP, UDP_PORT)
elif disassociated_pattern in line and TARGET_DEVICE in line and not ping_device(TARGET_DEVICE_IP):
logging.info("Target disconnected, broadcasting start signal...")
current_mode = MESSAGE.START
send_udp_broadcast(MESSAGE.START, UDP_PORT)
wake_network_devices()
if timer is not None:
timer.cancel() # Cancel the previous timer
broadcast_status_thread() # Start a new timer
except Exception as e:
logging.error(f"An error occurred: {e}")
if __name__ == "__main__":
main()
Result
The finnished network has a lot of great features.
- Secure DNS: All DNS requests use DoH by default.
- DNS Caching: The server caches 1024 DNS key-value pairs.
- Prime IP Address Allocation: All IP addresses that are dealt by DHCP are primes.
- Device Leave Broadcasting: The router broadcasts to LAN when I leave the network.
- Port Forwarding: Cloudflared installed for port forwarding of services.
- VPN: TailScale is installed directly on the router for easy LAN access from anywhere.
- Remote Monitoring: I use netdata for monitoring resource usage.
- Free Money: The network passivly generates (very little) income by mining monero.
It is actually really cool to see the network react to me comming home. It almost feels like the computers are alive, like in ToyStory when the toys only move when not observed. Anyway, I'm really happy with the setup and I think it is a great alternative to buying a router. I also think it is a great way to learn about networking, as you have to configure everything yourself.