阅读go语言工具源码系列之gopacket(谷歌出品)----第二集 layers-巧妙的抽象与无聊的协议包

news2024/9/30 5:26:59

上一集中我们讲到了wpcap.dll的go封装方法,对于linux系统下libpcap的go封装采用的是常用的cgo方式,想了解的可以看看pcap文件夹中的pcap_unix.go。

我们得到了wpcap.dll的go调用,就可以利用它来进行列举所有网络设备,例如以下代码

package main  
  
import (  
"fmt"  
"github.com/google/gopacket/pcap"  
"log"  
)
// 得到所有的(网络)设备  
devices, err := pcap.FindAllDevs()  
if err != nil {  
log.Fatal(err)  
}  
// 打印设备信息  
fmt.Println("Devices found:")  
for _, device := range devices {  
fmt.Println("\nName: ", device.Name)  
fmt.Println("Description: ", device.Description)  
fmt.Println("Devices addresses: ", device.Description)  
for _, address := range device.Addresses {  
fmt.Println("- IP address: ", address.IP)  
fmt.Println("- Subnet mask: ", address.Netmask)  
}  
}

也可以抓取某个网络设备的数据包,例如以下代码

package main  
  
import (  
"fmt"  
"github.com/google/gopacket"  
"github.com/google/gopacket/layers"  
"github.com/google/gopacket/pcap"  
"log"  
_ "strings"  
"time"  
)  
  
var (  
device string = "你的网络设备名"  
snapshotLen int32 = 1024  
promiscuous bool = false  
err error  
timeout time.Duration = 30 * time.Second  
handle *pcap.Handle  
)  
  
func main() {  
// Open device  
handle, err = pcap.OpenLive(device, snapshotLen, promiscuous, timeout)  
if err != nil {  
log.Fatal(err)  
}  
defer handle.Close()  
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())  
for packet := range packetSource.Packets() {  
printPacketInfo(packet)  
}  
}  
func printPacketInfo(packet gopacket.Packet) {  
// Let's see if the packet is an ethernet packet  
// 判断数据包是否为以太网数据包,可解析出源mac地址、目的mac地址、以太网类型(如ip类型)等  
ethernetLayer := packet.Layer(layers.LayerTypeEthernet)  
if ethernetLayer != nil {  
fmt.Println("Ethernet layer detected.")  
ethernetPacket, _ := ethernetLayer.(*layers.Ethernet)  
fmt.Println("Source MAC: ", ethernetPacket.SrcMAC)  
fmt.Println("Destination MAC: ", ethernetPacket.DstMAC)  
// Ethernet type is typically IPv4 but could be ARP or other  
fmt.Println("Ethernet type: ", ethernetPacket.EthernetType)  
fmt.Println()  
}  
// Let's see if the packet is IP (even though the ether type told us)  
// 判断数据包是否为IP数据包,可解析出源ip、目的ip、协议号等  
ipLayer := packet.Layer(layers.LayerTypeIPv4)  
if ipLayer != nil {  
fmt.Println("IPv4 layer detected.")  
ip, _ := ipLayer.(*layers.IPv4)  
// IP layer variables:  
// Version (Either 4 or 6)  
// IHL (IP Header Length in 32-bit words)  
// TOS, Length, Id, Flags, FragOffset, TTL, Protocol (TCP?),  
// Checksum, SrcIP, DstIP  
fmt.Printf("From %s to %s\n", ip.SrcIP, ip.DstIP)  
fmt.Println("Protocol: ", ip.Protocol)  
fmt.Println()  
}  
// Let's see if the packet is TCP  
// 判断数据包是否为TCP数据包,可解析源端口、目的端口、seq序列号、tcp标志位等  
tcpLayer := packet.Layer(layers.LayerTypeTCP)  
if tcpLayer != nil {  
fmt.Println("TCP layer detected.")  
tcp, _ := tcpLayer.(*layers.TCP)  
// TCP layer variables:  
// SrcPort, DstPort, Seq, Ack, DataOffset, Window, Checksum, Urgent  
// Bool flags: FIN, SYN, RST, PSH, ACK, URG, ECE, CWR, NS  
fmt.Printf("From port %d to %d\n", tcp.SrcPort, tcp.DstPort)  
fmt.Println("Sequence number: ", tcp.Seq)  
fmt.Println()  
}  
// Iterate over all layers, printing out each layer type  
fmt.Println("All packet layers:")  
for _, layer := range packet.Layers() {  
fmt.Println("- ", layer.LayerType())  
}  
///.......................................................  
// Check for errors  
// 判断layer是否存在错误  
if err := packet.ErrorLayer(); err != nil {  
fmt.Println("Error decoding some part of the packet:", err)  
}  
}

我们可以看到上述代码中,需要对抓取到的网络数据包进行解包操作,每个数据包就像是洋葱一样由各层协议层层封装而成。如果你愿意一层一层一层地剥开它的心,哈哈哈
在这里插入图片描述

gopacket库对于每个协议的解包(解析)操作都对应一个go语言文件,放在了layers文件夹中。

本集我们讲解的内容:gopacket库中对于数据包packet的抽象以及对于layers中对于常见协议的封装操作。

packet

我们先来聚焦一下packet.go。gopacket库中使用Packet来表示一个一个的数据包。这里是gopacket库抽象的Packet接口,

type Packet interface {  
 Functions for outputting the packet as a human-readable string:  
 ------------------------------------------------------------------  
// String returns a human-readable string representation of the packet.// It uses LayerString on each layer to output the layer.String() string  
// Dump returns a verbose human-readable string representation of the packet,// including a hex dump of all layers. It uses LayerDump on each layer to// output the layer.  
Dump() string  
  
 Functions for accessing arbitrary packet layers:  
 ------------------------------------------------------------------  
// Layers returns all layers in this packet, computing them as necessaryLayers() []Layer  
// Layer returns the first layer in this packet of the given type, or nilLayer(LayerType) Layer  
// LayerClass returns the first layer in this packet of the given class,// or nil.  
LayerClass(LayerClass) Layer  
  
 Functions for accessing specific types of packet layers. These functions  
 return the first layer of each type found within the packet.  
 ------------------------------------------------------------------  
// 返回第一个链路层  
// LinkLayer returns the first link layer in the packet
LinkLayer() LinkLayer  
// 返回第一个网络层  
// NetworkLayer returns the first network layer in the packet
NetworkLayer() NetworkLayer  
// 返回第一个传输层  
// TransportLayer returns the first transport layer in the packet
TransportLayer() TransportLayer  
// 返回第一个应用层  
// ApplicationLayer returns the first application layer in the packet
ApplicationLayer() ApplicationLayer  
// ErrorLayer is particularly useful, since it returns nil if the packet// was fully decoded successfully, and non-nil if an error was encountered  
// in decoding and the packet was only partially decoded. Thus, its output  
// can be used to determine if the entire packet was able to be decoded.  
ErrorLayer() ErrorLayer  
  
 Functions for accessing data specific to the packet:  
 ------------------------------------------------------------------  
// Data returns the set of bytes that make up this entire packet.
Data() []byte  
// Metadata returns packet metadata associated with this packet.
Metadata() *PacketMetadata  
}

packet结构中可以看到有几个layer类型,LinkLayer,NetworkLayer ,TransportLayer,ApplicationLayer ,ErrorLayer。我们从这几个名称可以猜出(除去ErrorLayer这个表示错误的layer,其他刚好四个),其对应的应该是TCP/IP体系结构。
在这里插入图片描述

有了数据包对应的结构,接下来就是对于相应数据包每一层协议封装相应操作。

以太网协议

请看源码中layers->ethernet.go

ethernet.go是gopacket库根据ethernet协议对ethernet数据包包装的各种操作函数,里面不涉及执行顺序,所以我建议我们先看一下代码里面具体类型结构

type Ethernet struct {  
BaseLayer  
// 源物理地址 目的物理地址  
SrcMAC, DstMAC net.HardwareAddr  
// 以太网类型  
EthernetType EthernetType  
// Length is only set if a length field exists within this header. Ethernet  
// headers follow two different standards, one that uses an EthernetType, the  
// other which defines a length the follows with a LLC header (802.3). If the// former is the case, we set EthernetType and Length stays 0. In the latter// case, we set Length and EthernetType = EthernetTypeLLC.  
//数据包长度
Length uint16  
}

补充

这里补充一点以太网协议相关知识

以太网是一种产生较早,使用相当广泛的局域网技术。最初是由Xerox(施乐)公司创建(大概是1973年诞生)并由Xerox、 Intel和DEC公司联合开发的基带局域网规范,后来被电气与电子工程师协会( IEEE)所采纳作为802.3的标准。

目前以太网根据速度等级分类大概分为:标准以太网(10Mbit/s),快速以太网(100Mbit/s),千兆以太网(1000Mbit/s),以及更快的万兆以太网(10Gbit/s)

以太网协议(Ethernet Protocol)按照七层(OSI)网络模型来划分的话属于数据链路层的协议,

常用的以太网MAC帧格式有两种标准,一种是DIX Ethernet V2标准(即以太网V2标准),另一种是IEEE的802.3标准。这里只介绍使用得最多的以太网V2的MAC帧格式(如下图)。
![[Pasted image 20240126142219.png]]
图中假定网络层使用的是IP协议。实际上使用其他的协议也是可以的。

IEEE 802.3标准规定的MAC帧格式与上面所讲的以太网V2 MAC帧格式的区别就是两个地方。
第一,IEEE 802.3规定的MAC帧的第三个字段是“长度/类型”。当这个字段值大于0x0600时(相当于十进制的1536),就表示“类型”。这样的帧和以太网V2 MAC帧完全一样。只有当这个字段值小于0x0600时才表示“长度”,即MAC帧的数据部分长度。显然,在这种情况下,若数据字段的长度与长度字段的值不一致,则该帧为无效的MAC帧。实际上,前面我们已经讲过,由于以太网采用了曼彻斯特编码,长度字段并无实际意义。

第二,当“长度/类型”字段值小于0x0600时,数据字段必须装入上面的逻辑链路控制LLC子层的LLC帧。
由于现在广泛使用的局域网只有以太网,因此LLC帧已经失去了原来的意义(见本章3.3.1节第1小节“以太网的两个标准”)。现在市场上流行的都是以太网V2的MAC帧,但大家也常常把它称为IEEE 802.3标准的MAC帧。------《计算机网络第七版 谢希仁》

图中所示的目的地址和源地址实际上就是MAC地址分别占了6字节,而 类型 则是用来标识上一层所使用的协议类型,如IP协议(0x0800),ARP(0x0806)等。FCS字段是帧校验字段,即Frame Check Sequence,用来保存CRC(循环冗余校验)校验值。
以太网协议中规定最小的以太网数据包长度应该是64字节,最大长度1518字节,除去目的地址、源地址(各6字节),类型(2字节),FEC字段(4字节),数据字段长度应该在46~1500字节。当数据字段的长度小于46字节时,MAC子层就会在数据字段的后面加入一个整数字节的填充字段,以保证以太网的MAC帧长不小于64字节。

OK,有了上面的知识补充,我们可以继续阅读ethernet.go源码了。

DecodeFromBytes

首先是对于以太网协议的解码操作,so look at function DecodeFromBytes():

func (eth *Ethernet) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {  
if len(data) < 14 {  
return errors.New("Ethernet packet too small")  
}  
eth.DstMAC = net.HardwareAddr(data[0:6])  
eth.SrcMAC = net.HardwareAddr(data[6:12])  
eth.EthernetType = EthernetType(binary.BigEndian.Uint16(data[12:14]))  
eth.BaseLayer = BaseLayer{data[:14], data[14:]}  
eth.Length = 0  
if eth.EthernetType < 0x0600 {  
eth.Length = uint16(eth.EthernetType)  
eth.EthernetType = EthernetTypeLLC  
if cmp := len(eth.Payload) - int(eth.Length); cmp < 0 {  
df.SetTruncated()  
} else if cmp > 0 {  
// Strip off bytes at the end, since we have too many bytes  
eth.Payload = eth.Payload[:len(eth.Payload)-cmp]  
}  
// fmt.Println(eth)  
}  
return nil  
}

首先当 len(data)<14时 即目的地址、源地址、类型三个字段不完整,如果长度够继续解析,

eth.DstMAC = net.HardwareAddr(data[0:6])  
eth.SrcMAC = net.HardwareAddr(data[6:12])  
eth.EthernetType = EthernetType(binary.BigEndian.Uint16(data[12:14]))  
eth.BaseLayer = BaseLayer{data[:14], data[14:]}
eth.Length = 0

目的地址、源地址、类型三个字段解析到变量中,然后将前14个字节和14个字节之后的数据复制到baselayer中。

// BaseLayer is a convenience struct which implements the LayerData and// LayerPayload functions of the Layer interface.
type BaseLayer struct {  
// Contents is the set of bytes that make up this layer. IE: for an// Ethernet packet, this would be the set of bytes making up the// Ethernet frame.
// 可以理解为封装头
Contents []byte  
// Payload is the set of bytes contained by (but not part of) this// Layer. Again, to take Ethernet as an example, this would be the// set of bytes encapsulated by the Ethernet protocol.
// 数据内容
Payload []byte  
}

然后是对于EthernetType进行一个判断,这个if语句是用来判断该数据packet采用的协议是以太网v2还是IEEE 802.3,依据见 补充 IEEE 802.3

if eth.EthernetType < 0x0600 {  
// 从第三个字段获取以太网数据长度信息  
eth.Length = uint16(eth.EthernetType)  
// 设置以太网协议类型  
eth.EthernetType = EthernetTypeLLC  
// 比较获取的以太网数据长度信息是否和实际的数据长度一样,小则设置截断,大则按照获取的以太网数据长度信息重置Payload  
if cmp := len(eth.Payload) - int(eth.Length); cmp < 0 {  
df.SetTruncated()  
} else if cmp > 0 {  
// Strip off bytes at the end, since we have too many bytes  
eth.Payload = eth.Payload[:len(eth.Payload)-cmp]  
}  
// fmt.Println(eth)  
}

其他

本文件代码中DecodeFromBytes是对于数据包的解包操作,而 SerializeTo 则是对于数据封装成数据包的操作 ,其他的没什么可讲的,基本是为了实现其他接口而写的,自己看吧。
基本上layers文件夹中每一个文件名都对应着一个协议,基本上操作和ethernet.go一样。

本集总结:
获取了抓取到的数据包后,需要对包进行解析,gopacket库的layers中封装了一系列对于各种协议的操作。我们知道了对应协议的格式内容也可以自己来编写类似的操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1413750.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker容器生命周期管理命令

文章目录 前言1、docker create2、docker run2.1、常用选项2.2、系统2.3、网络2.4、健康检查 3、docker start/stop/restart4、docker kill5、docker rm6、docker pause/unpause总结 前言 在云原生时代&#xff0c;Docker已成为必不可少的容器管理工具。通过掌握Docker常用的容…

派网AX50C做多宽带路由和核心交换机配置实战教程

接近300办公人员的工厂需要网络升级&#xff0c;我规划设计和部署实施了以下方案&#xff0c;同样是简约不简单&#xff0c;在满足性能需求稳定性的前提下&#xff0c;既有经济性&#xff0c;又有安全性。 派网做路由器&#xff0c;刚好开启默认防病毒策略&#xff0c;省下来一…

【QT+QGIS跨平台编译】之十一:【libzip+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、libzip介绍二、文件下载三、文件分析四、pro文件五、编译实践一、libzip介绍 libzip是一个开源C库,用于读取,创建和修改zip文件。 libzip可以从数据缓冲区,文件或直接从其他zip归档文件直接复制的压缩数据中添加文件。在不关闭存档的情况下所做的更改可以还原…

TCP 状态转换以及半关闭

TCP 状态转换&#xff1a; 上图中还没有进行握手的时候状态是关闭的。 三次握手状态的改变&#xff1a; 客户端发起握手。 调用 connect() 函数时状态转化为&#xff1a;SYN_SENT。调用 listen() 函数时状态转换为&#xff1a;LISTEN。ESTABLISHED是被连接的状态。 四次挥手…

林浩然的哲学奇遇记:与罗素一起穿越西方哲学史

林浩然的哲学奇遇记&#xff1a;与罗素一起穿越西方哲学史 The Philosophical Adventures of Lin Haoran: Journeying Through Western Philosophy with Russell 在一个阳光灿烂的早晨&#xff0c;林浩然同学迎着微风&#xff0c;拿着一本厚重的罗素名著《西方哲学史》&#xf…

了解维特比算法:通信系统和自然语言处理中解码的基石

一、介绍 在数字通信和信号处理领域&#xff0c;维特比算法是一种革命性的纠错和解码方法。该算法以 1967 年推出的 Andrew Viterbi 的名字命名&#xff0c;已成为数字通信和自然语言处理领域的基础。本文旨在深入研究维特比算法的复杂性&#xff0c;探讨其理论基础、实际应用以…

通俗易懂理解小波池化以及WaveCNet网络模型

重要说明&#xff1a;本文从网上资料整理而来&#xff0c;仅记录博主学习相关知识点的过程&#xff0c;侵删。 一、参考资料 github代码&#xff1a;WaveCNet 小波变换和曲波变换用于池化层 通俗易懂理解小波变换(Wavelet Transform) 二、相关介绍 关于小波变换的详细介绍…

C#,数据检索算法之插值搜索(Interpolation Search)的源代码

数据检索算法是指从数据集合&#xff08;数组、表、哈希表等&#xff09;中检索指定的数据项。 数据检索算法是所有算法的基础算法之一。 本文提供插值搜索&#xff08;Interpolation Search&#xff09;的源代码。 1 文本格式 using System; namespace Legalsoft.Truffer.…

极限【高数笔记】

【分类】分为了两大类&#xff0c;一个是数列的极限&#xff0c;一个是函数的极限 【数列的极限】 1.定义&#xff1a; 简单来讲&#xff0c;就是&#xff0c;当n无限趋近于无穷时&#xff0c;数列{an}无限趋近一个常数A&#xff0c;此时&#xff0c;常数A就是它们此时情况下的…

三极管实际电路设计

上图电路有个致命缺陷。那就是Q2正常我们是用NPN三极管。而上图用了PNP。导致MOS管高低电平都无法关闭。 解决方法&#xff1a; 把R2减小为200欧、或者330欧姆。 &#xff08;因为MOS打开需要压差&#xff0c;把SG端压差减小到规定以下就可以关闭&#xff09;。 同时增大R1为2…

RustDesk私有化部署,自建远程桌面搭建教程

以linux操作系统为例&#xff1a; 解压安装 # 使用wget进行下载1.1.8-2版本&#xff08;最新版本可以看上述发布地址&#xff09; wget https://github.com/rustdesk/rustdesk-server/releases/download/1.1.8-2/rustdesk-server-linux-amd64.zip # 使用unzip解压 unzip rust…

防火墙综合实验

实验需求&#xff1a; 1、生产区在工作时间内可以访问服务器区&#xff0c;仅可以访问http服务器。 2、办公区全天可以访问服务器区&#xff0c;其中&#xff0c;10.0.2.20可以访问FTP服务器和HTTP服务器&#xff0c;10.0.2.10仅可以ping通10.0.3.10。 3、办公区在访问服务器…

Chain-of-Thought Prompting Elicits Reasoning in Large Language Models导读

通过生成一系列中间推理步骤&#xff08;即“思维链”&#xff09;显著提高大型语言模型进行复杂推理的能力 这篇论文探讨了如何通过生成一系列中间推理步骤&#xff08;即“思维链”&#xff09;显著提高大型语言模型进行复杂推理的能力。研究人员使用一种简单的方法——思维…

BAT学习笔记:常用指令详解及图示

文章目录 一、 &#xff08;关闭本行命令回显&#xff09;详解及图示二、echo off&#xff08; 关闭命令回显&#xff09;详解及图示三、%VAR_NAME% (取变量值&#xff09;详解四、set (设置变量&#xff09;详解及图示五、if (条件判断) 详解及图示六、not (条件取反&#xff…

Elasticsearch8.11集群部署

集群就是多个node统一对外提供服务&#xff0c;避免单机故障带来的服务中断&#xff0c;保证了服务的高可用&#xff0c;也因为多台节点协同运作&#xff0c;提高了集群服务的计算能力和吞吐量。ES是一个去中心化的集群&#xff0c;操作一个节点和操作一个集群是一样的&#xf…

podman+centos和docker+alpine中作性能对比遇到的问题及解决

1.dockeralpine中遇到这个问题 这是由于缺少相关的配置和依赖造成的 通过以下命令在alpine中安装相关配置 apk add --no-cache build-base cairo-dev cairo cairo-tools jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev 2.alpine中python找…

Likeshop多商户商城源码系统,支持二开

在电商行业高速发展的当下&#xff0c;拥有一套功能强大、易于操作的开源商城系统至关重要。Likeshop多商户商城系统正是这样一款集H5、小程序、独立APP于一体的开源电商解决方案&#xff0c;助力商家实现智能营销。 一、产品简介 Likeshop多商户商城系统为商家提供了丰富的营…

使用代码取大量2*2像素图片各通道均值,存于Excel文件中。

任务是取下图RGB各个通道的均值及标签&#xff08;R, G&#xff0c;B&#xff0c;Label&#xff09;,其中标签由图片存放的文件夹标识。由于2*2像素图片较多&#xff0c;所以将结果放置于Excel表格中&#xff0c;之后使用SVM对他们进行分类。 from PIL import Image import os …

STM32 freertos 使用软件模拟串口uart

如题&#xff0c;为什么要这样做&#xff1f; 最近做的一个项目上使用了74HC595作为指示灯板使用&#xff1b; 这个灯板与驱动板是通过排线连接&#xff0c;排线约25cm长&#xff1b; 在实验室测试一切正常&#xff0c;发到客户手上使用就出现了某个LED跳动情况&#xff1b;…

JOSEF约瑟 静态中间继电器 RZY-600D 110VDC 六常开 导轨安装

RZ-D系列中间继电器 系列型号&#xff1a; RZY-004D中间继电器 RZL-004D中间继电器 RZY-022D中间继电器 RZL-022D中间继电器 RZY-112D中间继电器 RZL-112D中间继电器 RZY-202D中间继电器 RZL-202D中间继电器 RZY-002D中间继电器 RZL-002D中间继电器 RZY-060D中间继电器 RZL-060…