简体   繁体   中英

How to use IcmpSendEcho2() with APC callbacks in main thread?

First, the documentation for IcmpSendEcho2() contradicts itself:

It says:

The IcmpSendEcho2 function is called synchronously if the ApcRoutine or Event parameters are NULL

Then it says:

The IcmpSendEcho2 function is called asynchronously when either the ApcRoutine or Event parameters are specified

I presume the first one should be "if the ApcRoutine AND Event paramters are NULL"?

Also, it says under the return value:

When called asynchronously, the IcmpSendEcho2 function returns ERROR_IO_PENDING to indicate the operation is in progress

But I don't see that, I see it return 0 and GetLastError() returns ERROR_IO_PENDING . So, can both cases exist, or is the documentation completely wrong?

Now on to the next issue. I wanted to use IcmpSendEcho2() asynchronously using the ACP callback without events. This way, I didn't have to worry about resources should the number of hosts to process be extremely large. However, it doesn't work because no callback occurs. I found this in the documentation under the AcpRoutine parameter:

The routine that is called when the calling thread is in an alertable thread and an ICMPv4 reply arrives.

So I believe my problem is the main thread is not in an alterable state. Since I don't have an event to wait on, and I don't want to wait beyond the time it takes to complete everything, how do I put the main thread in an alterable state without having to guess using something like SleepEx() ? Also, if I did use something like SleepEx(10,TRUE) , would all the callbacks occur, or do you have to sit in a loop?

My callback context structure includes a shared global OutstandingCount type variable so I'd know when all requests were completed.

Also the ReplyBuffer is in the context structure. Another little nugget hidden in the documentation regarding the ReplyBuffer when using it asynchronously is:

The application must parse the data pointed to by ReplyBuffer parameter using the IcmpParseReplies function

So, the main question here: How are you supposed to properly use the IcmpSendEcho2() function with a AcpRoutine and no Event in a main thread?

-- Update --

Not sure if I should ask an entirely new question but now a problem where it doesn't call the ApcRoutine for every IcmpSendEcho2Ex() sent. The following code works for my normal network adapters (which are 255.255.255.0) but hangs for a 255.255.0.0 network because the outstandingcount never gets to zero.

The adapter it hangs on is:

VirtualBox Host-Only Ethernet Adapter DHCP Enable: Yes Autoconfiguration Enabled: Yes Autoconfiguration IPv4Address: 169.254.21.120 Subnet Mask: 255.255.0.0

Also wonder how long it would take on networks like 10. with a subnet of 255.0.0.0.

Here's the code that starts with the IPV4Scan() built as x64 on Win10 x64:

#define PIO_APC_ROUTINE_DEFINED

#include <winternl.h>
#include <iphlpapi.h>
#include <IcmpAPI.h>

//--------------
// types
//--------------
typedef DWORD (WINAPI *LPFN_IcmpSendEcho2)(HANDLE, HANDLE , PIO_APC_ROUTINE, PVOID, IPAddr, LPVOID, WORD, PIP_OPTION_INFORMATION, LPVOID, DWORD, DWORD);
typedef DWORD (WINAPI *LPFN_IcmpSendEcho2Ex)(HANDLE, HANDLE , PIO_APC_ROUTINE, PVOID, IPAddr, IPAddr, LPVOID, WORD, PIP_OPTION_INFORMATION, LPVOID, DWORD, DWORD);
typedef HANDLE (WINAPI *LPFN_IcmpCreateFile)();
typedef BOOL (WINAPI *LPFN_IcmpCloseHandle)(HANDLE);
typedef DWORD (WINAPI *LPFN_IcmpParseReplies)(LPVOID, DWORD);

BYTE PingSignature[]={ 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8' };

typedef struct _sPingContext
{
  ULONG *OutstandingCount;                  // shared number of pings outstanding
  CMutex *Mutex;                            // mutex for ipsfound
  CNumericBuffer<uint32_t> *IPsFound;       // list of ips found (MSBF format)
  LPFN_IcmpParseReplies fnIcmpParseReplies; // function pointer 
  BYTE ReplyBuffer[sizeof(ICMP_ECHO_REPLY) + sizeof(PingSignature) + sizeof(IO_STATUS_BLOCK) + 8]; // reply buffer (see API docs)

  _sPingContext(ULONG *outstandingcount, CMutex *mutex, CNumericBuffer<uint32_t> *ipsfound, LPFN_IcmpParseReplies fnicmpparsereplies)
  {
    OutstandingCount=outstandingcount;
    Mutex=mutex;
    IPsFound=ipsfound;
    fnIcmpParseReplies=fnicmpparsereplies;
    memset(ReplyBuffer, 0, sizeof(ReplyBuffer));
  };
} sPingContext, *psPingContext;



//-------------------------------------------------------------------------
// Purpose:  Callback for async ping
//
// Input:    ioresult     - [i] io result of async operation
//           pingccontext - [i] context passed on ping
//           replysize    - [i] reply size of ReplyBuffer
//
// Output:   na
//
// Notes:    
//
VOID PingCallbackCommon(DWORD ioresult, sPingContext* pingcontext, DWORD replysize)
{
  // parse response buffer
  if (pingcontext) {
    if (ioresult==IP_SUCCESS) {
      if (pingcontext->fnIcmpParseReplies(pingcontext->ReplyBuffer, replysize)) {
        // point to reply buffer
        PICMP_ECHO_REPLY pechoreply=reinterpret_cast<PICMP_ECHO_REPLY>(pingcontext->ReplyBuffer);
        if (pechoreply->Status==IP_SUCCESS) {
          // check response
          if (pechoreply->DataSize==sizeof(PingSignature)) {
            if (memcmp(pechoreply->Data, PingSignature, pechoreply->DataSize)==0) {
              // successful ping
              pingcontext->Mutex->Lock();
              pingcontext->IPsFound->AddItem(pechoreply->Address);
              pingcontext->Mutex->Unlock();
            }
          }
        }
      }
    }
    // reduce count
    InterlockedDecrement(pingcontext->OutstandingCount);
    // clean up
    delete pingcontext;
  }
}

//-------------------------------------------------------------------------
// Purpose:  Callback for async ping
//
// Input:    apccontext - [i] context passed on ping
//
// Output:   na
//
// Notes:    
//
VOID PingCallbackOld(PVOID apcontext)
{
  sPingContext *pingcontext=reinterpret_cast<sPingContext*>(apcontext);
  PingCallbackCommon(IP_SUCCESS, pingcontext, sizeof(pingcontext->ReplyBuffer));
}

//-------------------------------------------------------------------------
// Purpose:  Callback for async ping
//
// Input:    apccontext - [i] context passed on ping
//           iostatusblock - [i] status of request
//
// Output:   na
//
// Notes:    
//
VOID PingCallback(PVOID apcontext, PIO_STATUS_BLOCK iostatusblock, ULONG reserved)
{
  PingCallbackCommon(iostatusblock->Status, reinterpret_cast<sPingContext*>(apcontext), iostatusblock->Information);
}


//-------------------------------------------------------------------------
// Purpose:  build list of network hosts using IPv4 Ping
//
// Input:    subnet    - [i] subnet being scanned (LSB format)
//           hoststart - [i] host starting number for scan
//           hostend   - [i] host ending number for scan
//           ips       - [io] numeric buffer to update with found addresses
//
// Output:   na
//
// Notes:    
//
void IPV4Ping(IPAddr sourceip, uint32_t subnet, uint32_t hoststart, uint32_t hostend, CNumericBuffer<uint32_t> &ips)
{
  // skip 127. network
  if ((sourceip & 0xFF)==127) 
    return;

  bool oldlib=false;

  LPFN_IcmpSendEcho2Ex fnIcmpSendEcho2Ex=NULL;
  LPFN_IcmpCreateFile fnIcmpCreateFile=NULL;
  LPFN_IcmpCloseHandle fnIcmpCloseHandle=NULL;
  LPFN_IcmpParseReplies fnIcmpParseReplies=NULL;

  // first thing is first - check which set of functions to use
  HMODULE hlib=LoadLibrary(_T("iphlpapi.dll"));
  if (hlib) {
    // load functions
    fnIcmpCreateFile=(LPFN_IcmpCreateFile) GetProcAddress(hlib, "IcmpCreateFile");
    fnIcmpSendEcho2Ex=(LPFN_IcmpSendEcho2Ex) GetProcAddress(hlib, "IcmpSendEcho2Ex");
    fnIcmpCloseHandle=(LPFN_IcmpCloseHandle) GetProcAddress(hlib, "IcmpCloseHandle");
    fnIcmpParseReplies=(LPFN_IcmpParseReplies) GetProcAddress(hlib, "IcmpParseReplies");
  }

  // check if have everything
  if (!hlib || fnIcmpCreateFile==NULL || fnIcmpSendEcho2Ex==NULL || fnIcmpCloseHandle==NULL || fnIcmpParseReplies==NULL) {
    // no, try old version
    oldlib=true;
    // clean up
    if (hlib) {
      FreeLibrary(hlib);
    }
    // load old lib
    hlib=LoadLibrary(_T("icmp.dll"));
    // check if loaded
    if (hlib) {
      // load functions
      fnIcmpCreateFile=(LPFN_IcmpCreateFile) GetProcAddress(hlib, "IcmpCreateFile");
      fnIcmpSendEcho2Ex=(LPFN_IcmpSendEcho2Ex) GetProcAddress(hlib, "IcmpSendEcho2Ex");
      fnIcmpCloseHandle=(LPFN_IcmpCloseHandle) GetProcAddress(hlib, "IcmpCloseHandle");
      fnIcmpParseReplies=(LPFN_IcmpParseReplies) GetProcAddress(hlib, "IcmpParseReplies");
    }
  }

  // check if have everything
  if (hlib) {
    if (fnIcmpCreateFile!=NULL && fnIcmpSendEcho2Ex!=NULL && fnIcmpCloseHandle!=NULL && fnIcmpParseReplies!=NULL) {
      // open icmp
      HANDLE hicmp=fnIcmpCreateFile();
      if (hicmp!=INVALID_HANDLE_VALUE) {
        // variables for callback handling
        ULONG outstandingcount=0;
        CMutex mutex;

        // process pings
        for (uint32_t host=hoststart; host<=hostend; host++) {
          // build full ip
          IPAddr ip=subnet | host;
          ip=GETMSBFDWORD(&ip);
          // create context 
          sPingContext *pcontext;
          if ((pcontext=new sPingContext(&outstandingcount, &mutex, &ips, fnIcmpParseReplies))!=NULL) {
            // count request
            InterlockedIncrement(&outstandingcount);
            // now issue ping
            DWORD result=fnIcmpSendEcho2Ex(hicmp, 
                                           NULL, 
                                           oldlib ? (PIO_APC_ROUTINE) PingCallbackOld : PingCallback, 
                                           pcontext, 
                                           sourceip, 
                                           ip, 
                                           PingSignature, 
                                           sizeof(PingSignature), 
                                           NULL, 
                                           pcontext->ReplyBuffer, 
                                           sizeof(pcontext->ReplyBuffer), 
                                           50);

            // check if failed
            if (result==0) {
              // check if because pending
              if (GetLastError()!=ERROR_IO_PENDING) {
                // no - use callback to clean up
                CDebugPrint::DebugPrint(_T("IcmpSendEcho Error %u\n"), GetLastError());
                PingCallbackOld(pcontext);
              }
              else {
                // fire off pending APC callbacks ready
                SleepEx(0, TRUE);
              }
            }
            else {
              // completed sync - use callback to clean up
              PingCallbackOld(pcontext);
            }
          }
        }

        // wait for completion
        while (outstandingcount) {
          // handle callbacks
          SleepEx(10, TRUE);
        }
        // clean up
        fnIcmpCloseHandle(hicmp);
      }
    }
    // clean up
    FreeLibrary(hlib);
  }
}

//-------------------------------------------------------------------------
// Purpose:  build list of network hosts by way of IP scan for V4
//
// Input:    ipadapteraddress  - [i] adapter ip address to build for
//
// Output:   na
//
// Notes:    ip addresses are MSBF
//
void IPV4Scan(IP_ADAPTER_UNICAST_ADDRESS *ipadapteraddress)
{
  // build the subnet mask to use
  if (ipadapteraddress->OnLinkPrefixLength<=32 && ipadapteraddress->OnLinkPrefixLength!=0) {
    in_addr ia=reinterpret_cast<sockaddr_in*>(ipadapteraddress->Address.lpSockaddr)->sin_addr;
    // valid mask length - build mask
    uint32_t rangemask=((1U<<(32-ipadapteraddress->OnLinkPrefixLength))-1);
    uint32_t mask=~rangemask;
    uint32_t subnet=GETMSBFDWORD(&ia.s_addr) & mask;
    CDebugPrint::DebugPrint(_T("Subnet %u.%u.%u.%u/%u\n"), (subnet>>24) & 0xFF, (subnet>>16) & 0xFF, (subnet>>8) & 0xFF, (subnet>>0) & 0xFF, ipadapteraddress->OnLinkPrefixLength);
    CDebugPrint::DebugPrint(_T("Scanning %u hosts\n"), (UINT32_MAX & rangemask)-1);

    CNumericBuffer<uint32_t> ipsfound;
    IPV4Ping(ia.s_addr, subnet, 1, (UINT32_MAX & rangemask)-1, ipsfound);

    for (UINT i=0; i<(UINT)ipsfound.GetCount(); i++) {
      uint32_t ip=ipsfound[i];
      CDebugPrint::DebugPrint(_T("Ping found %u.%u.%u.%u\n"), ip & 0xFF, (ip>>8) & 0xFF, (ip>>16) & 0xFF, (ip>>24) & 0xFF);
    }
  }
  else CDebugPrint::DebugPrint(_T("Invalid subnet length %u\n"), ipadapteraddress->OnLinkPrefixLength);
}

I presume the first one should be "if the ApcRoutine AND Event paramters are NULL"?

yes, you correct.

But I don't see that, I see it return 0 and GetLastError() returns ERROR_IO_PENDING. So, can both cases exist, or is the documentation completely wrong?

documentation completely wrong. by fact IcmpSendEcho2[Ex] return BOOL and error code via SetLastError ( more exactly by RtlNtStatusToDosError )

so on asynchronous call it return FALSE (0) and GetLastError() will be ERROR_IO_PENDING if all ok - this mean apc callback will be called, or another error if fail - apc callback will be not called (better call it by self in this case, for common error handling)

how do I put the main thread in an alterable state

this already depend from what your thread doing. in some case possible write event loop with MsgWaitForMultipleObjectsEx function - at once wait on windows events and be alertable. also possible wait on some objects too. if you can not rewrite self message loop with MsgWaitForMultipleObjectsEx - you can do call from worked thread, or periodically call SleepEx(0, TRUE) or undocumented NtTestAlert . without know what your main thread doing - hard say exactly what is better.

demo code can look like:

#include <iphlpapi.h>
#include <IPExport.h>
#include <icmpapi.h>

class EchoRequestContext
{
    HANDLE _hFile = 0;
    PVOID _ReplyBuffer = 0;
    LONG _dwRefCount = 1;
    ULONG _dwThreadId = GetCurrentThreadId();

    static void WINAPI sOnApc(PVOID This, PIO_STATUS_BLOCK piosb, ULONG )
    {
        reinterpret_cast<EchoRequestContext*>(This)->OnApc(
            RtlNtStatusToDosError(piosb->Status), 
            (ULONG)piosb->Information);
    }

    void OnApc(ULONG dwError, ULONG ReplySize)
    {
        OnReply(dwError, (PICMP_ECHO_REPLY)_ReplyBuffer, ReplySize);
        if (_ReplyBuffer) delete [] _ReplyBuffer;
        Release();
    }

    void OnReply(ULONG dwError, PICMP_ECHO_REPLY ReplyBuffer, ULONG ReplySize)
    {
        if (dwError)
        {
            DbgPrint("dwError=%u\n", dwError);
            return ;
        }
        
        if (IcmpParseReplies(ReplyBuffer, ReplySize))
        {
            __nop();
        }
    }

    ~EchoRequestContext()
    {
        if (_hFile) IcmpCloseHandle(_hFile);
        PostThreadMessageW(_dwThreadId, WM_QUIT, 0, 0);
    }
public:

    void AddRef()
    {
        InterlockedIncrementNoFence(&_dwRefCount);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_dwRefCount))
        {
            delete this;
        }
    }

    ULONG Create()
    {
        HANDLE hFile = IcmpCreateFile();

        if (hFile == INVALID_HANDLE_VALUE) 
        {
            return GetLastError();
        }

        _hFile = hFile;

        return NOERROR;
    }

    void SendEcho(
        IPAddr DestinationAddress, 
        const void* RequestData, 
        WORD RequestSize,
        ULONG ReplySize, 
        ULONG Timeout, 
        UCHAR Flags, 
        UCHAR Ttl)
    {
        if (PVOID ReplyBuffer = new UCHAR[ReplySize])
        {
            _ReplyBuffer = ReplyBuffer;

            IP_OPTION_INFORMATION opt = { Ttl, 0, Flags };

            AddRef();

            ULONG dwError = IcmpSendEcho2Ex(_hFile, 0, sOnApc, this, 
                0, DestinationAddress, 
                const_cast<void*>(RequestData), RequestSize, 
                &opt, ReplyBuffer, ReplySize, Timeout) ? NOERROR : GetLastError();

            switch (dwError)
            {
            case NOERROR:
            case ERROR_IO_PENDING:
                break;
            default:
                OnApc(dwError, 0 );
            }
            return ;
        }

        OnApc(ERROR_OUTOFMEMORY, 0);
    }
};

#define IP(a, b, c, d) ((ULONG)(a + (b << 8) + (c << 16) + (d << 24)))

void EchoTest()
{
    WSADATA wd;
    if (NOERROR == WSAStartup(WINSOCK_VERSION, &wd))
    {
        if (EchoRequestContext* p = new EchoRequestContext)
        {
            if (p->Create() == NOERROR)
            {
                p->SendEcho(IP(8,8,8,8), "1234567890ABCDEF", 16, 0x100, 4000, IP_FLAG_DF, 255);
            }

            p->Release();
        }

        MSG msg;
__loop:
        switch (MsgWaitForMultipleObjectsEx(0, 0, INFINITE, 
                QS_ALLINPUT, MWMO_ALERTABLE|MWMO_WAITALL))
        {
        default:
            __debugbreak();
            break;
        case WAIT_FAILED:
            break;

        case WAIT_OBJECT_0:
            while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE))
            {
                if (msg.message == WM_QUIT)
                {
                    goto __exit;
                }
            }

        case STATUS_USER_APC: // == WAIT_IO_COMPLETION
            goto __loop;
        }
__exit:

        WSACleanup();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM