确保本机创建的IPv4数据包的正确性的方法有哪些?
来源:stackoverflow
时间:2024-02-13 09:42:23 442浏览 收藏
小伙伴们对Golang编程感兴趣吗?是否正在学习相关知识点?如果是,那么本文《确保本机创建的IPv4数据包的正确性的方法有哪些?》,就很适合你,本篇文章讲解的知识点主要包括。在之后的文章中也会多多分享相关知识点,希望对大家的知识积累有所帮助!
概述
我一直在从事一个业余项目,该项目本质上是一个网络故障排除工具。我的目的是加深对网络基础知识的理解,并熟练使用操作系统提供的故障排除工具。
这是一个 CLI 应用程序,它将获取主机名并尝试诊断问题(如果有)。计划是首先实现 ping 和 traceroute,然后根据我的舒适程度逐步实现其他工具。
但是,我的 ping 实现并不准确,因为 IPv4 数据包格式错误。这就是wireshark 必须说的。
1 0.000000 192.168.0.100 142.250.195.132 ICMP 300 Unknown ICMP (obsolete or malformed?)
代码
这是我实现 ping
的方法
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
}
自省
我不确定数据包的畸形是由单一原因还是多种原因造成的。 我觉得问题出在这两个地方之一(或两者?):
- 标头长度计算不正确 我手动计算的长度为
40 字节(wordsize = 4 字节)
。按照可防止结构体损坏的顺序编写结构体字段。 我参考这个来源来了解各种类型的大小。
// 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
}
- 校验和计算不正确我实现了互联网校验和算法。如果这不是我应该在这里做的事情,请告诉我。
实现中缺少一些部分,例如配置计数、为数据包分配序列号等,但在此之前需要修复基本实现,即接收 ICMP ECHO 数据包的响应。很高兴知道我在哪里犯了错误。
谢谢!
2023 年 8 月 24 日更新
考虑到我在评论中得到的建议,我已经更新了代码,即修复字节顺序并使用原始字节作为源地址、目标地址。然而,仅此并不能解决问题,数据包仍然格式错误,因此肯定还有其他问题。
正确答案
我终于让它工作了。我应该谈谈代码的几个问题。
序列化问题
正如 Andy 正确指出的那样,我发送的是 JSON 对象,而不是按网络字节顺序发送原始字节。这是使用 binary.Write(buf, binary.BigEndian, field)
修复的
但是,由于此方法仅适用于固定大小的值,因此我必须对每个结构体字段执行此操作,从而使代码重复且有些难看。
结构优化和序列化是不同的问题。
我知道将 Version
和 IHL
字段组合在一起以优化内存的做法,这就是为什么我的结构中有这个单个字段 VersionIHL
。但是在序列化时,字段值(在本例中为 4 和 5)将被单独序列化,而我没有这样做。相反,我将整个 VersionIHL
字段的值转换为字节。
结果,我发现自己在字节流中发送了一个意外的八位字节 69
,该字节流来自将 4
和 5
组合在一起的 0100 0101
。
不完整的 ICMP 数据包
我的 ICMP 结构不包含标识符和序列号字段。 Wikipedia 上的 ICMP 数据报标头部分提供的信息感觉有点通用。但是,我发现 RFC 页面(第 14 页) 上的详细信息要多得多富有洞察力。
考虑到 ping 实用程序的序列号的重要性,这感觉很奇怪。在实现过程中,我经常发现自己想知道序列号在代码中的适当位置。直到我偶然发现 RFC 页面,我才清楚地了解何时何地合并序列号。
对于任何可能感兴趣的人,这里是功能代码我已经整理好了。
终于介绍完啦!小伙伴们,这篇关于《确保本机创建的IPv4数据包的正确性的方法有哪些?》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布Golang相关知识,快来关注吧!
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
139 收藏
-
204 收藏
-
325 收藏
-
477 收藏
-
486 收藏
-
439 收藏
-
357 收藏
-
352 收藏
-
101 收藏
-
440 收藏
-
212 收藏
-
143 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习