支持 QUIC 协议中转
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
.vscode
|
||||
|
||||
config.json
|
||||
/gateway
|
||||
/logs/
|
||||
|
||||
161
cmd/client/main.go
Normal file
161
cmd/client/main.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var (
|
||||
localPort = 25565
|
||||
|
||||
mcHost = "bh.mc.tursom.cn"
|
||||
mcPort = 25565
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 {
|
||||
mcHost = os.Args[1]
|
||||
}
|
||||
|
||||
// 监听TCP端口
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", localPort))
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).
|
||||
Int("port", localPort).
|
||||
Msg("Failed to listen on port")
|
||||
}
|
||||
defer listener.Close()
|
||||
log.Info().
|
||||
Int("port", localPort).
|
||||
Msg("Listening for TCP connections")
|
||||
|
||||
for {
|
||||
// 接受传入的连接
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error accepting")
|
||||
continue
|
||||
}
|
||||
log.Info().
|
||||
Str("client", conn.RemoteAddr().String()).
|
||||
Int("port", localPort).
|
||||
Msg("Accepted connection")
|
||||
// 处理连接
|
||||
go handlerConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func handlerConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
tlsConf := &tls.Config{
|
||||
InsecureSkipVerify: true, // 跳过证书检查
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 3s handshake timeout
|
||||
defer cancel()
|
||||
|
||||
quicConn, err := quic.DialAddr(ctx, fmt.Sprintf("%s:%d", mcHost, mcPort), tlsConf, nil)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to dial QUIC")
|
||||
return
|
||||
}
|
||||
|
||||
stream, err := quicConn.OpenStream()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to open stream")
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
log.Info().
|
||||
Msg("QUIC stream opened")
|
||||
|
||||
// read and write stream data
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("client", conn.RemoteAddr().String()).
|
||||
Msg("Error reading hostname")
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
log.Err(errors.New("empty buffer")).
|
||||
Str("client", conn.RemoteAddr().String()).
|
||||
Msg("Error: buffer is empty")
|
||||
return
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
new_buf := replaceMcHost(buf, mcHost)
|
||||
_, err = stream.Write(new_buf) // 写入数据到 QUIC 流
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("client", conn.RemoteAddr().String()).
|
||||
Str("host", mcHost).
|
||||
Int("port", mcPort).
|
||||
Msg("Error writing to QUIC stream")
|
||||
return
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go copyData(stream, conn, &wg)
|
||||
copyData(conn, stream, nil)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func copyData(src io.Reader, dst io.Writer, wg *sync.WaitGroup) {
|
||||
if wg != nil {
|
||||
defer wg.Done()
|
||||
}
|
||||
|
||||
_, err := io.Copy(dst, src)
|
||||
if err != nil && err != io.EOF {
|
||||
log.Err(err).Msg("Error copying data")
|
||||
}
|
||||
}
|
||||
|
||||
func replaceMcHost(buf []byte, host string) []byte {
|
||||
if len(buf) < 5 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
head := buf[:4]
|
||||
|
||||
buf = buf[4:]
|
||||
host_len := buf[0]
|
||||
if len(buf) < int(host_len)+1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
raw_host := string(buf[1 : host_len+1])
|
||||
if spliterIndex := strings.IndexRune(raw_host, 0); spliterIndex != -1 {
|
||||
host = host + raw_host[spliterIndex:]
|
||||
}
|
||||
|
||||
// 修改标识数据包长度的字节
|
||||
head[0] += byte(len(host) - len(raw_host))
|
||||
|
||||
out.Write(head) // 保留前四个字节
|
||||
out.WriteByte(byte(len(host))) // 写入主机名长度
|
||||
out.Write([]byte(host)) // 写入主机名
|
||||
|
||||
out.Write(buf[host_len+1:]) // 写入剩余数据
|
||||
|
||||
return out.Bytes()
|
||||
}
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
type (
|
||||
Config struct {
|
||||
Port int `json:"port"`
|
||||
Tcp bool `json:"tcp"`
|
||||
Quic bool `json:"quic"`
|
||||
Hosts map[string]string `json:"hosts"`
|
||||
Default string `json:"default"`
|
||||
Log LogConfig `json:"log"`
|
||||
@@ -33,6 +35,8 @@ type (
|
||||
)
|
||||
|
||||
var (
|
||||
configFile = "config.json"
|
||||
|
||||
config Config
|
||||
currentLogFile string
|
||||
configLoadLock sync.Mutex
|
||||
@@ -42,7 +46,7 @@ func loadConfig() error {
|
||||
configLoadLock.Lock()
|
||||
defer configLoadLock.Unlock()
|
||||
|
||||
file, err := os.Open("config.json")
|
||||
file, err := os.Open(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -103,11 +107,14 @@ func watchConfig() *fsnotify.Watcher {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-watcher.Events:
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
log.Error().Msg("watcher.Events channel closed")
|
||||
return
|
||||
}
|
||||
if !strings.HasSuffix(event.Name, configFile) {
|
||||
continue
|
||||
}
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
log.Error().Msg("watcher.Errors channel closed")
|
||||
@@ -145,15 +152,37 @@ func main() {
|
||||
|
||||
go handleLogRotate()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
// 启动QUIC服务
|
||||
if config.Quic {
|
||||
wg.Add(1)
|
||||
go runQuic(&wg)
|
||||
}
|
||||
|
||||
// 监听TCP端口
|
||||
if config.Tcp {
|
||||
wg.Add(1)
|
||||
go runTcp(&wg)
|
||||
}
|
||||
}
|
||||
|
||||
func runTcp(wg *sync.WaitGroup) {
|
||||
if wg != nil {
|
||||
defer wg.Done()
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", config.Port))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatal().Err(err).
|
||||
Int("port", config.Port).
|
||||
Msg("Failed to listen on port")
|
||||
}
|
||||
defer listener.Close()
|
||||
log.Info().
|
||||
Int("port", config.Port).
|
||||
Msg("Listening")
|
||||
Msg("Listening for TCP connections")
|
||||
|
||||
for {
|
||||
// 接受传入的连接
|
||||
@@ -209,6 +238,13 @@ func handleRequest(conn net.Conn) {
|
||||
if !ok {
|
||||
host = config.Default
|
||||
}
|
||||
if host == "" {
|
||||
log.Err(errEmptyBuffer).
|
||||
Str("client", conn.RemoteAddr().String()).
|
||||
Str("host", mc_host).
|
||||
Msg("failed to route host")
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("client", conn.RemoteAddr().String()).
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
|
||||
func writePIDFile() error {
|
||||
pid := os.Getpid()
|
||||
return os.WriteFile("/var/run/mc-gateway.pid", []byte(fmt.Sprintf("%d\n", pid)), 0644)
|
||||
return os.WriteFile("/dev/shm/mc-gateway.pid", []byte(fmt.Sprintf("%d\n", pid)), 0644)
|
||||
}
|
||||
|
||||
func removePIDFile() {
|
||||
os.Remove("/var/run/mc-gateway.pid")
|
||||
os.Remove("/dev/shm/mc-gateway.pid")
|
||||
}
|
||||
|
||||
122
cmd/gateway/quic.go
Normal file
122
cmd/gateway/quic.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
quic "github.com/quic-go/quic-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type quicConn struct {
|
||||
quic.Connection
|
||||
quic.Stream
|
||||
}
|
||||
|
||||
func runQuic(wg *sync.WaitGroup) {
|
||||
if wg != nil {
|
||||
defer wg.Done()
|
||||
}
|
||||
|
||||
udpConn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: config.Port})
|
||||
if err != nil {
|
||||
log.Panic().Err(err).Msg("Failed to listen UDP")
|
||||
}
|
||||
defer udpConn.Close()
|
||||
|
||||
tlsConf, err := generateTLSConfig()
|
||||
if err != nil {
|
||||
log.Panic().Err(err).Msg("Failed to generate TLS config")
|
||||
}
|
||||
|
||||
ln, err := quic.Listen(udpConn, tlsConf, nil)
|
||||
if err != nil {
|
||||
log.Panic().Err(err).Msg("Failed to listen QUIC")
|
||||
}
|
||||
log.Info().Int("port", config.Port).Msg("Listening for QUIC connections")
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept(context.Background())
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error accepting QUIC connection")
|
||||
continue
|
||||
}
|
||||
|
||||
go handleQuicRequest(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func handleQuicRequest(conn quic.Connection) {
|
||||
defer conn.CloseWithError(0, "Closing connection")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
stream, err := conn.AcceptStream(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error accepting stream")
|
||||
return
|
||||
}
|
||||
|
||||
handleRequest(quicConn{
|
||||
Connection: conn,
|
||||
Stream: stream,
|
||||
})
|
||||
}
|
||||
|
||||
func generateTLSConfig() (*tls.Config, error) {
|
||||
// 生成私钥
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建证书模板
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Example Org"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(365 * 24 * time.Hour), // 有效期 1 年
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
// 自签名证书
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 编码证书和私钥
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
keyPEM, err := x509.MarshalECPrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyPEMBlock := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyPEM})
|
||||
|
||||
// 加载到 tls.Certificate
|
||||
cert, err := tls.X509KeyPair(certPEM, keyPEMBlock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 返回 tls.Config
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}, nil
|
||||
}
|
||||
21
go.mod
21
go.mod
@@ -2,14 +2,23 @@ module github.com/tursom/mc-gateway
|
||||
|
||||
go 1.23.1
|
||||
|
||||
require github.com/fsnotify/fsnotify v1.7.0
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/quic-go/quic-go v0.52.0
|
||||
github.com/rs/zerolog v1.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/rs/zerolog v1.33.0
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
)
|
||||
|
||||
55
go.sum
55
go.sum
@@ -1,19 +1,68 @@
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA=
|
||||
github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
Reference in New Issue
Block a user