2

我正在尝试以编程方式从 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.xxx 适配器上的 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"and调用下面的代码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 回复数据包:-(

4

0 回答 0