gopacket是什么?

gopacket是google出品的golang三方库,质量还是靠的住,项目地址为:github.com/google/gopacket
gopacket到底是什么呢?是个抓取网络数据包的库,这么说可能还有点抽象,但是抓包工具大家可能都使用过。
Windows平台下有Wireshark抓包工具,其底层抓包库是npcap(以前是winpcap);
Linux平台下有Tcpdump,其抓包库是libpcap;
而gopacket库可以说是libpcap和npcap的go封装,提供了更方便的go语言操作接口。

对于抓包库来说,常规功能就是抓包,而网络抓包有以下几个步骤:
1、枚举主机上网络设备的接口
2、针对某一网口进行抓包
3、解析数据包的mac层、ip层、tcp/udp层字段等
4、ip分片重组,或tcp分段重组成上层协议如http协议的数据
5、对上层协议进行头部解析和负载部分解析
[

](https://blog.csdn.net/RA681t58CJxsgCkJ31/article/details/115152820)

应用场景有哪些?

场景1:网络流量分析
对网络设备流量进行实时采集以及数据包分析。
场景2:伪造数据包
不少网络安全工具,需要伪造网络数据包,填充上必要的协议字段后发送给对端设备,从而达到一些目的。
场景3:离线pcap文件的读取和写入

安装

安装libpcap或npcap三方库

在使用gopacket包时,首先要确保在windows平台下安装了npcap或winpcap,或者是在linux平台下安装了libpcap库。
npcap下载地址:https://nmap.org/npcap/
libpcap下载地址:https://www.tcpdump.org/
下载自己电脑对应的操作系统版本的库
如果不想从官网下载libpcap库的话,也可以采用centos的yum命令或ubuntu的apt get命令来进行安装
[

](https://blog.csdn.net/RA681t58CJxsgCkJ31/article/details/115152820)

安装gopacket库

  1. go get github.com/google/gopacket

使用

1. 枚举网络设备

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "github.com/google/gopacket/pcap"
  6. )
  7. func main() {
  8. // 得到所有的(网络)设备
  9. devices, err := pcap.FindAllDevs()
  10. if err != nil {
  11. log.Fatal(err)
  12. }
  13. // 打印设备信息
  14. fmt.Println("Devices found:")
  15. for _, device := range devices {
  16. fmt.Println("\nName: ", device.Name)
  17. fmt.Println("Description: ", device.Description)
  18. fmt.Println("Devices addresses: ", device.Description)
  19. for _, address := range device.Addresses {
  20. fmt.Println("- IP address: ", address.IP)
  21. fmt.Println("- Subnet mask: ", address.Netmask)
  22. }
  23. }
  24. }

先调用pcap.FindAllDevs()获取当前主机所有的网络设备,网络设备有哪些属性呢?

  1. // Interface describes a single network interface on a machine.
  2. type Interface struct {
  3. Name string //设备名称
  4. Description string //设备描述信息
  5. Flags uint32
  6. Addresses []InterfaceAddress //网口的地址信息列表
  7. }
  8. // InterfaceAddress describes an address associated with an Interface.
  9. // Currently, it's IPv4/6 specific.
  10. type InterfaceAddress struct {
  11. IP net.IP
  12. Netmask net.IPMask // Netmask may be nil if we were unable to retrieve it.
  13. Broadaddr net.IP // Broadcast address for this IP may be nil
  14. P2P net.IP // P2P destination address for this IP may be nil
  15. }

2. 打开一个设备进行抓包

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/google/gopacket"
  5. "github.com/google/gopacket/pcap"
  6. "log"
  7. "time"
  8. )
  9. var (
  10. device string = "eth0"
  11. snapshot_len int32 = 1024
  12. promiscuous bool = false
  13. err error
  14. timeout time.Duration = 30 * time.Second
  15. handle *pcap.Handle
  16. )
  17. func main() {
  18. // 打开某一网络设备
  19. handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)
  20. if err != nil {log.Fatal(err) }
  21. defer handle.Close()
  22. // Use the handle as a packet source to process all packets
  23. packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
  24. for packet := range packetSource.Packets() {
  25. // Process packet here
  26. fmt.Println(packet)
  27. }
  28. }

1) 实时捕获

上面我们枚举了当前主机的所有网络设备,现在需要打开网络设备并进行实时捕获数据包,需调用pcap.OpenLive来打开网络设备,其函数原型如下:

  1. func OpenLive(device string, snaplen int32, promisc bool, timeout time.Duration) (handle *Handle, _ error)

device:网络设备的名称,如eth0,也可以填充pcap.FindAllDevs()返回的设备的Name
snaplen: 每个数据包读取的最大长度 the maximum size to read for each packet
promisc:是否将网口设置为混杂模式,即是否接收目的地址不为本机的包
timeout:设置抓到包返回的超时。如果设置成30s,那么每30s才会刷新一次数据包;设置成负数,会立刻刷新数据包,即不做等待
函数返回值:是一个*Handle类型的返回值,可能作为gopacket其他函数调用时作为函数参数来传递。

注意: 一定要记得释放掉handle,如文中的defer handle.Close()。

[

](https://blog.csdn.net/RA681t58CJxsgCkJ31/article/details/115152820)

2) 创建数据包源

  1. packetSource := gopacket.NewPacketSource(handle, handle.LinkType())

第一个参数为OpenLive的返回值,指向Handle类型的指针变量handle。
第二个参数为handle.LinkType()此参数默认是以太网链路,一般我们抓包,也是从2层以太网链路上抓取。
[

](https://blog.csdn.net/RA681t58CJxsgCkJ31/article/details/115152820)

3) 读取数据包

  1. //packetSource.Packets()是个channel类型,此处是从channel类型的数据通道中持续的读取网络数据包
  2. for packet := range packetSource.Packets() {
  3. // Process packet here
  4. fmt.Println(packet)
  5. }

解码数据包的各层

我们可以获取原始数据包,并尝试将其强制转换为已知格式。如ethernet、IP和TCP层。

Layers包是gopacket的Go库中的新功能,在底层libpcap库中不存在。它是gopacket库的非常有用的一部分。它允许我们轻松地识别数据包是否包含特定类型的层。这个代码示例将演示如何使用layers包来查看包是否是ethernet、IP和TCP,以及如何轻松访问这些头中的字段。

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/google/gopacket"
  5. "github.com/google/gopacket/layers"
  6. "github.com/google/gopacket/pcap"
  7. "log"
  8. "strings"
  9. "time"
  10. )
  11. var (
  12. device string = "eth0"
  13. snapshotLen int32 = 1024
  14. promiscuous bool = false
  15. err error
  16. timeout time.Duration = 30 * time.Second
  17. handle *pcap.Handle
  18. )
  19. func main() {
  20. // Open device
  21. handle, err = pcap.OpenLive(device, snapshotLen, promiscuous, timeout)
  22. if err != nil {log.Fatal(err) }
  23. defer handle.Close()
  24. packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
  25. for packet := range packetSource.Packets() {
  26. printPacketInfo(packet)
  27. }
  28. }
  29. func printPacketInfo(packet gopacket.Packet) {
  30. // Let's see if the packet is an ethernet packet
  31. // 判断数据包是否为以太网数据包,可解析出源mac地址、目的mac地址、以太网类型(如ip类型)等
  32. ethernetLayer := packet.Layer(layers.LayerTypeEthernet)
  33. if ethernetLayer != nil {
  34. fmt.Println("Ethernet layer detected.")
  35. ethernetPacket, _ := ethernetLayer.(*layers.Ethernet)
  36. fmt.Println("Source MAC: ", ethernetPacket.SrcMAC)
  37. fmt.Println("Destination MAC: ", ethernetPacket.DstMAC)
  38. // Ethernet type is typically IPv4 but could be ARP or other
  39. fmt.Println("Ethernet type: ", ethernetPacket.EthernetType)
  40. fmt.Println()
  41. }
  42. // Let's see if the packet is IP (even though the ether type told us)
  43. // 判断数据包是否为IP数据包,可解析出源ip、目的ip、协议号等
  44. ipLayer := packet.Layer(layers.LayerTypeIPv4)
  45. if ipLayer != nil {
  46. fmt.Println("IPv4 layer detected.")
  47. ip, _ := ipLayer.(*layers.IPv4)
  48. // IP layer variables:
  49. // Version (Either 4 or 6)
  50. // IHL (IP Header Length in 32-bit words)
  51. // TOS, Length, Id, Flags, FragOffset, TTL, Protocol (TCP?),
  52. // Checksum, SrcIP, DstIP
  53. fmt.Printf("From %s to %s\n", ip.SrcIP, ip.DstIP)
  54. fmt.Println("Protocol: ", ip.Protocol)
  55. fmt.Println()
  56. }
  57. // Let's see if the packet is TCP
  58. // 判断数据包是否为TCP数据包,可解析源端口、目的端口、seq序列号、tcp标志位等
  59. tcpLayer := packet.Layer(layers.LayerTypeTCP)
  60. if tcpLayer != nil {
  61. fmt.Println("TCP layer detected.")
  62. tcp, _ := tcpLayer.(*layers.TCP)
  63. // TCP layer variables:
  64. // SrcPort, DstPort, Seq, Ack, DataOffset, Window, Checksum, Urgent
  65. // Bool flags: FIN, SYN, RST, PSH, ACK, URG, ECE, CWR, NS
  66. fmt.Printf("From port %d to %d\n", tcp.SrcPort, tcp.DstPort)
  67. fmt.Println("Sequence number: ", tcp.Seq)
  68. fmt.Println()
  69. }
  70. // Iterate over all layers, printing out each layer type
  71. fmt.Println("All packet layers:")
  72. for _, layer := range packet.Layers() {
  73. fmt.Println("- ", layer.LayerType())
  74. }
  75. ///.......................................................
  76. // Check for errors
  77. // 判断layer是否存在错误
  78. if err := packet.ErrorLayer(); err != nil {
  79. fmt.Println("Error decoding some part of the packet:", err)
  80. }
  81. }

仅仅以此处tcp部分的代码详细解析下:

  1. // 判断数据包是否为TCP数据包,可解析源端口、目的端口、seq序列号、tcp标志位等
  2. tcpLayer := packet.Layer(layers.LayerTypeTCP)
  3. if tcpLayer != nil {
  4. fmt.Println("TCP layer detected.")
  5. tcp, _ := tcpLayer.(*layers.TCP)
  6. fmt.Printf("From port %d to %d\n", tcp.SrcPort, tcp.DstPort)
  7. }

此处需要研究下源代码中数据结构,以防理解错误:

  1. type Packet interface {
  2. // Layer returns the first layer in this packet of the given type, or nil
  3. Layer(LayerType) Layer //根据给定的类型,在数据包中寻找其第一个层
  4. }
  5. //看看Layer的结构
  6. type Layer interface {
  7. // LayerType is the gopacket type for this layer.
  8. LayerType() LayerType
  9. // LayerContents returns the set of bytes that make up this layer.
  10. LayerContents() []byte
  11. // LayerPayload returns the set of bytes contained within this layer, not
  12. // including the layer itself.
  13. LayerPayload() []byte
  14. }
  15. //tcp数据包格式
  16. type TCP struct {
  17. BaseLayer
  18. SrcPort, DstPort TCPPort
  19. Seq uint32
  20. Ack uint32
  21. DataOffset uint8
  22. FIN, SYN, RST, PSH, ACK, URG, ECE, CWR, NS bool
  23. Window uint16
  24. Checksum uint16
  25. Urgent uint16
  26. sPort, dPort []byte
  27. Options []TCPOption
  28. Padding []byte
  29. opts [4]TCPOption
  30. tcpipchecksum
  31. }

TCP结构体是实现了Layer接口的,其实Ethernet,IPV4,UDP等结构体也实现了Layer接口
在上述代码中,我们调用函数时,传入的LayerType协议层的类型为layers.LayerTypeTCP,函数返回值为interface类型,必须转换成TCP结构体
tcp, _ := tcpLayer.(*layers.TCP)
tcp是layers.TCP这个具体类型的指针,通过tcp则可以获取数据包中tcp协议的相关字段。

自定义层

自定义层有助于实现当前不包含在gopacket layers包中的协议。

  1. import (
  2. "fmt"
  3. "github.com/google/gopacket"
  4. )
  5. // 创建自定义层数据结构,并实现Layer接口中的函数LayerType()、LayerContents()、LayerPayload()
  6. type CustomLayer struct {
  7. // This layer just has two bytes at the front
  8. SomeByte byte
  9. AnotherByte byte
  10. restOfData []byte
  11. }
  12. // 注册自定义层类型,然后我们才可以使用它
  13. // 第一个参数是ID. 自定义层使用大于2000的数字,它必须是唯一的
  14. var CustomLayerType = gopacket.RegisterLayerType(
  15. 2001,
  16. gopacket.LayerTypeMetadata{
  17. "CustomLayerType",
  18. gopacket.DecodeFunc(decodeCustomLayer),
  19. },
  20. )
  21. //自定义层实现LayerType
  22. func (l CustomLayer) LayerType() gopacket.LayerType {
  23. return CustomLayerType
  24. }
  25. //自定义层实现LayerContents
  26. func (l CustomLayer) LayerContents() []byte {
  27. return []byte{l.SomeByte, l.AnotherByte}
  28. }
  29. //自定义层实现LayerPayload
  30. func (l CustomLayer) LayerPayload() []byte {
  31. return l.restOfData
  32. }
  33. //实现自定义的解码函数
  34. func decodeCustomLayer(data []byte, p gopacket.PacketBuilder) error {
  35. p.AddLayer(&CustomLayer{data[0], data[1], data[2:]})
  36. return p.NextDecoder(gopacket.LayerTypePayload)
  37. }
  38. func main() {
  39. rawBytes := []byte{0xF0, 0x0F, 65, 65, 66, 67, 68}
  40. packet := gopacket.NewPacket(
  41. rawBytes,
  42. CustomLayerType,
  43. gopacket.Default,
  44. )
  45. fmt.Println("Created packet out of raw bytes.")
  46. fmt.Println(packet)
  47. // Decode the packet as our custom layer
  48. customLayer := packet.Layer(CustomLayerType)
  49. if customLayer != nil {
  50. fmt.Println("Packet was successfully decoded with custom layer decoder.")
  51. customLayerContent, _ := customLayer.(*CustomLayer)
  52. // Now we can access the elements of the custom struct
  53. fmt.Println("Payload: ", customLayerContent.LayerPayload())
  54. fmt.Println("SomeByte element:", customLayerContent.SomeByte)
  55. fmt.Println("AnotherByte element:", customLayerContent.AnotherByte)
  56. }
  57. }

结合上述代码可知,实现自定义的层需要3步:
1、创建自定义层的结构体,并实现Layer接口中的函数LayerType()、LayerContents()、LayerPayload()
2、按照解码函数签名来实现自定义解码函数,名称可自行命名。
解码函数签名如下:
type DecodeFunc func([]byte, PacketBuilder) error
3、使用gopacket.RegisterLayerType函数来注册自定义层
[

](https://blog.csdn.net/RA681t58CJxsgCkJ31/article/details/115152820)

TCP流重组

为什么需要tcp流重组?

package main

import (
 "bufio"
 "flag"
 "io"
 "log"
 "net/http"
 "time"

 "github.com/google/gopacket"
 "github.com/google/gopacket/examples/util"
 "github.com/google/gopacket/layers"
 "github.com/google/gopacket/pcap"
 "github.com/google/gopacket/tcpassembly"
 "github.com/google/gopacket/tcpassembly/tcpreader"
)

var iface = flag.String("i", "eth0", "Interface to get packets from")
var snaplen = flag.Int("s", 1600, "SnapLen for pcap packet capture")

// Build a simple HTTP request parser using tcpassembly.StreamFactory and tcpassembly.Stream interfaces

// httpStreamFactory implements tcpassembly.StreamFactory
type httpStreamFactory struct{}

// httpStream will handle the actual decoding of http requests.
type httpStream struct {
 net, transport gopacket.Flow
 r              tcpreader.ReaderStream
}

func (h *httpStreamFactory) New(net, transport gopacket.Flow) tcpassembly.Stream {
 hstream := &httpStream{
  net:       net,
  transport: transport,
  r:         tcpreader.NewReaderStream(),
 }
 go hstream.run() // Important... we must guarantee that data from the reader stream is read.

 // ReaderStream implements tcpassembly.Stream, so we can return a pointer to it.
 return &hstream.r
}

func (h *httpStream) run() {
 buf := bufio.NewReader(&h.r)
 for {
  req, err := http.ReadRequest(buf)
  if err == io.EOF {
   // We must read until we see an EOF... very important!
   return
  } else if err != nil {
   log.Println("Error reading stream", h.net, h.transport, ":", err)
  } else {
   bodyBytes := tcpreader.DiscardBytesToEOF(req.Body)
   req.Body.Close()
   log.Println("Received request from stream", h.net, h.transport, ":", req, "with", bodyBytes, "bytes in request body")
  }
 }
}

func main() {
 defer util.Run()()
 var handle *pcap.Handle
 var err error

 // Set up pcap packet capture
 handle, err = pcap.OpenLive(*iface, int32(*snaplen), true, pcap.BlockForever)
 if err != nil {
  log.Fatal(err)
 }

 // Set up assembly
 streamFactory := &httpStreamFactory{}
 streamPool := tcpassembly.NewStreamPool(streamFactory)
 assembler := tcpassembly.NewAssembler(streamPool)

 // Read in packets, pass to assembler.
 packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
 packets := packetSource.Packets()
 ticker := time.Tick(time.Minute)
 for {
  select {
  case packet := <-packets:
   if packet.NetworkLayer() == nil || packet.TransportLayer() == nil || packet.TransportLayer().LayerType() != layers.LayerTypeTCP {
    log.Println("Unusable packet")
    continue
   }
   tcp := packet.TransportLayer().(*layers.TCP)
   //将数据包进行重组
   assembler.AssembleWithTimestamp(packet.NetworkLayer().NetworkFlow(), tcp, packet.Metadata().Timestamp)

  case <-ticker:
   //每隔一分钟,刷新之前两分钟内不活动的连接
   assembler.FlushOlderThan(time.Now().Add(time.Minute * -2))
  }
 }
}

基本步骤如下:
1、创建httpStreamFactory结构体,实现tcpassembly.StreamFactory接口
2、创建连接池
streamPool := tcpassembly.NewStreamPool(streamFactory)
3、创建重组器
assembler := tcpassembly.NewAssembler(streamPool)
4、将数据包添加到重组器中
assembler.AssembleWithTimestamp(packet.NetworkLayer().NetworkFlow(), tcp, packet.Metadata().Timestamp)

总结

首先,gopacket库是google大厂背书,从使用文档、质量、社区活跃度来说都很不错
其次,使用方式简单,扩展性好。gopacket提供了自定义的接口,可根据自身需要进行定制化开发
最后,gopacket定义的layers齐全,如果是实时捕获数据后进行协议解析,采用其内置的layer即可,无需自己手动去解析繁杂的协议了。