Go語言用GoPacket抓包分析
阿新 • • 發佈:2019-01-06
前言
最近有了一個抓取網路資料包來分析的需求,最近在使用go語言,於是乎,決定是用go語言來進行抓包分析。使用的Google的gopacket包來進行。基於的是libcap包。
我並不喜歡GoDoc的行文風格,對於demo可以看著這裡的文章。(自備梯子)
離線pcap包解析
package main
import (
"fmt"
"log"
"reflect"
"strings"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
)
func main() {
path := "/Users/liruopeng/Downloads/test2.pcap"
handler, err := pcap.OpenOffline(path)
if err != nil {
log.Fatal(err)
}
defer handler.Close()
packetSource := gopacket.NewPacketSource(handler, handler.LinkType())
i := 0
for packet := range packetSource.Packets() {
// if i < 1 {
// fmt.Println(packet)
// // break
// }
i++
if i == 2915 {
printPacketInfo(packet)
}
}
fmt.Println(i)
}
func printPacketInfo(packet gopacket.Packet) {
// Let's see if the packet is an ethernet packet
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)
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
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
fmt.Println("TCP layer detected.")
tcp, some := tcpLayer.(*layers.TCP)
// tcp := tcpLayer
fmt.Println(reflect.TypeOf(tcp), reflect.TypeOf(tcpLayer))
// fmt.Println(tcpLayer)
fmt.Println("some=", some)
// 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", tcpLayer.SrcPort, tcpLayer.DstPort)
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())
}
// When iterating through packet.Layers() above,
// if it lists Payload layer then that is the same as
// this applicationLayer. applicationLayer contains the payload
applicationLayer := packet.ApplicationLayer()
if applicationLayer != nil {
fmt.Println("Application layer/Payload found.")
fmt.Printf("%s\n", applicationLayer.Payload())
// Search for a string inside the payload
if strings.Contains(string(applicationLayer.Payload()), "HTTP") {
fmt.Println("HTTP found!")
}
}
// Check for errors
if err := packet.ErrorLayer(); err != nil {
fmt.Println("Error decoding some part of the packet:", err)
}
}
但是在這部分中,我發現他的程式碼呼叫了Cgo,看的比較迷糊,我們來按照這個demo的順序,分析一下原始碼。
離線資料包分析原始碼分析
handler, err := pcap.OpenOffline(path)
# 我們來看這一段程式碼
// OpenOffline opens a file and returns its contents as a *Handle.
func OpenOffline(file string) (handle *Handle, err error) {
// buf 其實是一個error標誌
buf := (*C.char)(C.calloc(errorBufferSize, 1))
defer C.free(unsafe.Pointer(buf))
cf := C.CString(file)
defer C.free(unsafe.Pointer(cf))
//
cptr := C.pcap_open_offline(cf, buf)
if cptr == nil {
return nil, errors.New(C.GoString(buf))
}
return &Handle{cptr: cptr}, nil
}
對於這個 pcap_open_offline 這個 c方法我們來看到 pcap.h的說明
pcap_t *pcap_open_offline(const char *fname, char *errbuf);
return a pcap_t * on success and NULL on failure. If NULL is returned, errbuf is filled in with an appropriate error message. errbuf is assumed to be able to hold at least PCAP_ERRBUF_SIZE
也就是 返回一個 pcap_t
那麼pcap_t到底是什麼?
有人是這麼解釋的
A pcap_t is a handle used to read packets from a network interface, or from a pcap (or, in newer versions of libpcap, pcap-ng) file containing packets. libpcap/WinPcap routines such as pcap_open_live() and pcap_open_offline() return a pcap_t from which you can read packets with routines such as pcap_loop(), pcap_dispatch(), pcap_next(), and pcap_next_ex(); in newer versions of libpcap (but not yet in WinPcap), you can also get a pcap_t by calling pcap_create(), set various options on that pcap_t, and then "activate" it, making it available to capture on, with pcap_activate().
pcap_t是用於從網路介面或包含資料包的pcap(或更新版本的libpcap,pcap-ng)檔案讀取資料包的控制代碼。 libpcap / WinPcap例程,如pcap_open_live()和pcap_open_offline()返回一個pcap_t,您可以從中讀取pcap_loop(),pcap_dispatch(),pcap_next()和pcap_next_ex()等例程的資料包。 在較新版本的libpcap(但尚未在WinPcap中),您還可以通過呼叫pcap_create()獲取pcap_t,在pcap_t上設定各種選項,然後“啟用”它,使其可以通過pcap_activate()進行捕獲,。
於是我們有了一個handle
type Handle struct {
// cptr is the handle for the actual pcap C object.
cptr *C.pcap_t
timeout time.Duration
device string
deviceIndex int
mu sync.Mutex
closeMu sync.Mutex
// stop is set to a non-zero value by Handle.Close to signal to
// getNextBufPtrLocked to stop trying to read packets
stop uint64
// Since pointers to these objects are passed into a C function, if
// they're declared locally then the Go compiler thinks they may have
// escaped into C-land, so it allocates them on the heap. This causes a
// huge memory hit, so to handle that we store them here instead.
pkthdr *C.struct_pcap_pkthdr
bufptr *C.u_char
}
新建packetSource
packetSource := gopacket.NewPacketSource(handler, handler.LinkType())
// LinkType is an enumeration of link types, and acts as a decoder for any
// link type it supports.
// NewPacketSource creates a packet data source.
func NewPacketSource(source PacketDataSource, decoder Decoder) *PacketSource {
return &PacketSource{
source: source,
decoder: decoder,
}
}
// LinkType returns pcap_datalink, as a layers.LinkType.
func (p *Handle) LinkType() layers.LinkType {
//型別強制轉換
return layers.LinkType(C.pcap_datalink(p.cptr))
}
pcap_datalink() returns the link-layer header type for the live capture or ``savefile'' specified by p.
這裡的decoder是 一個interface。實現在下面
func (a LinkType) Decode(data []byte, p gopacket.PacketBuilder) error {
return LinkTypeMetadata[a].DecodeWith.Decode(data, p)
}
// 這裡用到了 function types的知識點
// A function type denotes the set of all functions with the same parameter and result types.
我們的decode實際上是
LinkTypeMetadata[LinkTypeEthernet] = EnumMetadata{DecodeWith: gopacket.DecodeFunc(decodeEthernet), Name: "Ethernet"}
//也就是下面這個方法
func decodeEthernet(data []byte, p gopacket.PacketBuilder) error {
eth := &Ethernet{}
err := eth.DecodeFromBytes(data, p)
if err != nil {
return err
}
//新增layer
p.AddLayer(eth)
p.SetLinkLayer(eth)
//一層一層的解析
return p.NextDecoder(eth.EthernetType)
}
//functypes 的 特性demo如下:
// Greeting function types
//解釋請看這裡
讀取資料包
func (p *PacketSource) Packets() chan Packet {
if p.c == nil {
p.c = make(chan Packet, 1000)
go p.packetsToChannel()
}
return p.c
}
新建一個協程將包都讀取到Channel中來。
func (p *PacketSource) packetsToChannel() {
defer close(p.c)
for {
packet, err := p.NextPacket()
if err == io.EOF {
return
} else if err == nil {
p.c <- packet
}
}
}
p.NextPacket()方法
// NextPacket returns the next decoded packet from the PacketSource. On error,
// it returns a nil packet and a non-nil error.
func (p *PacketSource) NextPacket() (Packet, error) {
data, ci, err := p.source.ReadPacketData()
if err != nil {
return nil, err
}
packet := NewPacket(data, p.decoder, p.DecodeOptions)
m := packet.Metadata()
m.CaptureInfo = ci
m.Truncated = m.Truncated || ci.CaptureLength < ci.Length
return packet, nil
}
//呼叫handle的 ReadPcaketData()方法
// ReadPacketData returns the next packet read from the pcap handle, along with an error
// code associated with that packet. If the packet is read successfully, the
// returned error is nil.
func (p *Handle) ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) {
p.mu.Lock()
err = p.getNextBufPtrLocked(&ci)
if err == nil {
//這裡將資料進行格式轉換成 GoBytes
data = C.GoBytes(unsafe.Pointer(p.bufptr), C.int(ci.CaptureLength))
}
p.mu.Unlock()
if err == NextErrorTimeoutExpired {
runtime.Gosched()
}
return
}
// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte
func (p *Handle) getNextBufPtrLocked(ci *gopacket.CaptureInfo) error {
if p.cptr == nil {
return io.EOF
}
for atomic.LoadUint64(&p.stop) == 0 {
//這裡讀資料
// try to read a packet if one is immediately available
result := NextError(C.pcap_next_ex(p.cptr, &p.pkthdr, &p.bufptr))
switch result {
case NextErrorOk:
// 這裡將資料資訊填充進來
// got a packet, set capture info and return
sec := int64(p.pkthdr.ts.tv_sec)
// convert micros to nanos
nanos := int64(p.pkthdr.ts.tv_usec) * 1000
ci.Timestamp = time.Unix(sec, nanos)
ci.CaptureLength = int(p.pkthdr.caplen)
ci.Length = int(p.pkthdr.len)
ci.InterfaceIndex = p.deviceIndex
return nil
case NextErrorNoMorePackets:
// no more packets, return EOF rather than libpcap-specific error
return io.EOF
case NextErrorTimeoutExpired:
// Negative timeout means to loop forever, instead of actually returning
// the timeout error.
if p.timeout < 0 {
// must have had a timeout... wait before trying again
p.waitForPacket()
continue
}
default:
return result
}
}
// stop must be set
return io.EOF
}
返回一個 pcaket
// NewPacket creates a new Packet object from a set of bytes. The
// firstLayerDecoder tells it how to interpret the first layer from the bytes,
// future layers will be generated from that first layer automatically.
func NewPacket(data []byte, firstLayerDecoder Decoder, options DecodeOptions) Packet {
if !options.NoCopy {
dataCopy := make([]byte, len(data))
copy(dataCopy, data)
data = dataCopy
}
if options.Lazy {
p := &lazyPacket{
packet: packet{data: data, decodeOptions: options},
next: firstLayerDecoder,
}
p.layers = p.initialLayers[:0]
// Crazy craziness:
// If the following return statemet is REMOVED, and Lazy is FALSE, then
// eager packet processing becomes 17% FASTER. No, there is no logical
// explanation for this. However, it's such a hacky micro-optimization that
// we really can't rely on it. It appears to have to do with the size the
// compiler guesses for this function's stack space, since one symptom is
// that with the return statement in place, we more than double calls to
// runtime.morestack/runtime.lessstack. We'll hope the compiler gets better
// over time and we get this optimization for free. Until then, we'll have
// to live with slower packet processing.
return p
}
p := &eagerPacket{
packet: packet{data: data, decodeOptions: options},
}
p.layers = p.initialLayers[:0]
p.initialDecode(firstLayerDecoder)
return p
}
注意點
這裡我們要注重注意一個地方。
tcp, _ := tcpLayer.(*layers.Ethernet)
//這裡進行了一次型別推斷
//我當時一直不明白為什麼這裡要進行型別推斷
//後來我翻看原始碼發現了。
//packet.layers是
Layers() []Layer
//Layer,是一個父interface
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
}
//而Ethernet
// Ethernet is the layer for Ethernet frame headers.
type Ethernet struct {
BaseLayer
SrcMAC, DstMAC net.HardwareAddr
EthernetType EthernetType
// Length is only set if a length field exists within this header. Ethernet
// headers follow two different standards, one that uses an EthernetType, the
// other which defines a length the follows with a LLC header (802.3). If the
// former is the case, we set EthernetType and Length stays 0. In the latter
// case, we set Length and EthernetType = EthernetTypeLLC.
Length uint16
}
// LayerType returns LayerTypeEthernet
func (e *Ethernet) LayerType() gopacket.LayerType { return LayerTypeEthernet }
//Ethernet是 Layer的一個介面實現
//因此需要強制轉換,將父介面,轉換為子介面實現