gopacket使用

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: 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包中定义了两种打开设备的方式和两种打开文件的方式:

  • 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文件

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

  1. 打开一个文件句柄
  2. 通过NewWriter[^10]构造writer
  3. WriteFileHeader[^11]写入文件头
  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抓包结果:

  image

  ‍

  ‍

[^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
```
相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
JSON Kubernetes 数据格式
K8S client-go Patch example
我在本文中主要会介绍使用client-go的Patch方式,主要包括strategic merge patch和json-patch
|
3月前
|
安全 编译器 数据库连接
深入理解 go sync.Once
深入理解 go sync.Once
38 0
|
11月前
|
存储 缓存 网络协议
gopacket reassembly源码分析
gopacket reassembly源码分析
|
11月前
|
存储 缓存 网络协议
gopacket tcpassembly源码分析
gopacket tcpassembly源码分析
|
11月前
|
存储 网络协议 安全
gopacket API
gopacket API
go package 相关介绍
go package 相关介绍
57 0
|
存储 Unix Go
Go源码解析之format.go(1)
Go源码解析之format.go(1)
132 0
|
存储 机器学习/深度学习 Unix
Go源码解析之format.go(2)
Go源码解析之format.go(2)
115 0
|
存储 缓存 监控
Go源码解析之proc.go
Go源码解析之proc.go
255 0
|
存储 缓存 安全
Go源码解析之chan.go
Go源码解析之chan.go
249 0