>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://pkg.go.dev/github.com/google/gopacket#pkg-index)
视频:
[https://www.youtube.com/watch?v=APDnbmTKjgM](https://www.youtube.com/watch?v=APDnbmTKjgM)
文章:
[https://www.devdungeon.com/content/packet-capture-injection-and-analysis-gopacket](https://www.devdungeon.com/content/packet-capture-injection-and-analysis-gopacket)
## 查看版本
```go
func main() {
fmt.Println(pcap.Version())
}
```
win10测试:
`Npcap version 1.55, based on libpcap version 1.10.2-PRE-GIT`
## 网卡查看
有两个结构体
```go
// 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`就可以查看所有网卡信息
```go
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](https://static.golangjob.cn/220330/6e5b89848875eb8bc4d0b7ef29b55ee5.png)
## 读取数据包
大概步骤
- 获取网卡句柄
- new一个数据包的数据源
- 通过数据源下的方法获取数据包,一般都是`Packets`和`NextPacket`方法
```go
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)
}
}
```
## 分析数据
直接上代码
```go
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`语法
```go
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)
}
}
```
## 写入文件
把抓到的数据包写入文件保存
```go
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
}
}
}
```
## 解码器
- 快速解码
速度相对其他比较快,但是仅限于已经的数据包。会直接解码到预先分配的对象里面
相比于前面的分析数据那里,那里是每一层查看是否有此层的数据包。而这里解码器,是尝试将每一个数据包解码
```go
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, ð, &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)
}
}
}
}
}
```
- 基本解码器
```go
for p := range packetChan {
//第一个参数为数据
//第二个参数,如何解第一层
//第三个为解码模式,详细可跟进查看
pkt := gopacket.NewPacket(p.Data(),layers.LayerTypeEthernet,gopacket.Default)
}
```
## 自定义层
首先需要注册自定义层
在`gopacket/layers/layertypes.go`可以看到已经注册好了的层
```go
//第一个参数表示该层独有的ID,只要不和gopacket里面已经定义好了的冲突就行(一般大于200就没问题了)
//第二个参数,该层的名字和解码器,解码器要自己写
var LayerTypeJack = gopacket.RegisterLayerType(1000,gopacket.LayerTypeMetadata{Name: "Jack",Decoder: gopacket.DecodeFunc(decodeJackLayer)})
```
需要实现`Layer`接口
在`gopacket/base.go`里面可以看到,模仿它的写法,准没错
```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
}
```
需要定义这三个方法
```go
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
}
```
自定义解码器,同样模仿其他层的写法
```go
func decodeJackLayer(data []byte, p gopacket.PacketBuilder) error {
p.AddLayer(&JackLayer{data[:4], data[4:]})
return p.NextDecoder(layers.LayerTypeEthernet)
}
```
这时候就可以解码自己定义的层了
```go
p := gopacket.NewPacket(data, LayerTypeJack, gopacket.Lazy)
```
## 发送数据包
直接上代码
```go
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
}
}
```
有疑问加站长微信联系(非本文作者))