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

上一集中我们讲到了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)  
}  
}

我们可以看到上述代码中,需要对抓取到的网络数据包进行解包操作,每个数据包就像是洋葱一样由各层协议层层封装而成。如果你愿意一层一层一层地剥开它的心,哈哈哈
阅读go语言工具源码系列之gopacket(谷歌出品)----第二集 layers-巧妙的抽象与无聊的协议包_第1张图片

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体系结构。
阅读go语言工具源码系列之gopacket(谷歌出品)----第二集 layers-巧妙的抽象与无聊的协议包_第2张图片

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

以太网协议

请看源码中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中封装了一系列对于各种协议的操作。我们知道了对应协议的格式内容也可以自己来编写类似的操作。

你可能感兴趣的:(golang,golang,驱动开发,开发语言)