Gopacket的学习

DG9Jww · 2022-03-30 14:33:40 · 3900 次点击 · 预计阅读时间 11 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2022-03-30 14:33:40 的文章,其中的信息可能已经有所发展或是发生改变。

Gopacket的学习和使用

linux下,需要安装libpcap-dev,在windows下,需要安装WinPcap,在mac下可直接使用

涉及到的包

    "github.com/google/gopacket"
    "github.com/google/gopacket/layers"
    "github.com/google/gopacket/pcap"
    "github.com/google/gopacket/pcapgo"

官方文档:

https://pkg.go.dev/github.com/google/gopacket#pkg-index

视频:

https://www.youtube.com/watch?v=APDnbmTKjgM

文章:

https://www.devdungeon.com/content/packet-capture-injection-and-analysis-gopacket

查看版本

func main() {
    fmt.Println(pcap.Version())
}

win10测试:

Npcap version 1.55, based on libpcap version 1.10.2-PRE-GIT

网卡查看

有两个结构体

// 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
}

根据字段名就可以知道什么意思了

通过pcap.FindAllDevs就可以查看所有网卡信息

func main() {
    devices,_ := pcap.FindAllDevs()
    for _,device := range devices{
        fmt.Println("\nname:",device.Name)
        fmt.Println("describe:",device.Description)
        for _,address := range device.Addresses{
            fmt.Println("IP:",address.IP)
            fmt.Println("mask:",address.Netmask)
        }
    }
}

image-20220328215608455.png

读取数据包

大概步骤

  • 获取网卡句柄
  • new一个数据包的数据源
  • 通过数据源下的方法获取数据包,一般都是PacketsNextPacket方法
package main

import (
    "fmt"
    "log"
    "time"

    "github.com/google/gopacket"
    "github.com/google/gopacket/pcap"
)

var (
    device   string        = `\Device\NPF_{29BAB1CA-421D-466E-B3AD-F756F66E6DC6}` //网卡名
    snapshot int32         = 65535                     //读取一个数据包的最大值,一般设置成这65535即可
    promisc  bool          = true                       //是否开启混杂模式
    timeout  time.Duration = time.Second * -1            //抓取数据包的超时时间,负数表示立即刷新,一般都设为负数
)

func main() {
    //获取一个网卡句柄
    handle, err := pcap.OpenLive(device, snapshot, promisc, timeout)
    if err != nil {
        log.Fatal(err)
    }
    //别忘了释放句柄
    defer handle.Close()

    //NewPacketSource新建一个数据包数据源
    //捕捉一个数据包
    packetSource := gopacket.NewPacketSource(handle,handle.LinkType())
    packet,err := packetSource.NextPacket() //返回一个数据包
    if err != nil{
        log.Fatal(err)
    }
    fmt.Println(packet)

    //一直捕捉数据包
    p := packetSource.Packets()    //返回一个channel
    for data := range p{
        fmt.Println(data)
    }
}

分析数据

直接上代码

package main

import (
    "fmt"
    "log"
    "time"

    "github.com/google/gopacket"
    "github.com/google/gopacket/layers"
    "github.com/google/gopacket/pcap"
)

var (
    device   string        = `\Device\NPF_{29BAB1CA-421D-466E-B3AD-F756F66E6DC6}` //网卡名
    snapshot int32         = 65535                  //读取一个数据包的最大值,一般设置成这65535即可
    promisc  bool          = true                    //是否开启混杂模式
    timeout  time.Duration = time.Second * -1         //抓取数据包的超时时间,负数表示立即刷新,一般都设为负数
)

func main() {
    handle, err := pcap.OpenLive(device, snapshot, promisc, timeout)
    if err != nil {
        log.Fatal(err)
    }
    defer handle.Close()

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

    //分析每一个网络报文
    for packet := range packetChan{
        fmt.Println("---------Layer Type------------")
        for _,layer := range packet.Layers(){
            fmt.Println(layer.LayerType())    //打印这个包里面每一层的类型
        }

        //IP协议
        ipLayer := packet.Layer(layers.LayerTypeIPv4)    //返回layer接口
        if ipLayer != nil{
            fmt.Println("-----------IP Layer-----------")
            ip,_:= ipLayer.(*layers.IPv4)                //通过类型断言,得到对应协议的结构体
            fmt.Println("protocol:",ip.Protocol)
            fmt.Println("dstIP:",ip.DstIP)
            fmt.Println("srcIP:",ip.SrcIP)
        }else{
            fmt.Println("No IPv4 layer")
        }

        //TCP协议
        tcpLayer := packet.Layer(layers.LayerTypeTCP)
        if tcpLayer != nil{
            fmt.Println("-----------TCP Layer-----------")
            tcp,_ := tcpLayer.(*layers.TCP)
            fmt.Println("ack:",tcp.Ack)
            fmt.Println("checksum:",tcp.Checksum)
        }

        //应用层
        appLayer := packet.ApplicationLayer()
        if appLayer != nil{
            fmt.Println(appLayer.Payload())
        }       
        if strings.Contains(string(appLayer.Payload()), “HTTP”) {
            fmt.Println(“HTTP found!)
        }

        fmt.Printf("\n\n")
    }
}

过滤

采用BPF语法

func main() {
    handle, err := pcap.OpenLive(device, snapshot, promisc, timeout)
    if err != nil {
        log.Fatal(err)
    }
    defer handle.Close()

    //过滤规则
    handle.SetBPFFilter("port 80 and tcp")
    //处理数据包
    packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
    for packet := range packetSource.Packets() {
        fmt.Println(packet)
    }
}

写入文件

把抓到的数据包写入文件保存

func main() {
    var packetCount int8
    file, _ := os.Create("nihao2.pcap")
    defer file.Close()

    packetWriter := pcapgo.NewWriter(file)

    //这个writeheader会自动给每个数据报文前添加点东西,对应wireshark里面就是frame
    //这些信息不属于网络传输过程中传输协议数据或者传输内容数据的一部分,属于的是诸如winpcap 以及libpcap 这种用于抓取网络数据的库
    //可以理解为pcap文件的固定头部
    //写入数据时,这个函数一定要使用一次,一般都是layers.LinkTypeEthernet
    //第二个参数基本都是写这个
    packetWriter.WriteFileHeader(65535, layers.LinkTypeEthernet)

    handle,err := pcap.OpenLive(device,snapshot,promisc,timeout)
    if err != nil{
        log.Fatal(err)
    }
    defer handle.Close()

    packetSource := gopacket.NewPacketSource(handle,handle.LinkType())
    for packet := range packetSource.Packets(){
        log.Println(packet)

        //正式把数据写入文件,这里一般也不用变,就固定这样写即可
        packetWriter.WritePacket(packet.Metadata().CaptureInfo,packet.Data())
        packetCount++

        //抓10个包就停止
        if packetCount > 10{
            break
        }
    }
}

解码器

  • 快速解码

速度相对其他比较快,但是仅限于已经的数据包。会直接解码到预先分配的对象里面

相比于前面的分析数据那里,那里是每一层查看是否有此层的数据包。而这里解码器,是尝试将每一个数据包解码

func main() {

    handle, err := pcap.OpenLive(device, snapshot, promisc, timeout)
    if err != nil {
        fmt.Println(err)
        return
    }

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

    for p := range packetChan {
        //预先定义
        var eth layers.Ethernet
        var ip4 layers.IPv4
        var ip6 layers.IPv6
        var tcp layers.TCP
        parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &eth, &ip4, &ip6, &tcp)
        decodelayers := []gopacket.LayerType{}

        if err := parser.DecodeLayers(p.Data(), &decodelayers); err != nil {
            fmt.Fprintf(os.Stderr, "Could not decode layers: %v\n", err)
            continue
        } else {
            for _, layerType := range decodelayers {
                switch layerType {
                case layers.LayerTypeIPv6:
                    fmt.Println("    IP6 ", ip6.SrcIP, ip6.DstIP)
                case layers.LayerTypeIPv4:
                    fmt.Println("    IP4 ", ip4.SrcIP, ip4.DstIP)
                }
            }

        }

    }

}
  • 基本解码器
    for p := range packetChan {
        //第一个参数为数据
        //第二个参数,如何解第一层
        //第三个为解码模式,详细可跟进查看
        pkt := gopacket.NewPacket(p.Data(),layers.LayerTypeEthernet,gopacket.Default)

    }

自定义层

首先需要注册自定义层

gopacket/layers/layertypes.go可以看到已经注册好了的层

//第一个参数表示该层独有的ID,只要不和gopacket里面已经定义好了的冲突就行(一般大于200就没问题了)
//第二个参数,该层的名字和解码器,解码器要自己写
var LayerTypeJack = gopacket.RegisterLayerType(1000,gopacket.LayerTypeMetadata{Name: "Jack",Decoder: gopacket.DecodeFunc(decodeJackLayer)})

需要实现Layer接口

gopacket/base.go里面可以看到,模仿它的写法,准没错

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
}

需要定义这三个方法

type JackLayer struct{
    Header []byte
    Payload []byte
}

//实现接口
func (j JackLayer) LayerType() gopacket.LayerType{
    return LayerTypeJack
}

func (j JackLayer) LayerContents() []byte {
    return j.Header
}

func (j JackLayer) LayerPayload() []byte {
    return j.Payload
}

自定义解码器,同样模仿其他层的写法

func decodeJackLayer(data []byte, p gopacket.PacketBuilder) error {
    p.AddLayer(&JackLayer{data[:4], data[4:]})
    return p.NextDecoder(layers.LayerTypeEthernet)
}

这时候就可以解码自己定义的层了

p := gopacket.NewPacket(data, LayerTypeJack, gopacket.Lazy)

发送数据包

直接上代码

func main() {

    handle, err := pcap.OpenLive(device, snapshot, promisc, timeout)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer handle.Close()

    //创建缓冲区
    buf := gopacket.NewSerializeBuffer()

    //后面需要的参数,这个一般不用变
    option := gopacket.SerializeOptions{}

    //创建层
    ethernetLayer := &layers.Ethernet{
        SrcMAC:       net.HardwareAddr{0xFF, 0xAA, 0xFA, 0xAA, 0xFF, 0xAA},
        DstMAC:       net.HardwareAddr{0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD},
        EthernetType: layers.EthernetTypeIPv4,
    }

    ipLayer := &layers.IPv4{
        SrcIP:   net.IP{127,0,0,1},
        DstIP: net.IP{8,8,8,8,},
        Version: 4,
    }

    tcpLayer := &layers.TCP{
        SrcPort: layers.TCPPort(11111),
        DstPort: layers.TCPPort(80),
    }

    payload := []byte{1, 2, 3, 4, 5}

    //先清空缓冲区,再把数据写入缓存区
    gopacket.SerializeLayers(buf, option,
        ethernetLayer,
        ipLayer,
        tcpLayer,
        gopacket.Payload(payload),
    )

    //发送数据
    err = handle.WritePacketData(buf.Bytes())
    if err != nil{
        fmt.Println(err)
        return
    }
}

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3900 次点击  ∙  1 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传