gopacket

文档地址:https://godoc.org/github.com/google/gopacket
视频介绍:https://www.youtube.com/watch?v=APDnbmTKjgM

子包

  • layers: 用于解码数据包协议
  • pcap: 使用libpcap来读取数据包
  • pfring: 使用PF_RING来读取数据包
  • afpacket: 使用Linux’s AF_PACKET来读取数据包
  • tcpassembly: TCP流重组
  • gopacket使用示例:https://colobu.com/2019/06/01/packet-capture-injection-and-analysis-gopacket/
  • 流重组示例:https://github.com/google/gopacket/blob/master/examples/httpassembly/main.go

pcap处理

查看版本

version := pcap.Version()
fmt.Println(version)

测试(Win10 x64):
Npcap version 1.00, based on libpcap version 1.9.1

网络接口

类型:pcap.Interface

type Interface struct {
	Name        string
	Description string
	Flags       uint32
	Addresses   []InterfaceAddress
}
type InterfaceAddress struct {
	IP        net.IP
	Netmask   net.IPMask // Netmask may be nil if we were unable to retrieve it.
	Broadaddr net.IP     // Broadcast address for this IP may be nil
	P2P       net.IP     // P2P destination address for this IP may be nil
}

查找网络设备:

var devices []pcap.Interface
devices, _ = pcap.FindAllDevs()
fmt.Println(devices)

实时捕获

handle, err := pcap.OpenLive("\\Device\\NPF_{5C9384EF-DEBA-43A6-AE6A-5D10C952C481}", int32(65535), true, -1 * time.Second)
if err != nil {
    log.Fatal(err)
}
defer handle.Close()

pcap.OpenLive参数:

  • 设备名:pcap.FindAllDevs()返回的设备的Name
  • snaplen:捕获一个数据包的多少个字节,一般来说对任何情况65535是一个好的实践,如果不关注全部内容,只关注数据包头,可以设置成1024
  • promisc:设置网卡是否工作在混杂模式,即是否接收目的地址不为本机的包
  • timeout:设置抓到包返回的超时。如果设置成30s,那么每30s才会刷新一次数据包;设置成负数,会立刻刷新数据包,即不做等待
  • 要记得释放掉handle

打开pcap

handle, _ = pcap.OpenOffline("dump.pcap")
defer handle.Close()

创建一个数据包源

通过监听设备的实时流量或者来自文件的数据包,得到了一个handle,通过这个handle得到一个数据包源packetSource。

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

利用handle.LinkType()就不用知道链路类型了。

读取数据包

读取一个数据包:packet, _ := packetSource.NextPacket()
获得一个可以读取数据包的channel来读取全部数据包

packet := packetSource.Packets()
for packet := range packet{
    fmt.Println(packet)
}

设置过滤器

使用BPF语法即可。

handle.SetBPFFilter("tcp and port 80")

创建一个pcap用于写入

dumpFile, _ := os.Create("dump.pcap")
defer dumpFile.Close()
packetWriter := pcapgo.NewWriter(dumpFile)
packetWriter.WriteFileHeader(65535, layers.LinkTypeEthernet)

packetWriter.WriteFileHeader的参数是snaplen和链路类型

写入数据包

packet := packetSource.Packets()
for packet := range packet{
    packetWriter.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
}

packet处理

列出所有层

for _, layer := range packet.Layers() {
    fmt.Println(layer.LayerType())
    fmt.Println(layer.LayerContents())
    fmt.Println(layer.LayerPayload())
}
  • layer.LayerType():这层的类型
  • layer.LayerContents():当前层的内容
  • layer.LayerPayload():当前层承载的payload(不包括当前层)

分析某层的数据

IPv4
ipLayer := packet.Layer(layers.LayerTypeIPv4)
if ipLayer != nil {
    ip, _ := ipLayer.(*layers.IPv4)
    fmt.Println(ip.SrcIP, ip.DstIP)
    fmt.Println(ip.Protocol)
}
TCP
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
    ip, _ := tcpLayer.(*layers.TCP)
    fmt.Println(ip.SrcPort, ip.DstPort)
}

这里有一些需要注意的:

  • tcpLayer是通过接口packet.Layer返回的一个Layer,一个指向layers.TCP的指针
  • tcplayers.TCP这个具体类型的指针,也可以说tcp是真实的tcp层数据
单独的解码器

可以从多个起点对数据包进行解码。可以解码没有完整数据的数据包。

// Decode an ethernet packet
ethP := gopacket.NewPacket(packet, layers.LayerTypeEthernet, gopacket.Default)
// Decode an IPv6 header and everything it contains
ipP := gopacket.NewPacket(packet, layers.LayerTypeIPv6, gopacket.Default)
// Decode a TCP header and its payload
tcpP := gopacket.NewPacket(packet, layers.LayerTypeTCP, gopacket.Default)
懒惰解码(Lazy Decoding)

创建一个packet包,但是不立刻解码,只有后面需要用的时候再解码。比如第二行解码IPv4层,如果有的话,就解码IPv4层,然后不做进一步处理(不解码后续的层);如果没有会解码整个packet来寻找IPv4层。
packet.Layers()会解码所有层并且返回,已经解码的层不会再次做解码。

注意:这种方式并不是并发安全的,对Layers的每次调用可能会改变数据包。

packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Lazy)
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.
layers := packet.Layers()
NoCopy解码

上述两种解码方式会复制切片,对切片的字节进行更改不会影响数据包本身。如果可以保证不修改切片,可以使用NoCopy。

for data := range myByteSliceChannel { 
	p := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.NoCopy)
	doSomethingWithPacket(p)
}
快速解码

不会创建新的内存结构。会重用内存结构,所以只能用于预定义好层的结构。

for packet := range packetSource.Packets() {
    var eth layers.Ethernet
    var ip4 layers.IPv4
    var tcp layers.TCP
    parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &eth, &ip4, &tcp)
    var decodedLayers []gopacket.LayerType
    parser.DecodeLayers(packet.Data(), &decodedLayers)
    for _, layerType := range decodedLayers {
        fmt.Println(layerType)
    }
}

自定义层数据

注册自定义的层

gopacket.RegisterLayerType():传入一个独有的id,和层类型对应的Metadata,包括有名字和对应的解码器。

var MyLayerType = gopacket.RegisterLayerType(12345, gopacket.LayerTypeMetadata{Name: "MyLayerType", Decoder: gopacket.DecodeFunc(decodeMyLayer)})

定义层的结构

要实现三个方法。

type MyLayer struct {
	MyHeader  []byte
	MyPayload []byte
}
func (m MyLayer) LayerType() gopacket.LayerType {
    return MyLayerType
}
func (m MyLayer) LayerContents() []byte {
    return m.MyHeader
}
func (m MyLayer) LayerPayload() []byte {
    return m.MyPayload
}

定义解码器

func decodeMyLayer(data []byte, p gopacket.PacketBuilder) error {
	p.AddLayer(&MyLayer{data[:4], data[4:]})
	return p.NextDecoder(layers.LayerTypeEthernet)
}

对自定义的层进行解码

pkt := gopacket.NewPacket(packet.Data(), MyLayerType, gopacket.Default)

创建与发送

创建

创建一个新的序列化缓冲区;然后把所有层序列化到缓冲区中。

buffer := gopacket.NewSerializeBuffer()
options := gopacket.SerializeOptions{}
gopacket.SerializeLayers(buffer, options, &layers.Ethernet{}, &layers.IPv4{}, &layers.TCP{}, gopacket.Payload([]byte{65, 66, 67}))

发送数据包

handle.WritePacketData(buffer.Bytes())

流和端点(Flow and Endpoint)

注意:这个地方官方文档感觉有坑

  • pkt.NetworkLayer().NetworkFlow():取网络层,网络流,IP地址
  • pkt.TransportLayer().TransportFlow():取传输层,传输端点,端口
  • interestingFlow:定义好端点,对网络数据做匹配
pkt := gopacket.NewPacket(packet.Data(), layers.LayerTypeEthernet, gopacket.Lazy)
netFlow := pkt.NetworkLayer().NetworkFlow()
src, dst := netFlow.Endpoints()
fmt.Println(src, dst)
fmt.Println("Done.")
tcpFlow := pkt.TransportLayer().TransportFlow()
fmt.Println(tcpFlow.Endpoints())
interestingFlow, _ := gopacket.FlowFromEndpoints(layers.NewUDPPortEndpoint(1000), layers.NewUDPPortEndpoint(500))
if t := pkt.TransportLayer(); t != nil && t.TransportFlow() == interestingFlow {
    fmt.Println("Found that UDP flow I was looking for!")
}
GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐