top of page

The Connectivity Check Caper

7 minutes ago
3 min read

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.py

I 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/gen204

Then 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.target

Now 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 -f

Looks good:


Terminal window showing the status of my custom "gen204.service"; service is enabled and active (running) in green text

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:


Dark terminal window showing repeating python access logs with GET /generate_204 requests from a couple of different IP addresses.

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


Follow me:
  • Mastodon
  • Twitter
  • Youtube
  • flickr

 Technical Capers © Giles Moss 2023

bottom of page