Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(x): add h3/QUIC support to the fetch tool #305

Merged
merged 38 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b15bb9b
More extensible config
fortuna Oct 25, 2024
845dfa6
Add shadowsocks
fortuna Oct 25, 2024
470bdd3
Add TLS
fortuna Oct 25, 2024
bb0a827
Fixes
fortuna Oct 25, 2024
550384b
Add Websocket
fortuna Oct 25, 2024
5826709
Cleanup
fortuna Oct 28, 2024
22a0526
Refactor split
fortuna Oct 28, 2024
f2938cf
Update Shadowsocks
fortuna Oct 28, 2024
17182e0
Remove ConfigToObject for now
fortuna Oct 28, 2024
77906f1
Introduce registry type
fortuna Oct 28, 2024
ffc0654
Finish
fortuna Oct 28, 2024
7df7d2c
Add QUIC
fortuna Oct 29, 2024
d3712d1
Fix
fortuna Oct 29, 2024
67a190c
Fix test
fortuna Oct 29, 2024
6803dde
Fix name
fortuna Oct 29, 2024
7431238
Merge branch 'fortuna-config' into fortuna-quic
fortuna Oct 29, 2024
92eca9d
Use Generics
fortuna Oct 29, 2024
f194150
Add comments
fortuna Oct 29, 2024
76bcf64
Add type assertion
fortuna Oct 29, 2024
866f9c1
Remove unused Provider
fortuna Oct 29, 2024
eb793dc
Allow registration override
fortuna Oct 30, 2024
a848931
Rename newDialer
fortuna Oct 30, 2024
0e483c7
Update doc
fortuna Oct 30, 2024
b3fbf2b
Remove register methods
fortuna Oct 30, 2024
308ff7d
Add comment to SanitizeConfig
fortuna Oct 30, 2024
ffbdf49
Rename config
fortuna Oct 30, 2024
fadf314
Update Copyright line
fortuna Oct 30, 2024
47c9ad6
Use Providers
fortuna Oct 30, 2024
a716c1f
Cleanup
fortuna Oct 30, 2024
b3fec8a
Remove Close
fortuna Oct 30, 2024
f9b97cd
Update Go mod
fortuna Oct 30, 2024
6783ebf
Use slog
fortuna Nov 1, 2024
34682f1
Add key log
fortuna Nov 1, 2024
3fae6e0
Cleanup
fortuna Nov 1, 2024
ec133fe
Merge branch 'fortuna-config' into fortuna-quic
fortuna Nov 1, 2024
3e1442c
update mod
fortuna Nov 1, 2024
ad2492d
Merge branch 'main' into fortuna-quic
fortuna Nov 1, 2024
e245a9a
Update x/examples/fetch/main.go
fortuna Nov 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion x/configurl/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ DPI Evasion - To add packet splitting to a Shadowsocks server for enhanced DPI e
Defining custom strategies - You can define your custom strategy by implementing and registering [BuildFunc[ObjectType]] functions:

// Create new config parser.
// p := configurl.NewProviderContainer
// p := configurl.NewProviderContainer()
// or
p := configurl.NewDefaultProviders()
// Register your custom dialer.
Expand Down
146 changes: 116 additions & 30 deletions x/examples/fetch/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ package main
import (
"bufio"
"context"
"crypto/tls"
"flag"
"fmt"
"io"
"log"
"log/slog"
"net"
"net/http"
"net/textproto"
Expand All @@ -30,10 +31,12 @@ import (
"time"

"github.com/Jigsaw-Code/outline-sdk/x/configurl"
"github.com/lmittmann/tint"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"golang.org/x/term"
)

var debugLog log.Logger = *log.New(io.Discard, "", 0)

type stringArrayFlagValue []string

func (v *stringArrayFlagValue) String() string {
Expand All @@ -52,8 +55,24 @@ func init() {
}
}

func overrideAddress(original string, newHost string, newPort string) (string, error) {
host, port, err := net.SplitHostPort(original)
if err != nil {
return "", fmt.Errorf("invalid address: %w", err)
}
if newHost != "" {
host = newHost
}
if newPort != "" {
port = newPort
}
return net.JoinHostPort(host, port), nil
}

func main() {
verboseFlag := flag.Bool("v", false, "Enable debug output")
tlsKeyLogFlag := flag.String("tls-key-log", "", "Filename to write the TLS key log to allow for decryption on Wireshark.")
fortuna marked this conversation as resolved.
Show resolved Hide resolved
protoFlag := flag.String("proto", "h1", "HTTP version to use (h1, h2, h3)")
transportFlag := flag.String("transport", "", "Transport config")
addressFlag := flag.String("address", "", "Address to connect to. If empty, use the URL authority")
methodFlag := flag.String("method", "GET", "The HTTP method to use")
Expand All @@ -63,9 +82,15 @@ func main() {

flag.Parse()

logLevel := slog.LevelInfo
if *verboseFlag {
debugLog = *log.New(os.Stderr, "[DEBUG] ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
logLevel = slog.LevelDebug
}
slog.SetDefault(slog.New(tint.NewHandler(
os.Stderr,
&tint.Options{NoColor: !term.IsTerminal(int(os.Stderr.Fd())), Level: logLevel},
)))

var overrideHost, overridePort string
if *addressFlag != "" {
var err error
Expand All @@ -79,47 +104,104 @@ func main() {

url := flag.Arg(0)
if url == "" {
log.Println("Need to pass the URL to fetch in the command-line")
slog.Error("Need to pass the URL to fetch in the command-line")
flag.Usage()
os.Exit(1)
}

dialer, err := configurl.NewDefaultProviders().NewStreamDialer(context.Background(), *transportFlag)
if err != nil {
log.Fatalf("Could not create dialer: %v\n", err)
httpClient := &http.Client{
Timeout: time.Duration(*timeoutSecFlag) * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}

var tlsConfig tls.Config
if *tlsKeyLogFlag != "" {
f, err := os.Create(*tlsKeyLogFlag)
if err != nil {
slog.Error("Failed to creare TLS key log file", "error", err)
os.Exit(1)
}
defer f.Close()
tlsConfig.KeyLogWriter = f
}
dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
providers := configurl.NewDefaultProviders()
if *protoFlag == "h1" || *protoFlag == "h2" {
dialer, err := providers.NewStreamDialer(context.Background(), *transportFlag)
if err != nil {
return nil, fmt.Errorf("invalid address: %w", err)
slog.Error("Could not create dialer", "error", err)
os.Exit(1)
}
if overrideHost != "" {
host = overrideHost
dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
addressToDial, err := overrideAddress(addr, overrideHost, overridePort)
if err != nil {
return nil, fmt.Errorf("invalid address: %w", err)
}
if !strings.HasPrefix(network, "tcp") {
return nil, fmt.Errorf("protocol not supported: %v", network)
}
return dialer.DialStream(ctx, addressToDial)
}
if overridePort != "" {
port = overridePort
if *protoFlag == "h1" {
tlsConfig.NextProtos = []string{"http/1.1"}
httpClient.Transport = &http.Transport{
DialContext: dialContext,
TLSClientConfig: &tlsConfig,
}
} else if *protoFlag == "h2" {
tlsConfig.NextProtos = []string{"h2"}
httpClient.Transport = &http.Transport{
DialContext: dialContext,
TLSClientConfig: &tlsConfig,
ForceAttemptHTTP2: true,
}
}
if !strings.HasPrefix(network, "tcp") {
return nil, fmt.Errorf("protocol not supported: %v", network)
} else if *protoFlag == "h3" {
listener, err := providers.NewPacketListener(context.Background(), *transportFlag)
if err != nil {
slog.Error("Could not create listener", "error", err)
os.Exit(1)
}
return dialer.DialStream(ctx, net.JoinHostPort(host, port))
}
httpClient := &http.Client{
Transport: &http.Transport{DialContext: dialContext},
Timeout: time.Duration(*timeoutSecFlag) * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
conn, err := listener.ListenPacket(context.Background())
if err != nil {
slog.Error("Could not create PacketConn", "error", err)
os.Exit(1)
}
tr := &quic.Transport{
Conn: conn,
}
defer tr.Close()
httpClient.Transport = &http3.Transport{
TLSClientConfig: &tlsConfig,
Dial: func(ctx context.Context, addr string, tlsConf *tls.Config, quicConf *quic.Config) (quic.EarlyConnection, error) {
addressToDial, err := overrideAddress(addr, overrideHost, overridePort)
if err != nil {
return nil, fmt.Errorf("invalid address: %w", err)
}
udpAddr, err := net.ResolveUDPAddr("udp", addressToDial)
if err != nil {
return nil, err
}
return tr.DialEarly(ctx, udpAddr, tlsConf, quicConf)
},
Logger: slog.Default(),
}
} else {
slog.Error("Invalid HTTP protocol", "proto", *protoFlag)
os.Exit(1)
}

req, err := http.NewRequest(*methodFlag, url, nil)
if err != nil {
log.Fatalln("Failed to create request:", err)
slog.Error("Failed to create request", "error", err)
os.Exit(1)
}
headerText := strings.Join(headersFlag, "\r\n") + "\r\n\r\n"
h, err := textproto.NewReader(bufio.NewReader(strings.NewReader(headerText))).ReadMIMEHeader()
if err != nil {
log.Fatalf("invalid header line: %v", err)
slog.Error("Invalid header line", "error", err)
os.Exit(1)
}
for name, values := range h {
for _, value := range values {
Expand All @@ -128,19 +210,23 @@ func main() {
}
resp, err := httpClient.Do(req)
if err != nil {
log.Fatalf("HTTP request failed: %v\n", err)
slog.Error("HTTP request failed", "error", err)
os.Exit(1)
}
defer resp.Body.Close()

if *verboseFlag {
slog.Info("HTTP Proto", "version", resp.Proto)
slog.Info("HTTP Status", "status", resp.Status)
for k, v := range resp.Header {
debugLog.Printf("%v: %v", k, v)
slog.Debug("Header", "key", k, "value", v)
}
}

_, err = io.Copy(os.Stdout, resp.Body)
fmt.Println()
if err != nil {
log.Fatalf("Read of page body failed: %v\n", err)
slog.Error("Read of page body failed", "error", err)
os.Exit(1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that os.Exit will bypass all defer calls, I guess it's OK in this PR since all resources will be cleaned up when the process ends.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Though I'll also add that this is no different from what we currently have with the Fatal calls, which calls os.Exit.

}
}
23 changes: 12 additions & 11 deletions x/go.mod
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
module github.com/Jigsaw-Code/outline-sdk/x

go 1.21
go 1.22
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, quic-go requires 1.22. We'll need to keep this quite recent in order to support newer protocol updates, unfortunately.


require (
github.com/Jigsaw-Code/outline-sdk v0.0.17
// Use github.com/Psiphon-Labs/psiphon-tunnel-core@staging-client as per
// https://github.com/Psiphon-Labs/psiphon-tunnel-core/?tab=readme-ov-file#using-psiphon-with-go-modules
github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20240619172145-03cade11f647
github.com/lmittmann/tint v1.0.5
github.com/quic-go/quic-go v0.48.1
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/stretchr/testify v1.9.0
github.com/vishvananda/netlink v1.1.0
golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b
golang.org/x/net v0.25.0
golang.org/x/sys v0.20.0
golang.org/x/term v0.20.0
golang.org/x/net v0.28.0
golang.org/x/sys v0.23.0
golang.org/x/term v0.23.0
)

require (
Expand Down Expand Up @@ -57,7 +58,7 @@ require (
github.com/pion/transport/v2 v2.2.3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2 // indirect
github.com/refraction-networking/ed25519 v0.1.2 // indirect
github.com/refraction-networking/gotapdance v1.7.10 // indirect
Expand All @@ -71,12 +72,12 @@ require (
github.com/wader/filtertransport v0.0.0-20200316221534-bdd9e61eee78 // indirect
gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.21.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
44 changes: 24 additions & 20 deletions x/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,10 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
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/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.48.1 h1:y/8xmfWI9qmGTc+lBr4jKRUWLGSlSigv847ULJ4hYXA=
github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2 h1:m2ZH6WV69otVmBpWbk8et3MypHFsjcYXTNrknQKS/PY=
github.com/refraction-networking/conjure v0.7.11-0.20240130155008-c8df96195ab2/go.mod h1:7KuAtYfSL0K0WpCScjN9YKiOZ4AQ/8IzSjUtVwWbSv8=
github.com/refraction-networking/ed25519 v0.1.2 h1:08kJZUkAlY7a7cZGosl1teGytV+QEoNxPO7NnRvAB+g=
Expand Down Expand Up @@ -203,10 +205,10 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
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/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b h1:WX7nnnLfCEXg+FmdYZPai2XuP3VqCP1HZVMST0n9DF0=
golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b/go.mod h1:EiXZlVfUTaAyySFVJb9rsODuiO+WXu8HrUuySb7nYFw=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
Expand All @@ -222,14 +224,14 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
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.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -245,39 +247,41 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
Expand Down
Loading