GoPacket
这个库为Golang提供了数据包解码的功能。查看更多细节godoc
最低Go版本要求是1.5,除了pcapgo/EthernetHandle, afpacket和bsdbpf,因为x/sys/unix依赖至少需要1.9。
最初由Andreas Krennmair编写的gopcap项目派生出来ak@synflood.at (http://github.com/akrennmair/gopcap)。
Document
Overview
Basic Usage
Package gopacket提供了Go语言的数据包解码。
Gopacket包含许多子包,这些子包具有你可能会发现有用的附加功能,包括:
- layers: 你可能每次都会用到这个。这包含了逻辑内建到goppacket解码数据包协议。注意所有的例子下面的代码假设您已经导入了goppacket和gopacket /layers。
- pcap: C绑定来使用libpcap从线路上读取数据包。
- pfring: C绑定使用PF_RING从线路上读取数据包。
- afpacket: Linux的AF_PACKET的C绑定,用于从线路上读取数据包。
- tcpassembly: TCP流重组
此外,如果您想要深入研究代码,请参阅示例子目录,其中有许多使用goppacket库构建的简单二进制文件。
最低go版本要求是1.5,除了pcapgo/EthernetHandle, afpacket和bsdbpf,因为x/sys/unix依赖至少需要1.7。
Basic Usage
gopacket将数据包数据作为一个[]byte,并将其解码成一个非零“layers”数量的数据包。每个层对应于字节内的一个协议。一旦数据包被解码,就可以从数据包中请求数据包的各个层。
// Decode a packet 解码一个数据包
packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Default)
// Get the TCP layer from this packet
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
fmt.Println("This is a TCP packet!")
// Get actual TCP data from this layer 从这一层获得实际的TCP数据
tcp, _ := tcpLayer.(*layers.TCP)
fmt.Printf("From src port %d to dst port %d\n", tcp.SrcPort, tcp.DstPort)
}
// Iterate over all layers, printing out each layer type 遍历所有层,打印出每个层类型
for _, layer := range packet.Layers() {
fmt.Println("PACKET LAYER:", layer.LayerType())
}
数据包可以从多个起点进行解码。我们的许多基本类型都实现了Decoder,它允许我们对没有完整数据的数据包进行解码。
// Decode an ethernet packet 解码一个以太网数据包
ethP := gopacket.NewPacket(p1, layers.LayerTypeEthernet, gopacket.Default)
// Decode an IPv6 header and everything it contains 解码IPv6头和它包含的一切
ipP := gopacket.NewPacket(p2, layers.LayerTypeIPv6, gopacket.Default)
// Decode a TCP header and its payload 解码TCP报头及其有效载荷
tcpP := gopacket.NewPacket(p3, layers.LayerTypeTCP, gopacket.Default)
Reading Packets From A Source
大多数时候,您不会有一个[]byte(字节数组)的数据包数据。相反,您将希望从某个地方(file, interface, etc等)读取数据包并处理它们。要做到这一点,您需要构建一个PacketSource。
首先,您需要构造一个实现PacketDataSource接口的对象。有这个接口捆绑goppacket在goppacket /pcap和goppacket /pfring子包的实现…有关它们的用法的更多信息,请参阅它们的文档。一旦有了一个PacketDataSource,您就可以将它传递到NewPacketSource,以及您选择的一个Decoder,以创建一个PacketSource。
一旦有了PacketSource,就可以用多种方式从它读取数据包。更多细节请参见PacketSource文档。最简单的方法是Packets函数,它返回一个通道,然后异步地将新包写入该通道,如果packetSource到达文件结束符,则关闭该通道。
packetSource := ... // construct using pcap or pfring
for packet := range packetSource.Packets() {
handlePacket(packet) // do something with each packet
}
你可以通过在packetSource. decodeoptions中设置字段来改变packetSource的解码选项。有关更多细节,请参见以下几节。
Lazy Decoding
gopacket可以选择延迟解码数据包数据,这意味着它只在需要处理函数调用时解码数据包层。
// Create a packet, but don't actually decode anything yet 创建一个数据包,但实际上还不解码任何东西
packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Lazy)
// Now, decode the packet up to the first IPv4 layer found but no further. 解码数据包到发现的第一个IPv4层,但没有进一步。
// If no IPv4 layer was found, the whole packet will be decoded looking for 如果没有发现IPv4层,整个数据包将被解码寻找它。
// it.
ip4 := packet.Layer(layers.LayerTypeIPv4)
// Decode all layers and return them. The layers up to the first IPv4 layer
// are already decoded, and will not require decoding a second time.
//如果没有发现IPv4层,整个数据包将被解码寻找它。解码所有层并返回它们。
//第一个IPv4层之前的层已经解码,不需要第二次解码。
layers := packet.Layers()
延迟解码的数据包不是并发安全的。由于没有对所有层进行解码,所以对Layer()或layers()的每次调用都有可能改变数据包以解码下一层。如果一个包在多个goroutine中同时使用,不要使用goppacket . lazy。然后,goppacket将完全解码数据包,并且所有未来的函数调用将不会改变该对象。
NoCopy Decoding
默认情况下,gopacket将复制传递给NewPacket的slice,并将副本存储在数据包中,因此未来对slice底层的字节的变化不会影响数据包及其层。如果你能保证底层的slice字节不会被改变,你可以使用NoCopy来告诉goppacket。NewPacket,它将使用传入的切片本身。
// This channel returns new byte slices, each of which points to a new
// memory location that's guaranteed immutable for the duration of the
// packet.
//这个通道返回新的字节片,每个字节片指向一个新的内存位置,该位置在包的持续时间内保证是不可变的。
for data := range myByteSliceChannel {
p := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.NoCopy)
doSomethingWithPacket(p)
}
最快的解码方法是同时使用Lazy和NoCopy,但是请注意,从上面的许多警告中可以看到,对于某些实现,其中一种或两种都可能是危险的。
Pointer To Know Layers
在解码过程中,某些层作为众所周知的层类型存储在数据包中。例如,IPv4和IPv6都被认为是网络层,而TCP和UDP都是传输层。我们支持4层,对应TCP/IP分层方案的4层(与OSI模型的第2、3、4、7层大致相同)。要访问这些,您可以使用packet.LinkLayer , packet.NetworkLayer , packet.TransportLayer和packet.ApplicationLayer功能。每个函数返回一个相应的接口(gopacket.{Link,Network,Transport,Application}Layer)。前三层提供了获取特定层的src/dst地址的方法,而最后一层提供了获取有效载荷数据的有效载荷函数。这很有帮助,例如,获取所有数据包的有效负载,而不管它们的底层数据类型:
// Get packets from some source
for packet := range someSource {
if app := packet.ApplicationLayer(); app != nil {
if strings.Contains(string(app.Payload()), "magic string") {
fmt.Println("Found magic string in a packet!")
}
}
}
一个特别有用的层是ErrorLayer,它在数据包有错误解析部分时被设置。
packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Default)
if err := packet.ErrorLayer(); err != nil {
fmt.Println("Error decoding some part of the packet:", err)
}
注意,我们没有从NewPacket返回错误,因为在运行到错误层之前,我们可能已经成功解码了许多层。您仍然可以正确地获得以太网和IPv4层,即使TCP层是畸形的。
Flow And Endpoint (流和终端)
gopacket有两个有用的对象,Flow和Endpoint,用于以协议独立的方式通信数据包来自a和去B的事实。一般的层类型LinkLayer、NetworkLayer和TransportLayer都提供了提取它们的流信息的方法,而不用担心底层的类型。
flow是由两个端点(一个源和一个目的地)组成的简单对象。它详细说明了数据包层的发送方和接收方。
endpoint是源或目标的可哈希表示。例如,对于LayerTypeIPv4,一个endpoint包含v4 IP数据包的IP地址字节。一个flow可以被分解成endpoint,endpoint可以被组合成flow:
packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Lazy)
netFlow := packet.NetworkLayer().NetworkFlow()
src, dst := netFlow.Endpoints()
reverseFlow := gopacket.NewFlow(dst, src)
终端和流对象都可以作为映射键使用,等式运算符可以比较它们,因此您可以轻松地将基于端点标准的所有数据包分组。
flows := map[gopacket.Endpoint]chan gopacket.Packet
packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Lazy)
// Send all TCP packets to channels based on their destination port.
// 将所有TCP数据包发送到基于其目的端口的通道。
if tcp := packet.Layer(layers.LayerTypeTCP); tcp != nil {
flows[tcp.TransportFlow().Dst()] <- packet
}
// Look for all packets with the same source and destination network address
// 查找所有具有相同源和目的网络地址的数据包
if net := packet.NetworkLayer(); net != nil {
src, dst := net.NetworkFlow().Endpoints()
if src == dst {
fmt.Println("Fishy packet has same network source and dst: %s", src)
}
}
// Find all packets coming from UDP port 1000 to UDP port 500
// 查找所有从UDP端口1000到UDP端口500的数据包
interestingFlow := gopacket.FlowFromEndpoints(layers.NewUDPPortEndpoint(1000), layers.NewUDPPortEndpoint(500))
if t := packet.NetworkLayer(); t != nil && t.TransportFlow() == interestingFlow {
fmt.Println("Found that UDP flow I was looking for!")
}
出于负载平衡的目的,Flow和Endpoint都有FastHash()函数,它提供内容的快速、非加密散列。特别重要的是,Flow FastHash()是对称的:A->B与B->A具有相同的哈希值。用法示例如下:
channels := [8]chan gopacket.Packet
for i := 0; i < 8; i++ {
channels[i] = make(chan gopacket.Packet)
go packetHandler(channels[i])
}
for packet := range getPackets() {
if net := packet.NetworkLayer(); net != nil {
channels[int(net.NetworkFlow().FastHash()) & 0x7] <- packet
}
}
这允许我们分割一个包流,同时仍然确保每个流看到一个流的所有包(和它的双向相反)。
Implementing Your Own Decode
如果您的网络有一些奇怪的封装,您可以实现自己的解码器。在这个例子中,我们处理封装在4字节报头中的以太网数据包。
// Create a layer type, should be unique and high, so it doesn't conflict,
// giving it a name and a decoder to use.
//创建一个层类型,应该是唯一的和高的,所以它不会冲突,给它一个名称和解码器使用。
var MyLayerType = gopacket.RegisterLayerType(12345, gopacket.LayerTypeMetadata{Name: "MyLayerType", Decoder: gopacket.DecodeFunc(decodeMyLayer)})
// Implement my layer
type MyLayer struct {
StrangeHeader []byte
payload []byte
}
func (m MyLayer) LayerType() gopacket.LayerType { return MyLayerType }
func (m MyLayer) LayerContents() []byte { return m.StrangeHeader }
func (m MyLayer) LayerPayload() []byte { return m.payload }
// Now implement a decoder... this one strips off the first 4 bytes of the
// packet.
// 现在实现一个解码器..这一个剥去包的前4个字节。
func decodeMyLayer(data []byte, p gopacket.PacketBuilder) error {
// Create my layer
p.AddLayer(&MyLayer{data[:4], data[4:]})
// Determine how to handle the rest of the packet
return p.NextDecoder(layers.LayerTypeEthernet)
}
// Finally, decode your packets:
// 最后,解码你的数据包:
p := gopacket.NewPacket(data, MyLayerType, gopacket.Lazy)
关于编码解码器如何工作的更多细节,请参阅文档中的Decoder和PacketBuilder,或者查看RegisterLayerType和RegisterEndpointType来了解如何添加层/端点类型到goppacket。
Fast Decoding With DecodingLayerParser
TLDR: DecodingLayerParser解码数据包数据的时间大约是NewPacket的10%,但只针对已知的数据包栈。
基本解码使用gopacket.NewPacket或PacketSource。数据包有些慢,因为它需要分配一个新的数据包和每一层。它非常通用,可以处理所有已知的层类型,但有时您实际上只关心一组特定的层,因此多功能性被浪费了。
DecodingLayerParser通过将包层直接解码为预先分配的对象来避免内存分配,然后您可以引用这些对象来获得包的信息。一个快速的例子:
func main() {
var eth layers.Ethernet
var ip4 layers.IPv4
var ip6 layers.IPv6
var tcp layers.TCP
parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, ð, &ip4, &ip6, &tcp)
decoded := []gopacket.LayerType{}
for packetData := range somehowGetPacketData() {
if err := parser.DecodeLayers(packetData, &decoded); err != nil {
fmt.Fprintf(os.Stderr, "Could not decode layers: %v\n", err)
continue
}
for _, layerType := range decoded {
switch layerType {
case layers.LayerTypeIPv6:
fmt.Println(" IP6 ", ip6.SrcIP, ip6.DstIP)
case layers.LayerTypeIPv4:
fmt.Println(" IP4 ", ip4.SrcIP, ip4.DstIP)
}
}
}
}
这里需要注意的重要一点是,解析器正在修改层(eth、ip4、ip6、tcp)中传递的信息,而不是分配新的信息,因此大大加快了解码过程。它甚至是基于层类型的分支……它将处理(eth, ip4, tcp)或(eth, ip6, tcp)堆栈。然而,它不会处理任何其他类型…因为没有其他解码器被传入,(eth, ip4, udp)堆栈将在ip4之后停止解码,并且只通过“解码”片(伴随着一个错误说它不能解码udp包)传递回来[LayerTypeEthernet, LayerTypeIPv4]。
不幸的是,并非所有的层都可以被DecodingLayerParser使用…只有那些实现了DecodingLayer接口的才是可用的。同样,也可以创建解码层,而不是自己的层…看到层。IPv6ExtensionSkipper就是一个例子。
Faster And Customized Decoding with DecodingLayerContainer
默认情况下,DecodingLayerParser使用本机映射来存储和搜索要解码的层。虽然是通用的,但在某些情况下,这个解决方案可能不是那么理想。例如,如果您只有几层,那么可以通过稀疏数组索引或线性数组扫描提供更快的操作。
为了适应这些场景,我们引入了DecodingLayerContainer接口及其实现:DecodingLayerSparse, DecodingLayerArray和DecodingLayerMap。你可以通过SetDecodingLayerContainer方法来指定DecodingLayerParser的容器实现。例子:
dlp := gopacket.NewDecodingLayerParser(LayerTypeEthernet)
dlp.SetDecodingLayerContainer(gopacket.DecodingLayerSparse(nil))
var eth layers.Ethernet
dlp.AddDecodingLayer(ð)
// ... add layers and use DecodingLayerParser as usual...
为了跳过一个间接层(尽管牺牲了一些功能),您也可以使用DecodingLayerContainer作为一个解码工具。在这种情况下,您必须自己处理未知的层类型和层恐慌。例子:
func main() {
var eth layers.Ethernet
var ip4 layers.IPv4
var ip6 layers.IPv6
var tcp layers.TCP
dlc := gopacket.DecodingLayerContainer(gopacket.DecodingLayerArray(nil))
dlc = dlc.Put(ð)
dlc = dlc.Put(&ip4)
dlc = dlc.Put(&ip6)
dlc = dlc.Put(&tcp)
// you may specify some meaningful DecodeFeedback
decoder := dlc.LayersDecoder(LayerTypeEthernet, gopacket.NilDecodeFeedback)
decoded := make([]gopacket.LayerType, 0, 20)
for packetData := range somehowGetPacketData() {
lt, err := decoder(packetData, &decoded)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not decode layers: %v\n", err)
continue
}
if lt != gopacket.LayerTypeZero {
fmt.Fprintf(os.Stderr, "unknown layer type: %v\n", lt)
continue
}
for _, layerType := range decoded {
// examine decoded layertypes just as already shown above
}
}
}
DecodingLayerSparse是最快但最有效的,当使用的层可以解码的LayerType值不大时,因为否则会导致更大的内存占用。decode layerarray是非常紧凑的,如果解码层的数量不是很大(最多10-15,但请自己做基准测试),主要可用。DecodingLayerMap是最通用的,默认情况下由DecodingLayerParser使用。请参阅层子包中的测试和基准测试,以进一步研究使用示例和性能度量。
如果你想使用你自己的内部包解码逻辑,你也可以选择实现你自己的DecodingLayerContainer。
Creating Packet Data
除了提供解码数据包数据的能力,goppacket还将允许你从头开始创建数据包。许多gopacket层实现SerializableLayer接口;这些层可以按以下方式序列化为一个[]byte:
ip := &layers.IPv4{
SrcIP: net.IP{1, 2, 3, 4},
DstIP: net.IP{5, 6, 7, 8},
// etc...
}
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{} // See SerializeOptions for more details.
err := ip.SerializeTo(buf, opts)
if err != nil { panic(err) }
fmt.Println(buf.Bytes()) // prints out a byte slice containing the serialized IPv4 layer.
将给定的层添加到SerializeBuffer上,并且它们将当前缓冲区的Bytes()片作为序列化层的有效负载。因此,您可以通过反向顺序序列化一组层(例如,有效负载、TCP、IP、以太网)来序列化整个数据包。SerializeBuffer的SerializeLayers函数就是一个帮助器,它就是这样做的。
例如,要生成一个(空的无用的,因为没有设置字段)以太网(IPv4(TCP(Payload))数据包,可以运行:
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{}
gopacket.SerializeLayers(buf, opts,
&layers.Ethernet{},
&layers.IPv4{},
&layers.TCP{},
gopacket.Payload([]byte{1, 2, 3, 4}))
packetData := buf.Bytes()
A Final Note
如果你使用goppacket,你几乎肯定想要确保goppacket /layers被导入,因为当导入时,它设置了所有的LayerType变量,并填充了很多有趣的变量/映射(DecodersByLayerName,等等)。因此,建议即使你不直接使用任何层函数,你仍然使用:
import (
_ "github.com/google/gopacket/layers"
)