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”数量的数据包。每个层对应于字节内的一个协议。一旦数据包被解码,就可以从数据包中请求数据包的各个层

  1. // Decode a packet 解码一个数据包
  2. packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Default)
  3. // Get the TCP layer from this packet
  4. if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
  5. fmt.Println("This is a TCP packet!")
  6. // Get actual TCP data from this layer 从这一层获得实际的TCP数据
  7. tcp, _ := tcpLayer.(*layers.TCP)
  8. fmt.Printf("From src port %d to dst port %d\n", tcp.SrcPort, tcp.DstPort)
  9. }
  10. // Iterate over all layers, printing out each layer type 遍历所有层,打印出每个层类型
  11. for _, layer := range packet.Layers() {
  12. fmt.Println("PACKET LAYER:", layer.LayerType())
  13. }

数据包可以从多个起点进行解码。我们的许多基本类型都实现了Decoder,它允许我们对没有完整数据的数据包进行解码。

  1. // Decode an ethernet packet 解码一个以太网数据包
  2. ethP := gopacket.NewPacket(p1, layers.LayerTypeEthernet, gopacket.Default)
  3. // Decode an IPv6 header and everything it contains 解码IPv6头和它包含的一切
  4. ipP := gopacket.NewPacket(p2, layers.LayerTypeIPv6, gopacket.Default)
  5. // Decode a TCP header and its payload 解码TCP报头及其有效载荷
  6. tcpP := gopacket.NewPacket(p3, layers.LayerTypeTCP, gopacket.Default)

Reading Packets From A Source

大多数时候,您不会有一个[]byte(字节数组)的数据包数据。相反,您将希望从某个地方(file, interface, etc等)读取数据包并处理它们。要做到这一点,您需要构建一个PacketSource

首先,您需要构造一个实现PacketDataSource接口的对象。有这个接口捆绑goppacket在goppacket /pcapgoppacket /pfring子包的实现…有关它们的用法的更多信息,请参阅它们的文档。一旦有了一个PacketDataSource,您就可以将它传递到NewPacketSource,以及您选择的一个Decoder,以创建一个PacketSource

一旦有了PacketSource,就可以用多种方式从它读取数据包。更多细节请参见PacketSource文档。最简单的方法是Packets函数,它返回一个通道,然后异步地将新包写入该通道,如果packetSource到达文件结束符,则关闭该通道。

  1. packetSource := ... // construct using pcap or pfring
  2. for packet := range packetSource.Packets() {
  3. handlePacket(packet) // do something with each packet
  4. }

你可以通过在packetSource. decodeoptions中设置字段来改变packetSource的解码选项。有关更多细节,请参见以下几节。

Lazy Decoding

gopacket可以选择延迟解码数据包数据,这意味着它只在需要处理函数调用时解码数据包层。

  1. // Create a packet, but don't actually decode anything yet 创建一个数据包,但实际上还不解码任何东西
  2. packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Lazy)
  3. // Now, decode the packet up to the first IPv4 layer found but no further. 解码数据包到发现的第一个IPv4层,但没有进一步。
  4. // If no IPv4 layer was found, the whole packet will be decoded looking for 如果没有发现IPv4层,整个数据包将被解码寻找它。
  5. // it.
  6. ip4 := packet.Layer(layers.LayerTypeIPv4)
  7. // Decode all layers and return them. The layers up to the first IPv4 layer
  8. // are already decoded, and will not require decoding a second time.
  9. //如果没有发现IPv4层,整个数据包将被解码寻找它。解码所有层并返回它们。
  10. //第一个IPv4层之前的层已经解码,不需要第二次解码。
  11. layers := packet.Layers()

延迟解码的数据包不是并发安全的。由于没有对所有层进行解码,所以对Layer()或layers()的每次调用都有可能改变数据包以解码下一层。如果一个包在多个goroutine中同时使用,不要使用goppacket . lazy。然后,goppacket将完全解码数据包,并且所有未来的函数调用将不会改变该对象。

NoCopy Decoding

默认情况下,gopacket将复制传递给NewPacket的slice,并将副本存储在数据包中,因此未来对slice底层的字节的变化不会影响数据包及其层。如果你能保证底层的slice字节不会被改变,你可以使用NoCopy来告诉goppacket。NewPacket,它将使用传入的切片本身。

  1. // This channel returns new byte slices, each of which points to a new
  2. // memory location that's guaranteed immutable for the duration of the
  3. // packet.
  4. //这个通道返回新的字节片,每个字节片指向一个新的内存位置,该位置在包的持续时间内保证是不可变的。
  5. for data := range myByteSliceChannel {
  6. p := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.NoCopy)
  7. doSomethingWithPacket(p)
  8. }

最快的解码方法是同时使用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地址的方法,而最后一层提供了获取有效载荷数据的有效载荷函数。这很有帮助,例如,获取所有数据包的有效负载,而不管它们的底层数据类型:

  1. // Get packets from some source
  2. for packet := range someSource {
  3. if app := packet.ApplicationLayer(); app != nil {
  4. if strings.Contains(string(app.Payload()), "magic string") {
  5. fmt.Println("Found magic string in a packet!")
  6. }
  7. }
  8. }

一个特别有用的层是ErrorLayer,它在数据包有错误解析部分时被设置。

  1. packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Default)
  2. if err := packet.ErrorLayer(); err != nil {
  3. fmt.Println("Error decoding some part of the packet:", err)
  4. }

注意,我们没有从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:

  1. packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Lazy)
  2. netFlow := packet.NetworkLayer().NetworkFlow()
  3. src, dst := netFlow.Endpoints()
  4. reverseFlow := gopacket.NewFlow(dst, src)

终端流对象都可以作为映射键使用,等式运算符可以比较它们,因此您可以轻松地将基于端点标准的所有数据包分组。

  1. flows := map[gopacket.Endpoint]chan gopacket.Packet
  2. packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Lazy)
  3. // Send all TCP packets to channels based on their destination port.
  4. // 将所有TCP数据包发送到基于其目的端口的通道。
  5. if tcp := packet.Layer(layers.LayerTypeTCP); tcp != nil {
  6. flows[tcp.TransportFlow().Dst()] <- packet
  7. }
  8. // Look for all packets with the same source and destination network address
  9. // 查找所有具有相同源和目的网络地址的数据包
  10. if net := packet.NetworkLayer(); net != nil {
  11. src, dst := net.NetworkFlow().Endpoints()
  12. if src == dst {
  13. fmt.Println("Fishy packet has same network source and dst: %s", src)
  14. }
  15. }
  16. // Find all packets coming from UDP port 1000 to UDP port 500
  17. // 查找所有从UDP端口1000到UDP端口500的数据包
  18. interestingFlow := gopacket.FlowFromEndpoints(layers.NewUDPPortEndpoint(1000), layers.NewUDPPortEndpoint(500))
  19. if t := packet.NetworkLayer(); t != nil && t.TransportFlow() == interestingFlow {
  20. fmt.Println("Found that UDP flow I was looking for!")
  21. }

出于负载平衡的目的,Flow和Endpoint都有FastHash()函数,它提供内容的快速、非加密散列。特别重要的是,Flow FastHash()是对称的:A->B与B->A具有相同的哈希值。用法示例如下:

  1. channels := [8]chan gopacket.Packet
  2. for i := 0; i < 8; i++ {
  3. channels[i] = make(chan gopacket.Packet)
  4. go packetHandler(channels[i])
  5. }
  6. for packet := range getPackets() {
  7. if net := packet.NetworkLayer(); net != nil {
  8. channels[int(net.NetworkFlow().FastHash()) & 0x7] <- packet
  9. }
  10. }

这允许我们分割一个包流,同时仍然确保每个流看到一个流的所有包(和它的双向相反)。

Implementing Your Own Decode

如果您的网络有一些奇怪的封装,您可以实现自己的解码器。在这个例子中,我们处理封装在4字节报头中的以太网数据包。

  1. // Create a layer type, should be unique and high, so it doesn't conflict,
  2. // giving it a name and a decoder to use.
  3. //创建一个层类型,应该是唯一的和高的,所以它不会冲突,给它一个名称和解码器使用。
  4. var MyLayerType = gopacket.RegisterLayerType(12345, gopacket.LayerTypeMetadata{Name: "MyLayerType", Decoder: gopacket.DecodeFunc(decodeMyLayer)})
  5. // Implement my layer
  6. type MyLayer struct {
  7. StrangeHeader []byte
  8. payload []byte
  9. }
  10. func (m MyLayer) LayerType() gopacket.LayerType { return MyLayerType }
  11. func (m MyLayer) LayerContents() []byte { return m.StrangeHeader }
  12. func (m MyLayer) LayerPayload() []byte { return m.payload }
  13. // Now implement a decoder... this one strips off the first 4 bytes of the
  14. // packet.
  15. // 现在实现一个解码器..这一个剥去包的前4个字节。
  16. func decodeMyLayer(data []byte, p gopacket.PacketBuilder) error {
  17. // Create my layer
  18. p.AddLayer(&MyLayer{data[:4], data[4:]})
  19. // Determine how to handle the rest of the packet
  20. return p.NextDecoder(layers.LayerTypeEthernet)
  21. }
  22. // Finally, decode your packets:
  23. // 最后,解码你的数据包:
  24. p := gopacket.NewPacket(data, MyLayerType, gopacket.Lazy)

关于编码解码器如何工作的更多细节,请参阅文档中的Decoder和PacketBuilder,或者查看RegisterLayerType和RegisterEndpointType来了解如何添加层/端点类型到goppacket。

Fast Decoding With DecodingLayerParser

TLDR: DecodingLayerParser解码数据包数据的时间大约是NewPacket的10%,但只针对已知的数据包栈。

基本解码使用gopacket.NewPacket或PacketSource。数据包有些慢,因为它需要分配一个新的数据包和每一层。它非常通用,可以处理所有已知的层类型,但有时您实际上只关心一组特定的层,因此多功能性被浪费了。

DecodingLayerParser通过将包层直接解码为预先分配的对象来避免内存分配,然后您可以引用这些对象来获得包的信息。一个快速的例子:

  1. func main() {
  2. var eth layers.Ethernet
  3. var ip4 layers.IPv4
  4. var ip6 layers.IPv6
  5. var tcp layers.TCP
  6. parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &eth, &ip4, &ip6, &tcp)
  7. decoded := []gopacket.LayerType{}
  8. for packetData := range somehowGetPacketData() {
  9. if err := parser.DecodeLayers(packetData, &decoded); err != nil {
  10. fmt.Fprintf(os.Stderr, "Could not decode layers: %v\n", err)
  11. continue
  12. }
  13. for _, layerType := range decoded {
  14. switch layerType {
  15. case layers.LayerTypeIPv6:
  16. fmt.Println(" IP6 ", ip6.SrcIP, ip6.DstIP)
  17. case layers.LayerTypeIPv4:
  18. fmt.Println(" IP4 ", ip4.SrcIP, ip4.DstIP)
  19. }
  20. }
  21. }
  22. }

这里需要注意的重要一点是,解析器正在修改层(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的容器实现。例子:

  1. dlp := gopacket.NewDecodingLayerParser(LayerTypeEthernet)
  2. dlp.SetDecodingLayerContainer(gopacket.DecodingLayerSparse(nil))
  3. var eth layers.Ethernet
  4. dlp.AddDecodingLayer(&eth)
  5. // ... add layers and use DecodingLayerParser as usual...

为了跳过一个间接层(尽管牺牲了一些功能),您也可以使用DecodingLayerContainer作为一个解码工具。在这种情况下,您必须自己处理未知的层类型和层恐慌。例子:

  1. func main() {
  2. var eth layers.Ethernet
  3. var ip4 layers.IPv4
  4. var ip6 layers.IPv6
  5. var tcp layers.TCP
  6. dlc := gopacket.DecodingLayerContainer(gopacket.DecodingLayerArray(nil))
  7. dlc = dlc.Put(&eth)
  8. dlc = dlc.Put(&ip4)
  9. dlc = dlc.Put(&ip6)
  10. dlc = dlc.Put(&tcp)
  11. // you may specify some meaningful DecodeFeedback
  12. decoder := dlc.LayersDecoder(LayerTypeEthernet, gopacket.NilDecodeFeedback)
  13. decoded := make([]gopacket.LayerType, 0, 20)
  14. for packetData := range somehowGetPacketData() {
  15. lt, err := decoder(packetData, &decoded)
  16. if err != nil {
  17. fmt.Fprintf(os.Stderr, "Could not decode layers: %v\n", err)
  18. continue
  19. }
  20. if lt != gopacket.LayerTypeZero {
  21. fmt.Fprintf(os.Stderr, "unknown layer type: %v\n", lt)
  22. continue
  23. }
  24. for _, layerType := range decoded {
  25. // examine decoded layertypes just as already shown above
  26. }
  27. }
  28. }

DecodingLayerSparse是最快但最有效的,当使用的层可以解码的LayerType值不大时,因为否则会导致更大的内存占用。decode layerarray是非常紧凑的,如果解码层的数量不是很大(最多10-15,但请自己做基准测试),主要可用。DecodingLayerMap是最通用的,默认情况下由DecodingLayerParser使用。请参阅层子包中的测试和基准测试,以进一步研究使用示例和性能度量。

如果你想使用你自己的内部包解码逻辑,你也可以选择实现你自己的DecodingLayerContainer。

Creating Packet Data

除了提供解码数据包数据的能力,goppacket还将允许你从头开始创建数据包。许多gopacket层实现SerializableLayer接口;这些层可以按以下方式序列化为一个[]byte:

  1. ip := &layers.IPv4{
  2. SrcIP: net.IP{1, 2, 3, 4},
  3. DstIP: net.IP{5, 6, 7, 8},
  4. // etc...
  5. }
  6. buf := gopacket.NewSerializeBuffer()
  7. opts := gopacket.SerializeOptions{} // See SerializeOptions for more details.
  8. err := ip.SerializeTo(buf, opts)
  9. if err != nil { panic(err) }
  10. fmt.Println(buf.Bytes()) // prints out a byte slice containing the serialized IPv4 layer.

将给定的层添加到SerializeBuffer上,并且它们将当前缓冲区的Bytes()片作为序列化层的有效负载。因此,您可以通过反向顺序序列化一组层(例如,有效负载、TCP、IP、以太网)来序列化整个数据包。SerializeBuffer的SerializeLayers函数就是一个帮助器,它就是这样做的。

例如,要生成一个(空的无用的,因为没有设置字段)以太网(IPv4(TCP(Payload))数据包,可以运行:

  1. buf := gopacket.NewSerializeBuffer()
  2. opts := gopacket.SerializeOptions{}
  3. gopacket.SerializeLayers(buf, opts,
  4. &layers.Ethernet{},
  5. &layers.IPv4{},
  6. &layers.TCP{},
  7. gopacket.Payload([]byte{1, 2, 3, 4}))
  8. packetData := buf.Bytes()

A Final Note

如果你使用goppacket,你几乎肯定想要确保goppacket /layers被导入,因为当导入时,它设置了所有的LayerType变量,并填充了很多有趣的变量/映射(DecodersByLayerName,等等)。因此,建议即使你不直接使用任何层函数,你仍然使用:

  1. import (
  2. _ "github.com/google/gopacket/layers"
  3. )