简体   繁体   English

如何在主线程中使用 IcmpSendEcho2() 和 APC 回调?

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

First, the documentation for IcmpSendEcho2() contradicts itself:首先, IcmpSendEcho2()的文档自相矛盾:

It says:它说:

The IcmpSendEcho2 function is called synchronously if the ApcRoutine or Event parameters are NULL如果IcmpSendEcho2或 Event 参数为 NULL,则同步调用 IcmpSendEcho2 function

Then it says:然后它说:

The IcmpSendEcho2 function is called asynchronously when either the ApcRoutine or Event parameters are specified当指定 ApcRoutine 或 Event 参数时,异步调用IcmpSendEcho2 function

I presume the first one should be "if the ApcRoutine AND Event paramters are NULL"?我认为第一个应该是“如果 ApcRoutine AND Event 参数为 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异步调用时,IcmpSendEcho2 function 返回 ERROR_IO_PENDING 表示操作正在进行中

But I don't see that, I see it return 0 and GetLastError() returns ERROR_IO_PENDING .但我没有看到,我看到它返回 0 并且GetLastError()返回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.我想使用没有事件的 ACP 回调异步使用IcmpSendEcho2() 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:我在AcpRoutine参数下的文档中找到了这一点:

The routine that is called when the calling thread is in an alertable thread and an ICMPv4 reply arrives.当调用线程处于警报线程并且 ICMPv4 回复到达时调用的例程。

So I believe my problem is the main thread is not in an alterable state.所以我相信我的问题是主线程不在可更改的 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() ?由于我没有要等待的事件,并且我不想等待超过完成所有操作所需的时间,我如何将主线程放在一个可变的 state 中,而不必猜测使用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?另外,如果我确实使用了SleepEx(10,TRUE)之类的东西,所有的回调都会发生,还是你必须坐在一个循环中?

My callback context structure includes a shared global OutstandingCount type variable so I'd know when all requests were completed.我的回调上下文结构包括一个共享的全局OutstandingCount类型变量,所以我知道所有请求何时完成。

Also the ReplyBuffer is in the context structure. ReplyBuffer也在上下文结构中。 Another little nugget hidden in the documentation regarding the ReplyBuffer when using it asynchronously is:在异步使用ReplyBuffer时隐藏在文档中的另一个小块是:

The application must parse the data pointed to by ReplyBuffer parameter using the IcmpParseReplies function应用程序必须使用 IcmpParseReplies function 解析 ReplyBuffer 参数指向的数据

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?所以,这里的主要问题是:你应该如何正确使用IcmpSendEcho2() function 与AcpRoutine并且在主线程中没有Event

-- 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.不确定我是否应该问一个全新的问题,但现在的问题是它不会为每个发送的IcmpSendEcho2Ex()调用ApcRoutine 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.以下代码适用于我的普通网络适配器(即 255.255.255.0),但适用于 255.255.0.0 网络,因为outstandingcount永远不会为零。

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 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.还想知道像 10. 这样的网络需要多长时间,子网为 255.0.0.0。

Here's the code that starts with the IPV4Scan() built as x64 on Win10 x64:这是以在 Win10 x64 上构建为 x64 的IPV4Scan()开头的代码:

#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"?我认为第一个应该是“如果 ApcRoutine AND Event 参数为 NULL”?

yes, you correct.是的,你说得对。

But I don't see that, I see it return 0 and GetLastError() returns ERROR_IO_PENDING.但我没有看到,我看到它返回 0 并且 GetLastError() 返回 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 )事实上IcmpSendEcho2[Ex]通过SetLastError返回BOOL和错误代码(更确切地说是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)所以在异步调用它返回FALSE (0) 并且GetLastError()将是ERROR_IO_PENDING如果一切正常 - 这意味着将调用 apc 回调,或者如果失败则另一个错误 - apc 回调将不会被调用(在这种情况下最好自己调用它, 用于常见的错误处理)

how do I put the main thread in an alterable state如何将主线程放入可变的 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.在某些情况下,可能使用MsgWaitForMultipleObjectsEx function 编写事件循环 - 立即等待 windows 事件并发出警报。 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 .如果您无法使用MsgWaitForMultipleObjectsEx重写自我消息循环 - 您可以从工作线程调用,或定期调用SleepEx(0, TRUE)或未记录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();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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