Search code examples
cwindowswinpcapndisnpcap

How to enable 802.11 monitor mode (DOT11_OPERATION_MODE_NETWORK_MONITOR) in a NDIS 6 filter driver?


I have ported WinPcap to a NDIS 6 filter driver: https://github.com/nmap/npcap. But it still doesn't support capturing all 802.11 native packets (like control and management frames are not captured).

I noticed there is a way setting DOT11_OPERATION_MODE_NETWORK_MONITOR for the wireless adapter using WlanSetInterface function. But this call succeeds (the return value is OK, and my wi-fi network disconnects after this call). But the problem is I can't see any packets on the Wi-Fi interface using Wireshark, not even the 802.11 data in fake ethernet form. So there must be something wrong with it.

I know that from NDIS 6 and vista, enabing this feature is possible (at least Microsoft's own Network Monitor 3.4 supports this).

So I want to know how to enable monitor mode for the NDIS 6 version WinPcap? Thanks.

My code is shown as below:

// WlanTest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <wlanapi.h>

#define WLAN_CLIENT_VERSION_VISTA 2

void SetInterface(WLAN_INTF_OPCODE opcode, PVOID* pData, GUID* InterfaceGuid)
{
    DWORD dwResult = 0;
    HANDLE hClient = NULL;
    DWORD dwCurVersion = 0;
    DWORD outsize = 0;

    // Open Handle for the set operation
    dwResult = WlanOpenHandle(WLAN_CLIENT_VERSION_VISTA, NULL, &dwCurVersion, &hClient);
    dwResult = WlanSetInterface(hClient, InterfaceGuid, opcode, sizeof(ULONG), pData, NULL);
    WlanCloseHandle(hClient, NULL);

}

// enumerate wireless interfaces
UINT EnumInterface(HANDLE hClient, WLAN_INTERFACE_INFO sInfo[64])
{
    DWORD dwError = ERROR_SUCCESS;
    PWLAN_INTERFACE_INFO_LIST pIntfList = NULL;
    UINT i = 0;

    __try
    {
        // enumerate wireless interfaces
        if ((dwError = WlanEnumInterfaces(
            hClient,
            NULL,               // reserved
            &pIntfList
            )) != ERROR_SUCCESS)
        {
            __leave;
        }

        // print out interface information
        for (i = 0; i < pIntfList->dwNumberOfItems; i++)
        {
            memcpy(&sInfo[i], &pIntfList->InterfaceInfo[i], sizeof(WLAN_INTERFACE_INFO));
        }

        return pIntfList->dwNumberOfItems;
    }
    __finally
    {
        // clean up
        if (pIntfList != NULL)
        {
            WlanFreeMemory(pIntfList);
        }
    }
    return 0;
}

// open a WLAN client handle and check version
DWORD
OpenHandleAndCheckVersion(
    PHANDLE phClient
    )
{
    DWORD dwError = ERROR_SUCCESS;
    DWORD dwServiceVersion;
    HANDLE hClient = NULL;

    __try
    {
        *phClient = NULL;

        // open a handle to the service
        if ((dwError = WlanOpenHandle(
            WLAN_API_VERSION,
            NULL,               // reserved
            &dwServiceVersion,
            &hClient
            )) != ERROR_SUCCESS)
        {
            __leave;
        }

        // check service version
        if (WLAN_API_VERSION_MAJOR(dwServiceVersion) < WLAN_API_VERSION_MAJOR(WLAN_API_VERSION_2_0))
        {
            // No-op, because the version check is for demonstration purpose only.
            // You can add your own logic here.
        }

        *phClient = hClient;

        // set hClient to NULL so it will not be closed
        hClient = NULL;
    }
    __finally
    {
        if (hClient != NULL)
        {
            // clean up
            WlanCloseHandle(
                hClient,
                NULL            // reserved
                );
        }
    }

    return dwError;
}

// get interface state string
LPWSTR
GetInterfaceStateString(__in WLAN_INTERFACE_STATE wlanInterfaceState)
{
    LPWSTR strRetCode;

    switch (wlanInterfaceState)
    {
    case wlan_interface_state_not_ready:
        strRetCode = L"\"not ready\"";
        break;
    case wlan_interface_state_connected:
        strRetCode = L"\"connected\"";
        break;
    case wlan_interface_state_ad_hoc_network_formed:
        strRetCode = L"\"ad hoc network formed\"";
        break;
    case wlan_interface_state_disconnecting:
        strRetCode = L"\"disconnecting\"";
        break;
    case wlan_interface_state_disconnected:
        strRetCode = L"\"disconnected\"";
        break;
    case wlan_interface_state_associating:
        strRetCode = L"\"associating\"";
        break;
    case wlan_interface_state_discovering:
        strRetCode = L"\"discovering\"";
        break;
    case wlan_interface_state_authenticating:
        strRetCode = L"\"authenticating\"";
        break;
    default:
        strRetCode = L"\"invalid interface state\"";
    }

    return strRetCode;
}

int main()
{
    HANDLE hClient = NULL;
    WLAN_INTERFACE_INFO sInfo[64];
    RPC_CSTR strGuid = NULL;

    TCHAR szBuffer[256];
    DWORD dwRead;
    if (OpenHandleAndCheckVersion(&hClient) != ERROR_SUCCESS)
        return -1;

    UINT nCount = EnumInterface(hClient, sInfo);
    for (UINT i = 0; i < nCount; ++i)
    {
        if (UuidToStringA(&sInfo[i].InterfaceGuid, &strGuid) == RPC_S_OK)
        {
            printf(("%d. %s\n\tDescription: %S\n\tState: %S\n"),
                i,
                strGuid,
                sInfo[i].strInterfaceDescription,
                GetInterfaceStateString(sInfo[i].isState));

            RpcStringFreeA(&strGuid);
        }
    }

    UINT nChoice = 0;
//  printf("for choice wireless card:");
// 
//  if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE), szBuffer, _countof(szBuffer), &dwRead, NULL) == FALSE)
//  {
//      puts("error input");
//      return -1;
//  }
//  szBuffer[dwRead] = 0;
//  nChoice = _ttoi(szBuffer);
// 
//  if (nChoice > nCount)
//  {
//      puts("error input.");
//      return -1;
//  }

    //ULONG targetOperationMode = DOT11_OPERATION_MODE_EXTENSIBLE_STATION;
    ULONG targetOperationMode = DOT11_OPERATION_MODE_NETWORK_MONITOR;

    SetInterface(wlan_intf_opcode_current_operation_mode, (PVOID*)&targetOperationMode, &sInfo[nChoice].InterfaceGuid);

    return 0;
}

Update:

Guy has made me clear about what should the high-level library side of WinPcap do about the monitor mode, in nature is setting/getting OID values. But what should the WinPcap driver do, do I need to change the driver? I think the WlanSetInterface call is actually doing the same thing as setting the DOT11_OPERATION_MODE_NETWORK_MONITOR using OID request? Does the fact that it doesn't work mean that the npf driver also needs some kind of changes?


Solution

  • (Answer updated for question update and followup comments.)

    Use pcap_oid_set_request_win32(), which is in pcap-win32.c in the version of libpcap in the master branch, to do OID setting/getting operations. If p->opt.rfmon is set in pcap_activate_win32(), set the OID OID_DOT11_CURRENT_OPERATION_MODE with a DOT11_CURRENT_OPERATION_MODE structure with uCurrentOpMode set to DOT11_OPERATION_MODE_NETWORK_MONITOR.

    For pcap_can_set_rfmon_win32(), try to get a handle for the device (note that this is done before the activate call) and, if that succeeds, use pcap_oid_get_request_win32() to attempt to get the value of that OID; if it succeeds, you can set it, otherwise, either you can't set it or you got an error.

    The driver already supports a generic get/set OID operation - that's what PacketRequest() uses, and pcap_oid_get_request_win32()/pcap_oid_set_request_win32() are implemented atop PacketRequest(), so it's what they use.

    As I indicated in messages in the thread you started on the wireshark-dev list, the code that handles receive indications from NDIS has to be able to handle "raw packet" receive indications, and you might have to add those to the NDIS packet filter as well. (And you'll have to hack dumpcap, if you're going to use Wireshark to test the changes; you won't be able to change NPcap so that people can just drop it in and existing versions of Wireshark will support monitor mode.)

    I also indicated there how to query a device to find out whether it supports monitor mode.

    As for turning monitor mode back off, that's going to require driver, packet.dll, and libpcap work. In the drivers:

    • in the NDIS 6 driver, for each interface, have a count of "monitor mode instances" and a saved operating mode and, for each opened NPF instance for an interface, have a "monitor mode" flag;
    • in the Windows 9x and NDIS 4/5 drivers, add a "turn on monitor mode" BIOC call, which always fails with ERROR_NOT_SUPPORTED;
    • in the NDIS 6 driver, add the same BIOC call, which, if the instance's "monitor mode" flag isn't set, attempts to set the operating mode to monitor mode and, if it succeeds, saves the old operating mode if the interface's monitor mode count is zero, increments the interface's monitor mode count and sets the instance's "monitor mode" flag (it could also add the appropriate values to the packet filter);
    • have the routine that closes an opened NPF instance check the "monitor mode" flag for the instance and, if it's set, decrement the "monitor mode instances" count and, if the count reaches zero, restores the old operating mode.

    In packet.dll, add a PacketSetMonitorMode() routine, which is a wrapper around the BIOC ioctl in question.

    In pcap-win32.c, call PacketSetMonitorMode() if monitor mode was requested, rather than setting the operation mode directly.

    For setting OIDs in drivers, see the code path for BIOCQUERYOID and BIOCSETOID in NPF_IoControl() - the new BIOC ioctl would be handled in NPF_IoControl().

    (And, of course, do the appropriate MP locking.)

    The monitor mode count might not be necessary, if you can enumerate all the NPF instances for an interface - the count is just the number of instances that have the monitor mode flag set.

    Doing it in the driver means that if a program doing monitor-mode capturing terminates abruptly, so that no user-mode code gets to do any cleanup, the mode can still get reset.