gopacket学习(一)
gopacket为go语言提供了数据包处理的能力。使用者能够嗅探、解码网络数据包。
官方文档:gopacket package - github.com/google/gopacket - Go Packages
gopacket子包
总体上看,gopacket包提供了数据包解码的能力。解码过程中可能用到很多它的子包,先混个眼熟:
layers:该模块包含gopacket数据包协议的内部逻辑,简单理解就是用于解码数据包
pcap:使用libpcap读取数据包
pfring:使用PF_RING读取数据包
afpacket: 使用Linux’s AF_PACKET来读取数据包
tcpassembly:TCP流重组
gopacket的layers
要想理解gopacket如何解码数据包,首先我们需要理解gopacket的layers子包。
gopacket接收数据包作为一个[]byte类型,并将其解码为一个具有非零个“layers(层)”的数据包。每一层对应一种网络协议。通常情况下我们使用wireshark等软件抓到的数据流量包都是TCP/IP 四层模型,gopacket解码流量时也就是把对应层的协议或数据解析到相应的layer(层)中。
举个例子,解码一个数据包并从IP层获取IP地址。
package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
)
func main() {
path := "C:/Users/myAdmin/Desktop/tcpdemo.pcap"
// 使用pcap子包读取xxx.pcap数据包
handle, err := pcap.OpenOffline(path)
if err != nil {
panic(err)
}
// 使用gopacket创建数据包源对象
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
// 使用通道传送一个packet数据包
packet := <-packetSource.Packets()
// 查看数据包
fmt.Println(packet)
}
通过上述代码可以获取到一个数据包,packet对象包含网络接口层、网络层和传输层的所有相关信息,如下图。
如果你想要获取数据包中单个的信息,需要从packet中提取对应的层(layer),然后再通过指定的函数获取。比如IP信息:
// 使用Layer()函数,传入想要获取的layer
net := packet.Layer(layers.LayerTypeIPv4)
// net是一个接口类型,通过断言转化为IPv4类型
fmt.Println(net.(*layers.IPv4).SrcIP)
// 使用NetworkLayer()直接返回网络层的对象
netlayer := packet.NetworkLayer()
// 获取网络层对象的数据流,再通过流获取源IP地址
fmt.Println(netlayer.NetworkFlow().Src())
gopacket解码数据包
前面的实例代码中其实已经演示了gopacket解码数据包的方法。
1.从数据源(网络流量/pcap数据包等)获取gopacket.Packet对象
有以下几种方式可以获取Packet对象:
PacketSource对象的Packets()
gopacket.NewPacket()创建该对象
例如前面的示例使用gopacket.NewPacketSource()从pcap文件中获取一个PacketSource对象(即数据包的源),该对象的Packets()方法返回一个通道,使用range关键字遍历通道,可依次读取pcap源中的每一个数据包。比如下面的代码可读取指定pcap文件的所有数据包。
// 使用gopacket创建数据包源对象
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
fmt.Println(packet)
}
注意: 因为是从通道(channel)里获取数据,可能导致输出的数据包不是按顺序打印的。从通道获取的数据是gopacket.Packet类型的对象。
另外,gopacket.NewPacket()方法也创建一个gopacket.Packet对象,通过传入一个[]byte数组作为入参,可以生成Packet对象。使用Packet.Data()方法可以返回Packet数据包的[]byte类型(虽然有点多此一举)。
for packet := range packetSource.Packets() {
// Packet.Data()方法可以返回数据包的[]byte类型
p := gopacket.NewPacket(packet.Data(), layers.LayerTypeEthernet, gopacket.Default)
fmt.Println(p)
}
NewPacket()的第二个参数是:这个[]byte类型数据包的第一层是什么(通常都是网络接口层/数据链路层,因此是Ethernet类型);第三个参数则是告诉gopacket如何解码数据包。
2.解码数据包的各层
有了Packet对象后,就可以使用该对象访问网络接口层、网络层、传输层。Packet的Layer()方法便可以实现。开头讲到过layers子包,它是gopacket很有用的一个模块,使用它可以轻松地地识别数据包是否包含指定的层。
package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
)
func main() {
path := "C:/Users/myAdmin/Desktop/tcpdemo.pcap"
// 使用pcap子包读取xxx.pcap数据包
handle, err := pcap.OpenOffline(path)
if err != nil {
panic(err)
}
// 使用gopacket创建数据包源对象
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
// 获取网络接口层
ethLayer := packet.Layer(layers.LayerTypeEthernet)
if ethLayer != nil {
fmt.Println("****** Ethernet layer detected ******")
// 通过断言强制把Layer类型转换为Ethernet类型
if eth, ok := ethLayer.(*layers.Ethernet); ok {
// 解码相关信息,源和目的MAC地址
fmt.Println("Src MAC: ", eth.SrcMAC)
fmt.Println("Dst MAC: ", eth.DstMAC)
}
}
// 获取IP层
ip4Layer := packet.Layer(layers.LayerTypeIPv4)
if ip4Layer != nil {
fmt.Println("****** IPv4 layer detected ******")
if ipv4, ok := ip4Layer.(*layers.IPv4); ok {
// 解码相关信息,源和目的IP地址
fmt.Println("Src IP: ", ipv4.SrcIP)
fmt.Println("Dst IP: ", ipv4.DstIP)
// 其他信息
fmt.Println("Protocol: ", ipv4.Protocol)
fmt.Println(ipv4.IHL)
fmt.Printf("%x\n", ipv4.Checksum)
}
}
// 获取传输层
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
fmt.Println("****** TCP layer detected ******")
if tcp, ok := tcpLayer.(*layers.TCP); ok {
fmt.Println("Src port: ", tcp.SrcPort)
fmt.Println("Dst port: ", tcp.DstPort)
fmt.Printf("%x\n", tcp.Checksum)
fmt.Println(tcp.Seq)
}
}
}
}
以IP层为例,解码的结果与wireshark中的信息对比: