有什么方法可以更改同步 Windows API SendARP 的行为吗?

Any way to change the behavior of synchronous Windows API SendARP?

提问人:jfly 提问时间:4/26/2017 更新时间:9/4/2023 访问量:1070

问:

我正在 Windows 上编写一个本地网络扫描程序来查找具有 IP Helper 函数的在线主机,这相当于但没有 WinPcap。我知道如果远程主机没有响应,SendARP 会阻止并发送 arp 请求 3 次,所以我习惯于为每个主机创建一个线程,但问题是我想每 20 毫秒发送一次 ARP 请求,这样就不会在很短的时间内发送太多的 arp 数据包。nmap -PRstd::aync

#include <iostream>
#include <future>
#include <vector>

#include <winsock2.h>
#include <iphlpapi.h>


#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")

using namespace std;

int main(int argc, char **argv)
{
    ULONG MacAddr[2];       /* for 6-byte hardware addresses */
    ULONG PhysAddrLen = 6;  /* default to length of six bytes */
    memset(&MacAddr, 0xff, sizeof (MacAddr));
    PhysAddrLen = 6;

    IPAddr SrcIp = 0;
    IPAddr DestIp = 0;

    char buf[64] = {0};

    size_t start = time(NULL);

    std::vector<std::future<DWORD> > vResults;

    for (auto i = 1; i< 255; i++)
    {
        sprintf(buf, "192.168.1.%d", i);
        DestIp = inet_addr(buf);
        vResults.push_back(std::async(std::launch::async, std::ref(SendARP), DestIp, SrcIp, MacAddr, &PhysAddrLen));
        Sleep(20);
    }

    for (auto it= vResults.begin(); it != vResults.end(); ++it)
    {
        if (it->get() == NO_ERROR)
        {
            std::cout<<"host up\n";
        } 
    }

    std::cout<<"time elapsed "<<(time(NULL) - start)<<std::endl;

    return 0;
}

起初我可以通过在启动线程后调用来做到这一点,但是一旦在这些线程中重新发送 ARP 请求,如果没有来自远程主机的回复,这超出了我的控制范围,并且我在很短的时间内(<10 毫秒)在 Wireshark 中看到了很多请求,所以我的问题是:Sleep(20)SendARP

  1. 有什么方法可以异步吗?SendARP
  2. 如果不是,我可以控制 in 线程的发送时间吗?SendARP
C++ 多线程 WinAPI 异步 ARP

评论

1赞 Chris Becke 4/26/2017
简单地限制未解析的 ARP 请求的数量似乎更实用。即从发送 5 个请求开始,当您收到回复时,发送更多。
0赞 jfly 4/26/2017
我想要对本地网络进行快速 ARP 扫描(在 5 秒内),对于典型的网络,每 20 毫秒发送一次 ARP 请求扫描整个网络大约需要 5 秒,但如果远程主机在 Windows 7/8/10 上关闭,可能会阻塞 3 秒。/24SendARP
4赞 MSalters 4/26/2017
我错过了问题。“在很短的时间内提出许多请求”表明费率太高,而“我想要快速扫描”表明费率太低。这是什么?您将需要 254 个线程,因为在 5 秒的间隔内,您不能有两个持续 3 秒的阻塞调用。所有 254 个呼叫必须在前两秒内启动。
0赞 Chris Becke 4/26/2017
鉴于您无法控制 SendArp 超时和重新传输的内部结构......使用原始套接字发送您自己的 ARP 请求可能是唯一的选择......缺点是应用程序必须以管理员身份运行...
0赞 jfly 4/26/2017
这正是我想做的 - 控制速率,每 20 毫秒发送一次请求。 如果只发送一个 ARP 请求,我可以启动一个线程然后休眠 20 毫秒,但事实并非如此。SendARP
1赞 Hans Passant 4/28/2017
此功能没有任何意义。机器上可能会有什么变化,它会将 arp 数据包放在地板上,但 20 毫秒后它没有?操作系统的行为方式并非如此。唯一明智的方法是将 arp 数据包发送到不同的机器,但这使得时间完全无关紧要。您正在寻找的操作系统功能不存在,因为他们想不出任何人需要它的原因。你不需要它。
1赞 Hans Passant 4/28/2017
好吧,当然,你会得到更多的响应,纯粹来自目标机器无法在不到 20 毫秒的时间内做出响应。你只需要第一个。通过省略重复项来计算有效响应的数量。

答:

0赞 A. Smoliak 10/29/2021 #1

似乎没有任何方法可以强制以非阻塞方式执行操作,似乎当主机无法访问时,它会尝试重新查询几次,然后放弃。SendARP

至于解决方案,你不想听到。MSDN 文档指出,有一个名为 ResolveIpNetEntry2 的较新的 API 弃用,它也可以执行相同的操作,但它的行为方式似乎也相同。SendARP

它接收到的结构包含一个名为的字段,该字段为: 节点在未收到可访问性确认后假定邻居无法访问的时间(以毫秒为单位)。ReachabilityTime.LastUnreachable

然而,它似乎没有任何实际效果。

最好的方法是使用 WinPCap 或其他驱动程序,似乎没有办法解决您在用户空间中的问题。

0赞 vedant kashyap 9/4/2023 #2

遗憾的是,Windows 中的 SendARP 函数没有提供用于异步执行或控制 ARP 请求计时的内置机制。它是一种阻止功能,发送 ARP 请求并等待响应或超时,如果没有回复,则无法控制重新发送 ARP 请求的时间。

为了实现使用受控时间异步发送 ARP 请求的目标,您需要使用原始套接字或较低级别的网络库实现自己的 ARP 请求逻辑。这将使你能够更好地控制 ARP 请求的时间和并发性。

下面是您可以执行的操作的高级概述:

使用原始套接字或网络库(如 libpcap/winpcap)发送 ARP 请求。 为要向其发送 ARP 请求的每个主机创建一个单独的线程。 在每个线程中,发送一个 ARP 请求并等待响应(您可以设置自己的超时)。 在每个线程中使用 Sleep 或其他机制控制 ARP 请求的时间。

下面是如何在单独的线程中使用原始套接字发送 ARP 请求的示例:

#include <iostream>
#include <vector>
#include <thread>
#include <chrono>

#include <winsock2.h>
#include <iphlpapi.h>

#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")

using namespace std;

void sendArpRequest(const char* ipAddress) {
    // Create a raw socket and send ARP request here.
    // You'll need to implement the ARP packet structure and send/receive logic.
    // Use Sleep or another mechanism to control the timing.
}

int main() {
    vector<thread> threads;

    for (int i = 1; i < 255; i++) {
        char ipAddress[20];
        sprintf(ipAddress, "192.168.1.%d", i);
        threads.emplace_back(sendArpRequest, ipAddress);
        this_thread::sleep_for(chrono::milliseconds(20)); // Control the timing
    }

    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

使用原始套接字实现 ARP 请求可能很复杂,您需要在代码中处理 ARP 响应和超时。您可能还需要使用提升的权限运行程序以创建原始套接字。此外,请确保在代码中具有适当的错误处理和资源管理。

评论

1赞 Adrian Mole 9/4/2023
这具有 AI 生成的答案的许多特征;如果它来自 ChatGPT 或类似的东西,您应该删除它,因为此类内容目前在 Stack Overflow 上被禁止
-1赞 lewis 12/10/2023 #3

这并不能具体回答您关于 ARP 的问题,但“traceroute”必须执行类似操作 - 发送原始 IP 数据包,并等待其结果。

我编写了一个网络类库,可以很容易地对这种原始 IP(异步)IO 进行分层,并在 Windows(以及 unix 和 macos)上进行了测试。

例如,traceroute 由一系列小的 ICMP ping 组成,您可以在此处看到这些 ping 的编码非常简单:

使用 Stroika 执行低级套接字 IO 的示例代码

fSocket_.setsockopt (IPPROTO_IP, IP_TTL, ttl); // max # of hops

ICMP::V4::PacketHeader pingRequest = [&] () {
    ICMP::V4::PacketHeader tmp{};
    tmp.type      = ICMP::V4::ICMP_ECHO_REQUEST;
    tmp.id        = static_cast<uint16_t> (fAllUInt16Distribution_ (fRng_));
    tmp.seq       = ++fNextSequenceNumber_;
    tmp.timestamp = static_cast<uint32_t> (Time::GetTickCount () * 1000);
    return tmp;
}();
(void)::memcpy (fSendPacket_.begin (), &pingRequest, sizeof (pingRequest));
reinterpret_cast<ICMP::V4::PacketHeader*> (fSendPacket_.begin ())->checksum =
    IP::ip_checksum (fSendPacket_.begin (), fSendPacket_.begin () + fICMPPacketSize_);
fSocket_.SendTo (fSendPacket_.begin (), fSendPacket_.end (), SocketAddress{fDestination_, 0});

// Find first packet responding (some packets could be bogus/ignored)
Time::DurationSecondsType pingTimeoutAfter = Time::GetTickCount () + fPingTimeout_;
while (true) {
    ThrowTimeoutExceptionAfter (pingTimeoutAfter);
    SocketAddress    fromAddress;
    constexpr size_t kExtraSluff_{100}; // Leave a little extra room in case some packets return extra
    StackBuffer<byte> recv_buf{Memory::eUninitialized, fICMPPacketSize_ + sizeof (ICMP::V4::PacketHeader) + 2 * sizeof (IP::V4::PacketHeader) +
                                                           kExtraSluff_}; // icmpPacketSize includes ONE ICMP header and payload, but we get 2 IP and 2 ICMP headers in TTL Exceeded response
    size_t n = fSocket_.ReceiveFrom (begin (recv_buf), end (recv_buf), 0, &fromAddress, fPingTimeout_);

这里的方法是将这些“任务”写成同步 - 先写再读。然后使用 ThreadPool 类创建可以运行和执行这些 ping 的小“任务”。