Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Bagaimanakah cara saya memastikan ketepatan paket IPv4 yang dibuat untuk pelaksanaan "ping" asli?

Bagaimanakah cara saya memastikan ketepatan paket IPv4 yang dibuat untuk pelaksanaan "ping" asli?

王林
王林ke hadapan
2024-02-09 08:36:251003semak imbas

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

editor php Strawberry akan memperkenalkan kepada anda cara memastikan ketepatan paket IPv4 yang dibuat untuk pelaksanaan "ping" tempatan. Dalam komunikasi rangkaian, gunakan arahan Ping untuk menguji ketersambungan antara hos. Walau bagaimanapun, dalam aplikasi praktikal, kita perlu memastikan ketepatan paket data IPv4 yang dihantar untuk mengelakkan ralat atau kehilangan. Untuk tujuan ini, kami boleh mengambil beberapa langkah untuk memastikan ketepatan dan kesempurnaan paket data untuk memastikan kami mendapat keputusan Ping yang tepat. Seterusnya, mari kita lihat langkah-langkah ini.

Kandungan soalan

Ikhtisar

Saya telah mengusahakan projek sampingan yang pada asasnya merupakan alat penyelesaian masalah rangkaian. Matlamat saya adalah untuk mendalami pemahaman saya tentang asas rangkaian dan menjadi mahir dalam menggunakan alat penyelesaian masalah yang disediakan oleh sistem pengendalian.

Ini adalah aplikasi CLI yang akan mendapat nama hos dan cuba mendiagnosis masalah jika ada. Rancangannya adalah untuk melaksanakan ping dan traceroute terlebih dahulu dan kemudian secara beransur-ansur melaksanakan alat lain berdasarkan tahap keselesaan saya.

Walau bagaimanapun, pelaksanaan ping saya tidak tepat kerana paket IPv4 rosak. Inilah yang dikatakan wireshark.

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

Kod

Beginilah cara saya mencapai 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>

Introspeksi

Saya tidak pasti sama ada kecacatan paket disebabkan oleh satu punca atau berbilang punca. Saya rasa masalahnya terletak pada salah satu daripada dua tempat ini (atau kedua-duanya?):

  1. Pengiraan panjang pengepala tidak betul Saya mengira panjangnya secara manual 40 字节(wordsize = 4 字节). Tulis medan struktur dalam susunan yang menghalang rasuah struktur. Saya merujuk kepada sumber ini untuk maklumat tentang pelbagai jenis saiz.
<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. Pengiraan checksum tidak betulSaya melaksanakan algoritma checksum internet. Jika ini bukan perkara yang saya sepatutnya lakukan di sini, sila beritahu saya.

Terdapat beberapa bahagian yang tiada dalam pelaksanaan, seperti mengkonfigurasi kiraan, menetapkan nombor jujukan pada paket, dsb., tetapi sebelum itu pelaksanaan asas perlu diperbaiki, iaitu menerima respons untuk paket ICMP ECHO. Baik untuk mengetahui di mana saya membuat kesilapan.

Terima kasih!

Dikemaskini pada 24 Ogos 2023

Mengambil kira nasihat yang saya dapat dalam ulasan, saya telah mengemas kini kod untuk membetulkan susunan bait dan menggunakan bait mentah untuk alamat sumber, alamat destinasi. Walau bagaimanapun, itu sahaja tidak menyelesaikan masalah, paket masih cacat, jadi mesti ada perkara lain yang berlaku.

Penyelesaian

Saya akhirnya berjaya melakukannya. Saya harus bercakap tentang beberapa isu dengan kod.

Masalah siri

Seperti yang Andy nyatakan dengan betul, saya menghantar objek JSON, bukan bait mentah dalam susunan bait rangkaian. Ini telah dibetulkan menggunakan binary.Write(buf, binary.BigEndian, field)

Walau bagaimanapun, memandangkan kaedah ini hanya berfungsi dengan nilai saiz tetap, saya perlu melakukan ini untuk setiap medan struct, menjadikan kod berulang dan agak hodoh.

Pengoptimuman struktur dan bersiri adalah masalah yang berbeza.

Saya tahu untuk menukar nilai medan VersionIHL 字段组合在一起以优化内存的做法,这就是为什么我的结构中有这个单个字段 VersionIHL 。但是在序列化时,字段值(在本例中为 4 和 5)将被单独序列化,而我没有这样做。相反,我将整个 VersionIHL kepada bait.

Akibatnya, saya mendapati diri saya menghantar oktet yang tidak dijangka 69,该字节流来自将 45 组合在一起的 0100 0101 dalam strim bait.

Paket ICMP tidak lengkap

Struktur ICMP saya tidak mengandungi medan pengecam dan nombor jujukan. Maklumat yang diberikan dalam bahagian pengepala datagram ICMP di Wikipedia terasa agak generik. Walau bagaimanapun, saya dapati butiran pada halaman RFC (halaman 14) lebih bernas.

Ini terasa pelik memandangkan kepentingan nombor siri utiliti ping. Semasa pelaksanaan, saya sering tertanya-tanya di mana nombor siri diletakkan dengan sewajarnya dalam kod. Sehinggalah saya terjumpa halaman RFC barulah saya mempunyai idea yang jelas tentang bila dan di mana untuk memasukkan nombor siri.

Bagi sesiapa yang mungkin berminat, inilah kod fungsiyang telah saya susun.

Atas ialah kandungan terperinci Bagaimanakah cara saya memastikan ketepatan paket IPv4 yang dibuat untuk pelaksanaan "ping" asli?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:stackoverflow.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam