在无法访问的目的地上 ICMP 回显请求/回复的正确过程是什么



目标:

我需要能够 ping 网络交换机以确定它是否可用。这是为了告诉用户网络布线已拔出、网络交换机不可用或网络通信路径中存在其他问题。我意识到这不是一个全面的诊断工具,但有总比没有好。

设计:

我计划使用带有原始套接字的ICMP以IPv4点表示法向特定地址发送五(5(条ping消息。我将在套接字上设置ICMP过滤器,并且不会创建自己的IP标头。ICMP的传输将通过发送方法进行,接收将通过recvfrom方法进行。这将发生在单个线程上(尽管另一个线程可用于将传输和接收分开(。通过将收到的消息的 ID 与传输的消息的 ID 匹配,将进一步过滤消息的接收。存储的 ID 将是应用程序的正在运行的进程 ID。如果收到ICMP_ECHOREPLY消息并且消息的 ID 和存储的 ID 匹配,则计数器将递增,直到达到五 (4( 个(计数器从零开始(。我将尝试发送ping,等待其回复,然后重复此过程五(5(次。

问题:

实现我的设计后,每当我与活跃的网络参与者 ping 一个特定的有效网络地址(例如 192.168.11.15(时,我都会收到五 (5( 次 ping 中每 (5( 次 ping 的ICMP_ECHOREPLY条消息。但是,每当我使用非活动网络参与者(意味着没有设备连接到特定地址(ping有效的网络地址(例如192.168.30.30(时,我都会收到一(1(ICMP_DEST_UNREACH和四(4(ICMP_ECHOREPLY消息。回复消息中的 ID 与软件中存储的 ID 匹配。每当我从命令行执行"ping 192.168.30.30"时,我都会得到"来自 192.168.40.50 icmp_seq=xx 目标主机无法访问"。我不应该接收ICMP_DEST_UNREACH消息而不是ICMP_ECHOREPLY消息吗?

守则:

平:

#include <netinet/in.h>
#include <linux/ip.h>
#include <linux/ipmc.h>
#include <arpa/inet.h>
#include <cstdio>
#include <cstdlib>
#include <stdint.h>
#include <time.h>
#include <errno.h>
#include <string>
#include <cstring>
#include <netdb.h>
class Ping
{
    public:
        Ping(std::string host) : _host(host) {}
        ~Ping() {}
        void start()
        {
            int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
            if(sock < 0)
            {
                printf("Failed to create socket!n");
                close(sock);
                exit(1);
            }
            setuid(getuid());
            sockaddr_in pingaddr;
            memset(&pingaddr, 0, sizeof(sockaddr_in));
            pingaddr.sin_family = AF_INET;
            hostent *h = gethostbyname(_host.c_str());
            if(not h)
            {
                printf("Failed to get host by name!n");
                close(sock);
                exit(1);
            }
            memcpy(&pingaddr.sin_addr, h->h_addr, sizeof(pingaddr.sin_addr));
            // Set the ID of the sender (will go into the ID of the echo msg)
            int pid = getpid();
            // Only want to receive the following messages
            icmp_filter filter;
            filter.data = ~((1<<ICMP_SOURCE_QUENCH) |
                            (1<<ICMP_DEST_UNREACH) |
                            (1<<ICMP_TIME_EXCEEDED) |
                            (1<<ICMP_REDIRECT) |
                            (1<<ICMP_ECHOREPLY));
            if(setsockopt(sock, SOL_RAW, ICMP_FILTER, (char *)&filter, sizeof(filter)) < 0)
            {
                perror("setsockopt(ICMP_FILTER)");
                exit(3);
            }
            // Number of valid echo receptions
            int nrec = 0;
            // Send the packet
            for(int i = 0; i < 5; ++i)
            {
                char packet[sizeof(icmphdr)];
                memset(packet, 0, sizeof(packet));
                icmphdr *pkt = (icmphdr *)packet;
                pkt->type = ICMP_ECHO;
                pkt->code = 0;
                pkt->checksum = 0;
                pkt->un.echo.id = htons(pid & 0xFFFF);
                pkt->un.echo.sequence = i;
                pkt->checksum = checksum((uint16_t *)pkt, sizeof(packet));
                int bytes = sendto(sock, packet, sizeof(packet), 0, (sockaddr *)&pingaddr, sizeof(sockaddr_in));
                if(bytes < 0)
                {
                    printf("Failed to send to receivern");
                    close(sock);
                    exit(1);
                }
                else if(bytes != sizeof(packet))
                {
                    printf("Failed to write the whole packet --- bytes: %d, sizeof(packet): %dn", bytes, sizeof(packet));
                    close(sock);
                    exit(1);
                }
                while(1)
                {
                    char inbuf[192];
                    memset(inbuf, 0, sizeof(inbuf));
                    int addrlen = sizeof(sockaddr_in);
                    bytes = recvfrom(sock, inbuf, sizeof(inbuf), 0, (sockaddr *)&pingaddr, (socklen_t *)&addrlen);
                    if(bytes < 0)
                    {
                        printf("Error on recvfromn");
                        exit(1);
                    }
                    else
                    {
                        if(bytes < sizeof(iphdr) + sizeof(icmphdr))
                        {
                            printf("Incorrect read bytes!n");
                            continue;
                        }
                        iphdr *iph = (iphdr *)inbuf;
                        int hlen = (iph->ihl << 2);
                        bytes -= hlen;
                        pkt = (icmphdr *)(inbuf + hlen);
                        int id = ntohs(pkt->un.echo.id);
                        if(pkt->type == ICMP_ECHOREPLY)
                        {
                            printf("    ICMP_ECHOREPLYn");
                            if(id == pid)
                            {
                                nrec++;
                                if(i < 5) break;
                            }
                        }
                        else if(pkt->type == ICMP_DEST_UNREACH)
                        {
                            printf("    ICMP_DEST_UNREACHn");
                            // Extract the original data out of the received message
                            int offset = sizeof(iphdr) + sizeof(icmphdr) + sizeof(iphdr);
                            if(((bytes + hlen) - offset) == sizeof(icmphdr))
                            {
                                icmphdr *p = reinterpret_cast<icmphdr *>(inbuf + offset);
                                id = ntohs(p->un.echo.id);
                                if(origid == pid)
                                {
                                    printf("        IDs match!n");
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            printf("nrec: %dn", nrec);
        }
    private:
        int32_t checksum(uint16_t *buf, int32_t len)
        {
            int32_t nleft = len;
            int32_t sum = 0;
            uint16_t *w = buf;
            uint16_t answer = 0;
            while(nleft > 1)
            {
                sum += *w++;
                nleft -= 2;
            }
            if(nleft == 1)
            {
                *(uint16_t *)(&answer) = *(uint8_t *)w;
                sum += answer;
            }
            sum = (sum >> 16) + (sum & 0xFFFF);
            sum += (sum >> 16);
            answer = ~sum;
            return answer;
        }
        std::string _host;
};

主.cpp:

#include "Ping.h"
int main()
{
//     Ping ping("192.168.11.15");
    Ping ping("192.168.30.30");
    ping.start();
    while(1) sleep(10);
}

为了编译,只需在 Linux 框的命令行中键入 'g++ main.cpp -o ping',它应该编译(也就是说,如果安装了所有源代码(。

结论:

谁能告诉我为什么我从不在该特定网络地址上的设备收到一 (1( ICMP_DEST_UNREACH和四 (4( ICMP_ECHOREPLY消息?

注意:您可以从主文件更改网络 IP 地址.cpp。只需将 IP 更改为网络上实际存在的设备或网络上不存在的设备即可。

我对关于编码风格的批评也不感兴趣。我知道它不漂亮,"C"风格的转换与C++转换混合在一起,内存管理很差等,但这只是原型代码。它并不意味着漂亮。

好的,

我发现了错误。看看这两行。

int bytes = sendto(sock, packet, sizeof(packet), 0, (sockaddr *)&pingaddr, sizeof(sockaddr_in));
bytes = recvfrom(sock, inbuf, sizeof(inbuf), 0, (sockaddr *)&pingaddr, (socklen_t *)&addrlen);

这两个函数都使用指针作为参数pingaddr但应避免这样做,因为在sendto()函数中用于指向 ICMP 数据包的目标 IP,但在recvfrom()中用于获取正在回复的主机的 IP。

假设pingaddr设置了无法访问的 IP。在您第一次ICMP_REQUEST后,第一个网关将回复您ICMP_DEST_UNREACH并...错误来了...当调用 recvfrom 时,pingaddr 结构将被网关的 IP 覆盖。

所以。。。从第二个ping开始,您将指向显然存在的网关IP,并将以ICMP_ECHOREPLY回复。

溶液:

避免将相同的sockaddr_in结构指针传递给 sendto()recvfrom()

最新更新