jjzjj

c++ - ICMP 回显请求/回复无法到达目的地的正确过程是什么?

coder 2023-09-17 原文

目标:

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

设计:

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

问题:

在实现我的设计后,每当我对一个特定的有效网络地址(比如 192.168.11.15)与活跃的网络参与者进行 ping 操作时,我都会收到五 (5) 个 ping 操作中的每一个的 ICMP_ECHOREPLY 消息。但是,每当我 ping 一个有效的网络地址(比如 192.168.30.30)与不活动的网络参与者(意味着没有设备连接到特定地址)时,我都会收到一 (1) 条 ICMP_DEST_UNREACH 和四 (4) 条 ICMP_ECHOREPLY 消息。回复消息中的 ID 与存储在软件中的 ID 相匹配。每当我从命令行执行“ping 192.168.30.30”时,我都会收到“From 192.168.40.50 icmp_seq=xx Destination Host Unreachable”。我不应该接收 ICMP_DEST_UNREACH 消息而不是 ICMP_ECHOREPLY 消息吗?

代码:

平.h:

#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 receiver\n");
                    close(sock);
                    exit(1);
                }
                else if(bytes != sizeof(packet))
                {
                    printf("Failed to write the whole packet --- bytes: %d, sizeof(packet): %d\n", 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 recvfrom\n");
                        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_ECHOREPLY\n");
                            if(id == pid)
                            {
                                nrec++;
                                if(i < 5) break;
                            }
                        }
                        else if(pkt->type == ICMP_DEST_UNREACH)
                        {
                            printf("    ICMP_DEST_UNREACH\n");
                            // 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: %d\n", 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 消息?

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

我对编码风格的批评也不感兴趣。我知道它不漂亮,'C' 风格转换与 C++ 转换混合,内存管理不佳等,但这只是原型(prototype)代码。它并不意味着漂亮。

最佳答案

好的,我发现了错误。看看这两行。

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()

关于c++ - ICMP 回显请求/回复无法到达目的地的正确过程是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9913661/

有关c++ - ICMP 回显请求/回复无法到达目的地的正确过程是什么?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  4. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  5. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  6. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  7. ruby-on-rails - 无法使用 Rails 3.2 创建插件? - 2

    我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby​​1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在

  8. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  9. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  10. ruby-on-rails - 无法在centos上安装therubyracer(V8和GCC出错) - 2

    我正在尝试在我的centos服务器上安装therubyracer,但遇到了麻烦。$geminstalltherubyracerBuildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingtherubyracer:ERROR:Failedtobuildgemnativeextension./usr/local/rvm/rubies/ruby-1.9.3-p125/bin/rubyextconf.rbcheckingformain()in-lpthread...yescheckingforv8.h...no***e

随机推荐