Почему при ожидании ответа на эхо-запрос ICMPv6 вызывается тайм-аут recv? - PullRequest
2 голосов
/ 09 апреля 2019

Я пытаюсь программно отправлять и получать пинг-пакеты ICMPv6 с компьютера с Windows 7.Код, который я использую, адаптирован из существующего кода, который успешно используется для отправки / получения пакетов проверки связи IPv4.Единственное отличие, которое я вижу, состоит в том, что я использую IPv6 вместо IPv4, и что я использую локальные адреса связи для адресов источника и назначения.

Адрес назначения, на который я пингуюсь, - это 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, чтобы выбрать fe80 :: e540: 1d52: 7bf7: 3e4% 12 локальный адрес ссылки на ARM-Адаптер dev-board-10.xxx.

Я использовал wireshark для мониторинга сетевых пакетов, и я вижу, что мой код правильно отправляет запрос проверки связи, а цель отправляет ответ проверки связи.Проблема в том, что мой код никогда не получает ответный пакет.Вызов recv () истекает (возвращает SOCKET_ERROR, а WSAGetLastError возвращает 10600).

Есть ли какая-то опция волшебного сокета, которую мне нужно установить, чтобы заставить Windows передавать мне пакеты ответа?

Я пытался добавить вызов bind () перед sendto (), но это не имело никакого значения (я не думаю, что мне нужно вызывать 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;
    }

Отредактировано 10 апреля 2019 г. для добавления:

Просмотр в средстве просмотра событий Windowsна событиях с идентификатором 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 заблокировал пинг-ответ.После выключения брандмауэра (и временного отключения конечной точки Sophos) я больше не вижу событий ID 5152, показывающих, что пакет был отфильтрован.Но моя программа все еще не получает пакет ответа: - (

...