Home >Backend Development >Golang >How do I ensure the correctness of the IPv4 packets created for the native 'ping' implementation?
php editor Strawberry will introduce to you how to ensure the correctness of the IPv4 packets created for the local "ping" implementation. In network communication, use the Ping command to test the connectivity between hosts. However, in practical applications, we need to ensure the correctness of the IPv4 data packets sent to avoid errors or losses. To this end, we can take some measures to ensure the accuracy and completeness of data packets to ensure that we get accurate Ping results. Next, let’s take a look at these measures.
I've been working on a side project that is essentially a network troubleshooting tool. My goal is to deepen my understanding of networking basics and become proficient in using the troubleshooting tools provided by the operating system.
This is a CLI application that will get the hostname and try to diagnose the problem if any. The plan is to implement ping and traceroute first and then gradually implement other tools based on my comfort level.
However, my ping implementation is not accurate because the IPv4 packets are malformed. This is what wireshark has to say.
1 0.000000 192.168.0.100 142.250.195.132 ICMP 300 Unknown ICMP (obsolete or malformed?)
This is how I implement 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>
I'm not sure if the packet malformation is caused by a single cause or multiple causes. I think the problem lies in one of these two places (or both?):
40 bytes (wordsize = 4 bytes)
. Write structure fields in an order that prevents structure corruption.
I refer to this source for information on various types of sizes. <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>
There are some parts missing in the implementation, such as configuring counts, assigning sequence numbers to packets, etc., but before that the basic implementation needs to be fixed, i.e. receiving responses for ICMP ECHO packets. Good to know where I made the mistake.
Thanks!
Taking into account the advice I got in the comments, I have updated the code to fix the byte order and use raw bytes for the source address, destination address. However, this alone doesn't solve the problem, the packet is still malformed, so there must be something else going on.
I finally got it working. I should talk about a few issues with the code.
As Andy correctly pointed out, I'm sending a JSON object, not raw bytes in network byte order. This is fixed using binary.Write(buf, binary.BigEndian, field)
However, since this method only works with fixed-size values, I have to do this for each struct field, making the code repetitive and somewhat ugly.
I am aware of the practice of combining the Version
and IHL
fields together to optimize memory, that's why I have this single field VersionIHL
in my structure. But when serializing, the field values (4 and 5 in this case) will be serialized individually, and I didn't do that. Instead, I convert the entire VersionIHL
field's value into bytes.
As a result, I found myself sending an unexpected octet 69
in the byte stream from combining 4
and 5
grouped together 0100 0101
.
My ICMP structure does not contain identifier and sequence number fields. The information provided in the ICMP datagram header section on Wikipedia feels a bit generic. However, I found the details on the RFC page (page 14) to be much more insightful.
This feels strange considering the importance of the serial number of the ping utility. During implementation, I often found myself wondering where the serial number was placed appropriately in the code. It wasn't until I stumbled across the RFC page that I had a clear idea of when and where to incorporate serial numbers.
For anyone who might be interested, here is the function code I've put it together.
The above is the detailed content of How do I ensure the correctness of the IPv4 packets created for the native 'ping' implementation?. For more information, please follow other related articles on the PHP Chinese website!