我正在尝试以编程方式从Windows 7计算机发送和接收ICMPv6 ping数据包。我正在使用的代码改编自成功用于发送/接收IPv4 ping数据包的现有代码。我能看到的唯一区别是,我使用的是IPv6而不是IPv4,并且我同时使用链接本地地址作为源地址和目标地址。
我要ping的目标地址是fe80 :: b617:80ff:fe40:fe21%12,其中%12选择适当的接口。运行ipconfig会在我的机器上显示几个网络适配器:
Ethernet adapter Local Area Connection:
Connection-specific DNS Suffix . : dti.lan
Link-local IPv6 Address . . . . . : fe80::49d5:a4a1:1d10:7e42%11
IPv4 Address. . . . . . . . . . . : 192.168.0.71
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.0.1
Ethernet adapter ARM-dev-board-10.x.x.x:
Connection-specific DNS Suffix . :
Link-local IPv6 Address . . . . . : fe80::e540:1d52:7bf7:3e4%12
IPv4 Address. . . . . . . . . . . : 10.86.11.123
Subnet Mask . . . . . . . . . . . : 255.0.0.0
IPv4 Address. . . . . . . . . . . : 172.16.17.6
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 172.16.17.1
我正在使用%12范围ID在ARM-dev-board-10.x.x.x适配器上选择fe80 :: e540:1d52:7bf7:3e4%12链接本地地址。
我已经使用Wireshark监视网络数据包,并且可以看到我的代码正确发送了ping请求,而目标发送了ping回复。问题是我的代码从未收到回复数据包。对recv()的调用超时(返回SOCKET_ERROR,而WSAGetLastError返回10600)。
我是否需要设置一些魔术套接字选项以使Windows将答复数据包传递给我?
我尝试在sendto()之前添加对bind()的调用,但这没有任何区别(我认为我不需要调用bind(),因为sendto()应该为我隐式绑定接口)我认为)。
我正在用address = "fe80::b617:80ff:fe40:fe21%12"
和timeoutInMs = 1000
调用以下代码
bool Ping6Internal(const char* address, const int timeoutInMs)
{
bool result = false;
int timeout = timeoutInMs;
// get the destination address
struct addrinfo* addrInfo;
struct addrinfo hints = { 0 };
struct sockaddr_in6 dstAddr = { 0 };
// We only care about IPV6 results
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG;
int errcode = getaddrinfo(address, nullptr, &hints, &addrInfo);
if (errcode != 0)
{
perror("[ERROR] getaddrinfo ");
}
for (auto p = addrInfo; p; p = p->ai_next)
{
// Check to make sure we have a valid AF_INET6 address
if (p->ai_family == AF_INET6)
{
// Use memcpy since we're going to free the addrInfo variable
int foo = sizeof(sockaddr_in6);
memcpy(&dstAddr, p->ai_addr, p->ai_addrlen);
dstAddr.sin6_family = AF_INET6;
break;
}
}
freeaddrinfo(addrInfo);
int sockRaw = WSASocket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6, nullptr, 0, WSA_FLAG_OVERLAPPED);
if (sockRaw == INVALID_SOCKET)
{
throw std::runtime_error("WSASocket failed");
}
int rv = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<char*>(&timeout), sizeof(timeout));
if (rv == SOCKET_ERROR)
{
closesocket(sockRaw);
throw std::runtime_error("setsockopt SO_RCVTIMEO failed");
}
rv = setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<char*>(&timeout), sizeof(timeout));
if (rv == SOCKET_ERROR)
{
closesocket(sockRaw);
throw std::runtime_error("setsockopt SO_SNDTIMEO failed");
}
// Find out which local interface will be used when sending to this destination
DWORD bytes;
sockaddr_in6 srcAddr;
rv = WSAIoctl(sockRaw, SIO_ROUTING_INTERFACE_QUERY, &dstAddr, sizeof(dstAddr),
(SOCKADDR *)&srcAddr, sizeof(srcAddr), &bytes, nullptr, nullptr);
if (rv == SOCKET_ERROR)
{
closesocket(sockRaw);
throw std::runtime_error("could not determine which interface to use");
}
string localAddress = FormatAddress((SOCKADDR*)&srcAddr, sizeof(srcAddr));
#if 0
rv = bind(sockRaw, reinterpret_cast<struct sockaddr*>(&srcAddr), sizeof(srcAddr));
if (rv == SOCKET_ERROR)
{
int errCode = WSAGetLastError();
string errMsg = GetErrorString(errCode);
}
#endif
std::vector<char> icmpPacket(MAX_PACKET_SIZE);
IcmpHeader* pHeaderTx = reinterpret_cast<IcmpHeader*>(icmpPacket.data());
pHeaderTx->type = ICMPV6_ECHO;
pHeaderTx->code = 0;
pHeaderTx->checksum = 0;
pHeaderTx->id = static_cast<uint16_t>(GetCurrentProcessId());
pHeaderTx->seqNum = 0;
pHeaderTx->timestamp = GetTickCount();
// the upper 32 bits is the process id and the lower 32 bits is an incrementing counter
pHeaderTx->uniqueId = (uint64_t(GetCurrentProcessId()) << 32) | g_threadData.GetNextCounter();
const int headerSize = sizeof(IcmpHeader);
const int packetSize = headerSize + DEF_PACKET_SIZE;
std::fill(icmpPacket.data() + headerSize, icmpPacket.data() + packetSize, 'E');
// Calculate the packet checksum.
// The checksum is calculated over the IPv6 pseudo header plus the real packet.
IpV6PseudoHeader pseudoHeader = { 0 };
pseudoHeader.srcAddress = srcAddr.sin6_addr;
pseudoHeader.dstAddress = dstAddr.sin6_addr;
pseudoHeader.length = htonl(sizeof(IcmpHeader));
pseudoHeader.nextHeader = IPPROTO_ICMPV6;
unsigned long sum = 0;
const uint16_t* hdrU16 = reinterpret_cast<uint16_t*>(&pseudoHeader);
for (int n = 0; n < sizeof(IpV6PseudoHeader) / 2; n++)
{
sum += hdrU16[n];
}
const uint16_t* dataU16 = reinterpret_cast<uint16_t*>(icmpPacket.data());
for (int n = 0; n < packetSize / 2; n++)
{
sum += dataU16[n];
}
if (packetSize % 2) // odd number of bytes so grab the final byte
{
sum += icmpPacket[packetSize - 1];
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >>16);
pHeaderTx->checksum = static_cast<uint16_t>(~sum);
rv = sendto(sockRaw, icmpPacket.data(), packetSize, 0, (struct sockaddr*)&dstAddr, sizeof(dstAddr));
if (rv != SOCKET_ERROR)
{
char recvBuf[MAX_PACKET_SIZE];
// struct sockaddr_in6 from = { 0 };
// sockaddr_storage from;
auto now = boost::chrono::system_clock::now();
const auto timeExpired = now + boost::chrono::milliseconds(timeoutInMs);
do
{
// int fromSize = sizeof(from);
// const int rv = recvfrom(sockRaw, recvBuf, MAX_PACKET_SIZE, 0, (struct sockaddr*) &from, &fromSize);
const int rv = recv(sockRaw, recvBuf, MAX_PACKET_SIZE, 0);
if (rv == SOCKET_ERROR)
{
int errCode = WSAGetLastError();
string errMsg = GetErrorString(errCode);
break;
}
IcmpHeader* pHeaderRx = reinterpret_cast<IcmpHeader*>(recvBuf + IP_HEADER_SIZE);
if (pHeaderRx->uniqueId == pHeaderTx->uniqueId)
{
result = true;
break;
}
now = boost::chrono::system_clock::now();
} while (now < timeExpired);
}
closesocket(sockRaw);
return result;
}
于2019年4月10日编辑以添加:
在Windows事件查看器中的安全性部分中查看ID为5152的事件,我可以看到以下内容:
The Windows Filtering Platform has blocked a packet.
Application Information:
Process ID: 4
Application Name: System
Network Information:
Direction: Inbound
Source Address: fe80::b617:80ff:fe40:fe21
Source Port: 0
Destination Address: fe80::e540:1d52:7bf7:3e4
Destination Port: 129
Protocol: 58
Filter Information:
Filter Run-Time ID: 648218
Layer Name: Receive/Accept
Layer Run-Time ID: 46
似乎Windows防火墙已阻止ping答复。关闭防火墙(并暂时禁用Sophos Endpoint)之后,我再也看不到ID 5152事件,表明该数据包已被过滤。但是我的程序仍然没有收到ping回复数据包:-(