網路流量抓包庫 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 介面
在上述程式碼中,我們呼叫函式時,傳入的 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 即可,無需自己手動去解析繁雜的協議了。
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- Wireshark網路抓包
- 使用tcpdump+wireshark抓包分析網路資料包TCP
- Debookee 8.1.2 網路資料抓包及分析工具
- Flutter 跨端網路抓包 (以Android 為例)Flutter跨端Android
- 幾種常見網路抓包方式介紹
- 抓包Http/Https/Other資料包(小迪網路安全筆記~HTTP筆記
- 計算機網路實驗二——利用wireshark抓包計算機網路
- [網路]從wireshark抓包看百度的https流程HTTP
- Linux伺服器端網路抓包和分析實戰Linux伺服器
- 如何優雅的在 Kubernetes Pod 內進行網路抓包
- 網路流量模型模型
- 網路抓包工具Wireshark
- Https抓包HTTP
- tcpdump抓包TCP
- kubernetes pod內抓包,telnet檢查網路連線的幾種方式
- 網路流量預測入門(三)之LSTM預測網路流量
- 用whistle和proxifier抓包除錯任意客戶端的網路請求除錯客戶端
- 推薦一款網路資料抓包分析工具:Debookee 7 Mac版Mac
- iOS防止抓包iOS
- mitmproxy grpc 抓包MITRPC
- iOS Wireshark抓包iOS
- iOS Charles抓包iOS
- BLE抓包分析
- 談談HTTPS安全認證,抓包與反抓包策略HTTP
- https 真的安全嗎,可以抓包嗎,如何防止抓包嗎HTTP
- tshark 抓包 mysql 協議包MySql協議
- 緊抓短視訊同城流量,抓住同城“流量密碼”密碼
- wireshark抓包學習
- iperf測試抓包
- IPSEC隧道抓包分析
- Python 爬蟲、抓包Python爬蟲
- 前端抓包神器Charles前端
- APP抓包神器dronyAPP
- Charles 手機抓包
- Android https 抓包指南AndroidHTTP
- 容器網路流量轉發分析
- Linux網路流量安全審計Linux
- 網路分流器-網路分流器TAP網路流量分析