@@ -41,6 +41,8 @@ import (
41
41
"github.com/bufbuild/buf/private/pkg/netrc"
42
42
"github.com/bufbuild/buf/private/pkg/stringutil"
43
43
"github.com/bufbuild/buf/private/pkg/verbose"
44
+ "github.com/quic-go/quic-go"
45
+ "github.com/quic-go/quic-go/http3"
44
46
"github.com/spf13/pflag"
45
47
"go.uber.org/multierr"
46
48
"golang.org/x/net/http2"
@@ -60,6 +62,7 @@ const (
60
62
protocolFlagName = "protocol"
61
63
unixSocketFlagName = "unix-socket"
62
64
http2PriorKnowledgeFlagName = "http2-prior-knowledge"
65
+ http3FlagName = "http3"
63
66
64
67
// TLS flags
65
68
keyFlagName = "key"
@@ -207,6 +210,7 @@ type flags struct {
207
210
Protocol string
208
211
UnixSocket string
209
212
HTTP2PriorKnowledge bool
213
+ HTTP3 bool
210
214
211
215
// TLS
212
216
Key , Cert , CACert , ServerName string
@@ -324,6 +328,16 @@ choose either HTTP 1.1 or HTTP/2 for URLs with an https scheme. With this flag s
324
328
HTTP/2 is always used, even over plain-text.` ,
325
329
)
326
330
331
+ flagSet .BoolVar (
332
+ & f .HTTP3 ,
333
+ http3FlagName ,
334
+ false ,
335
+ `This flag can be used to indicate that HTTP/3 should be used. Without this, HTTP 1.1
336
+ will be used with URLs with an http scheme, and protocol negotiation will be used to
337
+ choose either HTTP 1.1 or HTTP/2 for URLs with an https scheme. With this flag set,
338
+ HTTP/3 is always used.` ,
339
+ )
340
+
327
341
flagSet .BoolVar (
328
342
& f .NoKeepAlive ,
329
343
noKeepAliveFlagName ,
@@ -544,6 +558,19 @@ func (f *flags) validate(hasURL, isSecure bool) error {
544
558
return fmt .Errorf ("grpc protocol cannot be used with plain-text URLs (http) unless --%s flag is set" , http2PriorKnowledgeFlagName )
545
559
}
546
560
561
+ if ! isSecure && f .HTTP3 {
562
+ return fmt .Errorf ("--%s cannot be used with plain-text URLs (http)" , http3FlagName )
563
+ }
564
+
565
+ if f .UnixSocket != "" && f .HTTP3 {
566
+ return fmt .Errorf ("--%s cannot be used with --%s" , unixSocketFlagName , http3FlagName )
567
+ }
568
+
569
+ // NOTE: This can be removed once trailer support lands for quic-go: https://github.com/quic-go/quic-go/issues/2266
570
+ if f .Protocol == connect .ProtocolGRPC && f .HTTP3 {
571
+ return fmt .Errorf ("--%s cannot be used with --%s=%s" , http3FlagName , protocolFlagName , connect .ProtocolGRPC )
572
+ }
573
+
547
574
if f .Netrc && f .NetrcFile != "" {
548
575
return fmt .Errorf ("--%s and --%s flags are mutually exclusive; they may not both be specified" , netrcFlagName , netrcFileFlagName )
549
576
}
@@ -691,6 +718,18 @@ func (f *flags) determineCredentials(
691
718
return basicAuth (username , password ), nil
692
719
}
693
720
721
+ func (f * flags ) getTLSConfig (authority string , printer verbose.Printer ) (* tls.Config , error ) {
722
+ return bufcurl .MakeVerboseTLSConfig (& bufcurl.TLSSettings {
723
+ KeyFile : f .Key ,
724
+ CertFile : f .Cert ,
725
+ CACertFile : f .CACert ,
726
+ ServerName : f .ServerName ,
727
+ Insecure : f .Insecure ,
728
+ HTTP2PriorKnowledge : f .HTTP2PriorKnowledge ,
729
+ HTTP3 : f .HTTP3 ,
730
+ }, authority , printer )
731
+ }
732
+
694
733
func promptForPassword (ctx context.Context , container app.Container , prompt string ) (string , error ) {
695
734
// NB: The comments below and the mechanism of handling I/O async was
696
735
// copied from the "registry login" command.
@@ -916,6 +955,9 @@ func run(ctx context.Context, container appext.Container, f *flags) (err error)
916
955
// This shouldn't be possible since we check in flags.validate, but just in case
917
956
return nil , errors .New ("URL positional argument is missing" )
918
957
}
958
+ if f .HTTP3 {
959
+ return makeHTTP3Client (f , bufcurl .GetAuthority (host , requestHeaders ), container .VerbosePrinter ())
960
+ }
919
961
return makeHTTPClient (f , isSecure , bufcurl .GetAuthority (host , requestHeaders ), container .VerbosePrinter ())
920
962
})
921
963
@@ -1035,6 +1077,7 @@ func makeHTTPClient(f *flags, isSecure bool, authority string, printer verbose.P
1035
1077
} else {
1036
1078
dialer .KeepAlive = secondsToDuration (f .KeepAliveTimeSeconds )
1037
1079
}
1080
+
1038
1081
var dialFunc func (ctx context.Context , network , address string ) (net.Conn , error )
1039
1082
if f .UnixSocket != "" {
1040
1083
dialFunc = func (ctx context.Context , network , addr string ) (net.Conn , error ) {
@@ -1055,14 +1098,7 @@ func makeHTTPClient(f *flags, isSecure bool, authority string, printer verbose.P
1055
1098
1056
1099
var dialTLSFunc func (ctx context.Context , network , address string ) (net.Conn , error )
1057
1100
if isSecure {
1058
- tlsConfig , err := bufcurl .MakeVerboseTLSConfig (& bufcurl.TLSSettings {
1059
- KeyFile : f .Key ,
1060
- CertFile : f .Cert ,
1061
- CACertFile : f .CACert ,
1062
- ServerName : f .ServerName ,
1063
- Insecure : f .Insecure ,
1064
- HTTP2PriorKnowledge : f .HTTP2PriorKnowledge ,
1065
- }, authority , printer )
1101
+ tlsConfig , err := f .getTLSConfig (authority , printer )
1066
1102
if err != nil {
1067
1103
return nil , err
1068
1104
}
@@ -1107,6 +1143,52 @@ func makeHTTPClient(f *flags, isSecure bool, authority string, printer verbose.P
1107
1143
return bufcurl .NewVerboseHTTPClient (transport , printer ), nil
1108
1144
}
1109
1145
1146
+ func makeHTTP3Client (f * flags , authority string , printer verbose.Printer ) (connect.HTTPClient , error ) {
1147
+ quicCfg := & quic.Config {
1148
+ KeepAlivePeriod : - 1 ,
1149
+ }
1150
+ if f .ConnectTimeoutSeconds != 0 {
1151
+ quicCfg .HandshakeIdleTimeout = secondsToDuration (f .ConnectTimeoutSeconds )
1152
+ }
1153
+ if ! f .NoKeepAlive {
1154
+ quicCfg .KeepAlivePeriod = secondsToDuration (f .KeepAliveTimeSeconds )
1155
+ }
1156
+
1157
+ tlsConfig , err := f .getTLSConfig (authority , printer )
1158
+ if err != nil {
1159
+ return nil , err
1160
+ }
1161
+
1162
+ udpConn , err := net .ListenUDP ("udp" , nil )
1163
+ if err != nil {
1164
+ return nil , err
1165
+ }
1166
+ transport := & quic.Transport {Conn : udpConn }
1167
+ roundTripper := & http3.RoundTripper {
1168
+ TLSClientConfig : tlsConfig ,
1169
+ QUICConfig : quicCfg ,
1170
+ Dial : func (ctx context.Context , addr string , tlsCfg * tls.Config , cfg * quic.Config ) (quic.EarlyConnection , error ) {
1171
+ printer .Printf ("* Dialing (udp) %s..." , addr )
1172
+ udpAddr , err := net .ResolveUDPAddr ("udp" , addr )
1173
+ if err != nil {
1174
+ return nil , err
1175
+ }
1176
+ printer .Printf ("* ALPN: offering %s" , strings .Join (tlsCfg .NextProtos , "," ))
1177
+ conn , err := transport .DialEarly (ctx , udpAddr , tlsCfg , cfg )
1178
+ if err != nil {
1179
+ return nil , err
1180
+ }
1181
+ printer .Printf ("* Connected to %s" , conn .RemoteAddr ().String ())
1182
+ return conn , err
1183
+ },
1184
+ EnableDatagrams : false ,
1185
+ AdditionalSettings : map [uint64 ]uint64 {},
1186
+ MaxResponseHeaderBytes : 0 ,
1187
+ DisableCompression : false ,
1188
+ }
1189
+ return bufcurl .NewVerboseHTTPClient (roundTripper , printer ), nil
1190
+ }
1191
+
1110
1192
func secondsToDuration (secs float64 ) time.Duration {
1111
1193
return time .Duration (float64 (time .Second ) * secs )
1112
1194
}
0 commit comments