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 packetif 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 pfringfor 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 sourcefor 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.Packetpacket := 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.Packetfor 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 layertype MyLayer struct {StrangeHeader []bytepayload []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 layerp.AddLayer(&MyLayer{data[:4], data[4:]})// Determine how to handle the rest of the packetreturn 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.Ethernetvar ip4 layers.IPv4var ip6 layers.IPv6var tcp layers.TCPparser := 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.Ethernetdlp.AddDecodingLayer(ð)// ... add layers and use DecodingLayerParser as usual...
为了跳过一个间接层(尽管牺牲了一些功能),您也可以使用DecodingLayerContainer作为一个解码工具。在这种情况下,您必须自己处理未知的层类型和层恐慌。例子:
func main() {var eth layers.Ethernetvar ip4 layers.IPv4var ip6 layers.IPv6var tcp layers.TCPdlc := 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 DecodeFeedbackdecoder := 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")
