1. 程式人生 > >Go語言用GoPacket抓包分析

Go語言用GoPacket抓包分析

前言

最近有了一個抓取網路資料包來分析的需求,最近在使用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的一個介面實現
//因此需要強制轉換,將父介面,轉換為子介面實現