在现代网络环境中,Captive Portal(劫持门户)是一种常见的技术,用于在用户连接到网络之前强制他们访问特定的登录页面。这在公共Wi-Fi热点、学校和企业网络中尤为常见。然而,有时可能需要检测网络连接是否被劫持,以便采取相应的措施。本文将介绍如何使用Windows Network List Manager API来实现这一功能。
微软开发者网络(MSDN)提供了一个方便的API,用于查询网络连接属性。通过NLM_INTERNET_CONNECTIVITY标志,可以获取IPV4和IPV6网络协议的额外属性,例如NLM_INTERNET_CONNECTIVITY_WEBHIJACK,这是想要找到的属性,以识别网络连接是否位于Captive Portal后面。
总体策略是使用Network List Manager来枚举已连接的连接。然后,通过INetwork接口查询IPropertyBag,寻找所需的NLM_INTERNET_CONNECTIVITY标志。正如MSDN文档所建议的:“这些连接标志可以通过查询INetwork或INetworkConnection接口的IPropertyBag接口的NA_InternetConnectivityV4或NA_InternetConnectivityV6属性来检索。”首先尝试了INetworkConnection接口来查询IPropertyBag,但在检索标志时失败了。所以转而使用INetwork接口(其余代码保持不变),它就工作了。没有介意调查为什么INetworkConnection失败了。然而,如果遇到了类似的问题,认为应该指出这一点。
最后,检查网络连接标志,查看NLM_INTERNET_CONNECTIVITY_WEBHIJACK标志是否已设置。如果这个标志对于NLM_CONNECTIVITY_IPV6_INTERNET或NLM_CONNECTIVITY_IPV4_INTERNET已设置,那么就在IPV6或IPV4的Captive Portal连接下。
以下是执行上述逻辑并设置布尔变量以检测Captive Portal是否被检测到的代码片段:
bool fCaptivePortalDetected = false;
// 初始化COM。
if (SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED))) {
// 声明INetworkListManager指针
INetworkListManager* pNetworkListManager;
// 创建CLSID_NetworkListManger COM对象的实例
if (SUCCEEDED(CoCreateInstance(CLSID_NetworkListManager, NULL,
CLSCTX_ALL, IID_INetworkListManager,
(LPVOID*)&pNetworkListManager))) {
// 声明IEnumNetworkConnections指针
IEnumNetworks* pEnum;
// 调用INetworkListManager接口的GetNetworks
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) && 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 = pEnum->Next(1, &pINetwork, nullptr);
}
}
}
}
// 反初始化COM。
// (这应该在应用程序关闭时调用。)
CoUninitialize();