gopacket 抓包 過濾器


 

 

//定義過濾器
func getFilter(port uint16) string {
// filter := fmt.Sprintf("udp and ((src port %v) or (dst port %v))", port, port)
filter := fmt.Sprintf("((src port %v) or (dst port %v))", port, port)
return filter
}

 

 

【酷Go推薦】網絡流量抓包庫 gopacket - 知乎 https://zhuanlan.zhihu.com/p/361737169

網絡流量抓包庫 gopacket · GoCN社區 https://gocn.vip/topics/11829

一、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 接口

在上述代碼中,我們調用函數時,傳入的 LayerType 協議層的類型為 layers.LayerTypeTCP,函數返回值為 interface 類型,必須轉換成 TCP 結構體

tcp, _ := tcpLayer.(*layers.TCP)

tcp 是 layers.TCP 這個具體類型的指針,通過 tcp 則可以獲取數據包中 tcp 協議的相關字段。

3、4 自定義層

自定義層有助於實現當前不包含在 gopacket layers 包中的協議。

import (
    "fmt"
    "github.com/google/gopacket"
)
// 創建自定義層數據結構,並實現Layer接口中的函數LayerType()、LayerContents()、LayerPayload()
type CustomLayer struct {
    // This layer just has two bytes at the front
    SomeByte    byte
    AnotherByte byte
    restOfData  []byte
}
// 注冊自定義層類型,然后我們才可以使用它
// 第一個參數是ID. 自定義層使用大於2000的數字,它必須是唯一的
var CustomLayerType = gopacket.RegisterLayerType(
    2001,
    gopacket.LayerTypeMetadata{
        "CustomLayerType",
        gopacket.DecodeFunc(decodeCustomLayer),
    },
)

//自定義層實現LayerType
func (l CustomLayer) LayerType() gopacket.LayerType {
    return CustomLayerType
}

//自定義層實現LayerContents
func (l CustomLayer) LayerContents() []byte {
    return []byte{l.SomeByte, l.AnotherByte}
}

//自定義層實現LayerPayload
func (l CustomLayer) LayerPayload() []byte {
    return l.restOfData
}

//實現自定義的解碼函數
func decodeCustomLayer(data []byte, p gopacket.PacketBuilder) error {
    p.AddLayer(&CustomLayer{data[0], data[1], data[2:]})
    return p.NextDecoder(gopacket.LayerTypePayload)
}
func main() {
    rawBytes := []byte{0xF0, 0x0F, 65, 65, 66, 67, 68}
    packet := gopacket.NewPacket(
        rawBytes,
        CustomLayerType,
        gopacket.Default,
    )
    fmt.Println("Created packet out of raw bytes.")
    fmt.Println(packet)
    // Decode the packet as our custom layer
    customLayer := packet.Layer(CustomLayerType)
    if customLayer != nil {
        fmt.Println("Packet was successfully decoded with custom layer decoder.")
        customLayerContent, _ := customLayer.(*CustomLayer)
        // Now we can access the elements of the custom struct
        fmt.Println("Payload: ", customLayerContent.LayerPayload())
        fmt.Println("SomeByte element:", customLayerContent.SomeByte)
        fmt.Println("AnotherByte element:", customLayerContent.AnotherByte)
    }
}

結合上述代碼可知,實現自定義的層需要 3 步:

1、創建自定義層的結構體,並實現 Layer 接口中的函數 LayerType()、LayerContents()、LayerPayload()

2、按照解碼函數簽名來實現自定義解碼函數,名稱可自行命名。

解碼函數簽名如下:

type DecodeFunc func([] byte, PacketBuilder) error

3、使用 gopacket.RegisterLayerType 函數來注冊自定義層

3、5 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 即可,無需自己手動去解析繁雜的協議了。

 

 

golang gopacket網絡抓包和分析 - 翔雲123456 - 博客園 https://www.cnblogs.com/lanyangsh/p/9821106.html

Demo

代碼中,抓取與端口3306相關的數據,也就是mysql通信數據。

package main import( "fmt" "net" "strings" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcap" ) func main() { fmt.Println("packet start...") deviceName := "eth0" snapLen := int32(65535) port := uint16(3306) filter := getFilter(port) fmt.Printf("device:%v, snapLen:%v, port:%v\n", deviceName, snapLen, port) fmt.Println("filter:", filter) //打開網絡接口,抓取在線數據 handle, err := pcap.OpenLive(deviceName, snapLen, true, pcap.BlockForever) if err != nil { fmt.Printf("pcap open live failed: %v", err) return } // 設置過濾器 if err := handle.SetBPFFilter(filter); err != nil { fmt.Printf("set bpf filter failed: %v", err) return } defer handle.Close() // 抓包 packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) packetSource.NoCopy = true for packet := range packetSource.Packets() { if packet.NetworkLayer() == nil || packet.TransportLayer() == nil || packet.TransportLayer().LayerType() != layers.LayerTypeTCP { fmt.Println("unexpected packet") continue } fmt.Printf("packet:%v\n",packet) // tcp 層 tcp := packet.TransportLayer().(*layers.TCP) fmt.Printf("tcp:%v\n", tcp) // tcp payload,也即是tcp傳輸的數據 fmt.Printf("tcp payload:%v\n", tcp.Payload) } } //定義過濾器 func getFilter(port uint16) string { filter := fmt.Sprintf("tcp and ((src port %v) or (dst port %v))", port, port) return filter } 

抓取到的數據包

packet start... device:lo0, snapLen:65535, port:3306 filter: tcp and ((src port 3306) or (dst port 3306)) packet:PACKET: 75 bytes, wire length 75 cap length 75 @ 2018-10-20 11:13:00.106452 +0800 CST - Layer 1 (04 bytes) = Loopback {Contents=[2, 0, 0, 0] Payload=[..71..] Family=IPv4} - Layer 2 (20 bytes) = IPv4 {Contents=[..20..] Payload=[..51..] Version=4 IHL=5 TOS=0 Length=71 Id=0 Flags=DF FragOffset=0 TTL=64 Protocol=TCP Checksum=0 SrcIP=172.16.1.103 DstIP=172.16.1.103 Options=[] Padding=[]} - Layer 3 (32 bytes) = TCP {Contents=[..32..] Payload=[..19..] SrcPort=50351 DstPort=3306(mysql) Seq=110592366 Ack=3116315438 DataOffset=8 FIN=false SYN=false RST=false PSH=true ACK=true URG=false ECE=false CWR=false NS=false Window=12753 Checksum=23336 Urgent=0 Options=[TCPOption(NOP:), TCPOption(NOP:), TCPOption(Timestamps:1064185591/1064170040 0x3f6e2ef73f6df238)] Padding=[]} - Layer 4 (19 bytes) = Payload 19 byte(s) tcp:&{{[196 175 12 234 6 151 129 110 185 191 51 46 128 24 49 209 91 40 0 0 1 1 8 10 63 110 46 247 63 109 242 56] [15 0 0 0 3 115 104 111 119 32 100 97 116 97 98 97 115 101 115]} 50351 3306(mysql) 110592366 3116315438 8 false false false true true false false false false 12753 23336 0 [196 175] [12 234] [TCPOption(NOP:) TCPOption(NOP:) TCPOption(Timestamps:1064185591/1064170040 0x3f6e2ef73f6df238)] [] [{1 1 []} {1 1 []} {8 10 [63 110 46 247 63 109 242 56]} {0 0 []}] {<nil>}} tcp payload:[15 0 0 0 3 115 104 111 119 32 100 97 116 97 98 97 115 101 115] 。。。 

對抓取到的mysql數據感興趣的同學,可以參考Mysql 通信協議抓包分析

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM