简介
gopacket是goole写的golang抓包库,针对libpcap和npcap进行封装,提供更方便的go接口,并且
安装
前提:
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包中定义了两种打开设备的方式和两种打开文件的方式:
- OpenLive[^1]
- NewInactiveHandle[^2]
OpenOffline[^3]
OpenOfflineFile[^4]
打开设备进行实时捕获
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)
}
}
上面示例中,我们通过OpenLive[^1]打开一个实时设备,然后将句柄传递给PacketSource[^5]进行处理,下面我们来解释下gopacket.NewPacketSource:
NewPacketSource[^9]只是构造一个PacketSource[^5]对象,但是当我们调用Packet[^6]函数时会启动一个协程来进行读取数据包,并将其写入到返回的管道中
打开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文件
下面示例了捕获包,并且转储到文件中,其过程如下:
- 打开一个文件句柄
- 通过NewWriter[^10]构造writer
- WriteFileHeader[^11]写入文件头
- 关闭文件
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(ðLayer)
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抓包结果:
[^1]: ### OpenLive
```go
// 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进行激活,使用或者你可以更加方便的更改其参数
```go
func NewInactiveHandle(device string) (*InactiveHandle, error)
```
[^3]: ### OpenOffline
打开一个存储libpcap支持的文件
```go
func OpenOffline(file string) (handle *Handle, err error)
```
[^4]: ### OpenOfflineFile
与OpenOffline类似,需要我们手动打开文件后返回的文件句柄作为参数
```go
func OpenOfflineFile(file * os . File ) (handle * Handle , err error )
```
[^5]: ## PacketSource
PacketSource可以从 PacketDataSource 中读取数据包,对其进行解码并返回它们。其定义入下
```go
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源
```go
// source:源,需要实现ReadPacketData接口
// decorder:解码器需要实现Decodeer接口
func NewPacketSource(source PacketDataSource, decoder Decoder) *PacketSource {
return &PacketSource{
source: source,
decoder: decoder,
}
}
```
目前有两种不同的方法可以通过 PacketSource 读取数据包:
* Packets[^6]
* NextPacket[^8]
### Packets
内部启用一个协程调用NextPacket进行读Packet[^7],并将其写入到返回的管道中
```go
func (p *PacketSource) Packets() chan Packet {
if p.c == nil {
p.c = make(chan Packet, 1000)
go p.packetsToChannel()
}
return p.c
}
```
使用示例:
```go
for packet := range packetSource.Packets() {
...
}
```
### NextPacket
返回下一个数据包Packet[^7]
```go
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
}
```
使用示例:
```go
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]: ### Packets
内部启用一个协程调用NextPacket进行读Packet[^7],并将其写入到返回的管道中
```go
func (p *PacketSource) Packets() chan Packet {
if p.c == nil {
p.c = make(chan Packet, 1000)
go p.packetsToChannel()
}
return p.c
}
```
使用示例:
```go
for packet := range packetSource.Packets() {
...
}
```
[^7]: ## Packet
packet是gopacket的主要对象,其主要由Decoder创建,一个数据包由一组数据组成,数据在解码时被分解为多个层。
```go
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
}
```
[^8]: ### NextPacket
返回下一个数据包Packet[^7]
```go
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
}
```
使用示例:
```go
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.
}
```
[^9]:
```go
// source:源,需要实现ReadPacketData接口
// decorder:解码器需要实现Decodeer接口
func NewPacketSource(source PacketDataSource, decoder Decoder) *PacketSource {
return &PacketSource{
source: source,
decoder: decoder,
}
}
```
[^10]: ### NewWriter与NewWriterNanos
返回一个新的 writer 对象,用于将数据包数据写入给定的 writer。如果这是一个新的空写入器(与追加相反),则必须在 WritePacket 之前调用 WriteFileHeader[^11]。数据包时间戳以微秒精度写入。
```go
func NewWriter(w io . Writer ) * Writer
func NewWriterNanos(w io . Writer ) * Writer
```
示例:
```go
// 创建一个空文件
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()
```
[^11]: ### WriteFileHeader
写入文件头通过链路层类型,每次新建文件时必须首先调用此函数
```go
func (w *Writer) WriteFileHeader(snaplen uint32, linktype layers.LinkType) error
```