Search code examples
delphiwlanapi

WlanAPI WlanGetNetworkBssList returning invalid data


I'm stuck in my debugging efforts with a call to WlanGetNetworkBssList and would appreciate some pointers. My end objective is to construct a Wifi scanner/profiler tool to help me troubleshoot networking issues on remote sites.

I am using the Windows Native Wifi API (link) and Delphi/Pascal interface found here using Delphi Berlin 10.1 Update 2 under Windows 10 (VCL).

I started with a simple and crude test app (VCL) to get a feel for the API and ran into a problem calling WlanGetNetworkBssList so I created a small console app focused on that problem. The issue is that it works in a console app running in a command prompt but not in my VCL test app. The functions are pretty much copy-paste equivalent and stepping through the code side-by-side shows that the data is identical except for the return data from WlanGetNetworkBssList call (pWlanBssList)

Question: Since the call is to an external DLL what steps can I do to further debug this and understand the difference between the VCL and the console app.

Note: The WlanGetNetworkBssList has two modes of operation where an SSID can be supplied to get the BSSID (MAC of the access point) for that specific SSID. By passing NULL instead of an SSID the API will return the BSSIDs of all APs. Passing NULL works on both the VLC and console app. What breaks is when a specific SSID is requested. After verification, the SSID data structure is identical in both apps but the data buffer returned is invalid with the VCL app. How can this be?

Console app:

program CWifiScan;

{$APPTYPE CONSOLE}

uses
  Windows,
  System.SysUtils,
  nduWlanAPI,
  nduWlanTypes;
const
  WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_ADHOC_PROFILES = $00000001;
var
  hWlan: THandle;
  guid : TGUID;
  dwSupportedVersion: DWORD = 0;
  dwClientVersion: DWORD = 1;
  i,j                   : integer;
  pInterfaceInfo        : Pndu_WLAN_INTERFACE_INFO;
  pInterfaceList        : Pndu_WLAN_INTERFACE_INFO_LIST;
  pAvailableNetworkList : Pndu_WLAN_AVAILABLE_NETWORK_LIST;

procedure GetBSSIDList(clientHandle  : THandle;
                          interfaceGUID : TGUID;
                          pSSID         : Pndu_DOT11_SSID = nil;
                          SSID_Type     : Tndu_DOT11_BSS_Type = dot11_BSS_type_any;
                          SecurityEnabled : BOOL = True);
var
  //to check if interface is connected
  pData         : Pndu_WLAN_INTERFACE_STATE;
  pdwDataSize   : DWORD;
  isConnected   : Boolean;
  //to get list of BSSids from available APs
  pWlanBssList : Pndu_WLAN_BSS_LIST;
  items         : integer;
  itemIndex     : integer;
  SSID          : string;
  MAC           : string;
begin
  //check if interface is connected
  isConnected := False;
  if WlanQueryInterface(clientHandle,
                        @interfaceGUID,
                        wlan_intf_opcode_interface_state,
                        nil,
                        @pdwDataSize,
                        @pData,
                        nil) = ERROR_SUCCESS then
  begin
    isConnected := (pData^ = Tndu_WLAN_INTERFACE_STATE.wlan_interface_state_connected);
  end;

  //get the list of BSSids for the provided interface
  if isConnected then
  begin
    if WlanGetNetworkBssList(clientHandle,
                             @interfaceGUID,
                             pSSID,
                             SSID_Type,
                             SecurityEnabled,
                             nil,
                             @pWlanBssList) = ERROR_SUCCESS then
    begin
      items := pWlanBssList^.dwNumberOfItems;
      for itemIndex := 0 to items - 1 do
      begin

        SSID := String(PAnsiChar(@pWlanBssList^.wlanBssEntries[itemIndex].dot11Ssid.ucSSID));

        MAC := Format('%.2x:%.2x:%.2x:%.2x:%.2x:%.2x', [
                     pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[0],
                     pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[1],
                     pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[2],
                     pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[3],
                     pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[4],
                     pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[5]]);

        Writeln('');
        Writeln('SSID: ................ '+SSID);
        Writeln('Physical Address: .... '+MAC);
      end; {for itemIndex}
      Writeln(#10+#13+'Done.');
    end; {WlanGetNetworkBssList succeeds}

  end; {isConnected}
end;

begin
  hWlan := 0;

  if WlanOpenHandle(2, nil,@dwSupportedVersion, @hWlan)= ERROR_SUCCESS then
  begin
    if WlanEnumInterfaces(hWlan, nil, @pInterfaceList) = ERROR_SUCCESS then
    begin
      try
        for i := 0 to pInterfaceList^.dwNumberOfItems-1 do
          begin
            Writeln('Wifi Adapter - '+GUIDToString( pInterfaceList^.InterfaceInfo[i].InterfaceGuid ) );
            Writeln('Scanning: .... '+pInterfaceList^.InterfaceInfo[i].strInterfaceDescription);
            guid := pInterfaceList^.InterfaceInfo[i].InterfaceGuid;

            //Get all BSSids for this interface
            GetBSSIDList(hWlan, guid);

            if WlanGetAvailableNetworkList(hWlan,
                                           @guid,
                                           WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_ADHOC_PROFILES,
                                           nil,
                                           pAvailableNetworkList) = ERROR_SUCCESS then
            begin
              try
                for j := 0 to pAvailableNetworkList^.dwNumberOfItems - 1 do
                  begin

                    //Get BSSid for this specific SSID
                    GetBSSIDList(hWlan,
                                 guid,
                                 @pAvailableNetworkList^.Network[j].dot11Ssid,
                                 pAvailableNetworkList^.Network[j].dot11BssType,
                                 pAvailableNetworkList^.Network[j].bSecurityEnabled);

                  end;
              finally
                if pAvailableNetworkList<>nil then
                  WlanFreeMemory(pAvailableNetworkList);
              end;
            end;

          end;
      finally
        if pInterfaceList<>nil then
          WlanFreeMemory(pInterfaceList);
      end;
    end;

    WlanCloseHandle(hWlan, nil);

    readln;

  end;
end.

The relevant parts of the VCL app are:

uses
  ... nduWlanAPI, nduWlanTypes, nduWinDot11;


function TForm1.GetBSSID(clientHandle: THandle;
                         interfaceGuid: TGUID;
                         pSSID: Pndu_DOT11_SSID = nil;
                         SSID_Type : Tndu_DOT11_BSS_TYPE = dot11_BSS_type_any;
                         SecurityEnabled: boolean = true): string;
var
  //used to determin if the interface is connected
  pData       : Pndu_WLAN_INTERFACE_STATE;
  isConnected : boolean;
  //used to extract a list of BSSIDs for a given interface
  pWlanBssList : Pndu_WLAN_BSS_LIST;

  lastError     : DWORD;
  pdwDataSize   : DWORD;
  items,
  itemIndex:    Integer;
begin
  pData := nil;
  pdwDataSize := 0;
  isConnected := False;

  //check if the interface is connected
  lastError := WlanQueryInterface(clientHandle,
                                 @interfaceGuid,
                                 wlan_intf_opcode_interface_state,
                                 nil,
                                 @pdwDataSize,
                                 @pData,
                                 nil);
    if (lastError = ERROR_SUCCESS) then
    begin
     //isConnected := (Tndu_WLAN_INTERFACE_STATE(pData^.isState) = Tndu_WLAN_INTERFACE_STATE.wlan_interface_state_connected);
     isConnected := (pData^ = Tndu_WLAN_INTERFACE_STATE.wlan_interface_state_connected);
    end
    else
      DisplayError('Error in WlanQueryInterface() function', lastError);

  if isConnected then
  begin
    pWlanBssList := nil;

    lastError := WlanGetNetworkBssList(clientHandle,
                                       @interfaceGuid,
                                       pSSID,
                                       SSID_Type,
                                       SecurityEnabled,
                                       nil,
                                       @pWlanBssList);

    try

      if (lastError = ERROR_SUCCESS) then
      begin

        items := pWlanBssList^.dwNumberOfItems;
        for itemIndex := 0 to items-1 do
        begin

          Result := (Format('%.2x:%.2x:%.2x:%.2x:%.2x:%.2x', [
                   pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[0],
                   pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[1],
                   pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[2],
                   pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[3],
                   pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[4],
                   pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[5]]));

        end;
      end
      else
        DisplayError('Error in the WlanGetNetworkBssList() function call', lastError);

    finally
      if pData<>nil then
        WlanFreeMemory(pData);

      if pWlanBssList<>nil then
        WlanFreeMemory(pWlanBssList);
    end;
  end;
end;

Which is called as follows:

function TForm1.ScanWifi(): THandle;
const
  WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_ADHOC_PROFILES = $00000001;
var
  hClient              : THandle;
  dwVersion            : DWORD;
  lastError            : DWORD;
  pInterface           : Pndu_WLAN_INTERFACE_INFO_LIST;
  i                    : Integer;
  j                    : Integer;
  pAvailableNetworkList: Pndu_WLAN_AVAILABLE_NETWORK_LIST;
  interfaceGuid        : TGUID;
  BSSID                : string;
begin
  lastError:=WlanOpenHandle(NDU_WLAN_API_VERSION, nil, @dwVersion, @hClient);
  if  lastError<> ERROR_SUCCESS then
  begin
     //DisplayError('Error in the WlanOpenHandle() function call', lastError);
     Result := 0;
     Exit;
  end;

  //L(Format('Requested WLAN interface version [%d], negotiated version [%d]', [NDU_WLAN_API_VERSION, dwVersion]));
  Result := hClient;

  try

      lastError:=WlanEnumInterfaces(hClient, nil, @pInterface);

      try
        if  lastError<> ERROR_SUCCESS then
        begin
           //DisplayError('Errorin the WlanEnumInterfaces() function call', lastError);
           Exit;
        end;

        for i := 0 to pInterface^.dwNumberOfItems - 1 do
        begin
          interfaceGuid:= pInterface^.InterfaceInfo[i].InterfaceGuid;

          lastError:=WlanGetAvailableNetworkList(hClient,
                                                 @interfaceGuid,
                                                 WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_ADHOC_PROFILES,
                                                 nil,
                                                 pAvailableNetworkList);

          try
            if  lastError<> ERROR_SUCCESS then
            begin
               //DisplayError('Error WlanGetAvailableNetworkList', lastError);

               Exit;
            end
            else
            begin
              for j := 0 to pAvailableNetworkList^.dwNumberOfItems - 1 do
              Begin

                 BSSID := GetBssid(hClient,
                                   interfaceGuid,
                                   @pAvailableNetworkList^.Network[j].dot11Ssid,
                                   pAvailableNetworkList^.Network[j].dot11BssType,
                                   pAvailableNetworkList^.Network[j].bSecurityEnabled
                 );

                 //FAPList.AddOrSetValue(BSSID,J);

              end;
            end;

          finally
            if pAvailableNetworkList <> nil then
              WlanFreeMemory(pAvailableNetworkList);
          end;

        end;
      finally
        if pInterface <> nil then
          WlanFreeMemory(pInterface);
      end;


  finally
    WlanCloseHandle(FhClient, nil);
  end;

end;

Comparing the data between the two apps the only difference is the result (pWlanBssList) as seen here (left=console, right=VCL): enter image description here


Solution

  • Looks like compiler bug in boolean conversion, problem is following line in VCL code

    SecurityEnabled: boolean = true
    

    you need to change it to

    SecurityEnabled: bool = true