Maison  >  Article  >  développement back-end  >  Comment puis-je m'assurer de l'exactitude des paquets IPv4 créés pour l'implémentation native du « ping » ?

Comment puis-je m'assurer de l'exactitude des paquets IPv4 créés pour l'implémentation native du « ping » ?

王林
王林avant
2024-02-09 08:36:251003parcourir

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

l'éditeur php Strawberry vous présentera comment garantir l'exactitude des paquets IPv4 créés pour l'implémentation locale du "ping". En communication réseau, utilisez la commande Ping pour tester la connectivité entre les hôtes. Cependant, dans les applications pratiques, nous devons garantir l’exactitude des paquets de données IPv4 envoyés pour éviter les erreurs ou les pertes. À cette fin, nous pouvons prendre certaines mesures pour garantir l’exactitude et l’exhaustivité des paquets de données afin de garantir l’obtention de résultats Ping précis. Examinons ensuite ces mesures.

Contenu des questions

Aperçu

J'ai travaillé sur un projet parallèle qui est essentiellement un outil de dépannage réseau. Mon objectif est d'approfondir ma compréhension des bases des réseaux et de maîtriser l'utilisation des outils de dépannage fournis par le système d'exploitation.

Il s'agit d'une application CLI qui obtiendra le nom d'hôte et tentera de diagnostiquer le problème le cas échéant. Le plan est d'implémenter d'abord ping et traceroute, puis d'implémenter progressivement d'autres outils en fonction de mon niveau de confort.

Cependant, mon implémentation de ping n'est pas précise car les paquets IPv4 sont mal formés. C’est ce que Wireshark a à dire.

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

Code

C'est ainsi que j'ai réalisé 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>

Introspection

Je ne sais pas si la malformation du paquet est causée par une seule cause ou par plusieurs causes. Je pense que le problème réside à l'un de ces deux endroits (ou les deux ?) :

  1. Le calcul de la longueur de l'en-tête est incorrect J'ai calculé manuellement la longueur 40 字节(wordsize = 4 字节). Écrivez les champs de structure dans un ordre qui empêche toute corruption de la structure. Je me réfère à cette source pour obtenir des informations sur différents types de tailles.
<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. Le calcul de la somme de contrôle est incorrectJ'ai implémenté l'algorithme de somme de contrôle Internet. Si ce n'est pas ce que je suis censé faire ici, dites-le-moi s'il vous plaît.

Il manque certaines parties dans l'implémentation, telles que la configuration des décomptes, l'attribution de numéros de séquence aux paquets, etc., mais avant cela, l'implémentation de base doit être corrigée, c'est-à-dire la réception des réponses pour les paquets ICMP ECHO. C'est bon de savoir où j'ai commis l'erreur.

Merci !

Mise à jour le 24 août 2023

En tenant compte des conseils que j'ai reçus dans les commentaires, j'ai mis à jour le code pour corriger l'ordre des octets et utiliser des octets bruts pour l'adresse source, l'adresse de destination. Cependant, cela ne résout pas le problème à lui seul, le paquet est toujours mal formé, il doit donc se passer autre chose.

Solution

Je l'ai enfin fait fonctionner. Je devrais parler de quelques problèmes avec le code.

Problème de sérialisation

Comme Andy l'a souligné à juste titre, j'envoie un objet JSON, pas des octets bruts dans l'ordre des octets du réseau. Cela a été corrigé en utilisant binary.Write(buf, binary.BigEndian, field)

Cependant, comme cette méthode ne fonctionne qu'avec des valeurs de taille fixe, je dois le faire pour chaque champ de structure, ce qui rend le code répétitif et quelque peu laid.

L'optimisation de la structure et la sérialisation sont des problèmes différents.

Je sais convertir la valeur du champ VersionIHL 字段组合在一起以优化内存的做法,这就是为什么我的结构中有这个单个字段 VersionIHL 。但是在序列化时,字段值(在本例中为 4 和 5)将被单独序列化,而我没有这样做。相反,我将整个 VersionIHL en octets.

En conséquence, je me suis retrouvé à envoyer un octet inattendu 69,该字节流来自将 45 组合在一起的 0100 0101 dans le flux d'octets.

Paquet ICMP incomplet

Ma structure ICMP ne contient pas de champs d'identifiant et de numéro de séquence. Les informations fournies dans la section d'en-tête du datagramme ICMP sur Wikipedia semblent un peu génériques. Cependant, j'ai trouvé les détails sur la page RFC (page 14) beaucoup plus perspicaces.

Cela semble étrange compte tenu de l'importance du numéro de série de l'utilitaire ping. Lors de la mise en œuvre, je me suis souvent demandé où le numéro de série était placé de manière appropriée dans le code. Ce n'est que lorsque je suis tombé sur la page RFC que j'ai eu une idée claire du moment et de l'endroit où incorporer les numéros de série.

Pour tous ceux qui pourraient être intéressés, voici le code de fonctionque j'ai mis en place.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer