【GoCN酷Go推荐】网络流量抓包库 gopacket介绍


本文摘自网络,作者,侵删。

gopacket简介

1. 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、对上层协议进行头部解析和负载部分解析

2. 应用场景有哪些?
场景1:网络流量分析

对网络设备流量进行实时采集以及数据包分析。

场景2:伪造数据包

不少网络安全工具,需要伪造网络数据包,填充上必要的协议字段后发送给对端设备,从而达到一些目的。

场景3:离线pcap文件的读取和写入

安装部署

2.1 安装libpcap或npcap三方库
在使用gopacket包时,首先要确保在windows平台下安装了npcap或winpcap,或者是在linux平台下安装了libpcap库。

npcap下载地址:https://nmap.org/npcap/

libpcap下载地址:https://www.tcpdump.org/

下载自己电脑对应的操作系统版本的库

如果不想从官网下载libpcap库的话,也可以采用centos的yum命令或ubuntu的apt get命令来进行安装。

2.2 安装gopacket
go get github.com/google/gopacket

使用方法

3.1 枚举网络设备


package main
import (
    "fmt"
    "log"
    "github.com/google/gopacket/pcap"
)
func main() {
    // 得到所有的(网络)设备
    devices, err := pcap.FindAllDevs()
    if err != nil {
        log.Fatal(err)
    }
    // 打印设备信息
    fmt.Println("Devices found:")
    for _, device := range devices {
        fmt.Println("\nName: ", device.Name)
        fmt.Println("Description: ", device.Description)
        fmt.Println("Devices addresses: ", device.Description)
        for _, address := range device.Addresses {
            fmt.Println("- IP address: ", address.IP)
            fmt.Println("- Subnet mask: ", address.Netmask)
        }
    }
}

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

// Interface describes a single network interface on a machine.
type Interface struct {
 Name        string //设备名称
 Description string //设备描述信息
 Flags       uint32 
 Addresses   []InterfaceAddress //网口的地址信息列表
}
// InterfaceAddress describes an address associated with an Interface.
// Currently, it's IPv4/6 specific.
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
}

3.2 打开一个设备进行抓包

package main
import (
    "fmt"
    "github.com/google/gopacket"
    "github.com/google/gopacket/pcap"
    "log"
    "time"
)
var (
    device       string = "eth0"
    snapshot_len int32  = 1024
    promiscuous  bool   = false
    err          error
    timeout      time.Duration = 30 * time.Second
    handle       *pcap.Handle
)
func main() {
    // 打开某一网络设备
    handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)
    if err != nil {log.Fatal(err) }
    defer handle.Close()
    // Use the handle as a packet source to process all packets
    packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
    for packet := range packetSource.Packets() {
        // Process packet here
        fmt.Println(packet)
    }
}

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


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()。

2) 创建数据包源


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

第一个参数为OpenLive的返回值,指向Handle类型的指针变量handle。

第二个参数为handle.LinkType()此参数默认是以太网链路,一般我们抓包,也是从2层以太网链路上抓取。

3)读取数据包


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

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

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

package main
import (
    "fmt"
    "github.com/google/gopacket"
    "github.com/google/gopacket/layers"
    "github.com/google/gopacket/pcap"
    "log"
    "strings"
    "time"
)
var (
    device      string = "eth0"
    snapshotLen int32  = 1024
    promiscuous bool   = false
    err         error
    timeout     time.Duration = 30 * time.Second
    handle      *pcap.Handle
)
func main() {
    // Open device
    handle, err = pcap.OpenLive(device, snapshotLen, promiscuous, timeout)
    if err != nil {log.Fatal(err) }
    defer handle.Close()
    packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
    for packet := range packetSource.Packets() {
        printPacketInfo(packet)
    }
}
func printPacketInfo(packet gopacket.Packet) {
    // Let's see if the packet is an ethernet packet
    // 判断数据包是否为以太网数据包,可解析出源mac地址、目的mac地址、以太网类型(如ip类型)等
    ethernetLayer := packet.Layer(layers.LayerTypeEthernet)
    if ethernetLayer != nil {
        fmt.Println("Ethernet layer detected.")
        ethernetPacket, _ := ethernetLayer.(*layers.Ethernet)
        fmt.Println("Source MAC: ", ethernetPacket.SrcMAC)
        fmt.Println("Destination MAC: ", ethernetPacket.DstMAC)
        // Ethernet type is typically IPv4 but could be ARP or other
        fmt.Println("Ethernet type: ", ethernetPacket.EthernetType)
        fmt.Println()
    }
    // Let's see if the packet is IP (even though the ether type told us)
    // 判断数据包是否为IP数据包,可解析出源ip、目的ip、协议号等
    ipLayer := packet.Layer(layers.LayerTypeIPv4)
    if ipLayer != nil {
        fmt.Println("IPv4 layer detected.")
        ip, _ := ipLayer.(*layers.IPv4)
        // IP layer variables:
        // Version (Either 4 or 6)
        // IHL (IP Header Length in 32-bit words)
        // TOS, Length, Id, Flags, FragOffset, TTL, Protocol (TCP?),
        // Checksum, SrcIP, DstIP
        fmt.Printf("From %s to %s\n", ip.SrcIP, ip.DstIP)
        fmt.Println("Protocol: ", ip.Protocol)
        fmt.Println()
    }
    // Let's see if the packet is TCP
    // 判断数据包是否为TCP数据包,可解析源端口、目的端口、seq序列号、tcp标志位等
    tcpLayer := packet.Layer(layers.LayerTypeTCP)
    if tcpLayer != nil {
        fmt.Println("TCP layer detected.")
        tcp, _ := tcpLayer.(*layers.TCP)
        // TCP layer variables:
        // SrcPort, DstPort, Seq, Ack, DataOffset, Window, Checksum, Urgent
        // Bool flags: FIN, SYN, RST, PSH, ACK, URG, ECE, CWR, NS
        fmt.Printf("From port %d to %d\n", tcp.SrcPort, tcp.DstPort)
        fmt.Println("Sequence number: ", tcp.Seq)
        fmt.Println()
    }
    // Iterate over all layers, printing out each layer type
    fmt.Println("All packet layers:")
    for _, layer := range packet.Layers() {
        fmt.Println("- ", layer.LayerType())
    }
    ///.......................................................
    // Check for errors
    // 判断layer是否存在错误
    if err := packet.ErrorLayer(); err != nil {
        fmt.Println("Error decoding some part of the packet:", err)
    }
}

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

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

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

type Packet interface {
 // Layer returns the first layer in this packet of the given type, or nil
 Layer(LayerType) Layer   //根据给定的类型,在数据包中寻找其第一个层
}
//看看Layer的结构
type Layer interface {
 // LayerType is the gopacket type for this layer.
 LayerType() LayerType
 // LayerContents returns the set of bytes that make up this layer.
 LayerContents() []byte
 // LayerPayload returns the set of bytes contained within this layer, not
 // including the layer itself.
 LayerPayload() []byte
}
//tcp数据包格式
type TCP struct {
 BaseLayer
 SrcPort, DstPort                           TCPPort
 Seq                                        uint32
 Ack                                        uint32
 DataOffset                                 uint8
 FIN, SYN, RST, PSH, ACK, URG, ECE, CWR, NS bool
 Window                                     uint16
 Checksum                                   uint16
 Urgent                                     uint16
 sPort, dPort                               []byte
 Options                                    []TCPOption
 Padding                                    []byte
 opts                                       [4]TCPOption
 tcpipchecksum
}

TCP结构体是实现了Layer接口的,其实Ethernet,IPV4,UDP等结构体也实现了Layer接口

阅读剩余部分

相关阅读 >>

Go 语言 协程和管道讲解

Golang 四则运算 计算器 yacc 归约

Golang判断是否存在不存在就创建文件

Go 将数组按指定大小分隔

Golang 读写锁 sync.rwmutex

分布式定时任务执行器

26 Goroutine channel实现并发和并行(三)

Go中sync.mutex源码解读

Go的垃圾回收机制(gc)

Go time

更多相关阅读请进入《Go》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...