Initial commit
This commit is contained in:
commit
51e3b5f01f
15
LICENSE
Normal file
15
LICENSE
Normal file
@ -0,0 +1,15 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2023, acidvegas <acid.vegas@acid.vegas>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
18
README.md
Normal file
18
README.md
Normal file
@ -0,0 +1,18 @@
|
||||
# DDoS Monit
|
||||
|
||||
## Description
|
||||
This Packet Analysis Tool is designed to capture and analyze network packets in real-time. It provides detailed insights into the traffic flowing through a network interface, including information about protocols, IP addresses, port numbers, packet length, Time-To-Live (TTL), window size, and checksum validation. This tool is particularly useful for network debugging, security analysis, and traffic monitoring.
|
||||
|
||||
## Features
|
||||
- Real-time packet capturing on specified network interfaces.
|
||||
- Supports analysis of TCP, UDP, and ICMP protocols.
|
||||
- Displays packet details such as source/destination IP, source/destination port, packet length, and TTL.
|
||||
- Identifies and displays printable payloads in network traffic.
|
||||
|
||||
## Usage
|
||||
| Argument | Description |
|
||||
| -------- | ----------------------------------------------------------- |
|
||||
| `-d` | Specify the network device to monitor *(e.g., eth0)*. |
|
||||
| `-c` | Set the packets-per-second threshold for logging. |
|
||||
| `-x` | Provide a comma-separated list of IPs and ports to exclude. |
|
||||
| `-i` | Provide a comma-separated list of IPs and ports to include. |
|
BIN
ddosmonit/dmon
Executable file
BIN
ddosmonit/dmon
Executable file
Binary file not shown.
432
ddosmonit/dmon.go
Normal file
432
ddosmonit/dmon.go
Normal file
@ -0,0 +1,432 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/google/gopacket/pcap"
|
||||
)
|
||||
|
||||
const (
|
||||
ColorReset = "\033[0m"
|
||||
ColorDarkGrey = "\033[90m"
|
||||
ColorYellow = "\033[33m"
|
||||
ColorRed = "\033[31m"
|
||||
ColorGreen = "\033[32m"
|
||||
ColorPurple = "\033[35m"
|
||||
ColorCyan = "\033[36m"
|
||||
ColorPink = "\033[95m"
|
||||
)
|
||||
|
||||
var (
|
||||
deviceMonitor = flag.String("d", "eth0", "Device to monitor")
|
||||
packetThreshold = flag.Int("c", 5000, "Packets per second threshold to start logging")
|
||||
excludeList = flag.String("x", "", "Comma-separated list of IPs and ports to exclude")
|
||||
includeList = flag.String("i", "", "Comma-separated list of IPs and ports to include")
|
||||
)
|
||||
|
||||
type PacketInfo struct {
|
||||
Timestamp string `json:"timestamp"`
|
||||
Protocol string `json:"protocol"`
|
||||
SourceIP net.IP `json:"source_ip"`
|
||||
DestIP net.IP `json:"dest_ip"`
|
||||
SourcePort int `json:"source_port"`
|
||||
DestPort int `json:"dest_port"`
|
||||
Length int `json:"length,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
WindowSize int `json:"window_size,omitempty"`
|
||||
TCPFlags string `json:"tcp_flags,omitempty"`
|
||||
Checksum int `json:"checksum,omitempty"`
|
||||
PayloadData string `json:"payload_data,omitempty"`
|
||||
ICMPData string `json:"icmp_data,omitempty"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
deviceMAC, err := getInterfaceMAC(*deviceMonitor)
|
||||
if err != nil {
|
||||
log.Fatalf("Error getting MAC address of %s: %v", *deviceMonitor, err)
|
||||
}
|
||||
|
||||
excludeIPs, excludePorts := parseAndValidateIPsAndPorts(*excludeList)
|
||||
includeIPs, includePorts := parseAndValidateIPsAndPorts(*includeList)
|
||||
|
||||
snaplen := int32(1600)
|
||||
promiscuous := false
|
||||
timeout := pcap.BlockForever
|
||||
|
||||
handle, err := pcap.OpenLive(*deviceMonitor, snaplen, promiscuous, timeout)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer handle.Close()
|
||||
|
||||
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
|
||||
|
||||
var totalBytes int64
|
||||
var packetCount int
|
||||
startTime := time.Now()
|
||||
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
elapsed := time.Since(startTime).Seconds()
|
||||
pps := int(float64(packetCount) / elapsed)
|
||||
mbps := (float64(totalBytes) / 1e6) / elapsed
|
||||
fmt.Print("\033[A\033[K") // Move up one line and clear it.
|
||||
fmt.Println(strings.Repeat(" ", 100)) // Clear the line with 50 spaces (or enough to cover the previous text).
|
||||
fmt.Print("\033[A") // Move up one line again to overwrite the cleared line.
|
||||
fmt.Printf("PP/s: %-7d %.2f MB/s\n", pps, mbps)
|
||||
packetCount = 0
|
||||
totalBytes = 0
|
||||
startTime = time.Now()
|
||||
}
|
||||
}()
|
||||
|
||||
for packet := range packetSource.Packets() {
|
||||
ethernetLayer := packet.Layer(layers.LayerTypeEthernet)
|
||||
if ethernetLayer != nil {
|
||||
ethernet, _ := ethernetLayer.(*layers.Ethernet)
|
||||
if !bytes.Equal(ethernet.DstMAC, deviceMAC) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if shouldProcessPacket(packet, excludeIPs, excludePorts, includeIPs, includePorts) {
|
||||
ipv4Layer := packet.Layer(layers.LayerTypeIPv4)
|
||||
tcpLayer := packet.Layer(layers.LayerTypeTCP)
|
||||
udpLayer := packet.Layer(layers.LayerTypeUDP)
|
||||
icmpLayer := packet.Layer(layers.LayerTypeICMPv4)
|
||||
if ipv4Layer != nil && (tcpLayer != nil || udpLayer != nil || icmpLayer != nil) {
|
||||
fmt.Print("\033[A\033[K")
|
||||
printPacketInfo(packet)
|
||||
|
||||
packetCount++
|
||||
totalBytes += int64(packet.Metadata().Length)
|
||||
|
||||
ppsColor := ""
|
||||
switch {
|
||||
case packetCount > *packetThreshold:
|
||||
ppsColor = ColorRed
|
||||
case packetCount > *packetThreshold/2:
|
||||
ppsColor = ColorYellow
|
||||
default:
|
||||
ppsColor = ColorReset
|
||||
}
|
||||
|
||||
elapsed := time.Since(startTime).Seconds()
|
||||
if elapsed > 0 {
|
||||
pps := int(float64(packetCount) / elapsed)
|
||||
mbps := (float64(totalBytes) * 8) / 1e6 / elapsed
|
||||
fmt.Printf("%sPP/s: %-7d %.2f%s MB/s\n", ppsColor, pps, mbps, ColorReset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getInterfaceMAC(interfaceName string) (net.HardwareAddr, error) {
|
||||
iface, err := net.InterfaceByName(interfaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iface.HardwareAddr, nil
|
||||
}
|
||||
|
||||
func parseAndValidateIPsAndPorts(list string) ([]net.IP, []int) {
|
||||
var ips []net.IP
|
||||
var ports []int
|
||||
|
||||
items := strings.Split(list, ",")
|
||||
for _, item := range items {
|
||||
item = strings.TrimSpace(item)
|
||||
if ip := net.ParseIP(item); ip != nil {
|
||||
ips = append(ips, ip)
|
||||
} else if port, err := strconv.Atoi(item); err == nil {
|
||||
ports = append(ports, port)
|
||||
}
|
||||
}
|
||||
|
||||
return ips, ports
|
||||
}
|
||||
|
||||
func shouldProcessPacket(packet gopacket.Packet, excludeIPs []net.IP, excludePorts []int, includeIPs []net.IP, includePorts []int) bool {
|
||||
ipv4Layer := packet.Layer(layers.LayerTypeIPv4)
|
||||
tcpLayer := packet.Layer(layers.LayerTypeTCP)
|
||||
udpLayer := packet.Layer(layers.LayerTypeUDP)
|
||||
|
||||
var srcIP, dstIP net.IP
|
||||
var srcPort, dstPort int
|
||||
|
||||
if ipv4Layer != nil {
|
||||
ipv4, _ := ipv4Layer.(*layers.IPv4)
|
||||
srcIP = ipv4.SrcIP
|
||||
dstIP = ipv4.DstIP
|
||||
}
|
||||
|
||||
if tcpLayer != nil {
|
||||
tcp, _ := tcpLayer.(*layers.TCP)
|
||||
srcPort = int(tcp.SrcPort)
|
||||
dstPort = int(tcp.DstPort)
|
||||
} else if udpLayer != nil {
|
||||
udp, _ := udpLayer.(*layers.UDP)
|
||||
srcPort = int(udp.SrcPort)
|
||||
dstPort = int(udp.DstPort)
|
||||
}
|
||||
|
||||
if containsIP(excludeIPs, srcIP) || containsIP(excludeIPs, dstIP) || containsPort(excludePorts, srcPort) || containsPort(excludePorts, dstPort) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(includeIPs) > 0 || len(includePorts) > 0 {
|
||||
return containsIP(includeIPs, srcIP) || containsIP(includeIPs, dstIP) || containsPort(includePorts, srcPort) || containsPort(includePorts, dstPort)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func containsIP(ips []net.IP, ip net.IP) bool {
|
||||
for _, listedIP := range ips {
|
||||
if ip.Equal(listedIP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsPort(ports []int, port int) bool {
|
||||
for _, listedPort := range ports {
|
||||
if port == listedPort {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isLikelyPlainText(data []byte) bool {
|
||||
if len(data) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var printableCount, controlCount int
|
||||
for _, runeValue := range string(data) {
|
||||
if unicode.IsPrint(runeValue) || unicode.IsSpace(runeValue) {
|
||||
printableCount++
|
||||
} else if unicode.IsControl(runeValue) {
|
||||
controlCount++
|
||||
}
|
||||
}
|
||||
|
||||
totalChars := len(data)
|
||||
printableRatio := float64(printableCount) / float64(totalChars)
|
||||
controlRatio := float64(controlCount) / float64(totalChars)
|
||||
|
||||
return printableRatio > 0.7 && controlRatio < 0.3
|
||||
}
|
||||
|
||||
func printPacketInfo(packet gopacket.Packet) {
|
||||
var srcIP, dstIP net.IP
|
||||
var srcPort, dstPort int
|
||||
var length, ttl, winSize, checksum, icmpCode, icmpType int
|
||||
var protocol, tcpFlags, payloadData, icmpData string
|
||||
timestamp := time.Now().Format("03:04:05")
|
||||
|
||||
ipv4Layer := packet.Layer(layers.LayerTypeIPv4)
|
||||
if ipv4Layer != nil {
|
||||
ipv4, _ := ipv4Layer.(*layers.IPv4)
|
||||
srcIP = ipv4.SrcIP
|
||||
dstIP = ipv4.DstIP
|
||||
length = int(ipv4.Length)
|
||||
ttl = int(ipv4.TTL)
|
||||
}
|
||||
|
||||
tcpLayer := packet.Layer(layers.LayerTypeTCP)
|
||||
udpLayer := packet.Layer(layers.LayerTypeUDP)
|
||||
icmpLayer := packet.Layer(layers.LayerTypeICMPv4)
|
||||
if tcpLayer != nil {
|
||||
tcp, _ := tcpLayer.(*layers.TCP)
|
||||
srcPort = int(tcp.SrcPort)
|
||||
dstPort = int(tcp.DstPort)
|
||||
protocol = "TCP"
|
||||
checksum = int(tcp.Checksum)
|
||||
payloadData = string(tcp.Payload)
|
||||
tcpFlags = getTCPFlags(tcp)
|
||||
winSize = int(tcp.Window)
|
||||
} else if udpLayer != nil {
|
||||
udp, _ := udpLayer.(*layers.UDP)
|
||||
srcPort = int(udp.SrcPort)
|
||||
dstPort = int(udp.DstPort)
|
||||
protocol = "UDP"
|
||||
checksum = int(udp.Checksum)
|
||||
payloadData = string(udp.Payload)
|
||||
} else if icmpLayer != nil {
|
||||
icmp, _ := icmpLayer.(*layers.ICMPv4)
|
||||
protocol = "ICMP"
|
||||
checksum = int(icmp.Checksum)
|
||||
payloadData = string(icmp.Payload)
|
||||
icmpType = int(icmp.TypeCode >> 8)
|
||||
icmpCode = int(icmp.TypeCode & 0xFF)
|
||||
icmpData = fmt.Sprintf("%d-%d", icmpType, icmpCode)
|
||||
}
|
||||
|
||||
packetInfo := PacketInfo{
|
||||
Timestamp: timestamp,
|
||||
Protocol: protocol,
|
||||
SourceIP: srcIP,
|
||||
SourcePort: srcPort,
|
||||
DestIP: dstIP,
|
||||
DestPort: dstPort,
|
||||
Length: length,
|
||||
TTL: ttl,
|
||||
WindowSize: winSize,
|
||||
TCPFlags: tcpFlags,
|
||||
Checksum: checksum,
|
||||
PayloadData: payloadData,
|
||||
ICMPData: icmpData,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(packetInfo)
|
||||
if err != nil {
|
||||
fmt.Println("Error marshaling JSON:", err)
|
||||
return
|
||||
}
|
||||
writeToFile(jsonData)
|
||||
printWithColors(packetInfo)
|
||||
}
|
||||
|
||||
func printWithColors(info PacketInfo) {
|
||||
payloadDisplay := info.PayloadData
|
||||
if len(payloadDisplay) != 0 {
|
||||
if isLikelyPlainText([]byte(payloadDisplay)) {
|
||||
reg := regexp.MustCompile(`[\s\r\n\v\f]+`)
|
||||
payloadDisplay = reg.ReplaceAllString(payloadDisplay, " ")
|
||||
payloadDisplay = strings.TrimSpace(payloadDisplay)
|
||||
if len(payloadDisplay) > 100 {
|
||||
payloadDisplay = fmt.Sprintf("%sPayload: %s%s... %s(%d)%s", ColorCyan, ColorPurple, payloadDisplay[:100], ColorDarkGrey, len(payloadDisplay), ColorReset)
|
||||
} else {
|
||||
payloadDisplay = fmt.Sprintf("%sPayload: %s%s%s", ColorCyan, ColorPurple, payloadDisplay, ColorReset)
|
||||
}
|
||||
} else {
|
||||
payloadDisplay = fmt.Sprintf("%sPayload: %sNon-printable data %s(%d)%s", ColorCyan, ColorPurple, ColorDarkGrey, len(payloadDisplay), ColorReset)
|
||||
}
|
||||
}
|
||||
|
||||
srcPortDisplay := ""
|
||||
if info.SourcePort == 0 {
|
||||
srcPortDisplay = ""
|
||||
} else {
|
||||
srcPortDisplay = fmt.Sprintf("%d", info.SourcePort)
|
||||
}
|
||||
|
||||
dstPortDisplay := ""
|
||||
if info.DestPort == 0 {
|
||||
dstPortDisplay = ""
|
||||
} else {
|
||||
dstPortDisplay = fmt.Sprintf("%d", info.DestPort)
|
||||
}
|
||||
|
||||
protocolColor := ""
|
||||
switch info.Protocol {
|
||||
case "TCP":
|
||||
protocolColor = ColorGreen
|
||||
case "UDP":
|
||||
protocolColor = ColorYellow
|
||||
case "ICMP":
|
||||
protocolColor = ColorPurple
|
||||
}
|
||||
|
||||
extraData := ""
|
||||
if info.Protocol == "ICMP" {
|
||||
extraData = fmt.Sprintf("%3s", info.ICMPData)
|
||||
} else if info.Protocol == "UDP" {
|
||||
extraData = " "
|
||||
} else if info.Protocol == "TCP" {
|
||||
if info.TCPFlags == "" {
|
||||
extraData = " "
|
||||
} else {
|
||||
extraData = info.TCPFlags
|
||||
}
|
||||
}
|
||||
|
||||
SEP := ColorDarkGrey + "│" + ColorReset
|
||||
fmt.Printf("%s %s %s %s %15s %-5s -> %15s %-5s %s %s %4d %s %s %3d %s %s %5d %s %s %5d %s %s %s %s\n",
|
||||
ColorDarkGrey+info.Timestamp+ColorReset,
|
||||
SEP,
|
||||
protocolColor+fmt.Sprintf("%4s", info.Protocol)+ColorReset,
|
||||
SEP,
|
||||
info.SourceIP,
|
||||
srcPortDisplay,
|
||||
info.DestIP,
|
||||
dstPortDisplay,
|
||||
SEP,
|
||||
ColorCyan+"Len:"+ColorReset, info.Length,
|
||||
SEP,
|
||||
ColorCyan+"TTL:"+ColorReset, info.TTL,
|
||||
SEP,
|
||||
ColorCyan+"Window:"+ColorReset, info.WindowSize,
|
||||
SEP,
|
||||
ColorCyan+"Checksum:"+ColorReset, info.Checksum,
|
||||
SEP,
|
||||
extraData,
|
||||
SEP,
|
||||
payloadDisplay,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func writeToFile(data []byte) {
|
||||
fileName := "packet_info.json"
|
||||
file, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.Write(data)
|
||||
if err != nil {
|
||||
fmt.Println("Error writing to file:", err)
|
||||
return
|
||||
}
|
||||
_, err = file.WriteString("\n")
|
||||
if err != nil {
|
||||
fmt.Println("Error writing newline to file:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getTCPFlags(tcp *layers.TCP) string {
|
||||
flagNames := map[bool]string{
|
||||
tcp.FIN: "FIN",
|
||||
tcp.SYN: "SYN",
|
||||
tcp.RST: "RST",
|
||||
tcp.PSH: "PSH",
|
||||
tcp.ACK: "ACK",
|
||||
tcp.URG: "URG",
|
||||
tcp.ECE: "ECE",
|
||||
tcp.CWR: "CWR",
|
||||
tcp.NS: "NS",
|
||||
}
|
||||
|
||||
var flags []string
|
||||
for flag, name := range flagNames {
|
||||
if flag {
|
||||
flags = append(flags, name)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(flags, ",")
|
||||
}
|
8
ddosmonit/go.mod
Normal file
8
ddosmonit/go.mod
Normal file
@ -0,0 +1,8 @@
|
||||
module dmon
|
||||
|
||||
go 1.21.5
|
||||
|
||||
require (
|
||||
github.com/google/gopacket v1.1.19 // indirect
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect
|
||||
)
|
15
ddosmonit/go.sum
Normal file
15
ddosmonit/go.sum
Normal file
@ -0,0 +1,15 @@
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
Loading…
Reference in New Issue
Block a user