gopacket使用示例

gopacket使用

简介

gopacket是goole写的golang抓包库,针对libpcap和npcap进行封装,提供更方便的go接口,并且

安装

前提:

  • windows系统需要npcap,你可以到npcap官网进行下载。
  • linux系统需要libpcap,你可以使用以下命令安装,也可以到官网进行下载。
sudo apt-get install libpcap-dev

安装gopacket

go get github.com/google/gopacket

项目结构

示例

查找设备

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 flag: ", device.Flags)
		for _, address := range device.Addresses {
			fmt.Println("- IP address: ", address.IP)
			fmt.Println("- Subnet mask: ", address.Netmask)
			fmt.Println("- Broadaddr:  ", address.Broadaddr)
			fmt.Println("- P2P:  ", address.P2P)
		}
	}
}

pcap包是gopacket针对pcap库的封装,FindAllDevs可以列出本机所有的网络设备,其定义如下

func FindAllDevs() (ifs []Interface, err error)

下面列出Interface的定义,

type InterfaceAddress struct {
	IP        net.IP	// IP地址
	Netmask   net.IPMask 	// 子网掩码
	Broadaddr net.IP    	// 广播地址
	P2P       net.IP     	// p2p地址
}


type Interface struct {
	Name        string	// 设备名称
	Description string	// 设备描述
	Flags       uint32	// 设备标志
	Addresses   []InterfaceAddress
}

flag取值
● PCAP_IF_LOOPBACK: 设备是否为环回接口 
● PCAP_IF_UP: : 设备是否启动 
● PCAP_IF_WIRELESS: 设备是否为无线接口,包括IrDA以及基于无线电的网络,不仅仅是wifi 
● PCAP_IF_CONNECTION_STATUS: 设备是否已连接的位掩码, 

打开设备

在pcap包中定义了两种打开设备的方式和两种打开文件的方式:

  • OpenLive1

  • NewInactiveHandle2

  • OpenOffline3

  • OpenOfflineFile4

打开设备进行实时捕获

package main

import (
	"fmt"
	"log"
	"time"

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

var (
	device       string = " \\Device\\NPF_{C410B1B0-56DE-4CD5-BC7A-5A5ACAB7619F}\n"
	snapshot_len int32  = 65536
	promiscuous  bool   = true
	err          error
	timeout      time.Duration = -1 * 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()
	// 构造一个数据包源
	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
	// 读取包
	for packet := range packetSource.Packets() {
		// 打印包
		fmt.Println(packet)
	}
}

上面示例中,我们通过OpenLive1打开一个实时设备,然后将句柄传递给PacketSource5进行处理,下面我们来解释下gopacket.NewPacketSource:

NewPacketSource6只是构造一个PacketSource5对象,但是当我们调用Packet7函数时会启动一个协程来进行读取数据包,并将其写入到返回的管道中

打开pcap文件

package main

import (
	"fmt"
	"log"

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

var (
	pcapFile string = "test.pcap"
	handle   *pcap.Handle
	err      error
)

func main() {
	// 打开pcap文件
	handle, err = pcap.OpenOffline(pcapFile)
	if err != nil {
		log.Fatal(err)
	}
	defer handle.Close()
	// Loop through packets in file
	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
	for packet := range packetSource.Packets() {
		fmt.Println(packet)
	}
}

捕获包并写入到pcap文件

下面示例了捕获包,并且转储到文件中,其过程如下:

  1. 打开一个文件句柄
  2. 通过NewWriter8构造writer
  3. WriteFileHeader9写入文件头
  4. 关闭文件
package main

import (
	"fmt"
	"os"
	"time"

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

var (
	device      string = "\\Device\\NPF_{C410B1B0-56DE-4CD5-BC7A-5A5ACAB7619F}"
	snaplen     int32  = 65536
	promiscuous bool   = true
	err         error
	timeout     time.Duration = -1 * time.Second
	handle      *pcap.Handle
	packetCount int = 0
)

func main() {
	// 打开一个输出的文件句柄
	f, _ := os.Create("test.pcap")
	// 创建一个writer对象
	w := pcapgo.NewWriter(f)
	// 写入文件头,必须在调用前调用
	w.WriteFileHeader(uint32(snaplen), layers.LinkTypeEthernet)
	defer f.Close()
	// 打开一个实时捕获设备
	handle, err = pcap.OpenLive(device, snaplen, promiscuous, timeout)
	if err != nil {
		fmt.Printf("Error opening device %s: %v", device, err)
		os.Exit(1)
	}
	defer handle.Close()

	// 创建包数据源
	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
	// 读取包
	for packet := range packetSource.Packets() {
		// 打印包
		fmt.Println(packet)
		// 写入包
		w.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
		packetCount++

		// Only capture 100 and then stop
		if packetCount > 100 {
			break
		}
	}
}

解析各个层

package main

import (
	"fmt"
	"log"
	"strings"
	"time"

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

var (
	device      string = "\\Device\\NPF_{C410B1B0-56DE-4CD5-BC7A-5A5ACAB7619F}"
	snaplen     int32  = 65536
	promiscuous bool   = true
	err         error
	timeout     time.Duration = -1 * time.Second
	handle      *pcap.Handle
)

func printEthInfo(packet gopacket.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()
	}
}

func printNetworkInfo(packet gopacket.Packet) {
	// 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()
	}
}
func printTransportInfo(packet gopacket.Packet) {
	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()
	}
}
func printApplicationInfo(packet gopacket.Packet) {
	applicationLayer := packet.ApplicationLayer()
	if applicationLayer != nil {
		fmt.Println("Application layer/Payload found.")
		fmt.Printf("%s\n", applicationLayer.LayerType())
		// Search for a string inside the payload
		if strings.Contains(string(applicationLayer.Payload()), "HTTP") {
			fmt.Println("HTTP found!")
		}
	}
}

func printLayer(packet gopacket.Packet) {
	fmt.Println("All packet layers:")
	for _, layer := range packet.Layers() {
		fmt.Println("- ", layer.LayerType())
	}
}

func printErrInfo(packet gopacket.Packet) {
	// Check for errors
	if err := packet.ErrorLayer(); err != nil {
		fmt.Println("Error decoding some part of the packet:", err)
	}
}

func main() {
	// Open device
	handle, err = pcap.OpenLive(device, snaplen, promiscuous, timeout)
	if err != nil {
		log.Fatal(err)
	}
	defer handle.Close()
	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
	for packet := range packetSource.Packets() {
		fmt.Println("=========================")
		printEthInfo(packet)
		printNetworkInfo(packet)
		printTransportInfo(packet)
		printApplicationInfo(packet)
		printLayer(packet)
		printErrInfo(packet)
	}
}

pcapgo捕获包

pcapgo在linux系统上支持使用原生的接口进行捕获包,不需要依赖与libpcap

package main

import (
	"log"
	"os"

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

func main() {
	f, err := os.Create("/tmp/lo.pcap")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	pcapw := pcapgo.NewWriter(f)
	if err := pcapw.WriteFileHeader(1600, layers.LinkTypeEthernet); err != nil {
		log.Fatalf("WriteFileHeader: %v", err)
	}

	handle, err := pcapgo.NewEthernetHandle("lo")
	if err != nil {
		log.Fatalf("OpenEthernet: %v", err)
	}

	pkgsrc := gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)
	for packet := range pkgsrc.Packets() {
		if err := pcapw.WritePacket(packet.Metadata().CaptureInfo, packet.Data()); err != nil {
			log.Fatalf("pcap.WritePacket(): %v", err)
		}
	}
}

解析FTP

package main

import (
	"fmt"
	"io"
	"log"
	"time"

	"github.com/bin-work/go-example/npacket/08-ftpCapture/layer"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
)

var (
	device       string = "\\Device\\NPF_{48641DC5-6BD6-4752-9CA4-5C9706829705}"
	snapshot_len int32  = 65536
	promiscuous  bool   = true
	err          error
	timeout      time.Duration = -1 * time.Second
	handle       *pcap.Handle
	// Will reuse these for each packet
	ethLayer layers.Ethernet
	ipLayer  layers.IPv4
	tcpLayer layers.TCP
	ftpLayer layer.FTPLayer
)

func main() {
	// Open device
	handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)
	if err != nil {
		log.Fatal(err)
	}
	defer handle.Close()
	//var filter string = "tcp and port 10021"
	//err = handle.SetBPFFilter(filter)
	//if err != nil {
	//	log.Fatal(err)
	//}

	layers.RegisterTCPPortLayerType(layers.TCPPort(21), layer.LayerTypeFTP)
	dlp := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet)
	dlp.SetDecodingLayerContainer(gopacket.DecodingLayerSparse(nil))
	//var eth layers.Ethernet
	dlp.AddDecodingLayer(&ethLayer)
	dlp.AddDecodingLayer(&ipLayer)
	dlp.AddDecodingLayer(&tcpLayer)
	dlp.AddDecodingLayer(&ftpLayer)

	// ... 添加层并照常使用 DecodingLayerParser...
	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())

	for {
		packet, err := packetSource.NextPacket()
		if err == io.EOF {
			break
		} else if err != nil {
			log.Println("Error:", err)
			continue
		}
		foundLayerTypes := []gopacket.LayerType{}
		err = dlp.DecodeLayers(packet.Data(), &foundLayerTypes)
		if err != nil {
			fmt.Println("Trouble decoding layers: ", err)
		}
		for _, layerType := range foundLayerTypes {
			//if layerType == layers.LayerTypeIPv4 {
			//	fmt.Println("IPv4: ", ipLayer.SrcIP, "->", ipLayer.DstIP)
			//}
			//if layerType == layers.LayerTypeTCP {
			//	fmt.Println("TCP Port: ", tcpLayer.SrcPort, "->", tcpLayer.DstPort)
			//	fmt.Println("TCP SYN:", tcpLayer.SYN, " | ACK:", tcpLayer.ACK)
			//}
			if layerType == layer.LayerTypeFTP {
				fmt.Println(ftpLayer.Command)
			}

		}
	}

}

// Copyright 2018 Google, Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.

package layer

import (
	"bytes"
	"fmt"
	"io"
	"regexp"
	"strconv"
	"strings"

	"github.com/google/gopacket"
)

var LayerTypeFTP = gopacket.RegisterLayerType(147, gopacket.LayerTypeMetadata{Name: "FTP", Decoder: gopacket.DecodeFunc(DecodeFTP)})

// FTPCommand defines the different commands fo the FTP Protocol
type FTPCommand uint16

// Here are all the FTP commands
const (
	FTPCommandAbor FTPCommand = iota + 1 // ABOR Abort an active file transfer.
	FTPCommandAcct                       // ACCT Account information.
	FTPCommandAdat                       // ADAT RFC 2228 Authentication/Security Data
	FTPCommandAllo                       // ALLO Allocate sufficient disk space to receive a file.
	FTPCommandAppe                       // APPE Append (with create)
	FTPCommandAuth                       // AUTH RFC 2228 Authentication/Security Mechanism
	FTPCommandAvbl                       // AVBL Streamlined FTP Command Extensions Get the available space
	FTPCommandCcc                        // CCC RFC 2228 Clear Command Channel
	FTPCommandCdup                       // CDUP Change to Parent Directory.
	FTPCommandConf                       // CONF RFC 2228 Confidentiality Protection Command
	FTPCommandCsid                       // CSID Streamlined FTP Command Extensions Client / Server Identification
	FTPCommandCwd                        // CWD RFC 697 Change working directory.
	FTPCommandDele                       // DELE Delete file.
	FTPCommandDsiz                       // DSIZ Streamlined FTP Command Extensions Get the directory size
	FTPCommandEnc                        // ENC RFC 2228 Privacy Protected Channel
	FTPCommandEprt                       // EPRT RFC 2428 Specifies an extended address and port to which the server should connect.
	FTPCommandEpsv                       // EPSV RFC 2428 Enter extended passive mode.
	FTPCommandFeat                       // FEAT RFC 2389 Get the feature list implemented by the server.
	FTPCommandHelp                       // HELP Returns usage documentation on a command if specified, else a general help document is returned.
	FTPCommandHost                       // HOST RFC 7151 Identify desired virtual host on server, by name.
	FTPCommandLang                       // LANG RFC 2640 Language Negotiation
	FTPCommandList                       // LIST Returns information of a file or directory if specified, else information of the current working directory is returned.
	FTPCommandLprt                       // LPRT RFC 1639 Specifies a long address and port to which the server should connect.
	FTPCommandLpsv                       // LPSV RFC 1639 Enter long passive mode.
	FTPCommandMdtm                       // MDTM RFC 3659 Return the last-modified time of a specified file.
	FTPCommandMfct                       // MFCT The 'MFMT', 'MFCT', and 'MFF' Command Extensions for FTP Modify the creation time of a file.
	FTPCommandMff                        // MFF The 'MFMT', 'MFCT', and 'MFF' Command Extensions for FTP Modify fact (the last modification time, creation time, UNIX group/owner/mode of a file).
	FTPCommandMfmt                       // MFMT The 'MFMT', 'MFCT', and 'MFF' Command Extensions for FTP Modify the last modification time of a file.
	FTPCommandMic                        // MIC RFC 2228 Integrity Protected Command
	FTPCommandMkd                        // MKD Make directory.
	FTPCommandMlsd                       // MLSD RFC 3659 Lists the contents of a directory if a directory is named.
	FTPCommandMlst                       // MLST RFC 3659 Provides data about exactly the object named on its command line, and no others.
	FTPCommandMode                       // MODE Sets the transfer mode (Stream, Block, or Compressed).
	FTPCommandNlst                       // NLST Returns a list of file names in a specified directory.
	FTPCommandNoop                       // NOOP No operation (dummy packet; used mostly on keepalives).
	FTPCommandOpts                       // OPTS RFC 2389 Select options for a feature (for example OPTS UTF8 ON).
	FTPCommandPass                       // PASS Authentication password.
	FTPCommandPasv                       // PASV Enter passive mode.
	FTPCommandPbsz                       // PBSZ RFC 2228 Protection Buffer Size
	FTPCommandPort                       // PORT Specifies an address and port to which the server should connect.
	FTPCommandProt                       // PROT RFC 2228 Data Channel Protection Level.
	FTPCommandPwd                        // PWD Print working directory. Returns the current directory of the host.
	FTPCommandQuit                       // QUIT Disconnect.
	FTPCommandRein                       // REIN Re initializes the connection.
	FTPCommandRest                       // REST RFC 3659 Restart transfer from the specified point.
	FTPCommandRetr                       // RETR Retrieve a copy of the file
	FTPCommandRmd                        // RMD Remove a directory.
	FTPCommandRmda                       // RMDA Streamlined FTP Command Extensions Remove a directory tree
	FTPCommandRnfr                       // RNFR Rename from.
	FTPCommandRnto                       // RNTO Rename to.
	FTPCommandSite                       // SITE Sends site specific commands to remote server (like SITE IDLE 60 or SITE UMASK 002). Inspect SITE HELP output for complete list of supported commands.
	FTPCommandSize                       // SIZE RFC 3659 Return the size of a file.
	FTPCommandSmnt                       // SMNT Mount file structure.
	FTPCommandSpsv                       // SPSV FTP Extension Allowing IP Forwarding (NATs) Use single port passive mode (only one TCP port number for both control connections and passive-mode data connections)
	FTPCommandStat                       // STAT Returns the current status.
	FTPCommandStor                       // STOR Accept the data and to store the data as a file at the server site
	FTPCommandStou                       // STOU Store file uniquely.
	FTPCommandStru                       // STRU Set file transfer structure.
	FTPCommandSyst                       // SYST Return system type.
	FTPCommandThmb                       // THMB Streamlined FTP Command Extensions Get a thumbnail of a remote image file
	FTPCommandType                       // TYPE Sets the transfer mode (ASCII/Binary).
	FTPCommandUser                       // USER Authentication username.
	FTPCommandXcup                       // XCUP RFC 775 Change to the parent of the current working directory
	FTPCommandXmkd                       // XMKD RFC 775 Make a directory
	FTPCommandXpwd                       // XPWD RFC 775 Print the current working directory
	FTPCommandXrcp                       // XRCP RFC 743
	FTPCommandXrmd                       // XRMD RFC 775 Remove the directory
	FTPCommandXrsq                       // XRSQ RFC 743
	FTPCommandXsem                       // XSEM RFC 737 Send, mail if cannot
	FTPCommandXsen                       // XSEN RFC 737 Send to terminal
)

func (fc FTPCommand) String() string {
	switch fc {
	case FTPCommandAbor:
		return "ABOR"
	case FTPCommandAcct:
		return "ACCT"
	case FTPCommandAdat:
		return "ADAT"
	case FTPCommandAllo:
		return "ALLO"
	case FTPCommandAppe:
		return "APPE"
	case FTPCommandAuth:
		return "AUTH"
	case FTPCommandAvbl:
		return "AVBL"
	case FTPCommandCcc:
		return "CCC"
	case FTPCommandCdup:
		return "CDUP"
	case FTPCommandConf:
		return "CONF"
	case FTPCommandCsid:
		return "CSID"
	case FTPCommandCwd:
		return "CWD"
	case FTPCommandDele:
		return "DELE"
	case FTPCommandDsiz:
		return "DSIZ"
	case FTPCommandEnc:
		return "ENC"
	case FTPCommandEprt:
		return "EPRT"
	case FTPCommandEpsv:
		return "EPSV"
	case FTPCommandFeat:
		return "FEAT"
	case FTPCommandHelp:
		return "HELP"
	case FTPCommandHost:
		return "HOST"
	case FTPCommandLang:
		return "LANG"
	case FTPCommandList:
		return "LIST"
	case FTPCommandLprt:
		return "LPRT"
	case FTPCommandLpsv:
		return "LPSV"
	case FTPCommandMdtm:
		return "MDTM"
	case FTPCommandMfct:
		return "MFCT"
	case FTPCommandMff:
		return "MFF"
	case FTPCommandMfmt:
		return "MFMT"
	case FTPCommandMic:
		return "MIC"
	case FTPCommandMkd:
		return "MKD"
	case FTPCommandMlsd:
		return "MLSD"
	case FTPCommandMlst:
		return "MLST"
	case FTPCommandMode:
		return "MODE"
	case FTPCommandNlst:
		return "NLST"
	case FTPCommandNoop:
		return "NOOP"
	case FTPCommandOpts:
		return "OPTS"
	case FTPCommandPass:
		return "PASS"
	case FTPCommandPasv:
		return "PASV"
	case FTPCommandPbsz:
		return "PBSZ"
	case FTPCommandPort:
		return "PORT"
	case FTPCommandProt:
		return "PROT"
	case FTPCommandPwd:
		return "PWD"
	case FTPCommandQuit:
		return "QUIT"
	case FTPCommandRein:
		return "REIN"
	case FTPCommandRest:
		return "REST"
	case FTPCommandRetr:
		return "RETR"
	case FTPCommandRmd:
		return "RMD"
	case FTPCommandRmda:
		return "RMDA"
	case FTPCommandRnfr:
		return "RNFR"
	case FTPCommandRnto:
		return "RNTO"
	case FTPCommandSite:
		return "SITE"
	case FTPCommandSize:
		return "SIZE"
	case FTPCommandSmnt:
		return "SMNT"
	case FTPCommandSpsv:
		return "SPSV"
	case FTPCommandStat:
		return "STAT"
	case FTPCommandStor:
		return "STOR"
	case FTPCommandStou:
		return "STOU"
	case FTPCommandStru:
		return "STRU"
	case FTPCommandSyst:
		return "SYST"
	case FTPCommandThmb:
		return "THMB"
	case FTPCommandType:
		return "TYPE"
	case FTPCommandUser:
		return "USER"
	case FTPCommandXcup:
		return "XCUP"
	case FTPCommandXmkd:
		return "XMKD"
	case FTPCommandXpwd:
		return "XPWD"
	case FTPCommandXrcp:
		return "XRCP"
	case FTPCommandXrmd:
		return "XRMD"
	case FTPCommandXrsq:
		return "XRSQ"
	case FTPCommandXsem:
		return "XSEM"
	case FTPCommandXsen:
		return "XSEN"
	default:
		return "Unknown command"
	}
}

// GetFTPCommand returns the constant of a FTP command from its string
func GetFTPCommand(command string) (FTPCommand, error) {
	switch strings.ToUpper(command) {
	case "ABOR":
		return FTPCommandAbor, nil
	case "ACCT":
		return FTPCommandAcct, nil
	case "ADAT":
		return FTPCommandAdat, nil
	case "ALLO":
		return FTPCommandAllo, nil
	case "APPE":
		return FTPCommandAppe, nil
	case "AUTH":
		return FTPCommandAuth, nil
	case "AVBL":
		return FTPCommandAvbl, nil
	case "CCC":
		return FTPCommandCcc, nil
	case "CDUP":
		return FTPCommandCdup, nil
	case "CONF":
		return FTPCommandConf, nil
	case "CSID":
		return FTPCommandCsid, nil
	case "CWD":
		return FTPCommandCwd, nil
	case "DELE":
		return FTPCommandDele, nil
	case "DSIZ":
		return FTPCommandDsiz, nil
	case "ENC":
		return FTPCommandEnc, nil
	case "EPRT":
		return FTPCommandEprt, nil
	case "EPSV":
		return FTPCommandEpsv, nil
	case "FEAT":
		return FTPCommandFeat, nil
	case "HELP":
		return FTPCommandHelp, nil
	case "HOST":
		return FTPCommandHost, nil
	case "LANG":
		return FTPCommandLang, nil
	case "LIST":
		return FTPCommandList, nil
	case "LPRT":
		return FTPCommandLprt, nil
	case "LPSV":
		return FTPCommandLpsv, nil
	case "MDTM":
		return FTPCommandMdtm, nil
	case "MFCT":
		return FTPCommandMfct, nil
	case "MFF":
		return FTPCommandMff, nil
	case "MFMT":
		return FTPCommandMfmt, nil
	case "MIC":
		return FTPCommandMic, nil
	case "MKD":
		return FTPCommandMkd, nil
	case "MLSD":
		return FTPCommandMlsd, nil
	case "MLST":
		return FTPCommandMlst, nil
	case "MODE":
		return FTPCommandMode, nil
	case "NLST":
		return FTPCommandNlst, nil
	case "NOOP":
		return FTPCommandNoop, nil
	case "OPTS":
		return FTPCommandOpts, nil
	case "PASS":
		return FTPCommandPass, nil
	case "PASV":
		return FTPCommandPasv, nil
	case "PBSZ":
		return FTPCommandPbsz, nil
	case "PORT":
		return FTPCommandPort, nil
	case "PROT":
		return FTPCommandProt, nil
	case "PWD":
		return FTPCommandPwd, nil
	case "QUIT":
		return FTPCommandQuit, nil
	case "REIN":
		return FTPCommandRein, nil
	case "REST":
		return FTPCommandRest, nil
	case "RETR":
		return FTPCommandRetr, nil
	case "RMD":
		return FTPCommandRmd, nil
	case "RMDA":
		return FTPCommandRmda, nil
	case "RNFR":
		return FTPCommandRnfr, nil
	case "RNTO":
		return FTPCommandRnto, nil
	case "SITE":
		return FTPCommandSite, nil
	case "SIZE":
		return FTPCommandSize, nil
	case "SMNT":
		return FTPCommandSmnt, nil
	case "SPSV":
		return FTPCommandSpsv, nil
	case "STAT":
		return FTPCommandStat, nil
	case "STOR":
		return FTPCommandStor, nil
	case "STOU":
		return FTPCommandStou, nil
	case "STRU":
		return FTPCommandStru, nil
	case "SYST":
		return FTPCommandSyst, nil
	case "THMB":
		return FTPCommandThmb, nil
	case "TYPE":
		return FTPCommandType, nil
	case "USER":
		return FTPCommandUser, nil
	case "XCUP":
		return FTPCommandXcup, nil
	case "XMKD":
		return FTPCommandXmkd, nil
	case "XPWD":
		return FTPCommandXpwd, nil
	case "XRCP":
		return FTPCommandXrcp, nil
	case "XRMD":
		return FTPCommandXrmd, nil
	case "XRSQ":
		return FTPCommandXrsq, nil
	case "XSEM":
		return FTPCommandXsem, nil
	case "XSEN":
		return FTPCommandXsen, nil
	default:
		return 0, fmt.Errorf("Unknown FTP command: '%s'", command)
	}
}

// FTP object contains information about an FTP packet
type FTPLayer struct {
	//BaseLayer
	Contents   []byte
	Command    FTPCommand
	CommandArg string

	IsResponse     bool
	ResponseCode   int
	ResponseStatus string

	Delimiter string
}

func DecodeFTP(data []byte, p gopacket.PacketBuilder) error {
	f := &FTPLayer{}
	err := f.DecodeFromBytes(data, p)
	if err != nil {
		return err
	}
	p.AddLayer(f)
	p.SetApplicationLayer(f)
	return nil

}

// LayerType returns gopacket.LayerTypeFTP
func (f *FTPLayer) LayerType() gopacket.LayerType { return LayerTypeFTP }

func (f *FTPLayer) LayerContents() []byte { return f.Contents }

func (f *FTPLayer) LayerPayload() []byte {
	var r []byte
	return r
}

// Payload returns the base layer payload (nil)
func (f *FTPLayer) Payload() []byte { return nil }

// CanDecode returns gopacket.LayerTypeFTP
func (f *FTPLayer) CanDecode() gopacket.LayerClass { return LayerTypeFTP }

// NextLayerType returns gopacket.LayerTypeZero
func (f *FTPLayer) NextLayerType() gopacket.LayerType { return gopacket.LayerTypeZero }

// DecodeFromBytes decodes the slice into the FTP struct.
func (f *FTPLayer) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	var countLines int
	var line []byte
	var err error

	//f.BaseLayer = BaseLayer{Contents: data[:len(data)]}
	f.Contents = data[:len(data)]
	// Clean leading new line
	data = bytes.Trim(data, "\n")

	buffer := bytes.NewBuffer(data)

	var lastLine bool
	for {
		// Read next line
		line, err = buffer.ReadBytes(byte('\n'))
		if err != nil {
			if err == io.EOF {
				lastLine = true
			} else {
				return err
			}
		}

		// Trim the new line delimiters
		line = bytes.Trim(line, "\r\n")

		if countLines == 0 {
			err = f.parseFirstLine(line)
			if err != nil {
				return err
			}
		} else {
			err = f.parseFollowupLine(line)
			if err != nil {
				return err
			}
		}
		countLines++
		if lastLine {
			break
		}
	}

	return nil
}

func (f *FTPLayer) parseFirstLine(line []byte) error {
	var err error
	if len(line) < 3 {
		return fmt.Errorf("invalid first FTP line: '%s'", string(line))
	}

	re := regexp.MustCompile("^([0-9]{3})(.?)(.*)")
	if res := re.FindStringSubmatch(string(line)); res != nil {
		f.IsResponse = true
		f.ResponseCode, err = strconv.Atoi(res[1])
		if err != nil {
			return err
		}
		f.Delimiter = res[2]
		f.ResponseStatus = res[3]
	} else {
		splits := strings.SplitN(string(line), " ", 2)
		f.Command, err = GetFTPCommand(splits[0])
		if err != nil {
			return err
		}
		if len(splits) > 1 {
			f.Delimiter = " "
			f.CommandArg = splits[1]
		}
	}
	return nil
}

func (f *FTPLayer) parseFollowupLine(line []byte) error {
	if f.IsResponse {
		f.ResponseStatus += "\n" + string(line)
	} else {
		f.CommandArg += "\n" + string(line)
	}
	return nil
}

// SerializeTo writes the serialized form of this layer into the
// SerializationBuffer, implementing gopacket.SerializableLayer.
func (f *FTPLayer) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
	if f.IsResponse {
		bytes, err := b.PrependBytes(len(f.ResponseStatus) + len(f.Delimiter) + 5)
		if err != nil {
			return err
		}
		copy(bytes[0:3], fmt.Sprintf("%03d", f.ResponseCode))
		copy(bytes[3:], f.Delimiter+f.ResponseStatus+"\r\n")
	} else {
		bytes, err := b.PrependBytes(len(f.Command.String()) + len(f.Delimiter) + len(f.CommandArg) + 2)
		if err != nil {
			return err
		}
		copy(bytes[0:], f.Command.String()+f.Delimiter+f.CommandArg+"\r\n")
	}
	return nil
}

IP重组

下面我们编写一个程序来对IP分片进行重组

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/google/gopacket/ip4defrag"
	"github.com/google/gopacket/layers"

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

var (
	device       string = "\\Device\\NPF_{C410B1B0-56DE-4CD5-BC7A-5A5ACAB7619F}"
	snapshot_len int32  = 65536
	promiscuous  bool   = true
	err          error
	timeout      time.Duration = -1 * 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()
	// 构造一个数据包源
	source := gopacket.NewPacketSource(handle, handle.LinkType())
	defragger := ip4defrag.NewIPv4Defragmenter()

	// 读取包
	for packet := range source.Packets() {
		//fmt.Println("=========================")
		//fmt.Println(packet)
		// Process packet here
		ip4Layer := packet.Layer(layers.LayerTypeIPv4)
		if ip4Layer == nil {
			continue
		}
		ip4 := ip4Layer.(*layers.IPv4)
		l := ip4.Length
		newip4, err := defragger.DefragIPv4(ip4)
		if err != nil {
			log.Fatalln("Error while de-fragmenting", err)
		} else if newip4 == nil {
			fmt.Println("Fragment...\n")
			continue // packet fragment, we don't have whole packet yet.
		}
		if newip4.Length != l {
			//stats.ipdefrag++
			fmt.Printf("Decoding re-assembled packet: %s\n", newip4.NextLayerType())
			pb, ok := packet.(gopacket.PacketBuilder)
			if !ok {
				panic("Not a PacketBuilder")
			}
			nextDecoder := newip4.NextLayerType()
			nextDecoder.Decode(newip4.Payload, pb)
		}

		udpLayer := packet.Layer(layers.LayerTypeUDP)
		if udpLayer != nil {
			udp := udpLayer.(*layers.UDP)
			if udp.DstPort == 30000 {
				fmt.Println(udp.Length)
				fmt.Println(string(udp.LayerPayload()))
				fmt.Println("=========================")
			}

		}

	}

}

然后我们编写有一个UDP server和client来验证下

package main

import (
	"fmt"
	"net"
)

func main() {
	listen, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	defer listen.Close()
	for {
		var data [1024]byte
		_, _, err := listen.ReadFromUDP(data[:]) // 接收数据
		if err != nil {
			fmt.Println("read udp failed, err:", err)
			continue
		}
	}
}

package main

import (
	"fmt"
	"net"
)

func main() {
	socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
		IP:   net.IPv4(192, 168, 3, 100),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("连接服务端失败,err:", err)
		return
	}
	defer socket.Close()
	sendData := []byte("Hello server")
	for i := 0; i < 1590; i++ {
		sendData = append(sendData, 'a')
	}
	_, err = socket.Write(sendData) // 发送数据
	if err != nil {
		fmt.Println("发送数据失败,err:", err)
		return
	}
}

运行server,然后运行client,我们可以看到程序成功识别并将UDP重组

Fragment...

Decoding re-assembled packet: UDP
1610
Hello serveraaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
=========================

wireshark抓包结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a3H5GDSN-1659279442634)(assets/image-20220728183147-m6tfgux.png)]

推荐一个零声学院免费公开课程,个人觉得老师讲得不错,
分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习]


  1. OpenLive

    // device:指定网络要捕获的网络设备名称,可以是FindAllDevs返回的设备的Name
    // snaplen:每个数据包读取的最大长度
    // promisc:是否将网口设置为混杂模式,即是否接收目的地址不为本机的包
    // timeout:设置抓到包返回的超时。如果设置成30s,那么每30s才会刷新一次数据包;设置成负数,会立刻刷新数据包,即不做等待
    func OpenLive(device string, snaplen int32, promisc bool, timeout time.Duration) (handle *Handle, _ error)
    
    ↩︎ ↩︎
  2. NewInactiveHandle

    创建一个pcap句柄,但是需要调用Active进行激活,使用或者你可以更加方便的更改其参数

    func NewInactiveHandle(device string) (*InactiveHandle, error)
    
    ↩︎
  3. OpenOffline

    打开一个存储libpcap支持的文件

    func OpenOffline(file string) (handle *Handle, err error)
    
    ↩︎
  4. OpenOfflineFile

    与OpenOffline类似,需要我们手动打开文件后返回的文件句柄作为参数

    func OpenOfflineFile(file * os . File ) (handle * Handle , err error )
    
    ↩︎
  5. PacketSource

    PacketSource可以从 PacketDataSource 中读取数据包,对其进行解码并返回它们。其定义入下

    type PacketSource struct {
    	// 数据源
    	source  PacketDataSource
    	// 解码器
    	decoder Decoder
    	// 解码选项设置,可以在外部改变
    	DecodeOptions
    	// 数据通道
    	c chan Packet
    }
    
    // 数据源接口
    type PacketDataSource interface {
    	ReadPacketData() (data []byte, ci CaptureInfo, err error)
    }
    
    // 解码器接口
    type Decoder interface {
    	Decode([]byte, PacketBuilder) error
    }
    
    // 解码器选项
    type DecodeOptions struct {
    	// 是否延迟解码
    	//注意在并发时要小心因为*可能会改变数据包,导致并发函数调用互斥
    	Lazy bool
    	// 不拷贝到缓冲区
    	// 如果能够保证传递给NewPacket的片的底层自己不会被修改,那么这可能更快
    	// 如果可能被修改,这可能使数据无效
    	NoCopy bool
    	// 在包解码时跳过recover恢复
    	// 一般在发生panic时,将会被recover捕获,
    	//并在详细描述问题的包添加DecodeFailure层
    	SkipDecodeRecovery bool
    	// 可以在TCP解码器中实现应用层路由
    	// 默认false,因为重报应该在解码TCP负载后
    	DecodeStreamsAsDatagrams bool
    }
    
    
    

    我们可以通过NewPacketSource方法来构造一个PacketSouce源

    // source:源,需要实现ReadPacketData接口
    // decorder:解码器需要实现Decodeer接口
    func NewPacketSource(source PacketDataSource, decoder Decoder) *PacketSource {
    	return &PacketSource{
    		source:  source,
    		decoder: decoder,
    	}
    }
    

    目前有两种不同的方法可以通过 PacketSource 读取数据包:

    • Packets7
    • NextPacket10

    Packets

    内部启用一个协程调用NextPacket进行读Packet11,并将其写入到返回的管道中

    func (p *PacketSource) Packets() chan Packet {
    	if p.c == nil {
    		p.c = make(chan Packet, 1000)
    		go p.packetsToChannel()
    	}
    	return p.c
    }
    

    使用示例:

    for packet := range packetSource.Packets() {
      ...
    }
    

    NextPacket

    返回下一个数据包Packet11

    func (p *PacketSource) NextPacket() (Packet, error) {
    	// 读取元数据
    	data, ci, err := p.source.ReadPacketData()
    	if err != nil {
    		return nil, err
    	}
    	// 根据数据基础Packet对象
    	packet := NewPacket(data, p.decoder, p.DecodeOptions)
    	m := packet.Metadata()
    	m.CaptureInfo = ci
    	m.Truncated = m.Truncated || ci.CaptureLength < ci.Length
    	return packet, nil
    }
    

    使用示例:

    for {
      packet, err := packetSource.NextPacket()
      if err == io.EOF {
        break
      } else if err != nil {
        log.Println("Error:", err)
        continue
      }
      handlePacket(packet)  // Do something with each packet.
    }
    
    ↩︎ ↩︎
  6. // source:源,需要实现ReadPacketData接口
    // decorder:解码器需要实现Decodeer接口
    func NewPacketSource(source PacketDataSource, decoder Decoder) *PacketSource {
    	return &PacketSource{
    		source:  source,
    		decoder: decoder,
    	}
    }
    
    ↩︎
  7. Packets

    内部启用一个协程调用NextPacket进行读Packet11,并将其写入到返回的管道中

    func (p *PacketSource) Packets() chan Packet {
    	if p.c == nil {
    		p.c = make(chan Packet, 1000)
    		go p.packetsToChannel()
    	}
    	return p.c
    }
    

    使用示例:

    for packet := range packetSource.Packets() {
      ...
    }
    
    ↩︎ ↩︎
  8. NewWriter与NewWriterNanos

    返回一个新的 writer 对象,用于将数据包数据写入给定的 writer。如果这是一个新的空写入器(与追加相反),则必须在 WritePacket 之前调用 WriteFileHeader9。数据包时间戳以微秒精度写入。

    func NewWriter(w io . Writer ) * Writer
    func NewWriterNanos(w io . Writer ) * Writer
    

    示例:

    // 创建一个空文件
    f, _ := os.Create("/tmp/file.pcap")
    // 新建一个Writer对象
    w := pcapgo.NewWriter(f)
    // 新文件,必须写入头
    w.WriteFileHeader(65536, layers.LinkTypeEthernet)  
    // 写入包
    w.WritePacket(gopacket.CaptureInfo{...}, data1)
    f.Close()
    // 追加
    f2, _ := os.OpenFile("/tmp/file.pcap", os.O_APPEND, 0700)
    // 创建一个writer
    w2 := pcapgo.NewWriter(f2)
    // 
    w2.WritePacket(gopacket.CaptureInfo{...}, data2)
    f2.Close()
    
    ↩︎
  9. WriteFileHeader

    写入文件头通过链路层类型,每次新建文件时必须首先调用此函数

    func (w *Writer) WriteFileHeader(snaplen uint32, linktype layers.LinkType) error
    
    ↩︎ ↩︎
  10. NextPacket

    返回下一个数据包Packet11

    func (p *PacketSource) NextPacket() (Packet, error) {
    	// 读取元数据
    	data, ci, err := p.source.ReadPacketData()
    	if err != nil {
    		return nil, err
    	}
    	// 根据数据基础Packet对象
    	packet := NewPacket(data, p.decoder, p.DecodeOptions)
    	m := packet.Metadata()
    	m.CaptureInfo = ci
    	m.Truncated = m.Truncated || ci.CaptureLength < ci.Length
    	return packet, nil
    }
    

    使用示例:

    for {
      packet, err := packetSource.NextPacket()
      if err == io.EOF {
        break
      } else if err != nil {
        log.Println("Error:", err)
        continue
      }
      handlePacket(packet)  // Do something with each packet.
    }
    
    ↩︎
  11. Packet

    packet是gopacket的主要对象,其主要由Decoder创建,一个数据包由一组数据组成,数据在解码时被分解为多个层。

    type Packet interface {
    	// 输出可读性的字符串
    	String() string
    	// 使用LayerDump输出所有layer的16进制
    	Dump() string
    	// 返回所有layer
    	Layers() []Layer
    	// 返回指定层的数据,如果nil则返回第一层数据
    	Layer(LayerType) Layer
    	// 返回指
    	LayerClass(LayerClass) Layer
    
    	// 返回链路层
    	LinkLayer() LinkLayer
    	// 返回网络层
    	NetworkLayer() NetworkLayer
    	// 返回传输层
    	TransportLayer() TransportLayer
    	/// 返回应用层
    	ApplicationLayer() ApplicationLayer
    	// 返回错误层的信息
    	ErrorLayer() ErrorLayer
    
    	// 数据
    	Data() []byte
    	// 元数据
    	Metadata() *PacketMetadata
    }
    
    ↩︎ ↩︎ ↩︎ ↩︎
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值