![为什么 Android 的强制门户检测不触发浏览器窗口?](https://linux22.com/image/129857/%E4%B8%BA%E4%BB%80%E4%B9%88%20Android%20%E7%9A%84%E5%BC%BA%E5%88%B6%E9%97%A8%E6%88%B7%E6%A3%80%E6%B5%8B%E4%B8%8D%E8%A7%A6%E5%8F%91%E6%B5%8F%E8%A7%88%E5%99%A8%E7%AA%97%E5%8F%A3%EF%BC%9F.png)
我有一个 Raspberry Pi,它使用 nginx 托管一个简单的网站。 RPi 充当无线接入点 - 用户可以连接到其无线网络,RPi 为他们提供一个 IP(它运行 DHCP 服务器),然后他们可以访问该站点。
因为 RPi 实际上并不为用户提供互联网(仅此一个站点),所以我让用户更容易找到该站点。我不知道该站点的确切 URL,而是告诉 DHCP 服务器告诉客户端使用的 dns 服务器 (dnsmasq),以解析对我的 RPi (192.168.30.1) 的 LAN IP 的所有查询。
此时,我的 nginx 配置中有一个条目:
- 如果用户请求的host字段不是MyRPiServer.com,则发送302重定向到MyRPiServer.com
- 如果主机是MyRPiServer.com,则为本地网站提供服务
这效果棒极了。
我想更进一步。当 Android 连接到无线网络时,它会尝试连接到http://connectivitycheck.gstatic.com/generate_204(或其他类似的谷歌页面之一)专门检查请求是否被重定向。如果收到 204 代码,则认为一切正常。如果没有,则假定它位于强制门户后面,并弹出一个浏览器窗口,打开强制门户登录。
出于某种原因,当我告诉 nginx 使用 302 重定向甚至 200(带有一些文本)响应对generate_204 页面的请求时,Android 不会弹出浏览器。
我有一个带有内置热点功能的 mikrotik 路由器,它确实让 android 弹出浏览器并使用强制门户登录(在同一台测试手机上)。当我查看它发送给我的客户端的流量时,它是一个简单的 HTTP 200,带有一些文本,就像我的一样。
似乎确实有效的一件事是,如果我禁用 DNS 服务器将所有内容解析为 192.168.30.1,并使用 iptables 将端口 80 重定向到 RPi 上的本地主机。
有谁知道为什么在 Android 的 Captive Portal 检测中重定向端口 80 有效,但配置 DNS 服务器将所有内容解析到 RPi 本地 IP 却不起作用?
查看此处找到的代码https://stackoverflow.com/a/14030276/4258196,看来 Android 唯一关心的是它是否可以连接到主机,以及是否返回 HTTP 204。就我而言,它肯定正在连接,并且绝对不会收到 204 返回(nginx 日志显示它发送 HTTP 302 和 HTTP 200)。
我的手机运行的是 Android 8,所以我想现在链接的代码可能有所不同。
答案1
我找到的解决方案是将我的 dnsmasq 设置为继续将所有内容解析为 192.168.30.1,但对于强制门户测试服务器有一些例外:
10.45.12.1 clients3.google.com
10.45.12.1 clients.l.google.com
10.45.12.1 connectivitycheck.android.com
10.45.12.1 connectivitycheck.gstatic.com
10.45.12.1 play.googleapis.com
基本上,如果有任何尝试使用我们的 DNS 服务器解析上述域,他们会得到 10.45.12.1 的回复。
10.45.12.1是一个随机IP,不属于任何东西。它不必是 192.168.30.1。
域列表来自这里。
完成此操作后,只要您连接到 RPi 的 WiFi,它就会弹出显示我的站点的浏览器页面。
这是一个解决方案,但并不能真正回答为什么会发生这种情况。如果有人能解释一下,我将不胜感激。
编辑:
使用此解决方案,如果我多次连接和断开设备上的 WiFi,Android 有时会弹出登录页面,有时则不会。对于任何做类似事情的人,最后,为了更好的解决方案,我选择了这个:
- 让 DNSmasq 将所有内容解析为 10.45.12.1(或 192.168.30.0/24 子网之外的任何内容)
- 它必须位于 192.168.30.0/24 子网(或任何 LAN 子网)之外,否则客户端将尝试使用 ARP 来找出给定设备的 MAC 地址,并且会失败,因为该设备实际上并不存在
- 让 iptables 将来自 wifi 接口的端口 80 转发到本地主机
这适用于 Android、OS X 和 Windows。我没有 iOS 设备来测试这个。根据这,iOS 设备可能需要一些额外的工作。
我仍然很好奇为什么这是必要的,以及为什么将所有内容解析为 192.168.30.1 最初不起作用。
答案2
我花了很多时间试图弄清楚这一点并最终做到了。你可以在这里看到代码https://github.com/tretos53/Captive-Portal
它是指向公共 IP、iptables 和 nginx 重定向的域的组合。
答案3
造成这种情况的原因有很多,也可能是您的特定硬件手机制造商造成的。我目前为 Mongoose OS 构建了我的 Captive Portal 库,用于响应所有带有设备 IP 的 DNS 查询,并且除了三星设备之外的设备没有任何问题,这些设备似乎需要 200 文本响应。
这是库堆栈: https://github.com/tripflex/captive-portal-wifi-stack
具体来说,这里是强制门户处理,以及我使用的端点的详细信息,以及自述文件中有关如何处理的详细信息:
https://github.com/tripflex/captive-portal
您将看到我用于三星设备的一种解决方案是使用元刷新标记返回 200 以及生成的 HTML 文件:
https://github.com/tripflex/captive-portal#cportalredirect_file-setting
<html>
<head>
<title>Redirecting to Captive Portal</title>
<meta http-equiv='refresh' content='0; url=PORTAL_URL'>
</head>
<body>
<p>Please wait, refreshing. If page does not refresh, click <a href='PORTAL_URL'>here</a> to login.</p>
</body>
</html>
但实际上最重要的是为每个特定设备设置端点处理:
https://github.com/tripflex/captive-portal#known-endpoints
/mobile/status.php
安卓 8.0(三星 s9+)/generate_204
安卓/gen_204
安卓/ncsi.txt
视窗/hotspot-detect.html
iOS/OSX/hotspotdetect.html
iOS/OSX/library/test/success.html
iOS系统/success.txt
操作系统X/kindle-wifi/wifiredirect.html
根据请求使用 com.android.captiveportallogin 进行 Kindle/kindle-wifi/wifistub.html
Kindle 在使用强制门户登录窗口请求之前(也许是为了检测?)