The Connectivity Check Caper
Sometimes, Android phones in my house "fall off" the WiFi. By which I mean, the phones determine they can't reach the internet over WiFi, and appear to disconnect. They haven't truly disconnected; but Android has this thing by which it says "connected but no internet"and gives up trying to use WiFi for a while.
I have checked repeatedly. As far as I've been able to tell, nothing is wrong with my WiFi or my internet (no, they're not the same thing) - other devices are fine and a quick reconnect of the offending Android device always persuades it that it can, after all, use the WiFi.
I haven't really figured out what's upsetting the Android Connectivity Check. It could be a minor delay in DNS resolution (my PiHole might be slow sometimes), it could be a brief WiFi dropout as I move around. Who knows.
In order to eliminate the possibility of the problem being caused by a slow response from whatever website Android is using to test for connection, I wondered if I could set up a spoof site myself so the bothersome phones are always guaranteed a quick reply to their "am I online on WiFi" probe. And, thanks to a chat with a couple of like-minded people on Mastodon, it turns out that this is indeed doable.
Android's Connectivitycheck
To determine if it's online (and if there's a captive WiFi portal in the way) Android reaches out to a special web service at the URL http://connectivitycheck.gstatic.com/generate_204. This special website returns a HTTP 204 "no content" success message, which is what Android is specifically looking for. you can try it from anywhere though a browser doesn't do much with it. But pop it into cURL or Insomnia or whatever and you'll see it working.
Python Webserver
I needed a super-simple web server to return HTTP 204 to a /generate_204 endpoint. There are plenty of ways this could be done and I chose a simple Python script using the built-in http server. For the very lightweight purpose of service a few phones' connectivity checks, this feels fine.
Full disclosure: I'm not overly familiar with Python so I asked Copilot to suggest the simplest solution.
This is the whole thing:
# /opt/gen204/server.py
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/generate_204":
print(f"204 request from {self.client_address[0]}", flush=True)
self.send_response(204)
self.end_headers()
else:
self.send_response(404)
self.end_headers()
if __name__ == "__main__":
HTTPServer(("", 80), Handler).serve_forever()Hosting
I spun up a new VM to run this. On a scale of one to colossal overkill, this certainly edges towards the more extreme end, but as I need to serve this on port 80, plus having an ESXi box with spare resources for another VM, it just seemed easiest.
OS: Debian 13.7 Trixie, SSH Server, no GUI. 2 GB RAM, 2 vCPU, 16GB HDD. Configure for a static IP. Apply all updates.
I put the Python script inside a venv, again for overkill (there are no required pip modules) but maybe it's good practice.
# Create a new venv
python3 -m venv /opt/gen204/.venv
# Edit the http server script
nano /opt/gen204/server.pyI usually run things in Docker, but that felt too heavy for this application, even by my own standards. So, instead, I just created a systemd unit file to start the script on boot.
# Add a group and a user for a user for systemd to run the script as
sudo groupadd --system gen204
sudo useradd --system --gid gen204 --no-create-home --shell /usr/sbin/nologin gen204
# Adjust permissions on the file path
sudo chown -R root:gen204 /opt/gen204
sudo chmod -R o-rwx /opt/gen204Then the systemd unit file.
Note the AmbientCapabilities=CAP_NET_BIND_SERVICE option, which allows the script to bind to a reserved port without requiring root privileges.
# /etc/systemd/system/gen204.service
[Unit]
Description=gen204 - responds 204 to /generate_204
After=network.target
[Service]
Type=simple
User=gen204
Group=gen204
WorkingDirectory=/opt/gen204
ExecStart=/opt/gen204/.venv/bin/python /opt/gen204/server.py
Restart=on-failure
RestartSec=2
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
[Install]
WantedBy=multi-user.targetNow make it do something:
sudo systemctl daemon-reload
sudo systemctl enable gen204.service
sudo systemctl start gen204.service
# View logs (optional)
journalctl -u gen204.service -fLooks good:

DNS
DNS in my house is served from a PiHole, so it was trivial to add a couple of local DNS entries to redirect requests to the official connectivity check websites to my own local spoof. I redirected the two addresses:
Proof of the Pudding
After checking the firewalling between my subnets at home to ensure the web server is accessible from any of my subnets, I changed DNS and within minutes started seeing requests from the Python log:

After a little while I saw requests from other devices as well, suggesting it's working as it needs.
So far my Android phone has not 'fallen off' the WiFi since I switched this on, but who knows if it's a long-term fix.




Comments