Heim  >  Artikel  >  Backend-Entwicklung  >  Wie stelle ich die Korrektheit der für die native „Ping“-Implementierung erstellten IPv4-Pakete sicher?

Wie stelle ich die Korrektheit der für die native „Ping“-Implementierung erstellten IPv4-Pakete sicher?

王林
王林nach vorne
2024-02-09 08:36:25949Durchsuche

如何确保为本机“ping”实现创建的 IPv4 数据包的正确性?

php-Editor Strawberry zeigt Ihnen, wie Sie die Korrektheit der IPv4-Pakete sicherstellen, die für die lokale „Ping“-Implementierung erstellt wurden. Verwenden Sie bei der Netzwerkkommunikation den Ping-Befehl, um die Konnektivität zwischen Hosts zu testen. In praktischen Anwendungen müssen wir jedoch die Korrektheit der gesendeten IPv4-Datenpakete sicherstellen, um Fehler oder Verluste zu vermeiden. Zu diesem Zweck können wir einige Maßnahmen ergreifen, um die Genauigkeit und Vollständigkeit der Datenpakete sicherzustellen, um sicherzustellen, dass wir genaue Ping-Ergebnisse erhalten. Schauen wir uns als Nächstes diese Maßnahmen an.

Frageninhalt

Übersicht

Ich habe an einem Nebenprojekt gearbeitet, bei dem es sich im Wesentlichen um ein Tool zur Fehlerbehebung im Netzwerk handelt. Mein Ziel ist es, mein Verständnis der Netzwerkgrundlagen zu vertiefen und die vom Betriebssystem bereitgestellten Fehlerbehebungstools besser zu nutzen.

Dies ist eine CLI-Anwendung, die den Hostnamen abruft und versucht, das Problem zu diagnostizieren, falls vorhanden. Der Plan besteht darin, zuerst Ping und Traceroute zu implementieren und dann nach und nach weitere Tools zu implementieren, je nach meinem Komfortniveau.

Allerdings ist meine Ping-Implementierung nicht korrekt, da die IPv4-Pakete fehlerhaft sind. Das sagt wireshark.

1   0.000000    192.168.0.100   142.250.195.132 ICMP    300 Unknown ICMP (obsolete or malformed?)

Code

So habe ich es geschafft ping

<code>package ping

import (
    "encoding/json"
    "net"

    "github.com/pkg/errors"
)

var (
    IcmpProtocolNumber uint8 = 1
    IPv4Version        uint8 = 4
    IPv4IHL            uint8 = 5
    ICMPHeaderType     uint8 = 8
    ICMPHeaderSubtype  uint8 = 0
)

type NativePinger struct {
    SourceIP string
    DestIP   string
}

type ICMPHeader struct {
    Type     uint8
    Code     uint8
    Checksum uint16
}

type ICMPPacket struct {
    Header  ICMPHeader
    Payload interface{}
}

type IPv4Header struct {
    SourceIP       string
    DestinationIP  string
    Length         uint16
    Identification uint16
    FlagsAndOffset uint16
    Checksum       uint16
    VersionIHL     uint8
    DSCPAndECN     uint8
    TTL            uint8
    Protocol       uint8
}

type IPv4Packet struct {
    Header  IPv4Header
    Payload *ICMPPacket
}

func (p *NativePinger) createIPv4Packet() (*IPv4Packet, error) {
    versionIHL := (IPv4Version << 4) | IPv4IHL

    icmpPacket := &ICMPPacket{
        Header: ICMPHeader{
            Type: ICMPHeaderType,
            Code: ICMPHeaderSubtype,
        },
    }
    ipv4Packet := &IPv4Packet{
        Header: IPv4Header{
            VersionIHL:     versionIHL,
            DSCPAndECN:     0,
            Identification: 0,
            FlagsAndOffset: 0,
            TTL:            64,
            Protocol:       IcmpProtocolNumber,
            SourceIP:       p.SourceIP,
            DestinationIP:  p.DestIP,
        },
        Payload: icmpPacket,
    }
    ipv4Packet.Header.Length = 40

    bytes, err := json.Marshal(icmpPacket)
    if err != nil {
        return nil, errors.Wrapf(err, "error converting ICMP packet to bytes")
    }

    icmpPacket.Header.Checksum = calculateChecksum(bytes)

    bytes, err = json.Marshal(ipv4Packet)
    if err != nil {
        return nil, errors.Wrapf(err, "error converting IPv4 packet to bytes")
    }

    ipv4Packet.Header.Checksum = calculateChecksum(bytes)

    return ipv4Packet, nil
}

func calculateChecksum(data []byte) uint16 {
    sum := uint32(0)

    // creating 16 bit words
    for i := 0; i < len(data)-1; i++ {
        word := uint32(data[i])<<8 | uint32(data[i+1])
        sum += word
    }
    if len(data)%2 == 1 {
        sum += uint32(data[len(data)-1])
    }

    // adding carry bits with lower 16 bits
    for (sum >> 16) > 0 {
        sum = (sum & 0xffff) + (sum >> 16)
    }

    // taking one's compliment
    checksum := ^sum
    return uint16(checksum)
}

func (p *NativePinger) ResolveAddress(dest string) error {
    ips, err := net.LookupIP(dest)
    if err != nil {
        return errors.Wrapf(err, "error resolving address of remote host")
    }

    for _, ip := range ips {
        if ipv4 := ip.To4(); ipv4 != nil {
            p.DestIP = ipv4.String()
        }
    }

    // The destination address does not need to exist as unlike tcp, udp does not require a handshake.
    // The goal here is to retrieve the outbound IP. Source: https://stackoverflow.com/a/37382208/3728336
    //
    conn, err := net.Dial("udp", "8.8.8.8:80")
    if err != nil {
        return errors.Wrapf(err, "error resolving outbound ip address of local machine")
    }
    defer conn.Close()

    p.SourceIP = conn.LocalAddr().(*net.UDPAddr).IP.String()

    return nil
}

func (p *NativePinger) Ping(host string) error {
    if err := p.ResolveAddress(host); err != nil {
        return errors.Wrapf(err, "error resolving source/destination addresses")
    }

    packet, err := p.createIPv4Packet()
    if err != nil {
        return errors.Wrapf(err, "error creating IPv4Packet")
    }

    conn, err := net.Dial("ip4:icmp", packet.Header.DestinationIP)
    if err != nil {
        return errors.Wrapf(err, "error eshtablishing connection with %s", host)
    }
    defer conn.Close()

    bytes, err := json.Marshal(packet)
    if err != nil {
        return errors.Wrapf(err, "error converting IPv4 packet into bytes")
    }

    _, err = conn.Write(bytes)
    if err != nil {
        return errors.Wrapf(err, "error sending ICMP echo request")
    }

    buff := make([]byte, 2048)
    _, err = conn.Read(buff) // The implementation doesn't proceed beyond this point
    if err != nil {
        return errors.Wrapf(err, "error receiving ICMP echo response")
    }

    return nil
}

</code>

Introspektion

Ich bin mir nicht sicher, ob die Paketfehlbildung durch eine einzelne Ursache oder durch mehrere Ursachen verursacht wird. Ich denke, das Problem liegt an einer dieser beiden Stellen (oder an beiden?):

  1. Die Berechnung der Kopfzeilenlänge ist falsch. Ich habe die Länge manuell auf 40 字节(wordsize = 4 字节) berechnet. Schreiben Sie Strukturfelder in einer Reihenfolge, die eine Beschädigung der Struktur verhindert. Ich verweise auf diese Quelle für Informationen zu verschiedenen Größentypen.
<code>// 1 word (4 bytes)
type ICMPHeader struct {
    Type     uint8  // 8 bit
    Code     uint8  // 8 bit
    Checksum uint16 // 16 bit
}

// 3 words (3*4 = 12 bytes)
type ICMPPacket struct {
    Header  ICMPHeader  // 1 word
    Payload interface{} // 2 words
}

// 7 words (7*4 = 28 bytes)
type IPv4Header struct {
    // Below group takes 4 words (each string takes 2 words)
    SourceIP      string
    DestinationIP string

    // Following group takes 2 words (each 16 bits)
    Length         uint16
    Identification uint16
    FlagsAndOffset uint16
    Checksum       uint16

    // Below group takes 1 word (each takes 8 bits)
    VersionIHL uint8
    DSCPAndECN uint8
    TTL        uint8
    Protocol   uint8
}

// 10 words (40 bytes)
type IPv4Packet struct {
    Header  IPv4Header // 7 words as calculated above
    Payload ICMPPacket // 3 words as calculated above
}
</code>
  1. Prüfsummenberechnung ist falschIch habe den Internet-Prüfsummenalgorithmus implementiert. Wenn das nicht das ist, was ich hier tun soll, sagen Sie es mir bitte.

In der Implementierung fehlen einige Teile, wie z. B. das Konfigurieren von Zählungen, das Zuweisen von Sequenznummern zu Paketen usw., aber vorher muss die grundlegende Implementierung korrigiert werden, d. h. der Empfang von Antworten für ICMP-ECHO-Pakete. Gut zu wissen, wo ich den Fehler gemacht habe.

Danke!

Aktualisiert am 24. August 2023

Unter Berücksichtigung der Ratschläge, die ich in den Kommentaren erhalten habe, habe ich den Code aktualisiert, um die Bytereihenfolge zu korrigieren und Rohbytes für die Quelladresse und die Zieladresse zu verwenden. Allerdings löst das allein das Problem nicht, das Paket ist immer noch fehlerhaft, also muss etwas anderes im Gange sein.

Lösung

Endlich habe ich es zum Laufen gebracht. Ich sollte über ein paar Probleme mit dem Code sprechen.

Serialisierungsproblem

Wie Andy richtig betont hat, sende ich ein JSON-Objekt, keine Rohbytes in Netzwerkbytereihenfolge. Dies wurde mit binary.Write(buf, binary.BigEndian, field)

behoben

Da diese Methode jedoch nur mit Werten fester Größe funktioniert, muss ich dies für jedes Strukturfeld tun, was den Code repetitiv und etwas hässlich macht.

Strukturoptimierung und Serialisierung sind unterschiedliche Probleme.

Ich weiß, wie man den Wert des Feldes VersionIHL 字段组合在一起以优化内存的做法,这就是为什么我的结构中有这个单个字段 VersionIHL 。但是在序列化时,字段值(在本例中为 4 和 5)将被单独序列化,而我没有这样做。相反,我将整个 VersionIHL in Bytes umwandelt.

Infolgedessen habe ich ein unerwartetes Oktett 69,该字节流来自将 45 组合在一起的 0100 0101 im Byte-Stream gesendet.

Unvollständiges ICMP-Paket

Meine ICMP-Struktur enthält keine Kennungs- und Sequenznummernfelder. Die Informationen im ICMP-Datagramm-Header-Abschnitt auf Wikipedia wirken etwas allgemein gehalten. Allerdings fand ich die Details auf der RFC-Seite (Seite 14) viel aufschlussreicher.

Das fühlt sich seltsam an, wenn man bedenkt, wie wichtig die Seriennummer des Ping-Dienstprogramms ist. Während der Implementierung habe ich mich oft gefragt, wo die Seriennummer im Code richtig platziert ist. Erst als ich über die RFC-Seite gestolpert bin, hatte ich eine klare Vorstellung davon, wann und wo ich Seriennummern einbauen sollte.

Für alle, die Interesse haben könnten, hier ist der Funktionscode, den ich zusammengestellt habe.

Das obige ist der detaillierte Inhalt vonWie stelle ich die Korrektheit der für die native „Ping“-Implementierung erstellten IPv4-Pakete sicher?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:stackoverflow.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen