使用 Windows API 检测强制门户
如何通过 Windows NLM_INTERNET_CONNECTIVITY 检测您的网络是否位于强制门户(Wi-Fi 热点)之后
引言
最近,我遇到了检测我的连接是否位于强制门户(Wi-Fi 热点)(请阅读此文档)的需求。我查阅了互联网,发现了关于 Mozilla 如何处理此问题的一些有趣的文章;以及 Safari 和其他可能的替代方案。但最终,并且本文将介绍的方法,我选择了使用 Windows 网络列表管理器 API 参考,并查看了 NLM_INTERNET_CONNECTIVITY
标志 此处。
背景
MSDN 提供了一个方便的 API 来查询网络连接属性。通过 NLM_INTERNET_CONNECTIVITY
,暴露了一组额外的属性,用于 IPV4 和 IPV6 网络协议;例如 NLM_INTERNET_CONNECTIVITY_WEBHIJACK
,这是我们想要找到的属性,以识别我们的网络连接是否位于强制门户之后。
Using the Code
这里的总体策略是使用网络列表管理器枚举已连接的连接。然后,通过 INetwork
接口查询 IPropertyBag
,查找所需的 NLM_INTERNET_CONNECTIVITY
标志。正如 MSDN 文档所建议:“可以使用 IPropertyBag
接口查询 INetwork
或 INetworkConnection
接口的 NA_InternetConnectivityV4
或 NA_InternetConnectivityV6
属性来检索这些连接标志。” 我首先尝试使用 INetworkConnection
接口查询 IPropertyBag
,但在检索标志时一直失败。因此,我切换到 INetwork
接口(其余代码保持不变),它就可以正常工作了。我没有花时间调查为什么 INetworkConnection
失败。但是,我想指出这一点,以防您遇到类似的情况。
最后,我们查看网络连接标志,检查 NLM_INTERNET_CONNECTIVITY_WEBHIJACK
标志是否已设置。如果此标志设置为 NLM_CONNECTIVITY_IPV6_INTERNET
或 NLM_CONNECTIVITY_IPV4_INTERNET
,则我们分别位于 IPV6 或 IPV4 的强制门户连接下。
以下是执行上述逻辑并设置布尔变量以检测是否检测到强制门户的代码片段
bool fCaptivePortalDetected = false;
// Initialize COM.
if (SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
{
// Declare a pointer to INetworkListManager
INetworkListManager* pNetworkListManager;
// Create instance of the CLSID_NetworkListManger COM object
if (SUCCEEDED(CoCreateInstance(CLSID_NetworkListManager, NULL,
CLSCTX_ALL, IID_INetworkListManager,
(LPVOID*)&pNetworkListManager)))
{
// Declare pointer to an IEnumNetworkConnections
IEnumNetworks* pEnum;
// Call to GetNetworks from INetworkListManager interface
if (SUCCEEDED(pNetworkListManager->GetNetworks
(NLM_ENUM_NETWORK_CONNECTED, &pEnum)) && pEnum != NULL)
{
INetwork *pINetwork;
HRESULT hr = pEnum->Next(1, &pINetwork, nullptr);
while (hr == S_OK)
{
if (pINetwork != NULL)
{
IPropertyBag *pNetworkPropertyBag;
HRESULT hrQueryInterface = pINetwork->QueryInterface
(IID_IPropertyBag, (LPVOID*)&pNetworkPropertyBag);
if (SUCCEEDED(hrQueryInterface 1) && pNetworkPropertyBag != nullptr)
{
NLM_CONNECTIVITY networkConnectivity;
VARIANT variantConnectivity;
if (SUCCEEDED(pINetwork->GetConnectivity(&networkConnectivity)))
{
if ((networkConnectivity &
NLM_CONNECTIVITY_IPV4_INTERNET) ==
NLM_CONNECTIVITY_IPV4_INTERNET)
{
VariantInit(&variantConnectivity);
if (SUCCEEDED(pNetworkPropertyBag->Read
(NA_InternetConnectivityV4,
&variantConnectivity, nullptr))
&& (V_UINT(&variantConnectivity) &
NLM_INTERNET_CONNECTIVITY_WEBHIJACK) ==
NLM_INTERNET_CONNECTIVITY_WEBHIJACK)
{
fCaptivePortalDetected = true;
}
auto t = V_UINT(&variantConnectivity);
VariantClear(&variantConnectivity);
}
if (!fCaptivePortalDetected && (networkConnectivity
& NLM_CONNECTIVITY_IPV6_INTERNET) ==
NLM_CONNECTIVITY_IPV6_INTERNET)
{
VariantInit(&variantConnectivity);
if (SUCCEEDED(pNetworkPropertyBag->Read
(NA_InternetConnectivityV6,
&variantConnectivity, nullptr)) &&
(V_UINT(&variantConnectivity) &
NLM_INTERNET_CONNECTIVITY_WEBHIJACK) ==
NLM_INTERNET_CONNECTIVITY_WEBHIJACK)
{
fCaptivePortalDetected = true;
}
VariantClear(&variantConnectivity);
}
}
}
pINetwork->Release();
}
if (fCaptivePortalDetected)
break;
hr = hr = pEnum->Next(1, &pINetwork, nullptr);
}
}
}
}
// Uninitialize COM.
// (This should be called on application shutdown.)
CoUninitialize();
参考和重要链接
我想说,由于本文使用了 Windows 函数,MSDN 库是您最好的朋友,也是您应该首先检查的信息来源。但同时,以下两篇主要文章帮助我理解了这些 API
- 强制门户 - https://msdn.microsoft.com/en-us/library/windows/hardware/dn408681.aspx
NLM_INTERNET_CONNECTIVITY
枚举 - https://msdn.microsoft.com/en-us/library/windows/desktop/gg445205(v=vs.85).aspx
历史
- 2016 年 3 月 27 日:初始版本