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 » ?
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.
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?)
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>
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 ?) :
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>
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 !
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.
Je l'ai enfin fait fonctionner. Je devrais parler de quelques problèmes avec le code.
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.
Je sais convertir la valeur du champ Version
和 IHL
字段组合在一起以优化内存的做法,这就是为什么我的结构中有这个单个字段 VersionIHL
。但是在序列化时,字段值(在本例中为 4 和 5)将被单独序列化,而我没有这样做。相反,我将整个 VersionIHL
en octets.
En conséquence, je me suis retrouvé à envoyer un octet inattendu 69
,该字节流来自将 4
和 5
组合在一起的 0100 0101
dans le flux d'octets.
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!