Contents

go第三方库-github.com.google.gopacket

godoc

godoc官网

重要子包:

  • layers:包含内置于 gopacket 中的用于解码数据包协议的逻辑。
  • pcap:C 绑定以使用 libpcap 读取离线数据包。
  • pfring:C 绑定使用 PF_RING 从线路上读取数据包。
  • afpacket:Linux 的 AF_PACKET 的 C 绑定,用于离线读取数据包。
  • tcpassembly:TCP 流重组

基本用法

gopacket 将数据包数据作为 []byte 并将其解码。数据包被解码后可以从数据包中获取包的头信息。

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

可以从多个起点解码数据包,允许解码没有完整数据的数据包

1
2
3
4
5
6
// 解码以太网数据包
ethP := gopacket.NewPacket(p1, layers.LayerTypeEthernet, gopacket.Default) 
// 解码 IPv6 标头及其包含的所有内容
ipP := gopacket.NewPacket(p2, layers.LayerTypeIPv6, gopacket.Default) 
// 解码 TCP 标头及其有效载荷
tcpP := gopacket.NewPacket(p3, layers.LayerTypeTCP, gopacket.Default)

从源读取数据包

构建一个 PacketSource从某处(文件、接口等)读取数据包并处理它们

  1. 构造一个实现 PacketDataSource 接口的对象
  2. 将PacketDataSource与选择的解码器一起传递到 NewPacketSource,以创建一个 PacketSource

最常用的方法是 Packets 函数,它返回一个channel,然后将新数据包异步写入该channel,如果 packetSource 到达文件末尾则关闭channel

1
2
3
4
packetSource := ... // 使用 pcap 或 pfring 构造
packet := range packetSource.Packets() { 
  handlePacket(packet) // 对每个数据包做一些事情
}

惰性解码

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

1
2
3
4
5
6
// 创建一个数据包,但实际上还没有解码任何东西
packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Lazy)
// 将数据包解码到找到的第一个 IPv4 层,但不再进一步。如果没有找到 IPv4 层,整个数据包将被解码以寻找ipv4。
ip4 := packet.Layer(layers.LayerTypeIPv4)
// 解码所有层并返回它们。每层解码一次。
layers := packet.Layers()

延迟解码的数据包不是并发安全的。

NoCopy解码

默认解码结果是原数据包的副本,添加这个选项返回的结果不进行拷贝使用原数据包。

1
p := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.NoCopy)

最快的解码方法是同时使用 Lazy 和 NoCopy,但从上面的许多警告中请注意,对于某些实现,其中一个或两个都可能是危险的。

指向已知层的指针

在解码期间,某些层作为众所周知的层类型存储在数据包中.可以使用 packet.LinkLayer、packet.NetworkLayer、packet.TransportLayer 和 packet.ApplicationLayer 函数。前三层提供了获取该特定层的 src/dst 地址的方法,而最后一层提供了一个 Payload 函数来获取有效负载数据

1
2
3
4
5
6
7
8
// 从某个来源获取数据包
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,它会在数据包的解析部分出现错误时设置

1
2
3
4
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 返回错误,因为在进入错误层之前,我们可能已经成功解码了多个层。即使您的 TCP 层格式不正确,您仍然可以正确获取以太网和 IPv4 层。

Flow和EndPoint

gopacket 有两个有用的对象,Flow 和 Endpoint,用于以独立于协议的方式传达数据包来自 A 和去往 B 的事实。通用层类型 LinkLayer、NetworkLayer 和 TransportLayer 都提供提取其Flow信息的方法,不用担心底层的类型

Flow 是一个简单的对象,由两个端点组成,一个源和一个目的地。它详细说明了数据包层的发送方和接收方。

Endpoint是源或目标的可散列表示。例如,对于 LayerTypeIPv4,端点包含 IPv4 数据包的 IP 地址字节。Flow 可以分解为 Endpoints,而 Endpoints 可以组合成 Flows:

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

Endpoint 和 Flow 对象都可以用作map的key,=可以比较它们,因此您可以根据端点标准轻松地将所有数据包组合在一起:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
flows := map[gopacket.Endpoint]chan gopacket.Packet 
packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Lazy) 
// 根据目标端口将所有 TCP 数据包发送到通道。
if tcp := packet.Layer(layers.LayerTypeTCP); tcp != nil {
  flows[tcp.TransportFlow().Dst()] <- packet
}
// 查找具有相同源和目标网络地址的所有数据包
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) 
  } 
} 
// 查找所有从 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 相同的哈希值。一个示例用法可能是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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
  }
}

这使我们能够拆分数据包流,同时仍然确保每个流都能看到流的所有数据包(及其双向相反)。

实现你自己的解码器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
//创建一个layer类型,参数1的值唯一且较大避免冲突, 给它一个名字和一个解码器来使用。
var MyLayerType = gopacket.RegisterLayerType(12345, gopacket.LayerTypeMetadata{Name: "MyLayerType", Decoder: gopacket.DecodeFunc(decodeMyLayer)})

//实现自定义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 }

//实现一个解码器。该函数去掉了前4个字节的数据(选择前 4 个字节为包头)。
func decodeMyLayer(data []byte, p gopacket.PacketBuilder) error {
  // 创建mylayer
  p.AddLayer(&MyLayer{data[:4], data[4:]})
  // 确定如何处理数据包的其余部分
  return p.NextDecoder(layers.LayerTypeEthernet)
}

//最后使用你的解码器
p := gopacket.NewPacket(data, MyLayerType, gopacket.Lazy)

使用 DecodingLayerParser 快速解码

TLDR:DecodingLayerParser 大约需要 NewPacket 的 10% 的时间来解码数据包数据,但仅限于已知的数据包堆栈。

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

DecodingLayerParser 通过将数据包层直接解码为预分配的对象来完全避免内存分配,然后您可以引用这些对象来获取数据包的信息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 somewhatGetPacketData() { 
    if err := parser.DecodeLayers(packetData, &decoded); err != nil { 
      fmt.Fprintf(os.Stderr, "Could not decode layers: %v\n", err) 
      continue 
    } 
    for _, layerType := range decoded {
      开关 layerType { 
        case layers.LayerTypeIPv6: 
          fmt.Println( IP6”、ip6.SrcIPip6.DstIP)

          fmt.Println(" IP4 ", ip4.SrcIP, ip4.DstIP) 
      } 
    } 
  } 
}

并不是所有的层都可以被 DecodingLayerParser 使用……只有那些实现了 DecodingLayer 接口的层才可用

使用 DecodingLayerContainer 进行更快的自定义解码

默认情况下,DecodingLayerParser 使用原生地图来存储和搜索要解码的图层。尽管用途广泛,但在某些情况下,此解决方案可能不是最佳选择。例如,如果您只有几层,则可以通过稀疏数组索引或线性数组扫描来提供更快的操作。

创建分组数据

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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.

SerializeTo 将给定layer添加到 SerializeBuffer 上,并将当前缓冲区的 Bytes() 切片视为序列化层的有效负载。因此,您可以通过以相反顺序序列化一组layer来序列化整个数据包(例如,有效负载、然后是 TCP、然后是 IP、然后是以太头)。

如果不想让手动按逆序序列化,可以使用SerializeLayers函数,你可以在他的参数中按正常顺序写入需要添加的layer

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

1
2
3
4
5
6
7
8
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()

最后

如果您使用 gopacket,肯定要确保导入 gopacket/layers,因为在导入时它会设置所有 LayerType 变量并填充许多有趣的变量/地图(DecodersByLayerName 等)。因此,建议即使您不直接使用任何layers 函数,您仍然可以导入:

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