Quirks with the Android Captive Portal

I have recently gotten my hands on a Raspberry Pi 3B+; in order to offset the ennui that settles over me once school is over, I decided to pursue some kind of project with it; I decided ultimately to create a guest network for my home network so that visitors do not need to bother me for my WPA2 key every time someone comes over. n Of course, several router firmwares (e.g. Linksys firmware comes to mind) have this feature baked in; however setting up a guest wireless access point (WAP) on a secondary piece of equipment allowed me to (1) continue broadcasting 5GHz and 2.4GHz signals for my main WAP (I would need to sacrifice an antenna to run the guest network) and (2) it just seemed really fun to do.

In the near future I will likely do a full writeup of the procedure I followed to create my guest network; the topic I am writing on today, however, is simply an exposition of some quirks I found while setting up the captive portal for Android devices.

As you may know, a captive portal is the first page presented to newly-connected wireless users; it often features a ToS agreement or a login page. The way these captive portals work is as follows:

  1. A new user connects to the (open) wireless network
  2. The user’s device checks if it can access the Internet by requesting an arbitrary page
  3. If it does not receive the expected response then the user is likely behind a captive portal and needs to sign in or agree to a ToS
  4. A browser is opened and the user is allowed to log in or agree

Captive portals are often used in bars, hotels, businesses and anywhere free wireless is available.

The Quirk

During the (2)nd step, Android OS (at least Android 9), may check any one of these URLs:

clients3.google.com
clients.l.google.com
connectivitycheck.android.com
connectivitycheck.gstatic.com
play.googleapis.com

Android knows what the response from these pages should be; if the response differs from what it expects then Android continues to the (3)rd step. However if Android notices that the IP address of the webserver is the same as the IP address of the DNS server (or maybe the router!) then Android (Pie) assumes something is broken and refuses to show the login page, instead insisting that the WAP has no Internet access nor captive portal.

The way to mitigate this is to resolve the above addresses into IP addresses which differ from the WAP’s address. Here is a snippet from my /etc/hosts file on the WAP:

0.0.0.0 clients3.google.com
0.0.0.0 clients.l.google.com
0.0.0.0 connectivitycheck.android.com
0.0.0.0 connectivitycheck.gstatic.com
0.0.0.0 play.googleapis.com

Notice that this configuration forces the URLs to resolve to an invalid IP address; any address will do so long as a query to that IP does not return the response which is expected (and of course an invalid IP address won’t return an expected response!); Android then will magically show you the captive portal! (assuming you got the DNS resolution bit right).

Enjoy! ^-^