提问人:jfly 提问时间:4/26/2017 更新时间:9/4/2023 访问量:1070
有什么方法可以更改同步 Windows API SendARP 的行为吗?
Any way to change the behavior of synchronous Windows API SendARP?
问:
我正在 Windows 上编写一个本地网络扫描程序来查找具有 IP Helper 函数的在线主机,这相当于但没有 WinPcap。我知道如果远程主机没有响应,SendARP 会阻止并发送 arp 请求 3 次,所以我习惯于为每个主机创建一个线程,但问题是我想每 20 毫秒发送一次 ARP 请求,这样就不会在很短的时间内发送太多的 arp 数据包。nmap -PR
std::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
- 有什么方法可以异步吗?
SendARP
- 如果不是,我可以控制 in 线程的发送时间吗?
SendARP
答:
似乎没有任何方法可以强制以非阻塞方式执行操作,似乎当主机无法访问时,它会尝试重新查询几次,然后放弃。SendARP
至于解决方案,你不想听到。MSDN 文档指出,有一个名为 ResolveIpNetEntry2 的较新的 API 弃用,它也可以执行相同的操作,但它的行为方式似乎也相同。SendARP
它接收到的结构包含一个名为的字段,该字段为: 节点在未收到可访问性确认后假定邻居无法访问的时间(以毫秒为单位)。ReachabilityTime.LastUnreachable
然而,它似乎没有任何实际效果。
最好的方法是使用 WinPCap 或其他驱动程序,似乎没有办法解决您在用户空间中的问题。
遗憾的是,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 响应和超时。您可能还需要使用提升的权限运行程序以创建原始套接字。此外,请确保在代码中具有适当的错误处理和资源管理。
评论
这并不能具体回答您关于 ARP 的问题,但“traceroute”必须执行类似操作 - 发送原始 IP 数据包,并等待其结果。
我编写了一个网络类库,可以很容易地对这种原始 IP(异步)IO 进行分层,并在 Windows(以及 unix 和 macos)上进行了测试。
例如,traceroute 由一系列小的 ICMP ping 组成,您可以在此处看到这些 ping 的编码非常简单:
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 的小“任务”。
评论
/24
SendARP
SendARP