9

ゴール:

ネットワーク スイッチが使用可能かどうかを確認するために、ネットワーク スイッチに ping を送信できる必要があります。これは、ネットワーク ケーブルが外れているか、ネットワーク スイッチが使用できないか、またはネットワーク通信経路に他の問題があることをユーザーに知らせるためのものです。これは包括的な診断ツールではありませんが、何もないよりはましです。

デザイン:

生のソケットで ICMP を使用して、IPv4 ドット表記の特定のアドレスに 5 つの ping メッセージを送信することを計画しました。ソケットに ICMP フィルターをセットアップし、独自の IP ヘッダーを作成しません。ICMP の送信は sendto メソッドを使用し、受信は recvfrom メソッドを使用します。これは単一のスレッドで発生します (ただし、別のスレッドを使用して送信と受信を分離することもできます)。メッセージの受信は、受信したメッセージの ID と送信された ID を照合することによってさらにフィルタリングされます。保存された ID は、アプリケーションの実行中のプロセス ID になります。ICMP_ECHOREPLY メッセージが受信され、メッセージの ID と格納された ID が一致する場合、カウンターは 5 (4) に達するまでインクリメントされます (カウンターはゼロから始まります)。ping を送信して応答を待ちます。

問題:

設計を実装した後、特定の有効なネットワーク アドレス (192.168.11.15 など) に対してアクティブなネットワーク参加者と ping を実行すると、5 回の ping ごとに ICMP_ECHOREPLY メッセージを受信します。ただし、非アクティブなネットワーク参加者 (特定のアドレスにデバイスが接続されていないことを意味する) を使用して有効なネットワーク アドレス (192.168.30.30 など) に ping を実行すると、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_ECHOREPLY メッセージの代わりに ICMP_DEST_UNREACH メッセージを受信するはずがないのですか?

コード:

Ping.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;
};

main.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 つの (1) ICMP_DEST_UNREACH メッセージと 4 つの (4) ICMP_ECHOREPLY メッセージを受信する理由を誰か教えてもらえますか?

注: ネットワーク IP アドレスは、main.cpp ファイルから変更できます。IP をネットワーク上に実際に存在するデバイスまたはネットワーク上に存在しないデバイスに変更するだけです。

また、コーディング スタイルに関する批判にも興味がありません。見栄えがよくない、「C」スタイルのキャストと C++ キャストが混在している、メモリ管理が貧弱であるなどのことはわかっていますが、これはプロトタイプ コードにすぎません。それはきれいであることを意味するものではありません。

4

1 に答える 1

6

エラーが見つかりました。この2行を見てください。

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

両方の関数はパラメーターとしてポインターを使用しますが、関数では icmp パケットの宛先 IP を指すために使用されますが、応答しているホストの IP を取得するために使用されるpingaddrため、これは避ける必要が あります。sendto()recvfrom()

pingaddr到達できないIPで設定されているとしましょう。あなたの最初ICMP_REQUESTのゲートウェイの後、最初のゲートウェイがあなたに返信しICMP_DEST_UNREACH、... エラーが発生します... recvfrom が呼び出されると、pingaddr 構造はゲートウェイの IP で上書きされます。

SO ... 2 番目の ping から、明らかに存在し、. で応答するゲートウェイ IP を指しますICMP_ECHOREPLY

解決:

と の両方に同じsockaddr_in構造体ポインタを渡さないようにします。sendto()recvfrom()

于 2013-01-16T19:38:16.890 に答える